使用隨機取樣實現 soft shadow

發表於2016-11-26

本篇文章主要針對《OpenGL 4 Sharding Language Cookbook》一書中第七章——Shadow的第四節Creating soft shadow edges with random sampling解釋而得。

鋸齒問題(aliasing)

基於shadow map實現陰影效果有很多不是那麼令人滿意的地方,其中一個就是鋸齒問題(aliasing)。出現鋸齒的原因是因為shadow texture的大小往往小於螢幕大小,當把shadow texture渲染到螢幕上時,那些多餘的畫素就會顯現出來鋸齒。

最基本的shadow map的思想,我想大家都知道,就不說了。它在最後會根據shadow texture上的深度值來判斷當前要渲染的fragment是否在陰影裡,如果是,那麼它的visibility為0,否則就是1。這樣0和1的突變在影像邊緣處就表現為鋸齒。

PCF 方法

為了解決這個問題,一種通常的方法是使用PCF(percentage-closer filtering)。它的基本思想就是避免visibility從0到1的突變,它的基本實現是,當把某一fragment轉換到陰影空間(也可以說是shadow texture上對應的某一個畫素)時,visibility的取值由以該畫素為中心的某一區域內一些取樣點的visibility乘以它們的相對於中心點的百分比來決定的。當這個區域越大,取樣點越多,它的邊緣模糊效果就越好。通常簡單的實現是,在生成shadow texture的時候採用線性插值,並在渲染時判斷某fragment是否在陰影中時,對其在shadow texture上對應的畫素的周圍固定的若干畫素(如取其左上、右上、左下、右下四個畫素)進行取樣,最後取平均值來模擬實現soft shadow。

但這個方法有幾個缺點,一個是得到的邊緣的blur效果不是那麼明顯,它是以取樣區域的大小和取樣點的數目決定的,但這勢必會帶來效能上的下降;另一個是對於一些完全在陰影裡或者完全在陰影外面的點來說,這些取樣完全是浪費的,因為所有采樣點肯定要麼是1,要麼是0。而隨機取樣就是為了解決這兩個問題而出現的。

隨機取樣

顧名思義,它的取樣是隨機的,如下圖所示。

上圖顯示的是一張shadow texture,上面的每一個畫素都對應了一個visibility,取值為0或1。我們假設當前需要處理的fragment對應到shadow texture上後是十字架中心的那個畫素點(下面簡稱為中心點)。現在我們的取樣點來自它周圍那些畫“X ”的所有點,最後,該fragment的visibility就是這些取樣點的visibility的均值。現在的問題是,這些取樣點紋理座標應該如何確定。我們假設中心點的紋理座標是知道的,那麼問題轉化為,如何確定這些取樣點的紋理座標相對於中心點的偏移量。還有一個問題,就是對於不同的中心點,它們對應的取樣點的偏移量是否一樣。這裡我們沒有真正完全隨機,也就是說,我們有一些候選的隨機座標組(這些座標在一開始是隨機生成的,每一組包含了隨機取樣所需的所有采樣點偏移座標),當需要取樣時,我們從這些候選的隨機座標組中選擇一組進行計算。當然,我們可以完全隨機,也就是說計算每一箇中心點的visibility時,它的所有采樣點都在一定區域內隨機選取,但是這樣的代價是巨大的,因為每個fragment都需要重新計算取樣點的座標偏移。

再看這張圖,我們發現所有的取樣點包含在一個圓內,並且被分為了8個區域,每個區域隨機取一個點,這就是我們演算法的基本思想。這裡我們引入兩個變數,sampleU和sampleV,sampleU表示的圓被直線分割的區域數,sampleV表示圓環的個數,那麼取樣點的個數就是sampleU*sampleV。拿上面這張圖來說,它的sampleU和sampleU都是4,而下面這張圖sampleU為8,sampleV為6,所以它共有8*6 = 48個取樣點。(好吧,請原諒它這麼醜。。)

前面所過,我們需要事先生成一些候選的取樣偏移座標組,然後把它們儲存起來,在計算時再讀取它們。這是通過一張三維紋理貼圖來實現的。我們在引入一個vec3型別的變數OffsetTexSize,它定義了該三維紋理的width、height以及depth的大小,那麼width*height就是我們候選座標組的個數,而depth的值是sampleU*sampleV除以2。又沒懂吧,我們看一下三維紋理的儲存。

下圖中的s t r分別對應我們之前說的width height 和depth,我們可以理解為,每一個小正方體儲存了一個型別為vec4值,也就是該三維紋理座標為(s, t, r)的點對應的值。而類似圖中紅色部分的一組正方體則儲存了一個候選座標組。這樣的一組小正方體組共有width*height組,因此候選座標組的個數為width*height。那麼,depth值(也就是每一組小正方體的個數)不是因為等於sampleU*sampleV也就是取樣點的個數嗎?為什麼是等於取樣點總數的一半呢?這是因為,我們之前說過每個小正方體儲存的是一個vec4型別的資料,一個vec4資料可以儲存兩個座標,因此一個小正方體內就可以儲存兩個取樣點偏移座標,所以只需一半就可以儲存所有的取樣點偏移座標。

這樣,座標組的儲存問題就解決了。剩下的問題是,如何生成這些座標組。我們依舊假設sampleU=sampleV=4,那麼其中一組偏移座標的生成過程如下圖。

對於座標為(u,v)的取樣點,轉換到最右圖中,對應的就是從外往裡數第v層圓環、從x軸正方向逆時針數第u個小區域內的點。它的計算公式為:

其中Wx和Wy是真正偏移座標。

該三維紋理生成過程的C++程式如下:

其對應的fragment shader中的關於陰影計算的部分如下:

相關文章