Phong 照明模型

注意:此示例為 WIP,將使用圖表,影象,更多示例等進行更新。

什麼是 Phong?

Phong 是一個非常基本,但看起來很真實的表面光模型,有三個部分:環境光,漫反射光和鏡面光。

環境照明:

環境照明是理解和計算的三個部分中最簡單的。環境照明是一種充滿場景的光線,可以在各個方向均勻地照亮物體。

環境照明中的兩個變數是環境強度和環境顏色。在片段著色器中,以下內容適用於環境:

in vec3 objColor;

out vec3 finalColor;

uniform vec3 lightColor;

void main() {
   float ambientStrength = 0.3f;
   vec3 ambient = lightColor * ambientStrength;
   finalColor = ambient * objColor;
}

漫射照明:

漫射照明比環境光稍微複雜一些。漫射光是定向光,基本上意味著面向光源的面將被更好地照亮,並且由於光如何擊中它們而指向的面將變暗。

注意:漫反射光照需要使用每個面的法線,我不會在這裡展示如何計算。如果你想學習如何操作,請檢視 3D 數學頁面。

為了模擬計算機圖形中光的反射,使用雙向反射分佈函式(BRDF)。BRDF 是一種函式,它給出了沿出射方向反射的光與從入射方向入射的光之間的關係。

完美的漫反射表面具有 BRDF,其對於所有入射和出射方向具有相同的值。這大大減少了計算,因此它通常用於模擬漫射表面,因為它在物理上是合理的,即使在現實世界中沒有純粹的漫射材料。這種 BRDF 被稱為朗伯反射,因為它符合蘭伯特的餘弦定律。

朗伯反射通常用作漫反射的模型。該技術使得所有閉合多邊形(例如 3D 網格內的三角形)在渲染時在所有方向上均勻地反射光。根據法向量和光向量之間的角度計算擴散係數。

f_Lambertian = max( 0.0, dot( N, L )

其中 N 是表面的法向量,L 是朝向光源的向量。

這個怎麼運作

通常,2 個向量的積等於 2 個向量之間的角度的餘弦乘以兩個向量的幅度(長度)。

dot( A, B ) == length( A ) * length( B ) * cos( angle_A_B ) 

由此得出,2 個單位向量的積等於 2 個向量之間角度的餘弦,因為單位向量的長度是 1。

uA = normalize( A )
uB = normalize( B )
cos( angle_A_B ) == dot( uA, uB )

StackOverflow 文件

如果我們看一下角度 -90°和 90°之間的 cos(x) 函式,那麼我們可以看到它在 0°的角度下最大為 1,在 90°的角度下它下降到 0 和 -90°。

StackOverflow 文件

這種行為正是我們想要的反射模型。當表面的光動量和光源的方向是相同的方向(角度在 0°之間)時,我們想要一個最大的反射。相反,如果向量是正交歸一化的(其間的角度是 90°),那麼我們想要最小的反射,並且我們希望在 0°和 90°的兩個邊界之間平滑且連續的功能執行。

StackOverflow 文件

如果每個頂點計算光,則計算基元的每個角的反射。在基元之間,反射根據其重心座標進行插值。在球面上看到產生的反射:

StackOverflow 文件

好的,所以從片段著色器開始,我們需要四個輸入。

  • 頂點法線(應該在緩衝區中並由頂點屬性指標指定)
  • 片段位置(應從頂點著色器輸出到 frag 著色器)
  • 光源位置(均勻)
  • 淺色(均勻)
in vec3 normal;
in vec3 fragPos;
    
out vec3 finalColor;
    
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 objColor;

主要內部是我們需要做數學的地方。漫射照明的整個概念基於法線方向和光線方向之間的角度。角度越大,直到 90°的光線越少,根本沒有光線。

在我們開始計算光量之前,我們需要光方向向量。這可以通過簡單地從片段位置減去光位置來檢索,該片段位置從指向片段位置的光位置返回向量。

vec3 lightDir = lightPos-fragPos;

此外,繼續並規範化 normallightDir 向量,使它們的長度相同。

normal  = normalize(normal);
lightDir = normalize(lightDir);

現在我們有了向量,我們可以計算它們之間的差異。為此,我們將使用點積函式。基本上,這需要 2 個向量並返回形成的角度的 cos()。這是完美的,因為在 90 度它將產生 0 並且在 0 度它將產生 1.結果,當光直接指向物體時它將完全點亮,反之亦然。

float diff = dot(normal, lightDir);

我們還需要對計算的數字做一件事,我們需要確保它總是正面的。如果你考慮一下,負數在上下文中沒有意義,因為這意味著光在臉後面。我們可以使用 if 語句,或者我們可以使用 max() 函式返回最多兩個輸入。

diff = max(diff, 0.0);

完成後,我們現在可以計算片段的最終輸出顏色。

vec3 diffuse = diff * lightColor;
finalColor = diffuse * objColor;

它應該如下所示: StackOverflow 文件

高光照明:

在進展中工作,稍後再回來檢視。

綜合

正在進行中,請稍後再回來檢視。

下面的程式碼和影象顯示了這三個照明概念的組合。