Unity中的shadows(一)


Unity中的陰影針對不同的光源類型,平行光,點光源,聚光燈有不同的處理方式,casting和receiving的實現都有些區別。我們根據光源類型的不同詳細看一下具體的實現。

平行光陰影

如圖中所示場景,有兩個平行光源,我們打開frame debug查看一下:

可以看到,對於平行光產生的陰影,Unity使用screen space shadow map來保存陰影信息。

首先,Unity對整個場景跑了一遍深度pass,記錄場景的深度信息:

Unity在這一階段定義了SHADOWS_DEPTH關鍵字,並且把ColorMask設置為0,意思是不會輸出任何顏色。另外,只有object的shader中包含ShadowCaster標簽,才會跑一遍該pass。ShadowCaster的代碼可以很簡單:

	float4 MyShadowVertexProgram (VertexData v) : SV_POSITION {
		return UnityObjectToClipPos(v.position);
	}

	half4 MyShadowFragmentProgram () : SV_TARGET {
		return 0;
	}

我們並不需要返回深度信息,z的值引擎底層會記錄下來。

screen space的深度圖有了之后,Unity會基於每個平行光源渲染一張shadow map,簡單來說,就是把平行光源當作一個攝像機,做一次正交投影,將場景中物體的深度信息記錄在shadow map上。

之后,Unity會做一次Collect Shadows陰影收集過程,用之前深度圖中每個pixel的深度信息,將其重建回世界坐標系下,然后變換到光源空間,對之前光源空間渲染的shadow map進行采樣。shadow map采樣出的信息就是被光源照射到最近物體的深度,通過比較兩個在光源空間下的深度信息,就可以判斷出當前pixel是否在陰影內,如果在陰影內則輸出0,能被光源照亮則輸出1。每個平行光源都對應一張各自的screen space shadow map。

聚光燈陰影

如圖中所示場景,有兩個聚光燈光源,我們打開frame debug查看一下:

可以看到,與平行光相比,少了深度pass,還有陰影收集的過程,Unity就只是簡單地把光源空間內的物體渲染到shadow map上。

點光源陰影

這次場景中有兩個點光源,打開frame debug:

可以看到,點光源渲染陰影所用到的pass數量非常多,一個點光源可能需要6次陰影pass,這是因為點光源的shadow map不是一張貼圖,而是一個cube map。Unity為點光源陰影定義了SHADOWS_CUBE關鍵字。

另外值得注意的是,ShadowCaster的pass需要添加如下定義:

			#pragma multi_compile_shadowcaster

用Visual Studio可以看到具體的keywords:

// -----------------------------------------
// Snippet #2 platforms ffffffff:
Builtin keywords used: SHADOWS_DEPTH SHADOWS_CUBE

2 keyword variants used in scene:

SHADOWS_DEPTH
SHADOWS_CUBE

如果要為主平行光(base pass)添加陰影,需要添加SHADOWS_SCREEN關鍵字:

			#pragma multi_compile _ SHADOWS_SCREEN

如果要想為第二個平行光,點光源,或者聚光燈(即add pass)添加陰影,則需要如下定義:

			#pragma multi_compile_fwdadd_fullshadows

用Visual Studio可以看到具體的keywords:

// -----------------------------------------
// Snippet #1 platforms ffffffff:
Builtin keywords used: POINT DIRECTIONAL SPOT POINT_COOKIE DIRECTIONAL_COOKIE SHADOWS_SHADOWMASK LIGHTMAP_SHADOW_MIXING SHADOWS_DEPTH SHADOWS_SOFT SHADOWS_SCREEN SHADOWS_CUBE

13 keyword variants used in scene:

POINT
DIRECTIONAL
SPOT
POINT_COOKIE
DIRECTIONAL_COOKIE
SHADOWS_DEPTH SPOT
SHADOWS_DEPTH SHADOWS_SOFT SPOT
DIRECTIONAL SHADOWS_SCREEN
DIRECTIONAL_COOKIE SHADOWS_SCREEN
POINT SHADOWS_CUBE
POINT SHADOWS_CUBE SHADOWS_SOFT
POINT_COOKIE SHADOWS_CUBE
POINT_COOKIE SHADOWS_CUBE SHADOWS_SOFT
陰影設置參數

在Unity的project settings中的quality項,有如下的若干有關陰影的設置:

Shadows有3個選項,一是完全關閉陰影,二是只使用hard shadows,三是hard shadows和soft shadows都使用。使用軟陰影的好處是陰影的邊緣更加光滑,減少鋸齒。根據不同的選項,以平行光源為例,unity會在收集陰影階段使用不同的subshader繪制screen space shadow map:

Shadow Distance控制陰影顯示的距離,太遠的物體往往沒有顯示陰影的必要,通過控制距離可以降低繪制的draw call:

可以看出,超過陰影顯示距離的物體,在光源繪制shadow map時直接就跳過了。

Shadow Cascade控制是否開啟級聯陰影,以及級聯的數量和區域划分。級聯陰影的好處是可以根據物體距離的遠近,生成不同分辨率的shadow map,近的物體對高分辨率的shadow map進行采樣,得到更多的陰影的細節,遠的物體只用對低分辨率的shadow map采樣即可,避免不必要的性能浪費。不同分辨率的shadow map在陰影收集階段同樣會被繪制到一張screen space shadow map上,因此只是在光源空間渲染shadow map時,draw call會根據選擇級聯的數量翻倍:

Shadow Projection控制級聯陰影的區域生成方式。Close Fit利用攝像機的深度信息來計算區域,而Stable Fit利用到攝像機位置的距離信息來計算區域。在scene窗口下,可以選擇Miscellaneous / Shadow Cascades來顯示級聯陰影區域:

Stable Fit的區域通常更穩定,不會受攝像機自身的位置和旋轉影響:

Shadow Acne

由於shadow map的精度問題,shadow map上的一個pixel,實際上是對應了場景上的一片區域。而這片區域中的所有點,在shadow caster階段,都會去取shadow map同一個pixel的深度值,當這個深度值與區域中的所有點的深度值相近的時候,就會出現一部分點比較深度后,在陰影中,而另一部分點卻不在陰影中,導致場景中會出現明顯的明暗條紋:

從數學角度上來看,如圖所示:

EF為shadw map采樣的深度,那么CF區域中的點由於深度都小於EF,因此不在陰影中;而DF區域中的點深度大於EF,會被判定在陰影中。

為了解決這一問題,可以在渲染shadow map的時候人為加上偏移,讓陰影位於物體之后。這個偏移就被稱作shadow bias。

常用的shadow bias有兩種,depth bias和normal bias。depth bias顧名思義,就是在shadow caster階段,將物體沿着相機空間的z方向往后偏移:

可以看到,我們將CD區域沿着相機空間的z方向平移到了GI區域。此時shadow map采樣到的深度值為EH,它等於BD。這樣,CD區域中所有的點深度都不會大於EH(因為最大的深度也就是BD,而BD等於EH),從而保證了CD區域中的所有點都不會判定在陰影中,也就可以消除shadow acne。這里,depth bias的值就是CG的大小。

normal bias是在shadow caster階段,將物體沿着自身的法線方向往后偏移:

原理與depth bias類似,這里normal bias的值就是CG的大小。

shadow bias的參數可以在光源的屬性中進行調整:

Reference

[1] Shadows

[2] 自適應Shadow Bias算法

本文是Unity中的shadows系列的第一篇文章,如果你覺得我的文章有幫助,歡迎關注我的微信公眾號(大齡社畜的游戲開發之路-


免責聲明!

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



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