本文為翻譯,附上原文鏈接。
轉載請注明出處——polobymulberry-博客園。
如果你滿足以下條件,我建議你閱讀這篇教程:
- 你想知道如何在表面着色器中進行混色(blend colour)
- 你想實現一個更加真實的積雪效果
引論
我覺得有雪區域向無雪區域過渡的有些突兀,感覺更像白色的油漆塗在了岩石上,而不是積雪!為了使我們積雪shader的效果更加完美所以下一步需要做的是允許積雪和岩石紋理同時進行渲染,從而達到混色的效果。
我們只要對表面着色器的pixel處理方式進行一些修改就可以達到很好的積雪效果,而且這也將證明saturate函數是非常有用的。
准備工作
教程第二部分中的_SnowLevel(表示積雪程度的變量)決定了該像素是否應該賦以積雪的顏色,此處我們也會使用_SnowLevel來使積雪的邊緣顏色變為半透明的白色。而不是之前完全的白色。當該像素的法向與雪落下的方向越一致(即兩者夾角越小),則該像素的不透明度越高,換句話說,就是此處積雪更多,直到完全不透明,此處的像素顏色值將變為白色(事實上就是變成了積雪的顏色)。
實現該Shader
和教程第二部分的shader最大的差別在於,第二部分的shader中,每個像素的顏色值要不是積雪顏色,要不就是紋理顏色,而此部分shader將此非黑即白的判斷改進成顏色值的變化過程。這導致我們將重寫了shader的邏輯部分,並使用數學計算的方法來代替if的條件判斷。
首先我們需要一個屬性值(Property)來表示我們混合積雪顏色的程度。我們給該屬性值取名為_Wetness(濕潤度,如果該值越大,則混色中積雪顏色所占比例越低,這表明積雪越濕潤,則雪的顏色越少,都化成水了):
_Wetness ("Wetness", Range(0, 0.5)) = 0.3
接着我們需要一個變量在shader中表示該屬性值
float _Wetness;
我們使用下面這個公式計算difference變量。
float difference = dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) - lerp(1,-1,_Snow);
其實第二部分的代碼中也可以使用difference變量(比如當difference大於0就將該區域賦以積雪顏色,小於0就使用原先紋理顏色),關鍵在於對於difference的處理和應用不同。
void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); o.Normal = UnpackNormal (tex2D (_Bump, IN.uv_Bump)); float difference = dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) - lerp(1,-1,_Snow);; difference = saturate(difference / _Wetness); o.Albedo = difference*_SnowColor.rgb + (1-difference) *c; o.Alpha = c.a; }
我們可以看到計算完difference后,首先將difference除以_Wetness(此處我們可以一窺_Wetness的具體用途),然后對difference使用saturate函數。saturate函數的具體用處如下:
- saturate函數將給定的值規范到0~1之間(大於1的置為1,小於0的置為0,其他不變)
- 所以當difference小於0時,即此處無積雪,我們通過saturate函數置difference為0。
- 如果_Wetness的默認值為0.3,並且法向和落雪方向夾角在73度~90度之間,則difference的值健在0~1之間,大於90度,則為0,小於73度則為1.
一個余弦值的范圍是1到-1,差距為2,代表了0度到180度的變化(一直從同向變化到反向),所以要是difference在0~1之間,則點乘的結果(即cos值)必須在0~0.3之間,所以對應夾角為73度~90度(cos73=0.3,cos90=1)。
然后我們用difference乘以積雪顏色,difference代表積雪顏色在最終顏色所占的比例。我們再設置紋理本身顏色的比重為1-difference,相加得到最終顏色。其中夾角小於73度的區域將全部填充為積雪顏色,在73度~90度之間(也就是積雪的邊緣)有一積雪過渡效果,大於90度的將使用紋理原來顏色。
o.Albedo = difference*_SnowColor.rgb + (1-difference) *c;
修正頂點數據
如果我們的的雪十分濕潤(wet),那么只使用原先的shader的話,我們會發現積雪少的區域,模型也會增厚,根本原因是我們的頂點變化方式沒有隨積雪混色方式(主要是添加了_Wetness參數)變化而變化,產生的效果將不真實。所以我們應該將參數_Wetness應用到計算中,使模型的增厚效果隨_Wetness變化。
void vert (inout appdata_full v) { if(dot(v.normal, _SnowDirection.xyz) >= lerp(1,-1, ((1-_Wetness) * _Snow*2)/3)) { v.vertex.xyz += (_SnowDirection.xyz + v.normal) * _SnowDepth * _Snow; } }
可以看到,我們決定是否增厚模型的判斷變成了是否大於
lerp(1,-1, ((1-_Wetness) * _Snow*2)/3))
如果_Wetness為0,則一切都沒變化,如果_Wetness變到了其最大范圍0.5,則_Snow所占的比例不再是上文的2/3,而是1/3了 - 使模型增厚范圍增加了50%。
下面是最終的效果圖:
源代碼
資源和代碼在這里。
Shader "Custom/Realistic Snow" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _Bump ("Bump", 2D) = "bump" {} _Snow ("Snow Level", Range(0,1) ) = 0 _SnowColor ("Snow Color", Color) = (1.0,1.0,1.0,1.0) _SnowDirection ("Snow Direction", Vector) = (0,1,0) _SnowDepth ("Snow Depth", Range(0,0.2)) = 0.1 _Wetness ("Wetness", Range(0, 0.5)) = 0.3 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf Lambert vertex:vert sampler2D _MainTex; sampler2D _Bump; float _Snow; float4 _SnowColor; float4 _SnowDirection; float _SnowDepth; float _Wetness; struct Input { float2 uv_MainTex; float2 uv_Bump; float3 worldNormal; INTERNAL_DATA }; void vert (inout appdata_full v) { //將_SnowDirection轉化到模型局部坐標系下 float4 sn = mul(UNITY_MATRIX_IT_MV, _SnowDirection); if(dot(v.normal, sn.xyz) >= lerp(1,-1, (_Snow*2)/3)) { v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth * _Snow; } } void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); o.Normal = UnpackNormal (tex2D (_Bump, IN.uv_Bump)); half difference = dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) - lerp(1,-1,_Snow); difference = saturate(difference / _Wetness); o.Albedo = difference*_SnowColor.rgb + (1-difference) *c; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }