本文为翻译,附上原文链接。
转载请注明出处——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"
}