用頂點Shader實現的實時陰影

如圖所見,壓扁的模型而已

看到大概都知道該怎麼做了,新加一個PASS並做頂點變換,新頂點為世界坐標系下光照方向射線過原頂點與地平面的交點,下面的是完整Shader文件

Shader "Unlit/MeshShadow"{ Properties { _MainTex ("Texture", 2D) = "white" {} _ShadowAlpha ("Shadow Alpha", Range(0,1)) = 0.5 _LightDir ("Light Dir", Vector) = (1,1,1) _GroundY ("GroundY", float) = 0 } SubShader { Pass { Tags { "RenderType"="Opaque" } CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 col = tex2D(_MainTex, i.uv); // apply fog UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } Pass { Tags{ "RenderType"="Transparent" "Queue"="Transparent" } Stencil { Ref 1 Comp NotEqual Pass Replace ReadMask 1 WriteMask 1 } Blend SrcAlpha OneMinusSrcAlpha ZWrite OFF CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; half _ShadowAlpha; half3 _LightDir; half _GroundY; v2f vert (appdata v) { v2f o; half4 worldPos = mul(unity_ObjectToWorld, v.vertex); float d = (_GroundY - worldPos.y) / _LightDir.y; worldPos.xyz += d * _LightDir; o.vertex = mul(UNITY_MATRIX_VP,worldPos); UNITY_TRANSFER_FOG(o,o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 col = fixed4(0,0,0,_ShadowAlpha); // apply fog UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } }}

關鍵代碼

v2f vert (appdata v){ v2f o; half4 worldPos = mul(unity_ObjectToWorld, v.vertex); float d = (_GroundY - worldPos.y) / _LightDir.y; worldPos.xyz += d * _LightDir; o.vertex = mul(UNITY_MATRIX_VP,worldPos); UNITY_TRANSFER_FOG(o,o.vertex); return o;}

需要專門提一下的是半透的處理,模型半透時難免會這樣

深度緩衝做過濾是沒用的,精度不夠,所以最後用的Stencil解決

Stencil{ Ref 1 //目標值設為1 Comp NotEqual //比較緩衝是否與1相等,相等說明已經繪製過了,不能再繪製 Pass Replace //如果成功繪製,則寫緩衝為1 ReadMask 1 //設定Mask只修改第一位,可做相應修改。這是為了避免和其他Stencil衝突,沒衝突可以刪掉 WriteMask 1}

至於缺點?

無法拐彎,會被起伏的地面遮擋。

另外實際使用的時候請注意CULL的問題,模型的Bounds需要擴大。


想不被地面起伏遮擋也是可以的。影子部分ZTest OFF,然後在繪製人物部分加上

Stencil{ Ref 1 Comp Always Pass Replace ReadMask 1 WriteMask 1}

保證影子絕對不遮擋人物就可以

但是就會出現這種情況

本來就是一個劣化方案,根據需要選擇把。

推薦閱讀:

讓角色半透明:後期模糊(二)
【GPU精粹與Shader編程】(一) 開篇 & 全系列11本書核心知識點總覽
走樣與反走樣(Aliasing/Anti-Aliasing):Graphics Cases
UE4中快速實現簡單GPU流體模擬

TAG:計算機圖形學 | Unity遊戲引擎 | 遊戲開發 |