
顏色
我們?cè)诂F(xiàn)實(shí)生活中看到某一物體的顏色并不是這個(gè)物體真正擁有的顏色,而是它所反射的(Reflected)顏色。當(dāng)我們?cè)贠penGL中創(chuàng)建一個(gè)光源時(shí),我們希望給光源一個(gè)顏色。當(dāng)我們把光源的顏色與物體的顏色值相乘,所得到的就是這個(gè)物體所反射的顏色(也就是我們所感知到的顏色)。
顏色計(jì)算 把光源的顏色與物體的顏色值相乘
基礎(chǔ)光照
現(xiàn)實(shí)的光照非常復(fù)雜,因此OpenGL的光照使用的是簡(jiǎn)化的模型,對(duì)現(xiàn)實(shí)的情況進(jìn)行近似,這樣處理起來(lái)會(huì)更容易一些,而且看起來(lái)也差不多一樣。其中一個(gè)模型被稱為馮氏光照模型(Phong Lighting Model)。
馮氏光照模型的主要結(jié)構(gòu)由3個(gè)分量組成:環(huán)境(Ambient)、漫反射 (Diffuse)和鏡面(Specular)光照
- 環(huán)境光照(Ambient Lighting):物體幾乎永遠(yuǎn)不會(huì)是完全黑暗的。所以環(huán)境光照一般是個(gè)常量
- 漫反射光照(Diffuse Lighting):模擬光源對(duì)物體的方向性影響,物體的某一部分越是正對(duì)著光源,它就會(huì)越亮。
- 鏡面光照(Specular Lighting):模擬有光澤物體上面出現(xiàn)的亮點(diǎn)。鏡面光照的顏色相比于物體的顏色會(huì)更傾向于光的顏色。
光照公式
最終片段顏色:環(huán)境顏色+漫反射顏色+鏡面反射顏色
- 環(huán)境顏色 = 光源的環(huán)境光顏色 × 物體的環(huán)境材質(zhì)顏色
- 漫反射顏色 = 光源的漫反射光顏色 × 物體的漫反射材質(zhì)顏色 × 漫反射因子
漫反射
DiffuseFactor = max(0, dot(N, L)) -
鏡面反射顏色 = 光源的鏡面光顏色 × 物體的鏡面材質(zhì)顏色 × 鏡面反射因子
鏡面光照
R=reflect(L, N)
SpecularFactor = power(max(0, dot(R,V)), shininess)
我們的測(cè)試模型如下紅色正方體
下面是正常正方體光照?qǐng)D

頂點(diǎn)著色器
attribute vec3 beginPostion; ///開(kāi)始位置
attribute vec3 vertexColor;
uniform mat4 u_mvpMatrix;
varying lowp vec3 vary_vertexColor;
void main(){
gl_Position =u_mvpMatrix * vec4(beginPostion, 1.0);
vary_vertexColor = vertexColor;
}
片段著色器
precision lowp float;
varying lowp vec3 vary_vertexColor;
void main()
{
gl_FragColor =vec4( vary_vertexColor,1.0);
}
環(huán)境光照

我們使用一個(gè)很小的常量(光照)顏色,添加到物體片段的最終顏色中,這樣子的話即便場(chǎng)景中沒(méi)有直接的光源也能看起來(lái)存在有一些發(fā)散的光。
把環(huán)境光照添加到場(chǎng)景里非常簡(jiǎn)單。我們用光的顏色乘以一個(gè)很小的常量環(huán)境因子,再乘以物體的顏色,然后將最終結(jié)果作為片段的顏色:
頂點(diǎn)著色器
attribute vec3 beginPostion; ///開(kāi)始位置
attribute vec3 vertexColor;
uniform mat4 u_mvpMatrix;
varying lowp vec3 vary_vertexColor;
void main(){
gl_Position =u_mvpMatrix * vec4(beginPostion, 1.0);
vary_vertexColor = vertexColor;
}
片段著色器
precision lowp float;
varying lowp vec3 vary_vertexColor;
uniform vec4 ambientLight; ///環(huán)境光
void main()
{
float ambientStrength = 0.2;
vec4 ambient = ambientStrength * ambientLight;
gl_FragColor = ambient * vec4( vary_vertexColor,1.0);;
}
漫反射光照

漫反射光照使物體上與光線方向越接近的片段能從光源處獲得更多的亮度。為了能夠更好的理解漫反射光照,請(qǐng)看下圖:

計(jì)算漫反射光照需要:
+ 法向量:一個(gè)垂直于頂點(diǎn)表面的向量。
+ 定向的光線:作為光源的位置與片段的位置之間向量差的方向向量。為了計(jì)算這個(gè)光線,我們需要光的位置向量和片段的位置向量。
法向量一般作為頂點(diǎn)的屬性傳入頂點(diǎn)著色器中
attribute vec3 aNormal; //法向量
所有光照的計(jì)算都是在片段著色器里進(jìn)行,所以我們需要將法向量由頂點(diǎn)著色器傳遞到片段著色器。我們這么做:
varying lowp vec3 normal;
void main(){
normal = aNormal;;
}
我們現(xiàn)在對(duì)每個(gè)頂點(diǎn)都有了法向量,但是我們?nèi)匀恍枰庠吹奈恢孟蛄亢推蔚奈恢孟蛄?。由于光源的位置是一個(gè)靜態(tài)變量,我們可以簡(jiǎn)單地在片段著色器中把它聲明為uniform。然后在渲染循環(huán)中(渲染循環(huán)的外面也可以,因?yàn)樗粫?huì)改變)更新uniform。
最后,我們還需要片段的位置。我們會(huì)在世界空間中進(jìn)行所有的光照計(jì)算,因此我們需要一個(gè)在世界空間中的頂點(diǎn)位置。我們可以通過(guò)把頂點(diǎn)位置屬性乘以模型矩陣(不是觀察和投影矩陣)來(lái)把它變換到世界空間坐標(biāo)。這個(gè)在頂點(diǎn)著色器中很容易完成,所以我們聲明一個(gè)輸出變量,并計(jì)算它的世界空間坐標(biāo):
attribute vec3 beginPostion; ///開(kāi)始位置
attribute vec3 vertexColor;
attribute vec3 aNormal; //法向量
uniform mat4 u_mvpMatrix;
uniform mat4 u_model;
uniform mat4 u_inverModel;
varying lowp vec3 normal;
varying lowp vec3 FragPos;
varying lowp vec3 vary_vertexColor;
void main(){
gl_Position =u_mvpMatrix *u_model* vec4(beginPostion, 1.0);
vary_vertexColor = vertexColor;
FragPos = vec3(u_model * vec4(beginPostion, 1.0));
normal = aNormal;;
}
最后,在片段著色器中添加相應(yīng)的輸入變量。現(xiàn)在,所有需要的變量都設(shè)置好了,我們可以在片段著色器中添加光照計(jì)算了。
我們需要做的第一件事是計(jì)算光源和片段位置之間的方向向量。前面提到,光的方向向量是光源位置向量與片段位置向量之間的向量差。我們希望確保所有相關(guān)向量最后都轉(zhuǎn)換為單位向量,所以我們把法線和最終的方向向量都進(jìn)行標(biāo)準(zhǔn)化:
void main()
{
vec3 norm = normalize(normal);
vec3 lightDir = normalize(lightPos - FragPos);
}
一步,我們對(duì)norm和lightDir向量進(jìn)行點(diǎn)乘,計(jì)算光源對(duì)當(dāng)前片段實(shí)際的漫發(fā)射影響。結(jié)果值再乘以光的顏色,得到漫反射分量。兩個(gè)向量之間的角度越大,漫反射分量就會(huì)越小:
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
如果兩個(gè)向量之間的角度大于90度,點(diǎn)乘的結(jié)果就會(huì)變成負(fù)數(shù),這樣會(huì)導(dǎo)致漫反射分量變?yōu)樨?fù)數(shù)。為此,我們使用max函數(shù)返回兩個(gè)參數(shù)之間較大的參數(shù),從而保證漫反射分量不會(huì)變成負(fù)數(shù)?,F(xiàn)在我們有了環(huán)境光分量和漫反射分量,我們把它們相加,然后把結(jié)果乘以物體的顏色,來(lái)獲得片段最后的輸出顏色。
vec3 diffuse = diff * lightColor;
gl_FragColor =vec4(diffuse,1.0) * vec4( vary_vertexColor,1.0);;
現(xiàn)在我們已經(jīng)把法向量從頂點(diǎn)著色器傳到了片段著色器??墒牵壳捌沃骼锏挠?jì)算都是在世界空間坐標(biāo)中進(jìn)行的。所以,我們是不是應(yīng)該把法向量也轉(zhuǎn)換為世界空間坐標(biāo)?基本正確,但是這不是簡(jiǎn)單地把它乘以一個(gè)模型矩陣就能搞定的。
首先,法向量只是一個(gè)方向向量,不能表達(dá)空間中的特定位置。同時(shí),法向量沒(méi)有齊次坐標(biāo)(頂點(diǎn)位置中的w分量)。這意味著,位移不應(yīng)該影響到法向量。因此,如果我們打算把法向量乘以一個(gè)模型矩陣,我們就要從矩陣中移除位移部分,只選用模型矩陣左上角3×3的矩陣(注意,我們也可以把法向量的w分量設(shè)置為0,再乘以4×4矩陣;這同樣可以移除位移)。對(duì)于法向量,我們只希望對(duì)它實(shí)施縮放和旋轉(zhuǎn)變換。
其次,如果模型矩陣執(zhí)行了不等比縮放,頂點(diǎn)的改變會(huì)導(dǎo)致法向量不再垂直于表面了。因此,我們不能用這樣的模型矩陣來(lái)變換法向量。下面的圖展示了應(yīng)用了不等比縮放的模型矩陣對(duì)法向量的影響:
每當(dāng)我們應(yīng)用一個(gè)不等比縮放時(shí)(注意:等比縮放不會(huì)破壞法線,因?yàn)榉ň€的方向沒(méi)被改變,僅僅改變了法線的長(zhǎng)度,而這很容易通過(guò)標(biāo)準(zhǔn)化來(lái)修復(fù)),法向量就不會(huì)再垂直于對(duì)應(yīng)的表面了,這樣光照就會(huì)被破壞。
修復(fù)這個(gè)行為的訣竅是使用一個(gè)為法向量專(zhuān)門(mén)定制的模型矩陣。這個(gè)矩陣稱之為法線矩陣(Normal Matrix),「模型矩陣左上角的逆矩陣的轉(zhuǎn)置矩陣」。
在頂點(diǎn)著色器中,我們可以使用inverse和transpose函數(shù)自己生成這個(gè)法線矩陣,這兩個(gè)函數(shù)對(duì)所有類(lèi)型矩陣都有效。注意我們還要把被處理過(guò)的矩陣強(qiáng)制轉(zhuǎn)換為3×3矩陣,來(lái)保證它失去了位移屬性以及能夠乘以vec3的法向量。
uniform mat4 u_inverModel;
void main(){
normal = mat3(u_inverModel) * aNormal;;
}
給頂點(diǎn)傳值代碼
bool isSuccess = YES;
mode = GLKMatrix4InvertAndTranspose(mode,&isSuccess);
glUniformMatrix4fv(self.bindObject->uniforms[CubeDiffuseLightingUniformLocationInvermodel], 1, 0,mode.m);
如果你進(jìn)行了不等比縮放,使用法線矩陣去乘以法向量就是必不可少的了。即使是對(duì)于著色器來(lái)說(shuō),逆矩陣也是一個(gè)開(kāi)銷(xiāo)比較大的運(yùn)算,因此,對(duì)于一個(gè)對(duì)效率有要求的應(yīng)用來(lái)說(shuō),在繪制之前你最好用CPU計(jì)算出法線矩陣,然后通過(guò)uniform把值傳遞給著色器(像模型矩陣一樣)
完整shader 代碼
頂點(diǎn)著色器
precision lowp float;
uniform vec3 lightPos; ///光源位置
uniform vec3 lightColor; ///光源位置
varying lowp vec3 normal;
varying lowp vec3 FragPos;
varying lowp vec3 vary_vertexColor;
void main()
{
vec3 norm = normalize(normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
///環(huán)境光的使用
gl_FragColor =vec4(diffuse,1.0) * vec4( vary_vertexColor,1.0);;
}
片段著色器
attribute vec3 beginPostion; ///開(kāi)始位置
attribute vec3 vertexColor;
attribute vec3 aNormal; //法向量
uniform mat4 u_mvpMatrix;
uniform mat4 u_model;
uniform mat4 u_inverModel;
varying lowp vec3 normal;
varying lowp vec3 FragPos;
varying lowp vec3 vary_vertexColor;
void main(){
gl_Position =u_mvpMatrix *u_model* vec4(beginPostion, 1.0);
vary_vertexColor = vertexColor;
FragPos = vec3(u_model * vec4(beginPostion, 1.0));
normal = mat3(u_inverModel) * aNormal;;
}
- 這里沒(méi)有探討具體求解法線矩陣
鏡面光照

和漫反射光照一樣,鏡面光照也是依據(jù)光的方向向量和物體的法向量來(lái)決定的,但是它也依賴于觀察方向,例如玩家是從什么方向看著這個(gè)片段的。鏡面光照是基于光的反射特性。如果我們想象物體表面像一面鏡子一樣,那么,無(wú)論我們從哪里去看那個(gè)表面所反射的光,鏡面光照都會(huì)達(dá)到最大化。你可以從下面的圖片看到效果:

從上圖中我們知道求鏡面光照需要的參數(shù)有
光源位置
法向量
-
眼睛的位置
我們通過(guò)反射法向量周?chē)獾姆较騺?lái)計(jì)算反射向量。然后我們計(jì)算反射向量和視線方向的角度差,如果夾角越小,那么鏡面光的影響就會(huì)越大。它的作用效果就是,當(dāng)我們?nèi)タ垂獗晃矬w所反射的那個(gè)方向的時(shí)候,我們會(huì)看到一個(gè)高光。
觀察向量是鏡面光照附加的一個(gè)變量,我們可以使用觀察者世界空間位置和片段的位置來(lái)計(jì)算它。之后,我們計(jì)算鏡面光強(qiáng)度,用它乘以光源的顏色,再將它加上環(huán)境光和漫反射分量。
為了得到觀察者的世界空間坐標(biāo),我們簡(jiǎn)單地使用攝像機(jī)對(duì)象的位置坐標(biāo)代替(它當(dāng)然就是觀察者)。所以我們把另一個(gè)uniform添加到片段著色器,把相應(yīng)的攝像機(jī)位置坐標(biāo)傳給片段著色器:
uniform vec3 viewPos;
現(xiàn)在我們已經(jīng)獲得所有需要的變量,可以計(jì)算高光強(qiáng)度了。首先,我們定義一個(gè)鏡面強(qiáng)度(Specular Intensity)變量,給鏡面高光一個(gè)中等亮度顏色,讓它不要產(chǎn)生過(guò)度的影響。
float specularStrength = 0.5;
下一步,我們計(jì)算視線方向向量,和對(duì)應(yīng)的沿著法線軸的反射向量:
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
需要注意的是我們對(duì)lightDir向量進(jìn)行了取反。reflect函數(shù)要求第一個(gè)向量是從光源指向片段位置的向量,但是lightDir當(dāng)前正好相反,是從片段指向光源(由先前我們計(jì)算lightDir向量時(shí),減法的順序決定)。為了保證我們得到正確的reflect向量,我們通過(guò)對(duì)lightDir向量取反來(lái)獲得相反的方向。第二個(gè)參數(shù)要求是一個(gè)法向量,所以我們提供的是已標(biāo)準(zhǔn)化的norm向量。
剩下要做的是計(jì)算鏡面分量。下面的代碼完成了這件事:
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;
我們先計(jì)算視線方向與反射方向的點(diǎn)乘(并確保它不是負(fù)值),然后取它的32次冪。這個(gè)32是高光的反光度(Shininess)。一個(gè)物體的反光度越高,反射光的能力越強(qiáng),散射得越少,高光點(diǎn)就會(huì)越小。
shader 代碼
attribute vec3 beginPostion; ///開(kāi)始位置
attribute vec3 vertexColor;
attribute vec3 aNormal; //法向量
uniform mat4 u_mvpMatrix;
uniform mat4 u_model;
uniform mat4 u_inverModel;
varying lowp vec3 normal;
varying lowp vec3 FragPos;
varying lowp vec3 vary_vertexColor;
void main(){
gl_Position =u_mvpMatrix *u_model* vec4(beginPostion, 1.0);
vary_vertexColor = vertexColor;
FragPos = vec3(u_model * vec4(beginPostion, 1.0));
normal = mat3(u_inverModel) * aNormal;;
}
precision lowp float;
uniform vec3 lightPos; ///光源位置
uniform vec3 lightColor; ///光源位置
uniform vec3 viewPos;
varying lowp vec3 normal;
varying lowp vec3 FragPos;
varying lowp vec3 vary_vertexColor;
void main()
{
float specularStrength = 0.5;
vec3 norm = normalize(normal);
vec3 lightDir = normalize(lightPos - FragPos);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 256.0);
vec3 specular = specularStrength * spec * lightColor;
///環(huán)境光的使用
gl_FragColor =vec4(specular,1.0) * vec4( vary_vertexColor,1.0);;
}