首先,在現實世界的光照是極其複雜的,並且會受到諸多因素的影響,這是咱們有限的計算能力所沒法模擬的。所以OpenGL的光照使用的是簡化的模型,對現實的狀況進行近似,這樣處理起來會更容易一些。這些光照模型都是基於咱們對光的物理特性的理解。其中一個模型被稱爲馮氏光照模型(Phong Lighting Model)。馮氏光照模型的主要結構由3個元素組成:環境(Ambient)光照、漫反射(Diffuse)光照和鏡面(Specular)光照。 下面咱們用示例圖來看一下這幾種光照:數組
OpenGL在處理光照時把光照系統分爲三部分:分別是光源、材質和光照環境。光源就是光的來源,能夠是前面所說的太陽或者電燈,蠟燭等。材質是指接受光照的各類物體的表面,因爲物體如何反射光線只由物體表面決定,材質特色就決定了物體反射光線的特色。光照環境是指一些額外的參數,它們將影響最終的光照畫面,好比一些光線通過屢次反射後,已經沒法分清它到底是由哪一個光源發出,這時,指定一個環境亮度參數,可使最後造成的畫面更接近於真實狀況。緩存
在物理學中,光線若是射入理想的光滑平面,則反射後的光線是很規則的(這樣的反射稱爲鏡面反射)。光線若是射入粗糙的、不光滑的平面,則反射後的光線是雜亂的(這樣的反射稱爲漫反射)。現實生活中的物體在反射光線時,並非絕對的鏡面反射或漫反射,但能夠當作是這兩種反射的疊加。對於光源發出的光線,能夠分別設置其通過鏡面反射和漫反射後的光線強度。對於被光線照射的材質,也能夠分別設置光線通過鏡面反射和漫反射後的光線強度。這些因素綜合起來,就造成了最終的光照效果。bash
光照特性 1.發射光:由物體自身發光 2.環境光:就是在環境中充分散射的光,並且沒法分辨光的方向 3.漫反射光:光線來自某個方向,可是在物體上各個方向反射 4.鏡面高光:光線來自一個特定的方向,而後在物體表面上以一個特定的方向反射出去ide
材質屬性 1.泛射材質:光線直射,反射率較高 2.漫反射材質:須要考慮光的入射角和反射角的 3.鏡面反射材質:斑點 4.發射材質:物體自己就能夠發光的材質函數
光照計算ui
1.環境光的計算atom
環境光是不來自任何特定方向的光,在整個場景中經典光照模型把它當成一個常量,組成一個合適的第一近似值來縮放場景中的光照部分。 環境光 = 光源的環境光顏色 * 物體的材質顏色
spa
varying vec3 objectColor;
void main()
{
//⾄至少有%10的光找到物體全部⾯面
float ambientStrength = 0.1;
//環境光顏⾊色
vec3 ambient = ambientStrength * lightColor;
//最終顏⾊色 = 環境光顏⾊色 * 物體顏⾊色
vec3 result = ambient * objectColor;
gl_FragColor = vec4(result, 1.0);
}
複製代碼
2.發射光的計算3d
若是這個物體自己就是有顏色的,好比說夜明珠,那麼這個時候這個光就是這個物體材質的顏色 發射顏色 = 物體的反射材質顏色
code
3.漫反射光的計算
漫反射光是散射在各個方向上的均勻的表面特定光源,漫反射光依賴於表面法線方向和光源方向來計算,可是沒有包含視線方向,它一樣依賴於表面的顏色。 首先來看一下環境光和漫反射光的比較:
光線照射到物體表面,決定了物體表面的光照強度,光照強度是光自己強度和光線與物體表面法線夾角cos的乘積
漫反射顏色 = 光源的漫反射光顏色 × 物體的漫反射材質顏色 × 漫反射因子
DiffuseFactor = max(0, dot(N, L))
uniform vec3 lightColor; //光源色
uniform vec3 lightPo; //光源位置
uniform vec3 objectColor; //物體⾊色
uniform vec3 viewPo; //物體位置
varying vec3 outNormal; //傳⼊當前頂點平面的法向量
//確保法線爲單位向量量
vec3 norm = normalize(outNormal);
//頂點指向光源 單位向量量
vec3 lightDir = normalize(lightPo - FragPo);
//獲得兩向量量的cos值 ⼩小於0則則爲0
float diff = max(dot(norm, lightDir),0.0);
//獲得漫反射收的光源向量量
vec3 diffuse = diff * lightColor;
vec3 result = diffuse * ojbectColor;
gl_FragColor = vec4(result,1.0);
複製代碼
4.鏡面光照計算 鏡面光是由表面直接反射的高亮光,這個高亮光就像鏡子同樣跟表面材質多少有關。
鏡面反射顏色 = 光源的鏡面光顏色 × 物體的鏡面材質顏色 × 鏡面反射因子
鏡面反射因子SpecularFactor = power(max(0,dot(N,R)),shininess)
dot(N,R):H,R的點積幾何意義:平⽅線與法線夾角的cos值
shiniess : ⾼光的反光度/發光值;(值越大反射度越強)
一個物體的發光值越高,反射光的能力越強,散射得越少,高光點越小。在下面的圖片裏,你會看到不一樣發光值對視覺(效果)的影響:
鏡面光的GLSL實現代碼:
//鏡⾯面強度
float specularStrength = 0.5;
//頂點指向觀察點的單位向量量
vec3 viewDir = normalize(viewPo - FragPo);
//求得光線 在 頂點的反射線(傳⼊入光源指向頂點的向量量)
vec3 reflectDir = reflect(-lightDir ,outNormal);
// 求得夾⻆角cos值 取256次冪 注意 pow(float,float)函數參數類型 float spec = pow(max(dot(viewDir, reflectDir),0.0),256.0);
vec3 specular = specularStrength * spec * lightColor;
複製代碼
咱們都知道光的傳播是會衰減的,因此光照顏色的公式可總結爲:
光照顏色 =(環境顏色 + 漫反射顏色 + 鏡⾯反射顏色)* 衰減因子
衰減因子 = 1.0/(距離衰減常量 + 線性衰減常量 * 距離 + ⼆次衰減常量 * 距離的平⽅)
//距離衰減常量量
float constantPara = 1.0f;
//線性衰減常量量
float linearPara = 0.09f;
//⼆二次衰減因⼦子
float quadraticPara = 0.032f;
//距離
float LFDistance = length(lightPo - FragPo);
//衰減因⼦子
float lightWeakPara = 1.0/(constantPara + linearPara * LFDistance + quadraticPara * (LFDistance*LFDistance));
複製代碼
距離衰減常量,線性衰減常量和⼆次衰減常量均爲常量值.
環境光,漫反射光和鏡面光的強度都會受距離的增大⽽衰減,只有發射光和全局環境光的強度不會受影響.
聚光燈夾角cos值 = power(max(0,dot(單位光源位置,單位光線向量)),聚光燈指數);
單位光線向量是從光源指向頂點的單位向量 聚光燈指數,表示聚光燈的亮度程度 公式解讀:單位光源位置 * 單位光線向量點積的聚光燈指數次⽅。
聚光燈因子 = clamp((外環的聚光燈角度cos值 - 當前頂點的聚光燈角度cos值)/ (外環的聚光燈角度cos值- 內環聚光燈的角度的cos值),0,1);
//聚光燈過渡計算
//(⼀些複雜的計算操做 應該讓CPU作,提⾼效率,不變的量也建議外部傳輸,避免重複計算)
//內錐角cos值
float inCutOff = cos(radians(10.0f));
//外錐角cos值
float outCutOff = cos(radians(15.0f)); //聚光朝向
vec3 spotDir = vec3(-1.2f,-1.0f,-2.0f);
//光源指向物體的向量和聚光朝向的 cos值
float theta = dot(lightDir ,normalize(-spotDir));
//內外錐⻆角cos差值
float epsilon = inCutOff - outCutOff;
//clamp(a,b,c);若b<a<c 則函數返回值爲a
//若不是,則返回值最小爲b ,最大爲c
// (theta - outCutOff)/epsilon 若theta的角度⼩於內錐角 則其值 >=1
//若theta的⻆度大於外錐角,則其值<=0 這樣光線就在內外錐角之間平滑變化.
float intensity = clamp((theta - outCutOff)/epsilon, 0.0,1.0)
複製代碼
光照顏色的最終公式爲:
光照顏色 = 發射顏色 + 全局環境顏色 + (環境顏色 + 漫反射顏色 + 鏡⾯反射顏色) * 聚光燈效果 * 衰減因子
下面經過一個GLKit繪製金字塔案例來看一下光照的使用:
若是把效果圖裏的三角形的頂點所有畫到平面上來,以下圖所示:
1.設置OpenGL ES
@property(nonatomic,strong)EAGLContext *mContext;
//基本Effect 繪圖
@property(nonatomic,strong)GLKBaseEffect *baseEffect;
//額外Effect 輔助線段
@property(nonatomic,strong)GLKBaseEffect *extraEffect;
//頂點緩存區 (頂點,顏色,紋理, 法線...)
@property(nonatomic,strong)AGLKVertexAttribArrayBuffer *vertexBuffer;
//法線位置緩存區(法線輔助線段也有頂點)
@property(nonatomic,strong)AGLKVertexAttribArrayBuffer *extraBuffer;
//是否繪製法線
@property(nonatomic,assign)BOOL shouldDrawNormals;
//中心點的高 默認在(0,0,0)
@property(nonatomic,assign) GLfloat centexVertexHeight;
{
//三角形-8面
SceneTriangle triangles[NUM_FACES];
}
複製代碼
設置GLKitView並設置上下文
//1.新建OpenGL ES 上下文
self.mContext = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
//2.設置GLKView
GLKView *view = (GLKView *)self.view;
view.context = self.mContext;
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
[EAGLContext setCurrentContext:self.mContext];
複製代碼
2. 設置金字塔Effect
//1.金字塔Effect
self.baseEffect = [[GLKBaseEffect alloc]init];
self.baseEffect.light0.enabled = GL_TRUE;
//光的漫射部分 GLKVector4Make(R,G,B,A)
self.baseEffect.light0.diffuseColor = GLKVector4Make(0.7f, 0.7f, 0.7, 1.0f);
//世界座標中的光的位置。
self.baseEffect.light0.position = GLKVector4Make(1.0f, 1.0f, 0.5f, 0.0f);
//2.法線Effect
self.extraEffect = [[GLKBaseEffect alloc]init];
self.extraEffect.useConstantColor = GL_TRUE;
//3.調整模型矩陣,更好的觀察
//能夠嘗試不執行這段代碼,改成false
if (true) {
//圍繞x軸旋轉-60度
//返回一個4x4矩陣進行繞任意矢量旋轉
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(-60.0f), 1.0f, 0.0f, 0.0f);
//圍繞z軸,旋轉-30度
modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix,GLKMathDegreesToRadians(-30.0f), 0.0f, 0.0f, 1.0f);
//圍繞Z方向,移動0.25f
modelViewMatrix = GLKMatrix4Translate(modelViewMatrix, 0.0f, 0.0f, 0.25f);
//設置baseEffect,extraEffect 模型矩陣
self.baseEffect.transform.modelviewMatrix = modelViewMatrix;
self.extraEffect.transform.modelviewMatrix = modelViewMatrix;
}
複製代碼
3.設置頂點
//肯定圖形的8個面
triangles[0] = SceneTriangleMake(vertexA, vertexB, vertexD);
triangles[1] = SceneTriangleMake(vertexB, vertexC, vertexF);
triangles[2] = SceneTriangleMake(vertexD, vertexB, vertexE);
triangles[3] = SceneTriangleMake(vertexE, vertexB, vertexF);
triangles[4] = SceneTriangleMake(vertexD, vertexE, vertexH);
triangles[5] = SceneTriangleMake(vertexE, vertexF, vertexH);
triangles[6] = SceneTriangleMake(vertexG, vertexD, vertexH);
triangles[7] = SceneTriangleMake(vertexH, vertexF, vertexI);
//初始化緩存區
self.vertexBuffer = [[AGLKVertexAttribArrayBuffer alloc]initWithAttribStride:sizeof(SceneVertex) numberOfVertices:sizeof(triangles)/sizeof(SceneVertex) bytes:triangles usage:GL_DYNAMIC_DRAW];
self.extraBuffer = [[AGLKVertexAttribArrayBuffer alloc]initWithAttribStride:sizeof(SceneVertex) numberOfVertices:0 bytes:NULL usage:GL_DYNAMIC_DRAW];
self.centexVertexHeight = 0.0f;
複製代碼
4.開始繪製
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
//1.
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//2.
[self.baseEffect prepareToDraw];
//準備繪製頂點數據
[self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribPosition numberOfCoordinates:3 attribOffset:offsetof(SceneVertex,position)shouldEnable:YES];
//準備繪製光照數據
[self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribNormal numberOfCoordinates:3 attribOffset:offsetof(SceneVertex, normal) shouldEnable:YES];
[self.vertexBuffer drawArrayWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:sizeof(triangles)/sizeof(SceneVertex)];
//3.是否要繪製光照法線
if (self.shouldDrawNormals) {
[self drawNormals];
}
}
複製代碼
繪製法線
//繪製法線
-(void)drawNormals
{
GLKVector3 normalLineVertices[NUM_LINE_VERTS];
//1.以每一個頂點的座標爲起點,頂點座標加上法向量的偏移值做爲終點,更新法線顯示數組
//參數1: 三角形數組
//參數2:光源位置
//參數3:法線顯示的頂點數組
SceneTrianglesNormalLinesUpdate(triangles, GLKVector3MakeWithArray(self.baseEffect.light0.position.v), normalLineVertices);
//2.爲extraBuffer從新開闢空間
[self.extraBuffer reinitWithAttribStride:sizeof(GLKVector3) numberOfVertices:NUM_LINE_VERTS bytes:normalLineVertices];
//3.準備繪製數據
[self.extraBuffer prepareToDrawWithAttrib:GLKVertexAttribPosition numberOfCoordinates:3 attribOffset:0 shouldEnable:YES];
//4.修改extraEffect
//法線
/*
指示是否使用常量顏色的布爾值。
若是該值設置爲gl_true,而後存儲在設置屬性的值爲每一個頂點的顏色值。若是該值設置爲gl_false,那麼你的應用將使glkvertexattribcolor屬性提供每頂點顏色數據。默認值是gl_false。
*/
self.extraEffect.useConstantColor = GL_TRUE;
//設置光源顏色爲綠色,畫頂點法線
self.extraEffect.constantColor = GLKVector4Make(0.0f, 1.0f, 0.0f, 1.0f);
//準備繪製-綠色的法線
[self.extraEffect prepareToDraw];
//繪製線段
[self.extraBuffer drawArrayWithMode:GL_LINES startVertexIndex:0 numberOfVertices:NUM_NORMAL_LINE_VERTS];
//設置光源顏色爲黃色,而且畫光源線
//Red+Green =Yellow
self.extraEffect.constantColor = GLKVector4Make(1.0f, 1.0f, 0.0f, 1.0f);
//準備繪製-黃色的光源方向線
[self.extraEffect prepareToDraw];
//(NUM_LINE_VERTS - NUM_NORMAL_LINE_VERTS) = 2 .2點肯定一條線
[self.extraBuffer drawArrayWithMode:GL_LINES startVertexIndex:NUM_NORMAL_LINE_VERTS numberOfVertices:2];
}
//更新法向量
-(void)updateNormals
{
//更新每一個點的平面法向量
SceneTrianglesUpdateFaceNormals(triangles);
[self.vertexBuffer reinitWithAttribStride:sizeof(SceneVertex) numberOfVertices:sizeof(triangles)/sizeof(SceneVertex) bytes:triangles];
}
複製代碼
更新中心頂點
- (IBAction)changeCenterVertexHeight:(UISlider *)sender {
self.centexVertexHeight = sender.value;
}
-(void)setCentexVertexHeight:(GLfloat)centexVertexHeight
{
_centexVertexHeight = centexVertexHeight;
//更新頂點 E
SceneVertex newVertexE = vertexE;
newVertexE.position.z = _centexVertexHeight;
triangles[2] = SceneTriangleMake(vertexD, vertexB, newVertexE);
triangles[3] = SceneTriangleMake(newVertexE, vertexB, vertexF);
triangles[4] = SceneTriangleMake(vertexD, newVertexE, vertexH);
triangles[5] = SceneTriangleMake(newVertexE, vertexF, vertexH);
//更新法線
[self updateNormals];
}
複製代碼
在實際的開發中,咱們在繪圖時是不會把法線繪製在圖形中的,因此繪製法線部分代碼能夠忽略。