UnityShader 從入門到蓋棺(三)

gta5_car

Phong和Blinn模型

上一篇我們講到了Lambert模型,這次我們講一下Phong和Blinn光照模型。這兩種模型都是基於Lambert模型加入了高光部分。

接下來的實現,我們用於完善我們上次房子場景的周邊環境。這是我上次構建完房子場景後的一個大膽想法,就是把所有知識點放在我們的房子場景上,然後大家看到場景的一部分可能就能想起某個點(→_→)。

來,老規矩,上結果圖。

sc1

我們先來實現phong光照模型。實現之前,我們在場景中擺兩個球,然後創建一個新的材質,名爲3_BallPhong。材質的Shader先選擇上一篇的2_WallDiffuse。然後貼圖使用Textures/3_MetalDiffuse。然後我們就可以看到結果如下

dc1

dc2

這是上一篇的效果,只應用了漫反射光。下面我們開始加入phong模型中的高光,所謂的高光其實是光照照射到表面的鏡面反射部分,就跟鏡子反射光照一樣的。所以我們需要算出光照通過表面直接反射到人眼的部分,只需要算出光反射向量和視線向量的夾角即可。

math

創建Shader文件3_CopperPhong.shader, 然後把上一篇的2_WallDiffuse.shader的內容全部複製過來。把片段着色器代碼修改成下面的樣子

fixed4 frag(v2f i) : SV_Target {
	float3 worldNormal = normalize(i.worldNormal);
	float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
	fixed4 albedo = tex2D(_MainTex, i.uv);

	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT * albedo.rgb;


    float lambert = saturate(dot(worldNormal, worldLightDir));
    fixed3 diffuse = _LightColor0.rgb * albedo.rgb  * lambert;

    // phong高光部分
    // 算出光照和表面的反射向量,Unity提供內置函數reflect幫我們完成。
    float3 worldRefelctDir = normalize(reflect(-worldLightDir, worldNormal));
    float3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
    // 用反射向量和視線向量的夾角模擬高光的影響程度
    float spec = saturate(dot(worldRefelctDir, worldViewDir));
    fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * spec;

	return fixed4(ambient + diffuse + specular, albedo.a); // 輸出物體主顏色

然後我們把一開始創建的3_BallPhong.mat材質的Shader改成我們新寫的這個Shader,注意這裏用到了一個_SpecularColor變量,他是一個顏色變量,是作爲輸入參數傳進來的。讀者應該記得怎麼添加一個輸入顏色變量吧,不記得的可以去看看源碼的Properties屬性塊複習一下,這個變量聲明的代碼我沒有貼出來的。我們在屬性面板把這個顏色調整爲金屬球的顏色。

pickcolor

然後效果圖如下

fc1

大家可以看到,加入高光後銅球的一部分的確很亮。但是現實生活中金屬球的高光好像範圍沒有那麼大。事實上,上面的高光係數計算漏了phong光照模型裏很重要的一步(我自己做的時候漏了 - -)。那就是對高光係數做一個冪次方,讓高光範圍變窄,因爲現實中,金屬高光只有直接反射到人眼的最明顯,然後隨着角度變大,衰減很快。因此我們再定義一個輸入變量_Shiness(這是一個RANGE變量,代表用戶可以在一個範圍內選取某個值)來完善高光係數的計算。

// 在屬性塊中定義_Shiness變量
_Shiness ("shiness", RANGE(8.0, 64.0)) = 8.0

// 在CGPROGRAM-ENDCG代碼中聲明_Shiness變量
float _Shiness;

// 修改Spec係數的計算
float spec = pow(saturate(dot(worldRefelctDir, worldViewDir)), _Shiness);

修改代碼後的結果圖如下,讀者可以自己調整_Shiness查看效果。

fc2

理論上,向光面的高光分量可以無限趨向於0,但不應該爲0。但是phong光照模型在反射光照和視線夾角大於90度時,是會被截斷爲0的。在某些情況下,這樣的截斷會導致高光區域出現明顯的斷層。可以參考LearnOpengl上的效果圖

接下來我們實現Blinn光照模型。Blinn光照模型在Phong光照模型上做了一點修改。提出了半程向量,即光照法向和視線方向的中間向量。然後用這個半程向量和法線進行點乘來算高光係數。讀者會發現,用這樣的計算方法,向光面的高光係數就不會小於0。

創建一個新的Shader文件,命名爲3_CopperBlinn.shader。把3_CopperPhong的代碼複製到這裏。然後修改spec係數部分爲

// blinn高光部分
float3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
// 算出半程向量
float3 halfDir = normalize(worldLightDir + worldViewDir);
// 用反射向量和視線向量的夾角模擬高光的影響程度
float spec = pow(saturate(dot(worldNormal, halfDir)), _Shiness);
fixed3 specular = _LightColor0.rgb *_SpecularColor.rgb * spec;

新建一個材質,應用3_CopperBlinn.shader,把這個材質應用到右邊的球體,然後把shiness係數調到32(shiness係數相同下,blinn光照的高光範圍比phong光照要大,讀者可以想想原因)。效果圖如下

bc1

好了,以上就是phong光照模型和blinn光照模型。這兩個模型沒有絕對的正確與否,大家都是經驗模型。實際用哪個需要在不同的情況下考慮。最後我們再看下最終場景圖,大家可以對比一下一開始的lambert模型。

sc1

結語

第三篇的分享結束了,有疑問就留言,之後的源碼會越來越少,發現不對勁可以到Github上看源碼。

但不管怎樣,支持作者環節必不可缺。

vxpay

鏈接

github地址:https://github.com/gjbian/Unity-Shader-Study

上一篇:UnityShader 從入門到蓋棺(二)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章