Unity移動(dòng)端動(dòng)態(tài)陰影總結(jié)

這是侑虎科技第239篇原創(chuàng)文章,感謝作者馮委供稿,歡迎轉(zhuǎn)發(fā)分享,未經(jīng)作者授權(quán)請(qǐng)勿轉(zhuǎn)載。當(dāng)然,如果您有任何獨(dú)到的見(jiàn)解或者發(fā)現(xiàn)也歡迎聯(lián)系我們,一起探討。(QQ群:465082844)

作者知乎:https://zhuanlan.zhihu.com/c_90452095。同時(shí),作者也是U Sparkle活動(dòng)參與者哦,UWA歡迎更多開(kāi)發(fā)朋友加入U Sparkle開(kāi)發(fā)者計(jì)劃,這個(gè)舞臺(tái)有你更精彩!

壹 · 基于Cubemap的動(dòng)態(tài)軟陰影

ARM公司曾利用Unity開(kāi)發(fā)過(guò)兩款技術(shù)Demo(Ice Cave 和 Chess Room),里面充分發(fā)揮了Cubemap的強(qiáng)大威力—既用來(lái)做地面反射、冰塊折射,還用來(lái)做動(dòng)態(tài)軟陰影,利用簡(jiǎn)單的技術(shù)做出了高品質(zhì)的畫面。下面是Ice Cave的效果:

其中反射、折射部分參考:Reflections Based on Local Cubemaps in Unity、ARM Guide for Unity Developers,下面主要介紹下軟陰影部分原理。

以此國(guó)際象棋屋為例,屋子中間放置一個(gè)Reflect probe來(lái)拍攝周圍環(huán)境,只用了Cubemap的RGB通道,而周圍環(huán)境的Alpha其實(shí)也代表了光是穿透了窗戶還是被墻壁遮擋,那就可以利用Cubemap剩余的Alpha通道就可以來(lái)存儲(chǔ)光和周圍環(huán)境的遮擋情況,Alpha通道圖如下:

生成Cubemap細(xì)節(jié)可以參考AssetStore中的源碼

利用生成的Cubemap渲染陰影主要分為兩步,一是向量L(vertex-to-light)轉(zhuǎn)換為L(zhǎng)p(校準(zhǔn)過(guò)的vertex-to-light,用來(lái)采樣Cubemap用),二是軟陰影處理。

1. L到Lp向量校準(zhǔn):

輸入?yún)?shù):

_EnviCubeMapPos >> Cubemap 中心坐標(biāo)

_BBoxMax >> 包圍盒最大坐標(biāo),生成Cubemap時(shí)自動(dòng)生成

_BBoxMin >> 包圍盒最小坐標(biāo),生成Cubemap時(shí)自動(dòng)生成

V: >> 頂點(diǎn)坐標(biāo)

L: >> vertex-to-light向量,已normalized

輸出參數(shù):

Lp >> 校準(zhǔn)后的vertex-to-light向量,作為UV去采樣Cubemap

校準(zhǔn)過(guò)程:

// Working in World Coordinate System.

vec3 intersectMaxPointPlanes = (_BBoxMax - V) / L;

vec3 intersectMinPointPlanes = (_BBoxMin - V) / L;

// Looking only for intersections in the forward direction of the ray.

vec3 largestRayParams = max(intersectMaxPointPlanes, intersectMinPointPlanes);

// Smallest value of the ray parameters gives us the intersection.

float dist = min(min(largestRayParams.x, largestRayParams.y), largestRayParams.z);

// Find the position of the intersection point.

vec3 intersectPositionWS = V + L * dist;

// Get the local corrected vector.

Lp = intersectPositionWS - _EnviCubeMapPos;

先利用線和包圍盒求交點(diǎn),從包圍盒位置到交點(diǎn)的向量就是Lp,然后利用Lp去采樣Cubemap用于著色。

float shadow = texCUBE(cubemap, Lp).a;

另外背面要特殊處理下,防止陰影穿透問(wèn)題。

if (dot(L,N) < 0)

shadow = 0.0;

shadow *= max(dot(L, N), 0.0);

2. 軟陰影:

陰影平滑的過(guò)程比較有趣,首先Cubemap過(guò)濾方式選擇tri-linear filtering,然后計(jì)算vertex-to-intersection-point(頂點(diǎn)到交點(diǎn))向量的長(zhǎng)度,然后乘以外部傳入系數(shù):

float texLod = length(IntersectPositionWS - V);

texLod *= distanceCoefficient;

為了平滑陰影,我們用texCUBElod 去采樣Cubemap,其中UV的XYZ來(lái)自Lp,W來(lái)自vertex-to-intersection-point(頂點(diǎn)到交點(diǎn))的距離。

Lp.w = texLod;

shadow = texCUBElod(cubemap, Lp).a;

下圖也可以看到離窗戶越遠(yuǎn)處的陰影越模糊。

這種陰影比較適合室內(nèi)環(huán)境、點(diǎn)光源位置不變、內(nèi)部有移動(dòng)物體的情況。

貳 · 地面云陰影

對(duì)于地面上云陰影,用實(shí)時(shí)燈光照射出陰影顯然是不劃算,可以直接在地面Shader中混合一個(gè)運(yùn)動(dòng)的云圖就能達(dá)到類似效果。

我用Shaderforge拖出了一個(gè)簡(jiǎn)單的版本:

另外這種方法也可以用來(lái)做地面風(fēng)雪效果。

叁 · 植物搖曳陰影

對(duì)于樹、草、旗子這類位置不變但有搖曳動(dòng)畫的物體,可以預(yù)先把陰影烘焙到貼圖中,然后把陰影圖作為單獨(dú)貼圖、或地面貼圖Alpha通道傳送到地面shader中,然后只需要添加陰影晃動(dòng)的特性就可以隨植物晃動(dòng)而晃動(dòng),伴隨有一種真實(shí)陰影的感覺(jué)。另外注意陰影的方向、和植物晃動(dòng)的方向同步等細(xì)節(jié)。

具體細(xì)節(jié)可以參考:手機(jī)游戲中大量植物圖像的偽陰影渲染

肆 · 結(jié)合Projector和Rendertexture的實(shí)時(shí)陰影

創(chuàng)建一個(gè)跟隨主相機(jī)的陰影相機(jī),改為正交投影,設(shè)置單獨(dú)的shadow Layer,將需要投射陰影物體設(shè)置到shadow layer,為此陰影相機(jī)設(shè)置渲染目標(biāo)到一個(gè)渲染紋理RTT_Shadow。另外創(chuàng)建一個(gè)Projector,為它設(shè)置一個(gè)材質(zhì)Mat_Proj,并將RTT_Shadow傳到Mat_Proj的shader中進(jìn)行著色,另外為防止投影相機(jī)邊緣的刺刺的長(zhǎng)線,要設(shè)置一個(gè)陰影衰減紋理,如果需要軟陰影則需要另外Blur。

這是最近幾年手游應(yīng)用比較廣泛的方法,網(wǎng)上有很多相關(guān)文章,比如:結(jié)合Projector和Rendertexture實(shí)現(xiàn)實(shí)時(shí)陰影、ProjectorShadow(手游上的實(shí)時(shí)陰影方案)

另外AssetStore也有不少類似插件:Fast Shadow Projector

伍 · 角色腳下陰影面片

對(duì)于游戲中的NPC、雜兵、野怪這些非關(guān)鍵性角色可以直接設(shè)置一個(gè)陰影面片來(lái)模擬陰影,當(dāng)然如果地面起伏比較大可能會(huì)有穿插問(wèn)題。

陸 · Light Probe

具體細(xì)節(jié)參考Unity手冊(cè)不贅述了:Light Probes

柒 · Shadow Maps

1.Standard Shadow Mapping

基本思想是在光源位置放置一個(gè)相機(jī)(Light space Camera),畫一遍深度得到深度圖,在渲染場(chǎng)景時(shí)將pixel坐標(biāo)轉(zhuǎn)換Light Space計(jì)算深度,然后比較它深度和深度圖中的深度,如果比深度圖中深度大就意味著在陰影中,否則在被照亮。

陰影的鋸齒有兩類:透視導(dǎo)致的鋸齒(Perspective alias)和投影導(dǎo)致的鋸齒(Project alias)。

2.PCF

投影導(dǎo)致的鋸齒是因?yàn)闊艄馔渡浞较蚝臀矬w表面夾角過(guò)小時(shí)多pixel對(duì)應(yīng)陰影圖的一個(gè)texel,這可以通過(guò)提高陰影圖的大小來(lái)解決,也可以通過(guò)Percentage Closer Filtering來(lái)柔化邊緣。PCF就是在繪制時(shí),除了繪制當(dāng)前點(diǎn)還會(huì)對(duì)周圍像素進(jìn)行多次采樣、混合來(lái)柔化鋸齒,常用PCF有:使用隨機(jī)采樣實(shí)現(xiàn)soft shadow泊松采樣等。

3.PSM

透視導(dǎo)致的鋸齒是因?yàn)橥敢暤慕筮h(yuǎn)小所導(dǎo)致的,于是就有了Perspective Shadow Map,它將整個(gè)Shadow Map的計(jì)算過(guò)程轉(zhuǎn)到歸一化設(shè)備空間(NDC)來(lái)計(jì)算,這就消除了近大遠(yuǎn)小的問(wèn)題。下圖是Standard Shadow Map和經(jīng)過(guò)Perspective Shadow Map優(yōu)化過(guò)的陰影,陰影明顯更細(xì)致。

可是PSM本身有很大局限性,比如影子質(zhì)量比較依賴視角方向、近處陰影與遠(yuǎn)處陰影Z分布過(guò)大。

4.LISPSM

在PSM的基礎(chǔ)上又有了新的陰影技術(shù)Light Space Perspective Shadow Maps,它是在和燈光方向垂直的方向構(gòu)建View Frustrum,然后將燈光、場(chǎng)景都轉(zhuǎn)到這個(gè)View Frustrum的Perspective space,然后再計(jì)算Shadow Map,這樣無(wú)論是點(diǎn)光、聚光、平行光就都轉(zhuǎn)為平行光。

左圖是Uniform(近處精度不足),中間是LISPSM(近處、遠(yuǎn)處都不錯(cuò)),右面是PSM(遠(yuǎn)處精度不足)。LISPSM具體細(xì)節(jié)參考:

https://www.cg.tuwien.ac.at/research/vr/lispsm/shadows_egsr2004_revised.pdf

5.VSM(方差陰影)

在使用PCF時(shí)一般不能提前對(duì)Shadow Map進(jìn)行模糊處理,因?yàn)檫@會(huì)導(dǎo)致PCF計(jì)算不準(zhǔn),而Variance Shadow Maps則沒(méi)有這樣的限制。VSM存儲(chǔ)的Shadow Map不僅包括深度,還有深度的平方,這時(shí)可以對(duì)Shadow Map做過(guò)濾,然后利用切比雪夫不等式計(jì)算出大于當(dāng)前深度的概率上限,也就是陰影區(qū)的概率。切比雪夫不等式:

左圖是Standard Shadow Map,右圖是Variance Shadow Map,具體細(xì)節(jié)參考:Variance Shadow Mapping、VSM的demosMatt's Variance Shadow Maps

6.CSM / PSSM

這是兩種分別研究發(fā)表但是原理幾乎一樣的陰影技術(shù),Unity用的就是CSM,而其中PSSM是幾個(gè)中國(guó)人(Zhang F, Sun H Q, Xu L L, et al,觀摩大佬風(fēng)采)提出的。它們的原理如下:

a) 對(duì)攝像機(jī)視錐體內(nèi)沿著Z由近到遠(yuǎn)切陰影圖分為多張,而切分是兩種切分規(guī)則的混合,一種是均勻切分,一種是指數(shù)切分,兩者按照一定比率混合起來(lái)。

b) 對(duì)每一塊分別計(jì)算一個(gè)光源投影空間內(nèi)平移、縮放的矩陣cropMatrix,它可以將切分的多塊移動(dòng)、縮放到光源的視椎中,這個(gè)矩陣和正交投影矩陣非常像。

// Build a matrix for cropping light's projection

// Given vectors are in light's clip space

Matrix Light::CalculateCropMatrix(Frustum splitFrustum)

{

Matrix lightViewProjMatrix = viewMatrix * projMatrix;

// Find boundaries in light's clip space

BoundingBox cropBB = CreateAABB(splitFrustum.AABB,

lightViewProjMatrix);

// Use default near-plane value

cropBB.min.z = 0.0f;

// Create the crop matrix

float scaleX, scaleY, scaleZ;

float offsetX, offsetY, offsetZ;

scaleX = 2.0f / (cropBB.max.x - cropBB.min.x);

scaleY = 2.0f / (cropBB.max.y - cropBB.min.y);

offsetX = -0.5f * (cropBB.max.x + cropBB.min.x) * scaleX;

offsetY = -0.5f * (cropBB.max.y + cropBB.min.y) * scaleY;

scaleZ = 1.0f / (cropBB.max.z - cropBB.min.z);

offsetZ = -cropBB.min.z * scaleZ;

return Matrix( scaleX,? ? 0.0f,? ? 0.0f,? 0.0f,

0.0f,? scaleY,? ? 0.0f,? 0.0f,

0.0f,? ? 0.0f,? scaleZ,? 0.0f,

offsetX,? offsetY,? offsetZ,? 1.0f);

}

c) 針對(duì)切分的每一塊渲染陰影圖,一般陰影圖大小一樣的,比如都是1024*1024,而近處包含的場(chǎng)景范圍比遠(yuǎn)處小,所以近處陰影圖的精度會(huì)更高。

d) 渲染場(chǎng)景陰影

關(guān)于CSM和PSSM具體細(xì)節(jié)參考:Cascaded Shadow MapsParallel-split shadow maps for large-scale virtual environments、PSSM from GPU Gems 3

另外Shadow Maps還有很多其他變種:已知Shadow Maps名稱匯總

文末,再次感謝馮委的分享,如果您有任何獨(dú)到的見(jiàn)解或者發(fā)現(xiàn)也歡迎聯(lián)系我們,一起探討。(QQ群:465082844)。

也歡迎大家來(lái)積極參與U Sparkle開(kāi)發(fā)者計(jì)劃,簡(jiǎn)稱"US",代表你和我,代表UWA和開(kāi)發(fā)者在一起!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容