我想试着实现一下下图中纪念碑谷中的全局渐变的效果。关于如何实现这种渐变效果之前和别人讨论过,一开始我认为是通过从下往上打光来实现的,后来同学说是利用了shader和雾。利用后者实现的效果更为缓和,而利用光照可能会造成底部过亮的问题,不好控制。
为什么我管这种效果叫“全局渐变”呢?因为当我移动模型的时候,渐变的范围并不是固定在模型身上的,而是在世界空间的“某个位置”(如下图所示)。实际上,我们可以自行规定这个渐变地平线的位置,只需要在shader里加个控制变量就可以了。
实现
变量
首先,我们需要这么几个变量:
- 顶部的颜色
- 底部的颜色
- 渐变混合的程度
- 光照的强度
- 渐变地平线
- 渐变纹理
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Properties { // 光照的强度 _Contrast("Contrast",Range(0,1))=0.1 // 渐变纹理 _RampTex("渐变纹理", 2D) = "white"{} // 上下渐变色 _UpColor("UpColor",Color)=(1,1,1,1) _DownColor("DownColor",Color)=(1,1,1,1) // 渐变程度 _WorldYDeno("WorldYDeno",float)=0 // 渐变中心位置 _Skyline("地平线", float) = 0 }
|
顶点着色器
在顶点着色器中我们需要获取几组坐标:
- 世界空间的顶点坐标
- 世界空间的顶点法线
- 裁剪空间的顶点坐标
世界空间的坐标和法线用于光照强度的控制;裁剪空间的坐标用于渐变的渲染
1 2 3 4 5 6 7 8 9 10 11
| v2f vert(appdata v){ v2f o; // 世界空间坐标 o.worldPo=mul(UNITY_MATRIX_M,v.vertex); // 世界空间法线 o.worldNormal = normalize(mul((float3x3)UNITY_MATRIX_M, v.normal)); // 裁剪空间坐标 o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _RampTex); return o; }
|
片元着色器
在片元着色器中,我们进行三个主要步骤:
- 计算渐变纹理的映射
- 光照强度
- 渐变混合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| float4 frag(v2f i) : SV_Target{ // 渐变纹理 fixed3 worldN = normalize(i.worldNormal); fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; fixed halfLambert = 0.5 * dot(worldN, worldLightDir) + 0.5; fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb; fixed3 diffuse = _LightColor0.rgb * diffuseColor; // 光照强度 float NdotL = dot(i.worldNormal, _WorldSpaceLightPos0); float light = clamp(NdotL * _Contrast + (1 - _Contrast), 0, 1); // 渐变混合 float4 col = fixed4(ambient + diffuse + light * lerp(_DownColor, _UpColor, clamp( (i.worldPos.y - _Skyline) / _WorldYDeno, 0, 1)), 1.0); return col; }
|
最终,我们可以得到如下的效果(暂未加上渐变纹理贴图):
可以看出效果不太好,并且在细节上表现的很不自然,画面的颜色很单一。如果加上渐变纹理的话可以得到更好的细节效果:
好,就是这样了。实际上,我们还可以在较远的物体表面加上一层雾。有关于雾效的实现,在下一篇demo文章中将会尝试一下。最终我们的材质球面板和面板各属性的控制情况如下所示: