孟蓉蓉
(視覺合成圖形圖像技術國防重點學科實驗室,四川大學,成都 610065)
現今,在電影和游戲軟件領域,反射是很重要的一項視覺效果。使用光線追蹤技術的全局光照算法結合雙向反射分布函數(Bidirectional Reflectance Distribu?tion Function,BRDF)可以在物體表面產生真實的反射效果。但是相關算法的代價相當昂貴,因為這些算法需要在光線追蹤步驟花費大量的計算資源。
游戲產業中,用來生成實時反射效果最常見的算法是環境映射。在理想的反射表面它可以模擬貌似真實的反射效果,但是它無法生成自反射效果。環境映射算法另一個顯著的缺點就是在動態場景里,它會降低渲染效率,因為動態更新每一幀的環境貼圖代價較大。
近來,很多 SSR(Screen-Space Reflections)算法被提出,這些方法基于屏幕空間而非基于世界坐標系。它從相機位置出發,發射一條入射光線到屏幕上的任意一個像素點,根據法線計算出出射光線,用光線追蹤技術計算出射光線和場景的交點,Hierarchical-Z算法可以用來加速求交的過程。SSR算法的主要問題在于物理緩沖區中的信息有限,無法處理反射光線與背面場景相交的問題。
本文提出一個改進的SSR算法,這個算法用來解決原有算法的背面丟失問題。解決這個問題的主要方法是擴充G-Buffer(Geometry Buffer)信息。原有算法的G-Buffer只有場景表面的信息,導致出射光線和場景背面求交時交點丟失。我們使用鏈表的方式動態對物理緩沖區進行擴充,將背面信息以鏈表的方式進行存儲。當求交點在場景背面時,我們會查找鏈表,使得背面交點信息被補全。
屏幕空間反射算法是基于圖像的算法,它從當前視點出發在G-Buffer中做光線跟蹤,求到實時的反射結果。屏幕空間反射算法存在一下幾點問題:
(1)對于幾何體不可見的部分,我們沒有辦法計算它在屏幕中的反射結果,因為反射結果是基于不完整的場景信息。如圖1所示,從視點出發發射兩條光線,求反射光線與場景的交點,a點和b點的結果分別是a'和b'點,因為b'對視點來說不可見,所以b處的反射值為空。
(2)場景中的信息不完整可能導致反射錯誤。如圖2所示,紅色區域被遮擋,處于視點不可見的范圍內,從視點出發計算a點和b的反射點,當到達紅色區域時,SSR算法進入一個進退兩難的境地,如果結束光線追蹤,那么a點和b點的反射結果為空,如果繼續光線追蹤,那么反射光線與場景進行求交的結果是a''和b',顯然a''不是a的結果,a的正確結果應該是a'。不管光線追蹤是否繼續,SSR算法都會存在錯誤結果。

圖1

圖2
本文算法將G-Buffer用鏈表的方式進行擴充。算法步驟如下:
(1)渲染整個場景,將處于同一像素的片元存放到一個G-Buffer鏈表中,再對每條鏈表中的片元按照深度進行排序;
(2)對像素進行遍歷,通過光線追蹤計算出每個像素的反射值,將反射值存放到紋理中;
(3)將反射值與場景原有的顏色混合。
本文算法將G-Buffer構建成單向鏈表的結構,鏈表訪問方式為先進后出模式。在這個鏈表結構中,我們要在節點中存放上一個節點的索引,同時我們需要存放片元的顏色信息。因為后續光線追蹤步驟中需要用到深度值,所以我們要存放每個片元的深度值。場景中背面求交的比例占整個場景的比例為10%到20%,所以鏈表第二層被查找的概率也是10%到20%,以此類推,鏈表中第三層被查找的概率微小,所以鏈表最大長度為3已經能夠滿足需求。通過上述描述,我們構建的鏈表結構如圖3所示。

圖3
G-Buffer中的內存因為鏈表結構被增加,為了減少內存和帶寬的開銷,我們壓縮鏈表的內存,整體內存壓縮率為50%左右。每個節點中,Index為整數,Color為4個浮點數,Depth為一個浮點數,在32位系統中,每個鏈表節點占24個字節。HLSL和GLSL本身都支持字節壓縮,以GLSL為例,GLSL中的packUnorm4x8函數支持將四個浮點型數壓縮為一個無符號型整數,也就是說可以將16個字節的內存壓縮為4字節。壓縮以后,每個鏈表節點占12個字節。
在后續處理過程中,我們需要找到每個像素中鏈表的起始位置,所以我們需要一個輔助的紋理存放每個鏈表頭結點的索引值。關于鏈表存儲方式,我們采用如下方式,我們分配一塊連續的物理內存存放鏈表數據,每個節點在物理內存中的偏移量為鏈表的節點索引。為了保證節點索引值的唯一性,我們使用GLSL中的Atomic Counter,這個緩存對象存儲一個unsigned int的變量值,我們每處理一個片元,緩存對象自增加1,我們把緩存對象的當前值作為鏈表的索引值。借助這個索引值,我們可以將節點數據存入到對應的物理內存中。
因為在光線追蹤步驟,我們需要遍歷鏈表去找反射點,所以我們需要對鏈表按照深度進行排序,方便以后對鏈表的遍歷。
我們增加了G-Buffer的內存,但是并沒有破壞GBuffer的結構,所以在我們的算法中仍然可以使用Hi-Z加速結構。在一般的SSR算法中,我們會對場景的二維深度值進行處理,構建Hi-Z算法所需要的層級深度圖,供光線追蹤時加速求交使用。在本文算法中,我們對G-Buffer進行擴充,由二維數據變成了三維數據,深度圖因此也變成了三維深度圖。為了方便起見,我們對G-Buffers中的最靠近視點的片元深度構建層級結構,不考慮鏈表中其他的深度值。因為在光線追蹤步驟,大部分情況是處理可見場景的反射,所以用Hi-Z加速可見場景的求交速度是很有必要的。用最靠近視點的片元深度生成層級圖,不會破壞原來的加速結構,也給鏈表求交提供了額外的空間。
我們從視點出發,發射一條入射光線到屏幕上的某一像素點,根據此像素點的法線求出反射光線的方向。當前像素點為反射光線的起始點,光線按照反射光線的方向前進。這個過程,我們會使用Hi-Z算法進行加速。如果我們在可見場景中找到反射光線與場景的相交點,那么求交過程結束。如果我們檢測到交點在背面,我們會對當前鏈表進行遍歷。如果當前鏈表中有滿足條件的片元,我們就視為找到反射點,否則光線跟蹤步驟會繼續前進,對后續的鏈表進行排查,直到尋找到合適的值。
我們將每個像素的反射結果放到一個紋理里,在光線追蹤結束以后,我們用反射結果貼圖與場景原來的貼圖做疊加。因為本文算法計算的是實時反射結果,所以我們只用一條反射光線做光線追蹤以節約計算成本。此外,我們計算反射值的步驟是在片元著色器階段,起點為片元坐標。本算法的這兩個特點決定了我們得到的反射值是離散的數據,在最終結果處理的時候會在屏幕上產生噪點,影響最后效果。所以我們得到反射值貼圖以后,我們會對反射值做高斯模糊,讓反射值貼圖的噪聲減少。
我們用本文算法和原有的SSR算法對比,光線追蹤步驟中的步長和迭代次數一致,生成出來的效果對比如圖4所示。圖4左邊為原有的SSR算法的效果,從圖中可以看到,原有的SSR算法因為背面信息缺失,導致該算法在處理背面反射時中失去作用。紅色圈中的部分應該反射鼎的底部,但是原有的算法沒有辦法得到底部的信息。圖4右邊為本文算法,因為鏈表結構補全了一部分缺失的信息,我們在求反射效果時能夠通過鏈表得到正確的反射結果。

圖4
SSR算法因為其效率高效,在很多追求效率的軟件和游戲中有廣泛的應用。但是由于其本身算法的局限性,處理效果會有瑕疵。本文算法針對原有算法信息不足導致反射結果不完整的問題,提出解決方案。通過動態鏈表對數據進行擴充。同時我們考慮到內存開銷,對數據進行壓縮。但是本文算法在光線追蹤步驟,因為數據量的增大,求交時間會增加。未來工作會進一步優化反射求交的效率,讓算法整體效率提升。
[1]Greene,Ned.Environment Mapping and Other Applications of World Projections[N].IEEE Computer Graphics and Applications:IEEE,1986:21-29.
[2]Stachowiak,Tomasz.Stochastic Screen-Space Reflections[R].SIGGRAPH 2015 Advances in Real-time Rendering in Games Course,2015.
[3]Uludag,Yasin.Hi-Z Screen-Space Cone-Traced Reflections[M].GPU Pro 5,2014:149-192.
[4]Thibieroz,Nicolas.Order-Independent Transparency Using Per-Pixel Linked Lists[M].GPU Pro,2011:409-431.
[5]Umenhoffer,Tamas and Patow,Gustavo,Szirmay-Kalos,Laszlo.Robust Multiple Specular Reflections and Refractions[M].GPU Gems,2007:387-407.
[6]王晨昊,湯曉安,孫即祥,馬伯寧.帶有位置修正的環境映射[J].中國圖象圖形學報,2012(03).