Unity SRP自定义渲染管线 -- 5.Directional Shadows
原文:https://catlikecoding.com/unity/tutorials/scriptable-render-pipeline/directional-shadows/
- 支持多個(gè)方向光陰影
- 控制陰影距離
- 定義獨(dú)立的主光源
- 渲染和采樣級(jí)聯(lián)陰影(cascaded shadow map)
- 使用球形剔除
1.?Shadows for Directional Lights
直射光和聚光源在概念上沒(méi)有什么本質(zhì)不同。除了直射光來(lái)自無(wú)限遠(yuǎn)的地方,兩者基本相同。所以只需要一些調(diào)整,就可以讓聚光源陰影的方法適用于直射光。我們將進(jìn)一步改進(jìn)我們的渲染管線,讓它能混合直射光和聚光源的陰影。
1.1?Configuring Shadows
目前ConfigureLights中只處理了聚光源的陰影數(shù)據(jù)。而處理直射光源陰影的代碼和聚光源的是相同的。為了重復(fù)利用這些代碼,我們對(duì)代碼進(jìn)行一些重構(gòu),把它們寫成單獨(dú)的方法。
與聚光燈相比,渲染方向光陰影貼圖時(shí)有些不同,因此需要在處理時(shí)指明它是直射光。我們用陰影數(shù)據(jù)的z分量來(lái)作為判斷的標(biāo)識(shí)
1.2?Rendering Shadows
對(duì)于方向光,我們需要用ComputeDirectionalShadowMatricesAndCullingPrimitives函數(shù)來(lái)獲得裁剪信息,然而聚光源是用ComputeSpotShadowMatricesAndCullingPrimitives函數(shù)。重構(gòu)部分代碼,聲明一個(gè)bool變量,用該變量保存是否有有效的方向光或者聚光源。
如果陰影數(shù)據(jù)表明我們處理的是方向光,就調(diào)用ComputeDirectionalShadowMatricesAndCullingPrimitives?。這個(gè)方法有更多的參數(shù),因?yàn)樗С株幱凹?jí)聯(lián)(這里我們先不用)。第一個(gè)參數(shù)要求提供一個(gè)光源序列,接著是級(jí)聯(lián)的序列以及級(jí)聯(lián)的數(shù)量。我們現(xiàn)在用不到級(jí)聯(lián),所以序列設(shè)為0,數(shù)量設(shè)為1。之后是定義了級(jí)聯(lián)分級(jí)的三維向量,我們使用(1,0,0)。然后是整型的圖塊尺寸和陰影近平面值。最后是投影矩陣等輸出參數(shù)。?
splitData數(shù)據(jù)中包含了一個(gè)有效的剔除球體。該球體包裹了所有需要被渲染進(jìn)直射光陰影貼圖的物體。這對(duì)于直射光來(lái)說(shuō)非常重要,因?yàn)橹鄙涔獠幌窬酃庠?#xff0c;他會(huì)影響所有物體,我們需要有一個(gè)剔除球體來(lái)限制渲染進(jìn)陰影貼圖的圖形數(shù)量。對(duì)于聚廣源該設(shè)置沒(méi)有效果,沒(méi)有什么影響。
1.3?Shadow Distance
此時(shí)我們應(yīng)該可以得到方向光的陰影貼圖了。但是看起來(lái)我們得到的貼圖是空的,即使有也可能只是幾個(gè)小點(diǎn)。造成這個(gè)現(xiàn)象的原因是這個(gè)貼圖需要涵蓋的范圍太廣了,覆蓋了攝像機(jī)能夠看到的所有東西,默認(rèn)是1000單位,由相機(jī)的遠(yuǎn)平面控制。想看到貼圖的內(nèi)容就要大幅降低相機(jī)遠(yuǎn)平面距離。
但陰影的渲染范圍并不應(yīng)該取決于相機(jī)遠(yuǎn)平面,這只是默認(rèn)的情況。陰影距離也能控制陰影的渲染范圍。陰影距離通常遠(yuǎn)小于相機(jī)的遠(yuǎn)平面。兩者綜合考慮既可以限制陰影渲染的范圍,又可以處理比陰影距離更小范圍的情況。
在MyPipeline中為陰影距離添加一個(gè)字段,并在構(gòu)造方法中設(shè)置。我們從相機(jī)中提取陰影距離并賦值給剔除參數(shù)。因?yàn)殇秩鞠鄼C(jī)可見(jiàn)范圍外的陰影沒(méi)有意義,所以使用陰影距離和相機(jī)遠(yuǎn)平面中的最小值更合理。
在MyPipelineAsset中為陰影距離添加一個(gè)配置選項(xiàng),并設(shè)置一個(gè)合理的默認(rèn)值,比如100。
1.4?Investigating Shadows
陰影距離降低到合理的值,直射光的陰影終于出現(xiàn)了。我們將光源的陰影偏移設(shè)為0并使用一個(gè)更大的平面作為地面。使用單個(gè)直射光照射。
因?yàn)槠圃O(shè)為了0,我們可以大致看到陰影貼圖覆蓋的區(qū)域。和聚光源的陰影不同,方向光的陰影貼圖隨相機(jī)移動(dòng)而改變。另外,陰影貼圖的邊緣也會(huì)影響超出陰影范圍的場(chǎng)景物體,這是因?yàn)槲覀儾蓸幼鴺?biāo)會(huì)超出貼圖邊緣(結(jié)果會(huì)采樣邊緣的值,因?yàn)槲覀兗y理設(shè)置的是clamp),這導(dǎo)致貼圖的邊緣被拉伸至無(wú)窮遠(yuǎn)。當(dāng)我們的陰影光源多于一個(gè)時(shí),拉伸消失了,因?yàn)槲覀優(yōu)槊總€(gè)圖塊使用scissoring清理了邊緣。
然而當(dāng)采樣多個(gè)超出范圍的tiles時(shí)我們會(huì)得到錯(cuò)誤的結(jié)果,tiles越多,結(jié)果越糟糕。?
1.5?Clamping to Shadow Tile
方向光陰影貼圖有些麻煩是因?yàn)楫?dāng)它們被采樣時(shí),無(wú)論物體是否在陰影貼圖的覆蓋范圍內(nèi)都會(huì)進(jìn)行采樣。解決方法就是限制陰影采樣坐標(biāo)在tile內(nèi)。我們?cè)诳s放到正確的圖塊前將陰影空間位置限制在0-1之間。之前沒(méi)有做限制我們可以在MyPipeline里完成整個(gè)轉(zhuǎn)換矩陣的計(jì)算,現(xiàn)在我們不得不把這一步移到shader里了。
tile scale信息對(duì)于變換時(shí)是必須的,我們將其傳入shader。聲明一個(gè)全局的vector用于保存陰影信息,將其命名為_(kāi)GlobalShadowData?并持有它的標(biāo)識(shí)符。
offset信息也是必需的,我們將其存儲(chǔ)在ZW分量中。
之后移除對(duì)圖塊矩陣的乘法計(jì)算并在shader中在陰影緩存區(qū)添加一個(gè)全局陰影向量
在ShadowAttenuation函數(shù)中,在透視除法后對(duì)陰影位置的xy坐標(biāo)做限制,將其限制在0-1范圍內(nèi),之后再應(yīng)用圖塊的坐標(biāo)變換。?
直射光陰影需要透視除法嗎?
不需要,因?yàn)橹鄙涔怅幱百N圖用的是正交投影。陰影位置的w向量恒為1,但是我們要混合直射光和聚光源,所以我們統(tǒng)一執(zhí)行透視除法。
1.6?Always Use Scissors
我們解決了在多個(gè)圖塊時(shí)的陰影雜亂( shadow soup)問(wèn)題,但是當(dāng)場(chǎng)景中僅有一個(gè)方向光時(shí)陰影貼圖的邊緣仍會(huì)被拉伸。我們?cè)赗enderShadows中設(shè)置成無(wú)論幾個(gè)光源都使用裁剪來(lái)解決這個(gè)問(wèn)題。
1.7?Clipping Shadows Based on Distance
盡管陰影的距離是根據(jù)相機(jī)視角的距離,但是陰影并不是在當(dāng)物體離開(kāi)這個(gè)范圍后立刻消失。這是因?yàn)殛幱百N圖覆蓋了一個(gè)立方體的空間區(qū)域,只要這個(gè)區(qū)域的一部分在范圍內(nèi),就會(huì)整個(gè)都渲染。方向光隨著相機(jī)的移動(dòng)陰影貼圖也會(huì)重新渲染,所以對(duì)于這一問(wèn)題方向光很合適,但是聚光源就不一樣了,它的陰影空間區(qū)域和光源鎖定,即使陰影距離只占空間的很小一部分,最終渲染的還是整個(gè)區(qū)域。結(jié)果就是聚光燈陰影貼圖包含的所有陰影同時(shí)出現(xiàn)和消失。
我們可以使用配置的陰影距離來(lái)剪切陰影使陰影消失的邊界線更統(tǒng)一。要想這么做,就得把陰影距離傳給shader。我們將它放在全局陰影數(shù)據(jù)向量的第二個(gè)分量中。在我們實(shí)際裁剪時(shí),我們用的是它的平方來(lái)進(jìn)行比較,所以我們就直接存儲(chǔ)陰影距離的平方。
在shader中,我們還需知道相機(jī)的位置。Unity在配置相機(jī)的時(shí)候就會(huì)自動(dòng)提供這個(gè)信息。所以我們要做的只是在UnityPerCamera?緩存區(qū)中添加一個(gè)_WorldSpaceCameraPos?變量。
創(chuàng)建一個(gè)DistanceToCameraSqr函數(shù),該函數(shù)輸入世界位置,輸出與相機(jī)的平方距離。
在ShadowAttenuation中調(diào)用這個(gè)方法,檢查是否超出了陰影距離,如果是就跳過(guò)陰影采樣。
現(xiàn)在所有的陰影都在相同的距離消失,而不會(huì)突然的出現(xiàn)和消失了。
我們可以平滑的過(guò)渡陰影嗎?
你可以添加一個(gè)漸變距離并使用一些過(guò)渡函數(shù)來(lái)實(shí)現(xiàn),如線性插值,smoothstep等。
2.?Cascaded Shadow Map
陰影貼圖的缺點(diǎn)就是作為紋理,其分辨率必然是有限的。雖然你可以提高紋理分辨率來(lái)獲得更好的效果,但仍沒(méi)有擺脫這個(gè)限制。聚光源只覆蓋了一小塊區(qū)域,所以它的效果可以接受。但對(duì)于方向光,它的照射范圍是無(wú)限大的。在視野遠(yuǎn)處的陰影效果也許還可以接受,但是近處的陰影卻會(huì)顯得非常塊狀。我們稱之為透視鋸齒(perspective aliasing)
我們需要給近處的陰影提供更高的分辨率,遠(yuǎn)處的可以分辨率低一些。我們可以根據(jù)距離使用不同的分辨率,解決方案就是為同一個(gè)光源渲染多張陰影貼圖。我們?cè)诮幨褂酶叻直媛赎幱百N圖,在遠(yuǎn)處使用低分辨率。這些陰影貼圖稱之為級(jí)聯(lián)陰影(shadow cascade)
2.1?Cascade Amount
Unity通常為級(jí)聯(lián)陰影數(shù)量提供三個(gè)選項(xiàng):0、2、4。我們也一樣,在MyPipelineAsset添加一個(gè)ShadowCascades枚舉用于配置數(shù)量,默認(rèn)為4。
2.2?Cascade Split
Untiy還允許指定級(jí)聯(lián)在陰影距離中的分布情況。通過(guò)將整個(gè)陰影距離劃分成二或四個(gè)部分來(lái)實(shí)現(xiàn)。如果是2個(gè)級(jí)聯(lián),就用一個(gè)值來(lái)決定在哪里劃分兩者。如果是四個(gè)級(jí)聯(lián),就用存儲(chǔ)在向量中的三個(gè)值,將陰影距離劃分成四個(gè)部分。我們使用與輕量級(jí)渲染管線相同的默認(rèn)值。
但是Unity不會(huì)直接將這些值暴露在檢視面板,而是顯示一個(gè)特殊的GUI控件來(lái)允許你調(diào)整級(jí)聯(lián)的區(qū)域。我們也來(lái)實(shí)現(xiàn)這種效果,先把這些屬性隱藏起來(lái)。
我們需要?jiǎng)?chuàng)建一個(gè)自定義編輯器來(lái)顯示聯(lián)級(jí)劃分的GUI,我們先創(chuàng)建一個(gè)最基礎(chǔ)的。將它的腳本資源放在Editor文件夾中。獲取三個(gè)相關(guān)屬性,并繪制默認(rèn)的檢視器。我們還需要使用UnityEditor.Experimental.Rendering命名空間
在繪制默認(rèn)檢視器之后使用switch語(yǔ)句來(lái)決定我們繪制哪種級(jí)聯(lián)的GUI。使用CoreEditorUtils.DrawCascadeSplitGUI?函數(shù)去繪制,之后調(diào)用序列化對(duì)象的ApplyModifiedProperties?方法來(lái)確保用戶的修改可以應(yīng)用到我們的資源中。
?MyPipeline只需要知道要使用多少級(jí)聯(lián)以及他們的分布值是多少。我們可以使用單個(gè)三維向量同時(shí)處理二段和四段級(jí)聯(lián)的分布數(shù)據(jù)。按要求添加字段和構(gòu)造參數(shù)。
當(dāng)MyPipelineAsset調(diào)用渲染管線的構(gòu)造方法是,總是要求傳入一個(gè)分布情況向量,即使實(shí)際是二段級(jí)聯(lián)。這這種情況下,我們將唯一的分布值作為向量的第一個(gè)分量,另外兩個(gè)設(shè)為0。
2.3?Cascades for Main Directional Light Only
我們不為所有的方向光都提供級(jí)聯(lián)陰影功能,因?yàn)殇秩径鄠€(gè)陰影貼圖性能消耗很大。我們將最明亮最重要的一個(gè)方向光源作為主光源,為其提供級(jí)聯(lián)陰影,其他方向光只提供單陰影貼圖。
主光源總是可見(jiàn)光列表中的第一個(gè)元素。我們可以在ConfigureLights中判斷第一個(gè)光源是否符合標(biāo)準(zhǔn),如果是方向光、陰影強(qiáng)度為正數(shù),并且開(kāi)啟了陰影級(jí)聯(lián),那就說(shuō)明是有效的主光源。我們用一個(gè)bool字段來(lái)記錄這個(gè)情況。
我們會(huì)為主光源提供單獨(dú)的渲染貼圖,所以當(dāng)我們擁有主光源時(shí),讓圖塊計(jì)數(shù)減1,并且在RenderShadows函數(shù)中將其從常規(guī)陰影貼圖渲染中排除。
將級(jí)聯(lián)陰影以tiles的形式渲染到一張單獨(dú)的陰影貼圖中,將其命名為_(kāi)CascadedShadowMap。添加相關(guān)的標(biāo)識(shí)符和字段。并在最后和其他陰影貼圖一樣釋放紋理資源。
2.4?Reusing Code
渲染級(jí)聯(lián)陰影和之前我們做的陰影渲染很相似,但是其中的差異還是有必要用一個(gè)單獨(dú)的方法才能完成。然而這兩個(gè)方法里許多代碼都是重復(fù)的,我們把這部分代碼重構(gòu)成單獨(dú)的方法。
首先是關(guān)于陰影渲染目標(biāo)的設(shè)置,兩者在這部分的代碼是相同的。我們只需要用兩個(gè)字段記錄兩者渲染目標(biāo)對(duì)應(yīng)的渲染紋理即可。
然后是設(shè)置陰影tiles。計(jì)算tiles偏移,設(shè)置視口以及剪裁,偏移值可以用二維向量返回值得到。
再然后,計(jì)算world-to-shadow矩陣這部分也可以放在一個(gè)單獨(dú)的方法里,我們將視角和投影矩陣作為引用類型的參數(shù)傳入,這樣可以避免不必要的拷貝變量。同樣,將world-to-shadow矩陣作為輸出參數(shù)。
最后,調(diào)整RenderShadows函數(shù)使其使用重構(gòu)后的函數(shù)。
2.5?Rendering Cascades
級(jí)聯(lián)陰影的world-to-shadow矩陣需要單獨(dú)存儲(chǔ)在數(shù)組中,添加對(duì)應(yīng)的字段,因?yàn)槲覀兗?jí)聯(lián)數(shù)量最多為4,所以數(shù)組大小設(shè)置為4。
創(chuàng)建一個(gè)RenderCascadedShadows方法,首先復(fù)制RenderShadows的代碼。接下來(lái)就簡(jiǎn)單了,我們不需要考慮聚光源并且只會(huì)用到第一個(gè)光源。我們不需要處理每個(gè)光源的陰影數(shù)據(jù),而且陰影設(shè)置肯定是開(kāi)啟級(jí)聯(lián)的。級(jí)聯(lián)不是四段就是兩段,也就是說(shuō)陰影貼圖總是分為四個(gè)tile。
在調(diào)用ComputeDirectionalShadowMatricesAndCullingPrimitives時(shí),我們光源序列為0,并使用for循環(huán)的迭代值作為級(jí)聯(lián)序列。在這里我們就需要提供實(shí)際的級(jí)聯(lián)數(shù)量和分布向量了。最后,我們?cè)侔褕D塊的坐標(biāo)轉(zhuǎn)換附加到world-to-shadow中,在tile界限范圍內(nèi)渲染陰影是之后shader能正確采樣到級(jí)聯(lián)陰影的重要前提。
在ConfigureLights后,如果有主光源調(diào)用該函數(shù)
現(xiàn)在我們最終可能有0張,一張或者兩張渲染紋理。如果只有主光源,只需要渲染級(jí)聯(lián)陰影貼圖。如果有另外帶陰影的光源還需要渲染常規(guī)的陰影貼圖。或者我們有陰影但沒(méi)有主光源,那我們就只需要常規(guī)陰影貼圖。如果你在frame debugger中檢查級(jí)聯(lián)陰影貼圖,你會(huì)看到它由四個(gè)圖塊組成。它們內(nèi)容是否可見(jiàn)取決于陰影距離和級(jí)聯(lián)分布。
2.6 Sampling the Cascaded Shadow Map
在shader里使用級(jí)聯(lián)陰影貼圖,需要做一些事。首先,我們得知道使用軟陰影還是硬陰影的方式采樣貼圖,這一點(diǎn)我們可以通過(guò)shader關(guān)鍵字來(lái)控制。我們使用兩個(gè)關(guān)鍵字來(lái)區(qū)分級(jí)聯(lián)的軟硬陰影,省去了在shader中創(chuàng)建分支的需要。
接下來(lái)我們需要知道陰影貼圖的尺寸和陰影強(qiáng)度,雖然我們可以直接用_ShadowMapSize但是為了讓shader能分開(kāi)處理兩者的大小,我們使用單獨(dú)的_CascadedShadowMapSize?來(lái)表示。
在RenderCascadedShadows函數(shù)末尾設(shè)置這些值和關(guān)鍵字。
同樣也需要在RenderCascadedShadow?沒(méi)有被調(diào)用的情況下關(guān)閉級(jí)聯(lián)陰影的關(guān)鍵字
在Lit?shader中為級(jí)聯(lián)陰影關(guān)鍵字添加多重編譯指令。共有三個(gè)選項(xiàng):無(wú)/級(jí)聯(lián)軟陰影/級(jí)聯(lián)硬陰影。
之后向shadow buffer中添加所需的變量,定義級(jí)聯(lián)陰影貼圖紋理和采樣器
添加一個(gè)表明是否采樣級(jí)聯(lián)陰影的bool參數(shù)來(lái)使HardShadowAttenuation既可以用于常規(guī)的陰影采樣也可以用于級(jí)聯(lián)陰影,參數(shù)默認(rèn)為false。用這個(gè)bool值來(lái)決定具體使用哪張紋理和采樣器。我們使用的bool參數(shù)是硬編碼的,也就是說(shuō)在實(shí)際編譯時(shí)并不會(huì)產(chǎn)生條件分支。
SoftShadowAttenuation?也一樣,不過(guò)在這里只需要選擇正確的紋理就好了,其余的由HardShadowAttenuation?函數(shù)完成,沒(méi)必要再寫一遍。
創(chuàng)建一個(gè)CascadedShadowAttenuation?方法,他就像ShadowAttenuation的簡(jiǎn)化版。如果沒(méi)有級(jí)聯(lián)陰影,衰減直接設(shè)為1,反之才會(huì)計(jì)算陰影位置獲取軟硬陰影的衰減值并應(yīng)用應(yīng)用強(qiáng)度。
選擇正確的級(jí)聯(lián)陰影貼圖是下一節(jié)介紹的內(nèi)容,這一節(jié)我們先硬編碼統(tǒng)一使用第三張級(jí)聯(lián)陰影貼圖,也就是使用_WorldToShadowCascadeMatrices?中序列為2的轉(zhuǎn)換矩陣。如果你用的是四段級(jí)聯(lián),使用第三張貼圖可以讓我們看到大部分區(qū)域的陰影。如果用第四張的話區(qū)域是大了,但是近處的陰影分辨率太低,影響觀察。
接下來(lái),創(chuàng)建一個(gè)MainLight函數(shù)來(lái)計(jì)算主光源,它和DiffuseLight方法做的事一樣,但是限制了只計(jì)算索引為0的方向光,并且使用CascadedShadowAttenuation?來(lái)獲取陰影
如果有級(jí)聯(lián)陰影,就把主光源也加入LitPassFragment?計(jì)算漫反射總和中。?
主光源的級(jí)聯(lián)陰影現(xiàn)在終于能夠看到了,但是主光源在光照循環(huán)中被計(jì)算了兩次,這是錯(cuò)誤的。我們不能簡(jiǎn)單地跳過(guò)循環(huán)中的第一個(gè)光源,因?yàn)閷?duì)每個(gè)物體而言,無(wú)法確保主光源就是最重要的那個(gè)光源。對(duì)此我們要么在shader的循環(huán)中添加分支,要么渲染前就干脆將主光源移出可見(jiàn)光列表。我們選擇后者,修改中ConfigureLight?的光源數(shù)量。這樣的副作用就是當(dāng)我們有主光源時(shí),像素光數(shù)量上限變成了5個(gè)。
從可見(jiàn)光列表中移除主光源的問(wèn)題是如果我們使用了級(jí)聯(lián)陰影,每一幀都會(huì)修改可見(jiàn)光列表,從而導(dǎo)致臨時(shí)內(nèi)存的分配。現(xiàn)在也沒(méi)什么好辦法,除非以后會(huì)出一個(gè)不會(huì)分配新數(shù)組的GetLightIndexMap?方法。
2.7?Selecting the Correct Cascade
現(xiàn)在主光源的級(jí)聯(lián)陰影貼圖終于能用了,但用的都是同一級(jí)別的級(jí)聯(lián)貼圖。第三張級(jí)聯(lián)貼圖對(duì)于遠(yuǎn)處的陰影效果挺好,但是對(duì)于近處效果就很差。而第二張級(jí)聯(lián)貼圖恰恰相反,近處表現(xiàn)的很好,但是范圍實(shí)在太小了,遠(yuǎn)處根本沒(méi)陰影。
Unity使用級(jí)聯(lián)分段值劃分每個(gè)級(jí)聯(lián)貼圖負(fù)責(zé)的陰影空間區(qū)域。它使用一個(gè)剔除球體來(lái)定義每個(gè)級(jí)聯(lián)貼圖的范圍。剔除球的半徑依次增加,球的位置也同樣。想知道我們使用哪一等級(jí)的級(jí)聯(lián)貼圖,我們就得找出片元處于哪個(gè)剔除球內(nèi)部。
我們要把剔除球的信息傳給shader。使用數(shù)組是最方便的方法。在MyPipeline中添加對(duì)應(yīng)的標(biāo)識(shí)符和字段。用四維向量表示每個(gè)球。xyz分量描述球的位置。w分量定義球的半徑。
在RenderCascadedShadows函數(shù)里我們可以獲取每個(gè)級(jí)聯(lián)的剔除球。我們只需要簡(jiǎn)單的把它拷貝到我們的數(shù)組然后再傳給shader就ok了。因?yàn)樵谂袛嗥挥谀膫€(gè)剔除球時(shí)只會(huì)用到半徑的平方,所以我們傳入半徑的平方來(lái)減少shader的運(yùn)算。
shader中,將剔除球數(shù)組變量添加到陰影緩沖區(qū)
?創(chuàng)建一個(gè)很便捷的方法,用于判斷一個(gè)點(diǎn)是否在剔除球體內(nèi)。
在CascadedShadowAttenuation里為四個(gè)剔除球各調(diào)用一次這個(gè)方法。返回1表示該點(diǎn)位于剔除球內(nèi),返回0就在球外面。返回值就是表示這些球是否有效的標(biāo)志。在確定級(jí)聯(lián)等級(jí)前將這四個(gè)值放在一個(gè)flaot4類型變量中。
一點(diǎn)位于一個(gè)球的同時(shí),還躺在更大的球里面。我們最終可能得到五種情況: (1,1,1,1), (0,1,1,1), (0,0,1,1), (0,0,0,1),(0,0,0,0)。我們將這四個(gè)值加起來(lái)除以四得到的值返回來(lái)觀察級(jí)聯(lián)層次。也就是點(diǎn)乘?。
我們使用第一張符合要求的貼圖(所渲染的點(diǎn)所在范圍最小的級(jí)聯(lián)貼圖),也就是說(shuō)我們需要把其對(duì)應(yīng)標(biāo)志位后邊的標(biāo)志值清零。
?
一個(gè)點(diǎn)至少在一個(gè)剔除球里面時(shí),結(jié)果是沒(méi)問(wèn)題的,但點(diǎn)如果在所有剔除球外面,結(jié)果為0,會(huì)錯(cuò)誤的采樣了第一張級(jí)聯(lián)陰影貼圖。Unity在這里用了一個(gè)小技巧,它在world-to-shadow中添加一個(gè)零矩陣作為第五個(gè)數(shù)組元素來(lái)表示不存在的那個(gè)聯(lián)級(jí)貼圖。零矩陣會(huì)將陰影位置轉(zhuǎn)換到近平面,自然就不可能產(chǎn)生陰影了。我們也這樣做,為MyPipeline的worldToShadowCascadeMatrices?數(shù)組添加第五個(gè)元素。
?然而,如果z緩沖區(qū)反轉(zhuǎn),那我們就得將陰影空間的z坐標(biāo)設(shè)為1才能表示近平面,我們?cè)跇?gòu)造函數(shù)把這個(gè)矩陣的m33字段改為1即可。
增加shader中對(duì)應(yīng)數(shù)組的長(zhǎng)度,并完成(0,0,0,0) → 4的轉(zhuǎn)換,我們?cè)摓閷⒅岛?#xff08;4,3,2,1)點(diǎn)乘,讓4減去它來(lái)得到級(jí)聯(lián)等級(jí)
我們可以混合級(jí)聯(lián)貼圖嗎?
和Unity的渲染管線一樣,我們直接選擇一個(gè)級(jí)聯(lián)貼圖采樣。結(jié)果可能在每個(gè)級(jí)聯(lián)之間會(huì)有不連續(xù)的圖像。也就是陰影的像素突然發(fā)生變化。你也可以定義一個(gè)過(guò)渡區(qū)域,并在其中對(duì)兩個(gè)相鄰的級(jí)聯(lián)貼圖插值。這要求我們尋找兩個(gè)級(jí)聯(lián)貼圖的序列,一個(gè)混合因子,以及雙倍的陰影采樣。
因?yàn)樘蕹虿粫?huì)與相機(jī)和陰影距離對(duì)齊,所以級(jí)聯(lián)陰影不會(huì)和其他陰影一樣在同一距離消失。我們也一樣可以在CascadedShadowAttenuation?中檢查陰影距離來(lái)實(shí)現(xiàn)統(tǒng)一的效果。
Unity采樣級(jí)聯(lián)陰影貼圖時(shí),不是應(yīng)該用一個(gè)屏幕空間的pass嗎?
沒(méi)錯(cuò),Unity使用一個(gè)單獨(dú)的屏幕空間pass,將級(jí)聯(lián)陰影渲染到另一張紋理中去。這其實(shí)和我們做的一樣,只不過(guò)它會(huì)有一個(gè)整體的顯示。以便在forward pass的每個(gè)片元中采樣其中的陰影數(shù)據(jù)。屏幕空間pass會(huì)比較迂回地完成這些工作,而逐片元的計(jì)算方式則更為直白簡(jiǎn)單,這也是我為什么會(huì)選擇這種方法作為教程教學(xué)。
使用單獨(dú)的全屏pass的一個(gè)原因是可以更快的采樣陰影。在有大量重復(fù)繪制的情況下,效果會(huì)更好,因?yàn)榇藭r(shí)可能會(huì)有多個(gè)片元對(duì)同一個(gè)位置采樣。通過(guò)增加一個(gè)僅深度的pass存入深度緩存,來(lái)消除不透明物體的重復(fù)繪制以減少計(jì)算量。屏幕空間陰影的方法總是需要一個(gè)depth-only?的pass來(lái)提取片元的深度值。
另一個(gè)原因則是因?yàn)閁nity的舊版渲染管線可以用它展現(xiàn)高質(zhì)量的軟陰影濾波結(jié)果。但是在輕量級(jí)渲染管線里就用不到了,他對(duì)于所有的陰影采樣都使用相同的代碼。
哪個(gè)方法最好呢?你可以自己測(cè)試這三種情況:逐片元,depth-only?的逐片元,以及depth-only?的屏幕空間
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的Unity SRP自定义渲染管线 -- 5.Directional Shadows的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 光大中青旅信用卡好吗?值得申请吗?
- 下一篇: 光大中青旅信用卡额度多少?额度太低怎么提