本文只要解決一個問題:
如何使用Blinn-Phong光照模型使高光更柔和、更平滑?
引言
在光照文章中,我們介紹了Phong光照模型來模擬真實的場景。看起來效果不錯,但還是有點細微差別,啥差別呢,別急,你往下看。
Phong光照模型的缺陷
在開始之前,請先下載這里的源碼配置一個新工程。編譯運行一下代碼,你會看到這樣的效果:
這就是Phong光照模型的缺陷。什么,你說你看到的不是這樣?哦,忘了說了,要把鏡面高光的光澤度信息改成1.0。在光澤度為1.0的情況下,Phong光照模型的缺陷就暴露無遺了。
啥是光澤度?這問題問的好,筆者也快忘了,趕緊翻翻之前寫過的文章。哦,原來是高光的集中度。找到片元著色器中的這一行代碼pow (max(dot(viewDir, reflectDir), 0.0), 32.0);,把32.0改成1.0你就能看到上圖中的效果了。Phong光照模型的缺陷也只有在這種特定情況下才會顯現。
可以非常清楚地看到,在邊緣部分,鏡面高光的區域被立刻切斷了!出現這種問題的原因是,在Phong光照模型中,觀察向量和反射向量之間的夾角不允許大于90度。一旦夾角大于90的,觀察方向和反射方向的點積就會變負數,而我們在計算的時候會把這個負數截斷到0,這樣,鏡面高光部分就完全看不到了。
你可能會這樣想,我們的觀察向量和反射方向夾角不會大于90度的,對吧?
大錯特錯!要知道,我們的觀察角度可以是四面八方的,沒有什么法律規定我們只能沿著反射方向觀察,如果我們觀察的方向和光源是在同一邊呢?這樣視線方向和反射方向的夾角就可能大于90度,產生不真實的效果:
從圖上看就非常明顯了。當我們的觀察方向在法向量右邊時,沒問題,反射方向和視線的夾角theta不會大于90度。但是,如果我們的觀察方向在法向量的左邊,我們的觀察方向和反射方向的夾角就可能大于90度了。這種情況下,誤差就會產生。
Blinn-Phong光照模型
1977年,一個名叫James F. Blinn的人提出了另一種計算鏡面高光的方法。運用這種方法的Phong光照模型就稱為Blinn-Phong光照模型。從名字上就可以看出,這種模型并沒有對Phong模型進行大改動(否則就是Blinn光照模型了)。沒錯,它只是對計算鏡面高光的算法進行了改動,換了一種鏡面高光的計算方法。
Blinn的鏡面高光計算原理是:不用反射向量計算,取而代之的是用一種名叫半角向量(halfway vector)的向量代替。半角向量是根據觀察方向和光線方向來進行計算的,取觀察向量和光線向量夾角的一半作為新向量的方向。這樣,觀察方向和反射方向的夾角就無關緊要了:
這樣,當觀察方向和反射方向就完美的重合時,半角向量就進化成法向量了。
讓我們來看看如何計算這個半角向量。這里有一個簡單的公式:
公式非常簡單,還記的向量相加的意義嗎?沒錯,就是將第二個向量平移到第一個向量的終點,然后從第一個向量的起點到第二個向量的終點所形成的一個新向量。我們將光線向量和觀察向量都規范化,這樣相加得出的向量必定能平分兩個向量的夾角。最后,再將其規范化成單位向量就大工告成了!
用GLSL來實現就是:
vec3 lightDir = normalize(lightPos - FragPos);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 halfwarDir = normalize(lightDir + viewDir);
于是,鏡面高光量的計算就變成了:
float spec = pow(max(dot(normal, halfwarDir), 0.0, shininess);
vec3 specular = lightColor * spec;
使用法向量和半角向量來代替觀察向量和反射向量計算點積,再計算光澤度的冪指數值,就成為了鏡面高光度。
好了,Blinn-Phong光照模型就這些東西,簡單吧?編譯運行代碼,我們來看看結果:
還有一點微妙的區別是,通常反射向量和觀察向量的夾角要比半角向量和法向量的夾角要大些。因此,如果想要用Blinn-Phong模型得到類似Phong模型的效果,計算時,用的光澤度信息要大點。經驗表明,Blinn-Phong模型的光澤度大小要是Phong模型的2到4倍。
下面兩張圖顯示了兩種模型在同一個角度觀察的顯示區別。Phong模型的光澤度為8,Blinn-Phong模型的光澤度為16:
在同一個位置就可以很明顯地看到區別,使用Blinn-Phong模型的光看上去聚攏程度更高。相對而言,Blinn-Phong的效果就更加真實。
最后,給出一個觀察相同角度不同模型的小技巧。我們可以在片元著色器中定義一個bool變量用作開關,當按下鍵盤上的b鍵時,開啟Blinn-Phong模式,再按一次b鍵就關閉Blinn-Phong模式。這樣,就能很明顯地看出差別。修改后的片元著色器代碼如下:
if (blinn) {
spec = pow (max (dot(normal, halfwayDir), 0.0), 16.0);
}
else {
spec = pow (max(dot(viewDir, reflectDir), 0.0), 8.0);
}
最后,按照我們一貫的作風,提供完整的代碼以供參考。
總結
在這一章中我們就只學了一個知識點就是Blinn的處理鏡面高光的方法。簡單點說,就是使用法向量和半角向量的點積值代替Phong模型中反射向量和觀察向量的點積值。半角向量是光線向量和觀察向量相加的結果向量,表示平分兩個向量的夾角。在結束的時候,提到了由于夾角值的差距,使用Blinn-Phong模型計算高光時要將光澤度信息設置成Phong模型的2到4倍較為合適。
好了,學習結束,休息、休息~
參考資料
www.learnopengl.com(非常好的網站,建議學習)