高級光照
在光照小節中,我們簡單地介紹了馮氏光照模型,它讓我們的場景有了一定的真實感。雖然馮氏模型看起來已經很不錯了,但是使用它的時候仍然存在一些細節問題,我們將在這一節裏討論它們。
首先修改下vulkan中加載模型部分,創建一個大地板紋理的模型,通過之前學習您應該完全可以自行修改,如果不熟悉可翻看前面教程。
一、Blinn-Phong
馮氏光照不僅對真實光照有很好的近似,而且性能也很高。但是它的鏡面反射會在一些情況下出現問題,特別是物體反光度很低時,會導致大片(粗糙的)高光區域。下面這張圖展示了當反光度爲1.0時地板會出現的效果:
可以看到,在鏡面高光區域的邊緣出現了一道很明顯的斷層。出現這個問題的原因是觀察向量和反射向量間的夾角不能大於90度。如果點積的結果爲負數,鏡面光分量會變爲0.0。你可能會覺得,當光線與視線夾角大於90度時你應該不會接收到任何光纔對,所以這不是什麼問題。
然而,這種想法僅僅只適用於漫反射分量。當考慮漫反射光的時候,如果法線和光源夾角大於90度,光源會處於被照表面的下方,這個時候光照的漫反射分量的確是爲0.0。但是,在考慮鏡面高光時,我們測量的角度並不是光源與法線的夾角,而是視線與反射光線向量的夾角。看一下下面這兩張圖:
現在問題就應該很明顯了。左圖中是我們熟悉的馮氏光照中的反射向量,其中角小於90度。而右圖中,視線與反射方向之間的夾角明顯大於90度,這種情況下鏡面光分量會變爲0.0。這在大多數情況下都不是什麼問題,因爲觀察方向離反射方向都非常遠。然而,當物體的反光度非常小時,它產生的鏡面高光半徑足以讓這些相反方向的光線對亮度產生足夠大的影響。在這種情況下就不能忽略它們對鏡面光分量的貢獻了。
1977年,James F. Blinn在馮氏着色模型上加以拓展,引入了Blinn-Phong着色模型。Blinn-Phong模型與馮氏模型非常相似,但是它對鏡面光模型的處理上有一些不同,讓我們能夠解決之前提到的問題。Blinn-Phong模型不再依賴於反射向量,而是採用了所謂的半程向量(Halfway Vector),即光線與視線夾角一半方向上的一個單位向量。當半程向量與法線向量越接近時,鏡面光分量就越大。
當視線正好與(現在不需要的)反射向量對齊時,半程向量就會與法線完美契合。所以當觀察者視線越接近於原本反射光線的方向時,鏡面高光就會越強。
現在,不論觀察者向哪個方向看,半程向量與表面法線之間的夾角都不會超過90度(除非光源在表面以下)。它產生的效果會與馮氏光照有些許不同,但是大部分情況下看起來會更自然一點,特別是低高光的區域。Blinn-Phong着色模型正是早期固定渲染管線時代時OpenGL所採用的光照模型。
獲取半程向量的方法很簡單,只需要將光線的方向向量和觀察向量加到一起,並將結果正規化(Normalize)就可以了:
翻譯成GLSL代碼如下:
vec3 lightDir = normalize(lightPos - fragPos);
vec3 viewDir = normalize(viewPos - fragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);
spec = pow(max(dot(normal, halfwayDir), 0.0),1);
運行,可見到在反光度爲1.0情況下Blinn-Phong效果爲:
除此之外Blinn-Phong模型就沒什麼好說的了,Blinn-Phong與馮氏模型唯一的區別就是,Blinn-Phong測量的是法線與半程向量之間的夾角,而馮氏模型測量的是觀察方向與反射向量間的夾角。
在引入半程向量之後,我們現在應該就不會再看到馮氏光照中高光斷層的情況了。
除此之外,馮氏模型與Blinn-Phong模型也有一些細微的差別:半程向量與表面法線的夾角通常會小於觀察與反射向量的夾角。所以,如果你想獲得和馮氏着色類似的效果,就必須在使用Blinn-Phong模型時將鏡面反光度設置更高一點。通常我們會選擇馮氏着色時反光度分量的2到4倍。
附:Blinn-Phong片元着色器main
void main() {
vec3 lightDir = normalize(lightPos - fragPos);
vec3 viewDir = normalize(viewPos - fragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);
vec3 color = vec3(texture(texSampler, fragTexCoord)).rgb;
// ambient
vec3 ambient = 0.05 * color;
// diffuse
vec3 normal = normalize(fragNormal);
float diff = max(dot(lightDir, normal), 0.0);
vec3 diffuse = diff * color;
// specular
vec3 reflectDir = reflect(-lightDir, normal);
float spec = 0.0;
spec = pow(max(dot(normal, halfwayDir), 0.0),1);
//spec = pow(max(dot(viewDir, reflectDir), 0.0),1);
vec3 specular = vec3(0.3) * spec;
FragColor = vec4(ambient + diffuse + specular, 1.0);
}