C#使用Shader实现夜幕降临倒计时的效果

2019-12-30 14:24:06刘景俊

Shader代码:


Shader "Image Effects/NightFall" 
{ 
Properties 
{ 
_NightColor ("Night Color", Color) = (0.05, 0.05, 0.05, 0.05) 
_Center ("Center", Vector) = (0,0,0,0) 
_Radius ("Radius", float) = 10 
} 
SubShader 
{ 
Pass 
{ 
ZTest Always 
Cull Off 
ZWrite Off 
Fog { Mode off } 
Blend SrcAlpha OneMinusSrcAlpha 
CGPROGRAM 
#pragma vertex vert_img 
#pragma fragment frag vertex:vert 
#pragma fragmentoption ARB_precision_hint_fastest 
#include "UnityCG.cginc" 
sampler2D _CameraDepthTexture; 
uniform float4x4 _InverseMVP; 
uniform float4 _CamPos; 
uniform half4 _NightColor; 
uniform half4 _Center; 
uniform half _Radius; 
struct Input 
{ 
float4 position : POSITION; 
float2 uv : TEXCOORD0; 
}; 
void vert (inout appdata_full v, out Input o) 
{ 
o.position = mul(UNITY_MATRIX_MVP, v.vertex); 
o.uv = v.texcoord.xy; 
} 
float3 CamToWorld (in float2 uv, in float depth) 
{ 
float4 pos = float4(uv.x, uv.y, depth, 1.0); 
pos.xyz = pos.xyz * 2.0 - 1.0; 
pos = mul(_InverseMVP, pos); 
return pos.xyz / pos.w; 
} 
fixed4 frag (Input i) : COLOR 
{ 
#if SHADER_API_D3D9 || SHADER_API_D3D11 
float2 depthUV = i.uv; 
depthUV.y = lerp(depthUV.y, 1.0 - depthUV.y, _CamPos.w); 
float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, depthUV)); 
float3 pos = CamToWorld(depthUV, depth); 
#else 
float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, i.uv)); 
float3 pos = CamToWorld(i.uv, depth); 
#endif 
// Limit to sea level 
if (pos.y < 0.0) 
{ 
// This is a simplified version of the ray-plane intersection formula: t = -( N.O + d ) / ( N.D ) 
float3 dir = normalize(pos - _CamPos.xyz); 
pos = _CamPos.xyz - dir * (_CamPos.y / dir.y); 
} 
half4 col; 
float dis = length(pos.xz - _Center.xz); 
if (dis < _Radius) 
{ 
col = fixed4(0,0,0,0); 
} 
else 
{ 
col = _NightColor; 
} 
return col; 
} 
ENDCG 
} 
} 
Fallback off 
}

需要说明的几个点:

1、因为平台差异性,为了兼容Direct3D,所以在C#和shader里通过CamPos(_CamPos)的w分量来调整uv坐标。

2、这里虽然没有声明_MainTex,但是_MainTex实际上就是即将成像的屏幕图像,所以这里的i.uv也就是指屏幕图像的纹理坐标。

3、_CameraDepthTexture是摄像机的深度纹理,通过UNITY_SAMPLE_DEPTH方法获取深度。

4、CamToWorld里面,先是根据uv坐标和深度depth创建了一个float4的坐标值pos,然后对pos乘2减1是将这个坐标范围从[0,1]转换到了[-1,1],对应世界坐标。然后使用传入的MVP逆矩阵_InverseMVP乘以这个坐标值,就得到了屏幕点的世界坐标。最后将pos的xyz分量除以w分量,这里w分量表示因为远近而产生的缩放值。

5、在计算过世界坐标之后,对于y小于0的坐标要做一下处理,将效果限制在海平面(sea level)之上,使用射线平面相交方程(ray-plane intersection formula)的简化版本来处理。

6、最后根据距离返回色彩值。