傳統光柵繪圖的階層式陰影貼圖技術

首先,讓我們回顧階層式陰影貼圖(cascaded shadow map)技術——這是現今用來產生光柵式繪圖(rasterized graphics)陰影的先進方法。階層式陰影貼圖的基本概念是利用截錐體(frustum),根據與視點之間的距離將其分割為數個區域,然後為每個區域產生一個陰影貼圖。此方法能為陰影貼圖提供不同的解析度:與相機較靠近的物件解析度較高,而距離較遠的物件則單位面積的解析度較低。

在下圖中,我們可以看到多個物件在某一場景中的例子。每個陰影貼圖依次被渲染,而且每個貼圖覆蓋的區域越來越大。由於所有的陰影貼圖都有相同的解析度,因此陰影貼圖畫素的密度會隨著我們遠離視點而逐漸下降。

最後,當利用相機視角在最後的場景中再次渲染物件時,我們可根據每個物件與視點間的距離來選擇適當的陰影貼圖,並在這些陰影貼圖之間執行內插計算,以決定最終的畫素是明亮的還是在陰影中。

20160613 IMGT TA31P1 圖1:階層式陰影貼圖。透過這種先進的非光線追蹤式技術,能以不同的解析度為每個光線渲染陰影貼圖

所有這些複雜的處理程序都是為了達到一個目的:降低由於陰影貼圖太粗糙所造成的解析度假影發生。此方法能順利運作是因為遠離視點的物件僅佔場景中較小的空間,因此較不需要清楚的陰影細節。而且它的執行效果還不錯——然而卻得耗費大量的GPU週期與記憶體流量。

光線追蹤陰影簡介

光線追蹤式陰影(Ray traced shadow)基本上是在螢幕空間中運作的。因此,陰影貼圖的解析度無需與螢幕解析度對準;自然就不會有解析度的問題存在。

光線追蹤式陰影演算法的基本運作方式如下:針對可視表面上的每一個點,我們會朝光源直接射出一道光線。如果光線會到達光源,表面就是明亮的,那麼我們就會採用傳統的照明常式(routine)。如果光線在到達光源前先碰到別的東西,那麼光線就會被捨棄,因為該表面就會在陰影中。這種技巧能產生鮮明、清晰的陰影(hard shadow),就像我們在晴朗無雲的好天氣時所看到的一樣。

然而,真實世界中的大部份陰影都會漸層地過渡到較亮和較暗的區域——我們將這些模糊的邊緣稱為半影(penumbra)。半影是由與光源物理相關的不同因素所造成的;即使大多數遊戲將光源建模為無維度的點光源,但實際上,光源是有表面的。這種表面區域就是造成模糊陰影的原因。在半影區域內,一部份的光源會被遮光物件擋住,但其他的光源仍會有清晰的路徑。這就是為什麼,你會看到有些區域不是完全明亮,也不會完全在陰影中。

20160613 IMGT TA31P2 圖2:模糊陰影的簡化模型。半影的大小依據與遮光物和光源間的距離比例而定

下圖顯示如何根據三個變數來計算半影的大小:光源的大小(R)、與光源之間的距離(L)、遮光物與陰影投射表面間的距離(O)。移動遮光物使其接近表面,半影就會縮小。

20160613 IMGT TA31P3 圖3:為了計算分析半影的大小,必須利用與遮光物的距離(O)、與光源的距離(L)以及光源半徑(R),計算出半影寬度(P)(註:若光源距離夠遠,則R/L可被視為常數)

根據這些變數,可推導出一個計算半影大小的簡單公式。

利用這種直接關係,我們能夠得到一個演算法,利用每個畫素只有一道光線的方式來渲染出正確的模糊陰影。我們先從以上的清晰陰影演算法開始,但是當光線穿過物件時,記錄表面與該物件在螢幕空間緩衝器中的距離。

20160613 IMGT TA31P4 圖4:當光線穿過物件時,記錄表面與該物件在螢幕空間緩衝器的距離

進一步延伸這種演算法,即可支援半透明表面的計算。例如,當穿過一個表面時,我們就能記錄它是否透明。如果表面透明,就能選擇繼續使光線穿越表面,並在單獨的密度緩衝器中註記其alpha值。

20160613 IMGT TA31P5 圖5:進一步延伸此演算法,可計算半透明的表面

與階層式陰影貼圖或其他常用的技巧相較,此方法有多項優點:

  • 不會有陰影貼圖解析度的問題,因為它都是以螢幕空間為基礎
  • 不會有由於取樣錯誤造成的邊帶(banding)、雜訊或buzzing效應
  • 不會有偏移問題(有時稱為Peter-Panning),這是因為光線直接投射在幾何形狀上,因而能獲得陰影與投射物件間的完美接觸

接著展示利用光線追蹤技術產生緩衝器的範例。

首先是光線追蹤密度緩衝器。在螢幕中的大部份物件都不是透明的,因此,我們的陰影密度是1。但是,柵欄區域包含了許多數值介於0到1之間的畫素。

20160613 IMGT TA31P6 圖6:光線追蹤密度緩衝器

接下來,是與遮光物緩衝器間的距離。當我們遠離遮光物件時,紅色分量(red component)的數值增加,代表陰影畫素與遮光物間的距離越來越遠。

20160613 IMGT TA31P7 圖7:光線追蹤距離緩衝器

最後,我們會執行一個通帶濾波器(filter pass),利用這兩種緩衝器來計算每個畫素的陰影數值。

我們先計算影響每個畫素的半影大小,利用此半影選擇模糊核心半徑,然後依此模糊螢幕空間的陰影密度緩衝器。對於已填入與遮光物緩衝器間距離數值的畫素來說,計算半影十分簡單。因為現在已經有了到遮光物的距離值,只需要將此數值從世界空間投射到螢幕空間就好,然後利用投射的半影來選擇模糊核心半徑。

20160613 IMGT TA31P8 圖8:計算半影大小——如果現有的畫素在陰影中,利用現有畫素到遮光物距離的數值(a);而當畫素不在陰影中,可利用交叉搜尋陰影附近的畫素,再選擇到遮光物的最大距離(b)

如果畫素是亮的,我們需要多一點的運算工作。利用交叉搜尋演算法可算出另一個畫素,以及造成半影產生的光線。如果在位於陰影中的X或Y軸上找到任何的畫素(即擁有有效的距離值),就能選擇到遮光物的最大距離,並利用它來計算此畫素的半影大小。然後調整此畫素的距離,並計算半影區域。

由此開始,演算法都是相同的:我們從世界空間取得半影大小,並將其投射在螢幕空間,然後計算出半影覆蓋的畫素區域,最後再執行「模糊」。如果發生未找到陰影畫素的情況,即可假設畫素是完全明亮的。

下圖顯示最終的濾波器核心。

20160613 IMGT TA31P9 圖9:深度駁回——保留不連續的邊緣 相對於螢幕空間執行偏微分計算; 計算中間畫素與畫素取樣之間的深度差異,如果在臨界值內,則可接受該樣本 (注意此圖中有公式)

透過箱式濾波器(box filter)覆蓋半影區域,即可為其取樣,以得知是否為不連續表面。在此可利用深度駁回(depth rejection)來作為輔助工具。為了計算深度駁回數值,可利用偏微分計算現有的畫素與X和Y軸上特定數值間的差異。此結果會告訴我們,我們需要退回多遠,才能在螢幕空間中移動。當取樣核心時,可根據我們與中心畫素之間的距離來擴大深度臨界值。

結果:階層式陰影貼圖vs.光線追蹤式陰影

在以上的例子中,我們駁回了所有標示紅色的樣本,因為其對應區域是屬於柵欄,而我們有興趣的是取樣地面上的一點。經過模糊作用後,所取得的緩衝器代表準確地估算了螢幕上的陰影密度。

下圖顯示原始畫面的最終螢幕截圖比較;請注意,與階層式陰影貼圖相較,光線追蹤陰影的影像品質有了更明顯的提升。

20160613 IMGT TA31P10 圖10:光線追蹤 vs 階層式陰影貼圖