使用頂點投射的方法製作實時陰影

寫在前面

陰影是計算機圖形學中一個很重要的部分,陰影的加入使得物體更加具有立體感,也有助於我們理解物體間的相互位置關係和大小。

實時陰影的實現方法有很多種,shadowMap適用性最好,但性能開銷也大,有時候我們的項目其實並不需要那麼通用的陰影,我們只需要一個「適用某些特定場合」的一個「看起來正確」的實時陰影。

本文所說的,就是一種利用頂點投射的方法實現的實時陰影技術,在一些陰影質量要求不高,地面平整的項目是一個非常合適的方案,現在很火的手游《王者榮耀》就用了類似的技術。

(經提醒,這個技術叫平面投影陰影(Planar Projected Shadows)技術,由Jim Blinn 1988年提出。twinklingstar.cn/2015/1)

原理

忽略自身陰影不談,如果我們只考慮物體在地面上的陰影的話,其實就可以把這個問題簡單概括為求一個物體每一個頂點在某個平面上的投影位置了。

公式推導

說到求投影,我當時首先想到的就是點積,後來經群友提醒,其實可以更進一步簡化為求相似三角形,這樣理解起來似乎還更簡單些,以下是推導過程,為了簡化計算,我們在二維空間內進行推導。

根據已知條件,我們可以得到一個這樣的題目:已知平面坐標系內一個單位向量L(Lx,Ly),坐標系內一點M(Mx,My),求點M沿著L方向 在y = h上的投影位置P,如下圖所示:

根據相似三角形定理,我們很容易可以得出下面的式子:

於是我們有:

在shader中實現

有了公式以後,剩下的就簡單了,我們只需要在shader中多寫一個pass,在這個pass中把所有的頂點移動到投影的位置進行渲染即可,注意要轉換到世界空間中進行計算,核心代碼如下:

//陰影passnPassn{ntName "Shadow"nnt//用使用模板測試以保證alpha顯示正確ntStencilnt{nttRef 0nttComp equalnttPass incrWrapnttFail keepnttZFail keepnt}nnt//透明混合模式ntBlend SrcAlpha OneMinusSrcAlphannt//關閉深度寫入ntZWrite offnnt//深度稍微偏移防止陰影與地面穿插ntOffset -1 , 0nntCGPROGRAMnt#pragma vertex vertnt#pragma fragment fragnnt#include "UnityCG.cginc"ntstruct appdatant{nttfloat4 vertex : POSITION;nt};nntstruct v2fnt{nttfloat4 vertex : SV_POSITION;nttfloat4 color : COLOR;nt};nntfloat4 _LightDir;ntfloat4 _ShadowColor;ntfloat _ShadowFalloff;nntfloat3 ShadowProjectPos(float4 vertPos)nt{nttfloat3 shadowPos;nntt//得到頂點的世界空間坐標nttfloat3 worldPos = mul(unity_ObjectToWorld , vertPos).xyz;nntt//燈光方向nttfloat3 lightDir = normalize(_LightDir.xyz);nntt//陰影的世界空間坐標(低於地面的部分不做改變)nttshadowPos.y = min(worldPos .y , _LightDir.w);nttshadowPos.xz = worldPos .xz - lightDir.xz * max(0 , worldPos .y - _LightPos.w) / lightDir.y; nnttreturn shadowPos;nt}nntv2f vert (appdata v)nt{nttv2f o;nntt//得到陰影的世界空間坐標nttfloat3 shadowPos = ShadowProjectPos(v.vertex);nntt//轉換到裁切空間ntto.vertex = UnityWorldToClipPos(shadowPos);nntt//得到中心點世界坐標nttfloat3 center =float3( unity_ObjectToWorld[0].w , _LightPos.w , unity_ObjectToWorld[2].w);ntt//計算陰影衰減nttfloat falloff = 1-saturate(distance(shadowPos , center) * _ShadowFalloff);nntt//陰影顏色ntto.color = _ShadowColor; ntto.color.a *= falloff;nnttreturn o;nt}nntfixed4 frag (v2f i) : SV_Targetnt{nttreturn i.color;nt}ntENDCGn}n

其中_LightDir.xyz是燈光方向,_LightDir.w是地面高度,_ShadowColor為陰影顏色,這幾個值可以設一個全局變數對場景中的所有物體統一賦值。

重疊的面會導致透明混合結果錯誤,用Stencil解決,最後用頂點和中心點的距離算一個陰影衰減

最終效果如下:


推薦閱讀:

一個簡單的探照燈shader
Unity中的單例模式、回調函數、消息分發的使用區別?

TAG:shader | 手机游戏开发 | unity |