在Unity3D中使用Projector實現動態陰影
無意中看見一篇博客敘述使用Projector實現動態陰影可以在移動平台擁有非常好的性能,遂按照其想法實現了一遍,發現其中竟有許多細節,寫下這篇博客記錄以供將來參考。
Projector
從上圖中我們發現Projector中的參數參數Camera的參數非常的相似,那Projector是做什么的呢?
官方解釋:A Projector allows you to project a Material onto all objects that intersect its frstum.也就是Projector是把一個材質投影到與Projector視錐體相交的物體上,這個描述比較抽象,我們可以用以前的膠片電影來類比一下:Projector就是膠片放映機,被投影的材質就是膠片,Projector投影就像膠片放映機把膠片內容投影到電影幕布一樣。
按照這個理解,我們發現這個與平時在OpenGL中提到的攝像機投影有點不一樣,OpenGL的投影矩陣干的事是把三維物體投影到攝像機的近平面,也就是三維到二維的一個改變,但Projector投影確是相反,把一個紋理投影到三維物體的表面。
原理
按照上述Projector的理解,我們可以設想一個產生陰影的方法:先把要產生陰影的物體繪制到紋理中,然后把這個紋理投影到要接收陰影的物體表面上(注意與產生陰影的物體區分開),這樣就有了陰影,而這就是Projector產生陰影的原理。
實現細節
首先是要生成要被投影的陰影,因為這個陰影要與物體完美銜接,所以我們需要用Projector的參數來生成這個紋理,在Unity3D中我們的做法是:
1.創建一個新Camera,Camera的參數與Projector的一致;
2.設置Camera的Culling Mask為要產生陰影的物體所在的LayerMask,Projector的ignore Layers同樣設置為這個LayerMask,同時把要產生陰影的物體的Layer設置為這個LayerMask;
3.設置Camera渲染使用的shader,即camera.setReplaceShader;
4.創建RenderTexture,使用的分辨率視自己需求而定,分辨率越高,陰影越精細;
5.設置新建的Camera的TargetTexture為新建的RenderTexture;
6.新建Projector所需材質,可以使用standard assets中的“ProjectorMultipy”shader創建,設置材質的_ShadowTex為新建的RenderTexture;
7.運行即可看到效果。
需要注意的是:
1.傳遞給Projector材質的RenderTexture必須是clamp模式,但是如果陰影到了RenderTexture邊緣的像素,因為是Clamp的原因,地板就會出現整個長條形的陰影,解決方案可以通過給projector材質添加mask圖來處理邊緣的像素;
2.RenderTexture其實我們只需要表示產生陰影物體的位置,所以Camera使用的ReplaceShader可以使用最簡單的shader,只寫入一個通道值就可以了;
效果
ProjectorMultiply.Shader
被投影的材質需要特殊的shader,其實主要是要計算陰影的uv坐標。因為我們使用的是新建的一個紋理,這個紋理如何應用到物體的表面,需要使用Projector定義的一個投影矩陣,也就是通過這個矩陣來計算投影后的uv坐標。比較人性化的是Unity3D已經幫我們計算好了,直接在shader中聲明float4x4 unity_Projector就可以使用了。shader中還用到了falloff的一個texture,通常是一張左白右黑的貼圖,用於控制陰影的強弱。Falloff左邊為白色,alpha值為1,對應投影距離最近時最亮,右邊接近全黑,alpha值為0,表示投影距離變遠時投影會漸漸接近透明甚至看不見。具體代碼如下:
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' // Upgrade NOTE: replaced '_Projector' with 'unity_Projector' // Upgrade NOTE: replaced '_ProjectorClip' with 'unity_ProjectorClip' Shader "Projector/Multiply" { Properties { _ShadowTex ("Cookie", 2D) = "black"{} _FalloffTex ("FallOff", 2D) = "white" {} } Subshader { Tags {"Queue"="Transparent"} Pass { ZWrite Off ColorMask RGB Blend DstColor Zero Offset -1, -1 CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "UnityCG.cginc" struct v2f { float4 uvShadow : TEXCOORD0; UNITY_FOG_COORDS(1) float4 pos : SV_POSITION; }; float4x4 unity_Projector; v2f vert (float4 vertex : POSITION) { v2f o; o.pos = UnityObjectToClipPos (vertex); o.uvShadow = mul (unity_Projector, vertex); UNITY_TRANSFER_FOG(o,o.pos); return o; } sampler2D _ShadowTex; sampler2D _FalloffTex; fixed4 frag (v2f i) : SV_Target { fixed4 texS = tex2Dproj (_ShadowTex, UNITY_PROJ_COORD(i.uvShadow)); fixed4 texF = tex2Dproj (_FalloffTex, UNITY_PROJ_COORD(i.uvShadow)); fixed ratio = texF.r * texS.a; fixed4 res = fixed4(1,1,1,1) * (1 - ratio); UNITY_APPLY_FOG_COLOR(i.fogCoord, res, fixed4(1,1,1,1)); return res; } ENDCG } } }
優缺點
優點:
1.可控性強。可以看出我們可以控制在哪個區域、哪些物體、什么時間產生或更新陰影,也可以對陰影的質量進行控制(使用不同分辨率的RenderTexture);
2.可以很方便的是實現模糊和軟陰影(這個還沒實踐,不過因為我們可以取得陰影的rendertexture,所以完全可以實現);
缺點:
1.很明顯,產生陰影的物體不能接收陰影;
2.Unity3D 的Betch無法使用(因為要分層);
工程源代碼:https://github.com/xin-lover/ProjectorShadow
Unity3D AssetStore中有一個使用這種方法生成陰影的插件,做的比較完善,可以參考使用:Dynamic Shadow Projector.
參考博客: