馮氏光照模型
首先,在現(xiàn)實世界的光照是極其復(fù)雜的,而且會受到諸多因素的影響,這是我們有限的計算能力所無法模擬的。因此OpenGL的光照使用的是簡化的模型,對現(xiàn)實的情況進(jìn)行近似,這樣處理起來會更容易一些。這些光照模型都是基于我們對光的物理特性的理解。其中一個模型被稱為馮氏光照模型(Phong Lighting Model)。馮氏光照模型的主要結(jié)構(gòu)由3個元素組成:環(huán)境(Ambient)光照、漫反射(Diffuse)光照和鏡面(Specular)光照。
下面我們用示例圖來看一下這幾種光照:

環(huán)境(Ambient)光照:即使在最黑暗的正常環(huán)境下,世界上依然會存在一些光亮,也就是說物體幾乎永遠(yuǎn)不會是完全黑暗的。環(huán)境光照一般是由室外的太陽通過各種折射來讓我們看到,這樣的光是沒有任何起點,沒有方向的光。主要通過設(shè)置物體顏色來獲取。
漫反射(Diffuse)光照:模擬的是光源對物體方向的影響,一個物體面向光源的時候,朝向光的那個面會亮一點,物體背面和其他面會暗一點。
鏡面(Specular)光照:模擬的是有光澤物體上面出現(xiàn)的亮點。鏡面光照的顏色相比于物體的顏色會更傾向于光的顏色。
光照基礎(chǔ)
OpenGL在處理光照時把光照系統(tǒng)分為三部分:分別是光源、材質(zhì)和光照環(huán)境。光源就是光的來源,可以是前面所說的太陽或者電燈,蠟燭等。材質(zhì)是指接受光照的各種物體的表面,由于物體如何反射光線只由物體表面決定,材質(zhì)特點就決定了物體反射光線的特點。光照環(huán)境是指一些額外的參數(shù),它們將影響最終的光照畫面,比如一些光線經(jīng)過多次反射后,已經(jīng)無法分清它究竟是由哪個光源發(fā)出,這時,指定一個環(huán)境亮度參數(shù),可以使最后形成的畫面更接近于真實情況。
在物理學(xué)中,光線如果射入理想的光滑平面,則反射后的光線是很規(guī)則的(這樣的反射稱為鏡面反射)。光線如果射入粗糙的、不光滑的平面,則反射后的光線是雜亂的(這樣的反射稱為漫反射)?,F(xiàn)實生活中的物體在反射光線時,并不是絕對的鏡面反射或漫反射,但可以看成是這兩種反射的疊加。對于光源發(fā)出的光線,可以分別設(shè)置其經(jīng)過鏡面反射和漫反射后的光線強(qiáng)度。對于被光線照射的材質(zhì),也可以分別設(shè)置光線經(jīng)過鏡面反射和漫反射后的光線強(qiáng)度。這些因素綜合起來,就形成了最終的光照效果。
光照特性
1.發(fā)射光:由物體自身發(fā)光
2.環(huán)境光:就是在環(huán)境中充分散射的光,而且無法分辨光的方向
3.漫反射光:光線來自某個方向,但是在物體上各個方向反射
4.鏡面高光:光線來自一個特定的方向,然后在物體表面上以一個特定的方向反射出去
材質(zhì)屬性
1.泛射材質(zhì):光線直射,反射率較高
2.漫反射材質(zhì):需要考慮光的入射角和反射角的
3.鏡面反射材質(zhì):斑點
4.發(fā)射材質(zhì):物體本身就可以發(fā)光的材質(zhì)
光照計算
1.環(huán)境光的計算
環(huán)境光是不來自任何特定方向的光,在整個場景中經(jīng)典光照模型把它當(dāng)成一個常量,組成一個合適的第一近似值來縮放場景中的光照部分。
環(huán)境光 = 光源的環(huán)境光顏色 * 物體的材質(zhì)顏色

varying vec3 objectColor;
void main()
{
//?至少有%10的光找到物體所有?面
float ambientStrength = 0.1;
//環(huán)境光顏?色
vec3 ambient = ambientStrength * lightColor;
//最終顏?色 = 環(huán)境光顏?色 * 物體顏?色
vec3 result = ambient * objectColor;
gl_FragColor = vec4(result, 1.0);
}
2.發(fā)射光的計算
如果這個物體本身就是有顏色的,比如說夜明珠,那么這個時候這個光就是這個物體材質(zhì)的顏色
發(fā)射顏色 = 物體的反射材質(zhì)顏色
3.漫反射光的計算
漫反射光是散射在各個方向上的均勻的表面特定光源,漫反射光依賴于表面法線方向和光源方向來計算,但是沒有包含視線方向,它同樣依賴于表面的顏色。


光線照射到物體表面,決定了物體表面的光照強(qiáng)度,光照強(qiáng)度是光本身強(qiáng)度和光線與物體表面法線夾角cos的乘積

漫反射顏色 = 光源的漫反射光顏色 × 物體的漫反射材質(zhì)顏色 × 漫反射因子
DiffuseFactor = max(0, dot(N, L))
uniform vec3 lightColor; //光源色
uniform vec3 lightPo; //光源位置
uniform vec3 objectColor; //物體?色
uniform vec3 viewPo; //物體位置
varying vec3 outNormal; //傳?當(dāng)前頂點平面的法向量
//確保法線為單位向量量
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.鏡面光照計算
鏡面光是由表面直接反射的高亮光,這個高亮光就像鏡子一樣跟表面材質(zhì)多少有關(guān)。

鏡面反射顏色 = 光源的鏡面光顏色 × 物體的鏡面材質(zhì)顏色 × 鏡面反射因子鏡面反射因子
SpecularFactor = power(max(0,dot(N,R)),shininess)dot(N,R):H,R的點積幾何意義:平?線與法線夾角的cos值
shiniess : ?光的反光度/發(fā)光值;(值越大反射度越強(qiáng))
一個物體的發(fā)光值越高,反射光的能力越強(qiáng),散射得越少,高光點越小。在下面的圖片里,你會看到不同發(fā)光值對視覺(效果)的影響:

鏡面光的GLSL實現(xiàn)代碼:
//鏡?面強(qiáng)度
float specularStrength = 0.5;
//頂點指向觀察點的單位向量量
vec3 viewDir = normalize(viewPo - FragPo);
//求得光線 在 頂點的反射線(傳?入光源指向頂點的向量量)
vec3 reflectDir = reflect(-lightDir ,outNormal);
// 求得夾?角cos值 取256次冪 注意 pow(float,float)函數(shù)參數(shù)類型 float spec = pow(max(dot(viewDir, reflectDir),0.0),256.0);
vec3 specular = specularStrength * spec * lightColor;
我們都知道光的傳播是會衰減的,所以光照顏色的公式可總結(jié)為:
光照顏色 =(環(huán)境顏色 + 漫反射顏色 + 鏡?反射顏色)* 衰減因子

衰減因子 = 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));
距離衰減常量,線性衰減常量和?次衰減常量均為常量值.
環(huán)境光,漫反射光和鏡面光的強(qiáng)度都會受距離的增大?衰減,只有發(fā)射光和全局環(huán)境光的強(qiáng)度不會受影響.
聚光燈夾角cos值 = power(max(0,dot(單位光源位置,單位光線向量)),聚光燈指數(shù));
單位光線向量是從光源指向頂點的單位向量
聚光燈指數(shù),表示聚光燈的亮度程度
公式解讀:單位光源位置 * 單位光線向量點積的聚光燈指數(shù)次?。

聚光燈因子 = clamp((外環(huán)的聚光燈角度cos值 - 當(dāng)前頂點的聚光燈角度cos值)/ (外環(huán)的聚光燈角度cos值- 內(nèi)環(huán)聚光燈的角度的cos值),0,1);
//聚光燈過渡計算
//(?些復(fù)雜的計算操作 應(yīng)該讓CPU做,提?效率,不變的量也建議外部傳輸,避免重復(fù)計算)
//內(nèi)錐角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));
//內(nèi)外錐?角cos差值
float epsilon = inCutOff - outCutOff;
//clamp(a,b,c);若b<a<c 則函數(shù)返回值為a
//若不是,則返回值最小為b ,最大為c
// (theta - outCutOff)/epsilon 若theta的角度?于內(nèi)錐角 則其值 >=1
//若theta的?度大于外錐角,則其值<=0 這樣光線就在內(nèi)外錐角之間平滑變化.
float intensity = clamp((theta - outCutOff)/epsilon, 0.0,1.0)
光照顏色的最終公式為:
光照顏色 = 發(fā)射顏色 + 全局環(huán)境顏色 + (環(huán)境顏色 + 漫反射顏色 + 鏡?反射顏色) * 聚光燈效果 * 衰減因子


1.設(shè)置OpenGL ES
@property(nonatomic,strong)EAGLContext *mContext;
//基本Effect 繪圖
@property(nonatomic,strong)GLKBaseEffect *baseEffect;
//額外Effect 輔助線段
@property(nonatomic,strong)GLKBaseEffect *extraEffect;
//頂點緩存區(qū) (頂點,顏色,紋理, 法線...)
@property(nonatomic,strong)AGLKVertexAttribArrayBuffer *vertexBuffer;
//法線位置緩存區(qū)(法線輔助線段也有頂點)
@property(nonatomic,strong)AGLKVertexAttribArrayBuffer *extraBuffer;
//是否繪制法線
@property(nonatomic,assign)BOOL shouldDrawNormals;
//中心點的高 默認(rèn)在(0,0,0)
@property(nonatomic,assign) GLfloat centexVertexHeight;
{
//三角形-8面
SceneTriangle triangles[NUM_FACES];
}
設(shè)置GLKitView并設(shè)置上下文
//1.新建OpenGL ES 上下文
self.mContext = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
//2.設(shè)置GLKView
GLKView *view = (GLKView *)self.view;
view.context = self.mContext;
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
[EAGLContext setCurrentContext:self.mContext];
2. 設(shè)置金字塔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);
//世界坐標(biāo)中的光的位置。
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.調(diào)整模型矩陣,更好的觀察
//可以嘗試不執(zhí)行這段代碼,改為false
if (true) {
//圍繞x軸旋轉(zhuǎn)-60度
//返回一個4x4矩陣進(jìn)行繞任意矢量旋轉(zhuǎn)
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(-60.0f), 1.0f, 0.0f, 0.0f);
//圍繞z軸,旋轉(zhuǎn)-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);
//設(shè)置baseEffect,extraEffect 模型矩陣
self.baseEffect.transform.modelviewMatrix = modelViewMatrix;
self.extraEffect.transform.modelviewMatrix = modelViewMatrix;
}
3.設(shè)置頂點
//確定圖形的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);
//初始化緩存區(qū)
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];
//準(zhǔn)備繪制頂點數(shù)據(jù)
[self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribPosition numberOfCoordinates:3 attribOffset:offsetof(SceneVertex,position)shouldEnable:YES];
//準(zhǔn)備繪制光照數(shù)據(jù)
[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.以每個頂點的坐標(biāo)為起點,頂點坐標(biāo)加上法向量的偏移值作為終點,更新法線顯示數(shù)組
//參數(shù)1: 三角形數(shù)組
//參數(shù)2:光源位置
//參數(shù)3:法線顯示的頂點數(shù)組
SceneTrianglesNormalLinesUpdate(triangles, GLKVector3MakeWithArray(self.baseEffect.light0.position.v), normalLineVertices);
//2.為extraBuffer重新開辟空間
[self.extraBuffer reinitWithAttribStride:sizeof(GLKVector3) numberOfVertices:NUM_LINE_VERTS bytes:normalLineVertices];
//3.準(zhǔn)備繪制數(shù)據(jù)
[self.extraBuffer prepareToDrawWithAttrib:GLKVertexAttribPosition numberOfCoordinates:3 attribOffset:0 shouldEnable:YES];
//4.修改extraEffect
//法線
/*
指示是否使用常量顏色的布爾值。
如果該值設(shè)置為gl_true,然后存儲在設(shè)置屬性的值為每個頂點的顏色值。如果該值設(shè)置為gl_false,那么你的應(yīng)用將使glkvertexattribcolor屬性提供每頂點顏色數(shù)據(jù)。默認(rèn)值是gl_false。
*/
self.extraEffect.useConstantColor = GL_TRUE;
//設(shè)置光源顏色為綠色,畫頂點法線
self.extraEffect.constantColor = GLKVector4Make(0.0f, 1.0f, 0.0f, 1.0f);
//準(zhǔn)備繪制-綠色的法線
[self.extraEffect prepareToDraw];
//繪制線段
[self.extraBuffer drawArrayWithMode:GL_LINES startVertexIndex:0 numberOfVertices:NUM_NORMAL_LINE_VERTS];
//設(shè)置光源顏色為黃色,并且畫光源線
//Red+Green =Yellow
self.extraEffect.constantColor = GLKVector4Make(1.0f, 1.0f, 0.0f, 1.0f);
//準(zhǔn)備繪制-黃色的光源方向線
[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];
}
在實際的開發(fā)中,我們在繪圖時是不會把法線繪制在圖形中的,所以繪制法線部分代碼可以忽略。
