參考
https://docs.cocos.com/creator/3.3/manual/zh/concepts/scene/shadow.html
在 3D 世界中,光與影一直都是極其重要的組成部分,它們能夠豐富整個(gè)環(huán)境,質(zhì)量好的陰影可以達(dá)到以假亂真的效果,并且使得整個(gè)世界具有立體感。
Creator 3.0 目前支持 Planar 和 ShadowMap 兩種陰影類(lèi)型。
關(guān)于ShadowMap理論部分,可以參考圖形學(xué)筆記九 陰影 光追一 求交
一、Planar簡(jiǎn)介
參考
使用頂點(diǎn)投射的方法制作實(shí)時(shí)陰影
【Unity Shader】平面陰影(Planar Shadow)
1.概述
陰影是計(jì)算機(jī)圖形學(xué)中一個(gè)很重要的部分,陰影的加入使得物體更加具有立體感,也有助于我們理解物體間的相互位置關(guān)系和大小。
實(shí)時(shí)陰影的實(shí)現(xiàn)方法有很多種,shadowMap適用性最好,但性能開(kāi)銷(xiāo)也大,有時(shí)候我們的項(xiàng)目其實(shí)并不需要那么通用的陰影,我們只需要一個(gè)“適用某些特定場(chǎng)合”的一個(gè)“看起來(lái)正確”的實(shí)時(shí)陰影。
本文所說(shuō)的,就是一種利用頂點(diǎn)投射的方法實(shí)現(xiàn)的實(shí)時(shí)陰影技術(shù),在一些陰影質(zhì)量要求不高,地面平整的項(xiàng)目是一個(gè)非常合適的方案,現(xiàn)在很火的手游《王者榮耀》就用了類(lèi)似的技術(shù)。
(經(jīng)提醒,這個(gè)技術(shù)叫平面投影陰影(Planar Projected Shadows)技術(shù),由Jim Blinn 1988年提出。http://www.twinklingstar.cn/2015/1717/tech-of-shadows/#21_Blinns)
2.推導(dǎo)
說(shuō)到求投影,我當(dāng)時(shí)首先想到的就是用投影矩陣,后來(lái)經(jīng)群友提醒,其實(shí)可以更進(jìn)一步簡(jiǎn)化為求相似三角形,這樣理解起來(lái)似乎還更簡(jiǎn)單些,以下是推導(dǎo)過(guò)程,為了簡(jiǎn)化計(jì)算,我們?cè)诙S空間內(nèi)進(jìn)行推導(dǎo)。
根據(jù)已知條件,我們可以得到一個(gè)這樣的題目:已知平面坐標(biāo)系內(nèi)一個(gè)單位向量L(Lx,Ly),坐標(biāo)系內(nèi)一點(diǎn)M(Mx,My),求點(diǎn)M沿著L方向 在y = h上的投影位置P,如下圖所示:

3.優(yōu)缺點(diǎn)
這種平面陰影的優(yōu)點(diǎn)是性能消耗小,陰影品質(zhì)較高,簡(jiǎn)單好實(shí)現(xiàn),非常適合MOBA類(lèi)、俯視視角類(lèi)型的游戲(角色和相機(jī)有一定距離)。
但是此方法的局限性也很大,最明顯的缺點(diǎn)是陰影只投影在特定平面上,會(huì)插到墻體等障礙物中。另外它模擬的并不是真實(shí)的物理陰影和實(shí)際差別很大,陰影邊緣很硬,真實(shí)的邊緣會(huì)有虛化,如果鏡頭距離較近的話缺陷就會(huì)比較明顯。

二、Shadow Map簡(jiǎn)介
參考
Shadow Map陰影貼圖小講
作為目前絕大多數(shù)電子游戲使用的陰影技術(shù),我們來(lái)看一下shadow mapping的原理,優(yōu)缺點(diǎn)都有哪些。這篇文章主要講解Directional Light平行光產(chǎn)生的陰影, Omnidirectional light(也就是點(diǎn)光源,具體分為point light和spot light)產(chǎn)生的陰影放在下一篇中。
我們先講原理,拿OpenGL舉例,不談具體的代碼,聊聊大概的框架和偏原理的數(shù)學(xué),以及實(shí)現(xiàn)過(guò)程中出現(xiàn)的問(wèn)題,問(wèn)題產(chǎn)生的原因和相對(duì)應(yīng)的優(yōu)化方案。
1.Shadow Mapping的原理
對(duì)于比較了解實(shí)時(shí)渲染的同學(xué)們應(yīng)該知道 實(shí)時(shí)渲染中z-buffer的概念。這里簡(jiǎn)單的講一下z-buffer是什么。z,取于3D場(chǎng)景中xyz坐標(biāo)中的z。在將一個(gè)3D場(chǎng)景空間的三角形轉(zhuǎn)換在2D圖形空間的過(guò)程中,保留每個(gè)像素點(diǎn)的深度值儲(chǔ)存起來(lái),這就是z-buffer,這個(gè)過(guò)程在vertex shader中完成??偨Y(jié)來(lái)說(shuō),zbuffer就是每個(gè)像素點(diǎn)在場(chǎng)景空間的深度,通過(guò)比較每個(gè)像素點(diǎn)的深度,可以確定哪些像素點(diǎn)被遮擋而不需要被渲染,可以很大的提高渲染的效率。
介紹完z-buffer,現(xiàn)在開(kāi)始講講Shadow Mapping的原理。原理其實(shí)非常簡(jiǎn)單,在確定遮擋關(guān)系的時(shí)候, z-buffer的深度值是相對(duì)于攝像機(jī)鏡頭,也就是眼睛所見(jiàn)來(lái)確定物體是否被遮擋,這個(gè)很容易理解。眼睛能看不見(jiàn)的地方,當(dāng)然被擋住了。那我們換個(gè)角度想想,陰影是如何產(chǎn)生的?
陰影其實(shí)就是光線看不見(jiàn)的地方。因此,如果我們把攝像機(jī)放到光源,那么光能看見(jiàn)的地方,亮度就會(huì)高;而光看不見(jiàn)的地方,就是陰影。所以,通過(guò)把攝像機(jī)放到光源出獲得的每個(gè)像素點(diǎn)的深度值z(mì)-buffer保存起來(lái)得到一張類(lèi)似于map的緩沖,就是Shadow Map。
當(dāng)我們有了這一張Shadow Map以后,我們回到攝像機(jī)空間中,把每個(gè)點(diǎn)在光照空間的坐標(biāo)求出來(lái),獲得每個(gè)點(diǎn)的深度值z(mì),在fragment shader中通過(guò)blinn-phong光照模型來(lái)渲染場(chǎng)景。這就是Shadow Map的大概原理。

圖片來(lái)自http://LearnOpenGL.com
這張圖可以很好地幫助理解。我們通過(guò)矩陣T來(lái)獲得每個(gè)點(diǎn)在光照空間的坐標(biāo)。如左圖,P點(diǎn)是攝像機(jī)可以看到的點(diǎn),因此需要通過(guò)T(P)轉(zhuǎn)化到光照空間中,從而獲得深度值0.9。而C點(diǎn)攝像機(jī)捕捉不到,但是可以直接在光照空間中獲得深度值0.4。通過(guò)比較兩個(gè)點(diǎn)深度值,我們判斷出P點(diǎn)處在陰影當(dāng)中。
以下參考
7.2 Shadow Map
Shadow Map實(shí)現(xiàn)原理:首先將光源想像成一個(gè)相機(jī)對(duì)場(chǎng)景做第一次渲染,將光源所能看到的所有場(chǎng)景物體的深度寫(xiě)入z-buffer中,如果光源為定向光,如太陽(yáng),光源將對(duì)觀察者所看到的視錐體內(nèi)的所有物體投射陰影, 光源使用正交投影,投影的視圖的寬(x),高(y)要設(shè)置得足夠大來(lái)包含所有可視物體。如果光源為局部(Local)光源,也需要做類(lèi)似調(diào)整,如果局部光源離投影陰影的物體足夠遠(yuǎn),一個(gè)視錐體就可以包含這些物體, 特別地,如果 局部光源是一盞聚光燈(spotlight),它本身與有一個(gè)視錐關(guān)聯(lián),視錐外的一切物體都認(rèn)為是不會(huì)受光照影響。
并不是場(chǎng)景中的所有物體都要渲染到shadow map中,只有投影陰影的物體才需要被渲染。例如,如果已知地面只接收陰影而不投影陰影,那么就不需要將其渲染到shadow map。
三、cocos中開(kāi)啟陰影
1.在 層級(jí)管理器 中選中 Scene,然后在 屬性檢查器 的 shadows 組件中勾選 Enabled 屬性。

2.在 層級(jí)管理器 中選中需要顯示陰影的 3D 節(jié)點(diǎn),然后在 屬性檢查器 的 MeshRenderer 組件中將 ShadowCastingMode 屬性設(shè)置為 ON。

若陰影類(lèi)型是 ShadowMap,還需要將 MeshRenderer 組件上的 ReceiveShadow 屬性設(shè)置為 ON。注意:Planar 類(lèi)型的陰影只有投射在平面上才能正常顯示,不會(huì)投射在物體上,也就是說(shuō) MeshRenderer 組件中的 ReceiveShadow 屬性是無(wú)效的。
注意:如果陰影無(wú)法正常顯示,需要調(diào)整一下方向光的照射方向。
3.Planar shadow
Planar 陰影類(lèi)型一般用于較為簡(jiǎn)單的場(chǎng)景。

- Enabled 是否開(kāi)啟陰影效果
- Type 陰影類(lèi)型
- Saturation 調(diào)節(jié)陰影飽和度
- ShadowColor 設(shè)置陰影顏色
- Normal 垂直于陰影的法線,用于調(diào)整陰影的傾斜度
- Distance 陰影在法線的方向上與坐標(biāo)原點(diǎn)的距離
4.ShadowMap
ShadowMap 是以光源為視點(diǎn)來(lái)渲染場(chǎng)景的。從光源位置出發(fā),場(chǎng)景中看不到的地方就是陰影產(chǎn)生的地方。

- Enabled 是否開(kāi)啟陰影效果
- Type 設(shè)置陰影類(lèi)型
- Saturation 調(diào)節(jié)陰影飽和度
- Pcf 設(shè)置陰影邊緣反走樣等級(jí),目前支持 HARD、SOFT、SOFT_2X,詳情可參考下文 PCF 軟陰影 部分的介紹。
- MaxReceived 最多支持產(chǎn)生陰影的光源數(shù)量,默認(rèn)為 4 個(gè),可根據(jù)需要自行調(diào)整
- Bias 設(shè)置陰影偏移值,防止 z-fiting
- NormalBias 設(shè)置法線偏移值,防止曲面出現(xiàn)鋸齒狀
- ShadowMapSize 設(shè)置陰影紋理大小,目前支持 Low_256x256、Medium_512x512、High_1024x1024、Ultra_2048x2048 四種精度的紋理
- InvisibleOcclusionRange 設(shè)置 Camera 可見(jiàn)范圍外的物體產(chǎn)生的陰影是否投射到可見(jiàn)范圍內(nèi),若需要?jiǎng)t調(diào)大該值即可
- ShadowDistance 設(shè)置 Camera 可見(jiàn)范圍內(nèi)顯示陰影效果的范圍,陰影質(zhì)量與該值的大小成反比
- FixedArea 設(shè)置是否通過(guò)手動(dòng)設(shè)置下列屬性來(lái)控制 Camera 可見(jiàn)范圍內(nèi)顯示陰影效果的范圍,詳情可參考下文 FixedArea 模式 部分的介紹
- Near 設(shè)置主光源相機(jī)的近裁剪面
- Far 設(shè)置主光源相機(jī)的遠(yuǎn)裁剪面
- OrthoSize 設(shè)置主光源相機(jī)的正交視口大小
注意:從 v3.3 開(kāi)始,屬性檢查器 中陰影的 Linear、Packing 項(xiàng)被移除,Creator 將自動(dòng)判斷硬件能力,并選用最優(yōu)方式進(jìn)行陰影渲染。
ShadowMap 在開(kāi)啟了物體 MeshRenderer 組件上的 ReceiveShadow 后,就會(huì)接收并顯示其它物體產(chǎn)生的陰影效果。
ShadowMap 一般用于要求光影效果比較真實(shí),且較為復(fù)雜的場(chǎng)景。但不足之處在于如果不移動(dòng)光源,那么之前生成的 Shadow Map 就可以重復(fù)使用,而一旦移動(dòng)了光源,那么就需要重新計(jì)算新的 ShadowMap。
5.PCF 軟陰影
百分比漸近過(guò)濾(PCF)是一個(gè)簡(jiǎn)單、常見(jiàn)的用于實(shí)現(xiàn)陰影邊緣反走樣的技術(shù),通過(guò)對(duì)陰影邊緣進(jìn)行平滑處理來(lái)消除陰影貼圖的鋸齒現(xiàn)象。原理是在當(dāng)前像素(也叫做片段)周?chē)M(jìn)行采樣,然后計(jì)算樣本跟片段相比更接近光源的比例,使用這個(gè)比例對(duì)散射光和鏡面光成分進(jìn)行縮放,然后再對(duì)片段著色,以達(dá)到模糊陰影邊緣的效果。
目前 Cocos Creator 支持 硬采樣、4 倍采樣(SOFT 模式)、9 倍采樣(SOFT_2X 模式),倍數(shù)越大,采樣區(qū)域越大,陰影邊緣也就越柔和。
6.FixedArea 模式
FixedArea 模式用于設(shè)置是否手動(dòng)控制 Camera 可見(jiàn)范圍內(nèi)顯示陰影效果的范圍:
- 若不勾選該項(xiàng)(默認(rèn)),則引擎會(huì)使用和 CSM(級(jí)聯(lián)陰影算法)相同的裁切流程和相機(jī)計(jì)算,根據(jù) Camera 的方向和位置來(lái)計(jì)算陰影產(chǎn)生的范圍。
- 若勾選該項(xiàng),則根據(jù)手動(dòng)設(shè)置的
Near、Far、OrthoSize屬性來(lái)控制陰影產(chǎn)生的范圍。
四、官方示例test-case-3d中的shadow-map
五、問(wèn)題
1.關(guān)于3.0 ShadowMap的疑問(wèn)
離燈很近的地方不產(chǎn)生陰影
2.一個(gè)bug,Creator3.0的地形Terrain 不能接收陰影,角色無(wú)法在地形上顯示陰影
Planar只能在XOZ面上投,如果的地形的高度(Y軸坐標(biāo))超過(guò)了0,就看不到了。
3.請(qǐng)問(wèn)游戲中如何關(guān)閉陰影
目前強(qiáng)行關(guān)閉了
let planarShadows = director.getRunningScene()._renderScene.planarShadows
planarShadows.enabled = false
planarShadows.onGlobalPipelineStateChanged()
planarShadows[’_pendingModels’].length = 0;