07 - OpenGL ES學(xué)習(xí)之基本光照

在學(xué)習(xí)光照之前,我們先要學(xué)習(xí)一下基礎(chǔ)知識。
示例代碼在本系列文章中第一篇里的github倉庫。

顏色

在現(xiàn)實世界中,我們看到一個物體的顏色,是由于它能反射這種顏色的光,比如我們看見一個紅色的蘋果,是因為光照經(jīng)過蘋果表面時,蘋果表面吸收了除了紅色之外的光,而紅光反射后進入人眼,就能看見紅色。

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


截屏2021-12-07 上午10.40.15.png
  • 環(huán)境光照(Ambient Lighting):即使在黑暗的情況下,世界上通常也仍然有一些光亮(月亮、遠處的光),所以物體幾乎永遠不會是完全黑暗的。為了模擬這個,我們會使用一個環(huán)境光照常量,它永遠會給物體一些顏色。
  • 漫反射光照(Diffuse Lighting):模擬光源對物體的方向性影響(Directional Impact)。它是馮氏光照模型中視覺上最顯著的分量。物體的某一部分越是正對著光源,它就會越亮。
  • 鏡面光照(Specular Lighting):模擬有光澤物體上面出現(xiàn)的亮點。鏡面光照的顏色相比于物體的顏色會更傾向于光的顏色。

環(huán)境光照

下面我們來模擬一下環(huán)境光照,環(huán)境光照非常簡單,把環(huán)境光照加入場景很簡單,就是一個光照強度常量因子乘以光的顏色,得到的結(jié)果再乘以物體的顏色,就是片段的顏色了。
下面我們接著用上一篇文章中的代碼來實現(xiàn)為立方體加入環(huán)境光照。這里我們在片段著色器中加入一個LightColor 三分量,這里表示顏色的光,我們傳入白色的環(huán)境光,就是(1.0,1.0,1.0);

#version 300 es
precision mediump float;

uniform vec3 lightColor;

in vec3 fColor;

out vec4 gColor;

void main() {
    //計算環(huán)境光照
    float ambientStrength = 0.2;

    vec3 ambient =  ambientStrength  * lightColor;

    //最終顏色等于環(huán)境光照乘以物體顏色
    vec3 result = ambient * fColor;

    gColor = vec4(result,1.0);

}

環(huán)境光照強度0.2 效果圖:


IMG_6683.PNG

環(huán)境光照強度0.5效果圖:


IMG_6684.PNG

環(huán)境光照強度1.0效果圖:


IMG_6685.PNG

從上面幾種效果可以大致感受到環(huán)境光照強度對物體表面最終成色的影響??梢泽w會到,當(dāng)環(huán)境光照強度越小的情況,物體表面最終成色越趨近于黑色,這個是比較符合現(xiàn)實

漫反射

環(huán)境光照本身不能提供最有趣的結(jié)果,但是漫反射光照就能開始對物體產(chǎn)生顯著的視覺影響了,對于漫反射我們可以這樣理解:漫反射光照使物體上與光線方向越接近的片段能從光源處獲得更多的亮度。為了能夠更好的理解漫反射光照,請看下圖:


漫反射示意圖

圖左上方有一個光源,它所發(fā)出的光線落在物體的一個片段上。我們需要測量這個光線是以什么角度接觸到這個片段的。如果光線垂直于物體表面,這束光對物體的影響會最大化。當(dāng)θ接近于90度時,這束光對物體的影響最小。我們用法向量和代表光照的向量點乘的結(jié)果表示這個影響的值,正正好負責(zé)上述的結(jié)論:當(dāng)光纖垂直于物體表面時,對物體的影響最大。
所以我們?nèi)绻胍嬎懵瓷涔庹?,需要兩個條件:1.垂直于頂點的法向量(單位向量)2.定向的光線:光源的位置與片段的位置之間向量差的方向向量。為了計算這個光線,我們需要光的位置向量和片段的位置向量。
注意: 這里片段的位置還必須是世界坐標中的位置!

下面我們來計算一下漫反射
先看頂點著色器:

#version 300 es

precision mediump float;

in vec4 vPosition;

//垂直于頂點的法向量
in vec3 N;

out vec3 fColor;
out vec3 Normal;
//片段的位置,轉(zhuǎn)化為世界坐標系下的
out vec3 fragPos;

uniform mat4 modelTransform;
uniform mat4 viewTransform;
uniform mat4 projectTransform;

//法線變換矩陣,把法線向量轉(zhuǎn)化到世界空間中,需要運用到法線變換矩陣
uniform mat4 invertTransposeMatrix;

void main(){
    
    mat4 mvpTransform = projectTransform * viewTransform * modelTransform;
    
    gl_Position = mvpTransform * vPosition;
    
    //將頂點位置轉(zhuǎn)換為世界空間坐標
    fragPos = (modelTransform * vPosition).xyz;
    
    //法線向量轉(zhuǎn)換為世界空間坐標
    Normal = (invertTransposeMatrix * vec4(N,0.0)).xyz;
}

這里需要注意的有三點:
1.我們?nèi)〉捻旤c位置需要轉(zhuǎn)換到世界空間坐標下。
2.法線向量的轉(zhuǎn)換:這里需要用法線矩陣來乘以法線向量,將法線向量也轉(zhuǎn)換到世界空間坐標下。法線矩陣被定義為「模型矩陣左上角的逆矩陣的轉(zhuǎn)置矩陣」。至于法線矩陣為什么這樣取值,感興趣的可以去百度一下,這里不做深入探討了。
3.法線向量我們通過屬性Normal傳遞進來,因為我們這里繪制的是立方體,它每個面的法向量的取值非常簡單,比如+X軸的面的法線向量就是(1.0, 0.0, 0.0)。
代碼中是這樣獲取逆轉(zhuǎn)置矩陣:

 //獲取法線變換矩陣
    BOOL canConvert = YES;
    GLKMatrix4 invertTransposeMatrix = GLKMatrix4InvertAndTranspose(modelMatrix, &canConvert);
    
    //如果能獲取到法線矩陣,就加載
    if (canConvert) {
        glUniformMatrix4fv(invertTransposeIndex, 1, GL_FALSE, invertTransposeMatrix.m);
    }

GLKit提供了很多矩陣操作的API,可以提高開發(fā)效率。

接下來看一下頂點著色器有哪些改變:

#version 300 es
precision mediump float;

uniform vec3 lightColor;
//光源位置
uniform vec3 lightPos;

in vec3 Normal;
in vec3 fragPos;

out vec4 gColor;

void main() {
    //計算環(huán)境光照
    float ambientStrength = 0.2;

    vec3 ambient =  ambientStrength  * lightColor;
    
    //計算漫反射
    //計算光線位置
    vec3 norm = normalize(Normal);
    vec3 lightDirection = normalize(lightPos - fragPos);
    
    float diffu = dot(norm,lightDirection);
    vec3 diffuse = diffu * lightColor;

    //最終顏色(這里指定物體顏色為紅色)
    vec3 result = (ambient + diffuse) * vec3(1.0,0.0,0.0);

    gColor = vec4(result,1.0);

}

這里需要注意:
我們傳入的光源位置也是基于世界空間坐標系,光源位置減去頂點位置就是光線的方向向量,然后我們對法線向量和光線方向向量取單位向量,因為我們當(dāng)計算光照時我們通常不關(guān)心一個向量的模長或它的位置,我們只關(guān)心它們的方向。所以,幾乎所有的計算都使用單位向量完成,因為這簡化了大部分的計算(比如點乘)。所以當(dāng)進行光照計算時,確保你總是對相關(guān)向量進行標準化,來保證它們是真正地單位向量。忘記對向量進行標準化是一個十分常見的錯誤。

最后我們看一下加入了環(huán)境光照和漫反射的效果:


IMG_6694.PNG

這里是不是看起來更像一個立方體了,不要著急,后面還有一個很關(guān)鍵的因素:鏡面反射:

鏡面反射

截屏2021-12-07 下午3.50.23.png

在生活中你也許會有過這種經(jīng)歷,窗外的陽光照射到鏡子的時候,你挪動位置看向鏡子,會發(fā)現(xiàn)在一個位置,光線最為刺眼,鏡面反射就是模擬這種效果,如上圖所示。
光線照在物體表面,反射后形成反射光線(我們計為R),人看向物體表面被照射的點形成一個觀察向量,當(dāng)這兩條向量重合的時候(θ = 0°時),人眼看到的光線強度最大,當(dāng)兩條向量夾角θ 越大時,看到的光線強度最小,我們就需要計算這個鏡面分量。
下面我們看一下頂點著色器的變化:

version 300 es

precision mediump float;

uniform vec3 lightColor;
//光源位置
uniform vec3 lightPos;

//觀察點的位置
uniform vec3 viewPos;

in vec3 Normal;
in vec3 fragPos;

out vec4 gColor;

void main() {
//計算環(huán)境光照
float ambientStrength = 0.3;

vec3 ambient =  ambientStrength  * lightColor;

//計算漫反射
//計算光線位置
vec3 norm = normalize(Normal);
vec3 lightDirection = normalize(lightPos - fragPos);

float diffu = dot(norm,lightDirection);
vec3 diffuse = diffu * lightColor;

//計算鏡面光照
//定義一個鏡面強度
float specularStrength = 0.5;

//觀察向量
vec3 viewDirection = normalize(viewPos - fragPos);
//反射向量
vec3 reflectDirection = reflect(-lightDirection,norm);

float spec = pow(max(dot(viewDirection,reflectDirection), 0.0) , 32.0);

vec3 specular = specularStrength * spec * lightColor;

//最終顏色(這里指定物體顏色為紅色)
vec3 result = (ambient + diffuse + specular) * vec3(1.0,0.0,0.0);

gColor = vec4(result,1.0);

}

這里我們要注意以下幾點:
1.計算反射向量的時候,我們第一個傳入的光線向量取值是反的,因為這里lightDirection是頂點到光源的向量,而reflect函數(shù)要求第一個傳入的參數(shù)是光源到頂點的向量,所以要取反。
2.計算鏡面分量:float spec = pow(max(dot(viewDirection,reflectDirection), 0.0) , 32.0);
這里我們?nèi)∮^察向量和反射向量的點乘結(jié)果,只取正值,這個32是高光的反光度(Shininess)。一個物體的反光度越高,反射光的能力越強,散射得越少,高光點就會越小。改變這個反光度,你會看到不同反光度的視覺效果影響。

在加入鏡面光照后,我們改變了光源的位置,這樣是為了使光線向量和觀察向量
的夾角處于一個合適的范圍,以便于我們觀察到添加鏡面光照后效果。

//設(shè)置環(huán)境光顏色為白色
    static float light[3] = {1.0f, 1.0f, 1.0f};
    
    //設(shè)置光源位置(世界空間坐標系)
    static float lightPos[3] = {0.0,1.5,-1.5};
    
    //這里設(shè)置觀察點為攝像機的位置(cameraMatrix里eye的位置)
    static float viewPos[3] = {0.0,0.0,2.0};

最終效果如下圖:


IMG_6695.PNG
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容