Plannar Shadows,即平面陰影,是一個適用於平坦地形的假陰影技術。要求陰影的Receiver為平面,Occluder不與其他物體穿插。
實現效果
1.定向光源Planar Shadows
2.點光源Planar Shadows
實現步驟
實現Planar Shadows,這里主要考慮兩種光源,定向光和點光源兩種十分常用的光源類型。實現Planar Shadows的核心就是推導兩種光源的投影矩陣,為了簡化矩陣,將xz平面(y=0)作為Receiver平面。
頂點着色器核心代碼如下:
1 v2f vert(a2v v) 2 { 3 v2f o; 4 o.uv = v.uv; 5 half3 wordPos=mul(unity_ObjectToWorld,v.vertex).xyz; 6 //投影:中心投影(透視投影,點光源),平行投影 7 //平行投影:斜投影(定向光),正投影 8 9 half3 l=_WorldSpaceLightPos0.xyz; 10 //透視投影矩陣:將點光源投影到xz平面 11 half4x4 Projection=half4x4( 12 l.y,-l.x,0,0, 13 0,0,0,0, 14 0,-l.z,l.y,0, 15 0,-1,0,l.y 16 ); 17 //斜投影矩陣 18 // half4x4 Projection=half4x4( 19 // l.y,-l.x,0,0, 20 // 0,0,0,0, 21 // 0,-l.z,l.y,0, 22 // 0,0,0,l.y 23 // ); 24 half4 shadowPos=mul(Projection,half4(wordPos,1)); 25 shadowPos.x/=shadowPos.w; 26 shadowPos.z/=shadowPos.w; 27 shadowPos.y=_HeightBias; 28 o.pos=UnityWorldToClipPos(shadowPos); 29 return o; 30 }
1.額外的陰影Pass
需要在Shader中,常規渲染Pass外增加新的用於Planar Shadows的額外的渲染Pass。
2.推導定向光和點光源的投影矩陣
定向光的投影類型為斜投影,點光源的投影類型為中心投影,需要提前推導出投影矩陣。為了條理清晰,在頂點着色器中進行了矩陣的計算。實際上,可以預計算矩陣然后腳本傳遞給Shader。
3.矩陣變換
首先將頂點變換到世界空間,然后進行投影變換。由於使用齊次坐標系,需要除w分量來得到正確的結果。
_WorldSpaceLightPos0——是Unity內置着色器變量,在光源為定向光的情況下,xyz分量表示方向,w分量為0,在光源為點光源的情況下,xyz分量表示位置,w分量為1。
_HeightBias——用來解決深度沖突造成的顯示錯誤。
效果優化
以上的步驟雖然完成了陰影的形式上的正確,但仍然有優化的空間。接下來介紹使用模板緩沖,實現更優的效果。
優化效果如下:
上圖實現了陰影邊緣裁剪和半透明的效果。
1.邊緣裁剪
為了實現邊緣裁剪的效果,這里用到了模板緩沖,用於Mask掉超出Receiver邊緣的陰影像素。
首先,在Receiver的Shader中,添加模板緩沖的代碼:
1 Stencil { 2 Ref 1 3 Comp always 4 Pass replace 5 }
這段代碼的用處是,將模板值1寫入到模板緩沖中,即Receiver覆蓋像素位置的對應模板緩沖區的模板值均為1。
然后在我們Occluder的Shader中,將Queue值調整為Receiver的Queue+1,同時添加一下的代碼:
1 Stencil { 2 Ref 1 3 Comp equal 4 Pass keep 5 }
這段代碼的用處是,繪制陰影像素的模板值為1,與深度緩存中已有的模板值比較,如果相同的話,那就保持緩沖區值不變,模板測試通過,像素將被繪制。關閉深度緩沖寫入的意義是使得渲染按照先渲染Receiver然后Occluder的順序,讓Receiver的模板值先寫入模板緩沖中。
2.半透明
如果我們直接對陰影進行半透明着色,會發現在簡單物體上效果還可以。但是在復雜的模型上,有很大的問題,如下圖所示:
因此,我們為了解決這個問題,需要用模板緩沖,來保證每個像素被繪制一次。
做法很簡單,除了對一些透明的相關設置外,需要對Occluder的模板緩沖代碼進行修改。
1 Stencil { 2 Ref 1 3 Comp equal 4 Pass incrWrap 5 Fail keep 6 }
這段代碼的和上面的不同是,如果模板測試通過,將模板緩沖中的模板值+1,像素被繪制。這樣的話,如果一個重疊像素要被繪制,因為模板值+1=2,不通過模版測試(條件為Equal),並不會被繪制。
小結
雖然實現了陰影的正確投射,以及邊緣裁剪和半透明問題,現在的效果於Unity中Hard Shadow的效果已經十分接近。對於對於如何實現軟陰影(本影+半影),希望有較好的解決方案。