Unity3D中使用Projector生成陰影


在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.

參考博客:

  1.https://yrsc.github.io/2018/03/Projector%E5%AE%9E%E7%8E%B0%E4%BC%AA%E5%8A%A8%E6%80%81%E9%98%B4%E5%BD%B1/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM