【USparkle專欄】如果你深懷絕技,愛“搞點研究”,樂于分享也博采眾長,我們期待你的加入,讓智慧的火花碰撞交織,讓知識的傳遞生生不息!
這是侑虎科技第1754篇文章,感謝作者AKG4e3供稿。歡迎轉發分享,未經作者授權請勿轉載。如果您有任何獨到的見解或者發現也歡迎聯系我們,一起探討。(QQ群:793972859)
作者主頁:
https://www.zhihu.com/people/long-ruo-li-21
承上啟下
在上一篇文章中,我們從0到1實現了一套完整的軟件光追管線。我們使用稀疏的體素來存儲場景的Material信息和Radiance,體素的顏色并不直接運用到屏幕像素上,因為體素的精度僅能表達粗粒度的Lighting信息。要想獲得像素級別的光照頻率,我們必須從屏幕像素開始發射光線進行Final Gather。
即使距離場追蹤已經足夠快速了,為了滿足實時這一命題,我們的預算仍然是1/4SPP(半分辨率1SPP),巧婦難為無米炊,在如此緊巴的光線預算下,基礎的蒙特卡羅積分會產生巨大的BIAS,因此本篇文章的重心將放在如何從低SPP的圖像重建出清晰干凈的信號。
一、初始樣本生成
在開始編寫降噪器之前,我們要為降噪器提供初始樣本作為輸入信號,我們執行最普通的蒙特卡洛采樣即可。為了獲得像素級別的細節,同時避免了體素追蹤的自遮擋現象,我們需要先從屏幕空間利用Depth Buffer進行追蹤。對于屏幕追蹤失敗的像素我們才回退到距離場追蹤。
1.1 Mini G-Buffer
我們的光線追蹤發生在1/4分辨率下1SPP,和SSAO類似,在開始追蹤之前我們也要先對G-Buffer進行降采樣。我們沿用SSAO的代碼,根據深度加權計算法線的平均值到RGB通道,同時將深度一起存儲到A通道。在后續的Pass中我們一次采樣就可以獲得采樣點的法線和世界坐標。
1.2 屏幕空間追蹤
在UE引擎中已經實現了基于HZB的射線追蹤,并且網上也有很多相應的教程,這里筆者就不重復了。對于屏幕追蹤,我們將命中點重投影回上一幀,從上一幀的Scene Color中獲取Hit Radiance。
我們重點關注如何組合屏幕追蹤與距離場追蹤。我們當然可以在屏幕追蹤著色器內直接進行距離場追蹤,但是每個Wavefront內各個Pixel屏幕追蹤的命中情況各不相同,這會產生巨大的Divergence。和上一篇文章中的Voxel Lighting部分類似,對于屏幕追蹤失敗的像素,我們單獨將它們挑選出來排列到緊湊的Buffer中單獨進行距離場追蹤。
我們將屏幕分為8x8的Tile并配套一個線程組,每個Tile內的每個像素分配一個線程進行屏幕追蹤。每個線程將屏幕追蹤的成功與否寫入到Shared Memory,然后由第一個線程統計并負責在緊湊Buffer中開辟空間,最后各個線程將追蹤失敗的像素寫入Buffer。
1.3 距離場追蹤
距離場追蹤是一個Indirect Dispatch,我們根據屏幕追蹤中確認的追蹤失敗的像素數目來決定派遣多少組線程組。對每個像素我們從緊湊Buffer中讀取其PixelCoord,然后從Mini G-Buffer中重建其世界坐標以及射線方向,根據World Normal等信息決定一個BIAS將光線的起始點偏移,最后開始距離場追蹤。
有了距離場的追蹤作為兜底,我們能有效地彌補Screen Trace幾何信息不足的問題。不過此時的Hit Radiance還不足以直接拿來使用,這就輪到降噪器出場了。
二、ReSTIR理論知識
ReSTIR是由NVIDIA提出的先驗的降噪算法,核心思路是為每個像素盡可能地保留“最優秀”的樣本來降低圖像的整體方差,ReSTIR先隨機采樣得到一堆“普通樣本”,在普通樣本中選出1個“最優樣本”作為降噪器的輸出樣本。備選的普通樣本數量越多,選出的最優樣本就越好。
ReSTIR將每個“普通樣本”的亮度作為其被選中的概率,通過重采樣重要性采樣(RIS)抽樣方法,在普通樣本中抽出“最優樣本”,并無偏地估計其對蒙特卡洛抽樣的貢獻。本質上是從普通樣本的分布(Source PDF)中,生成了符合周圍環境光照的最優樣本的分布(Target PDF)。
直觀地理解,按照這個策略我們抽到的最優樣本都是很亮的,更符合樣本點周圍環境光照(Target PDF)分布規律的,相當于在對光照的分布進行重要性采樣。每個像素會隨著光照環境的變化而改變采樣策略,這減少了圖像的BIAS。
選出N個普通樣本意味著N次Trace,沒有辦法每幀進行,ReSTIR退而求其次將普通樣本的采樣分攤到時域進行。通過蓄水池抽樣(WRS)算法漸進式地從歷史樣本數據流里選擇最佳樣本,這可以保證每像素每幀只產生一個新樣本(光追一次)的預算,以匹配Realtime這個命題。
除了歷史幀的樣本,同一幀內周圍鄰居像素產生的樣本也可以進行復用,本質上是重建了一條當前像素到鄰居像素光線命中點的光路。有了時間和空間上的復用,我們只用每像素一條光線的代價就獲得了成百上千光線的效果。
三、ReSTIR工程實踐
3.1 整體流程
在筆者的實現中,蓄水池完全沿用了NVIDIA論文的實現。其中光線生成點、命中點的法線都經過了八面體映射編碼,這樣我們通過2張RGBA16的貼圖和兩張RGBA32的貼圖就能存儲全部的數據。
來看一下整體的管線流程。在第1小結中我們通過混合光線追蹤生成了初始樣本,我們會嘗試用這個初始樣本去更新Temporal Reservoir,再將其作為當前幀Spatial Reuse的輸入,以及下一幀的Temporal Reuse的輸入,這樣確保Temporal Reservoir的樣本都來自同一個像素具有相同的Domain。最后我們從Spatial Reservoir中根據RIS estimator的公式計算蒙特卡洛積分的結果,再加上時間空間的Filter進行最后的降噪。
可以看到整個流程還是比較簡單和清晰的,基本是按部就班地按照NVIDIA論文走下來。不過其中也有一些值得注意的細節,讓我們接著往下走。
3.2 時間重采樣
時間重采樣的第一步是要將當前像素重投影回上一幀的屏幕空間。而重投影總是存在一些計算精度誤差,直接用上一幀的UV是無法準確地找到上一幀相同位置的像素。如果貿然使用重投影的UV就會產生如下的Artifact,在相機移動時尤其明顯。
因此這里我們需在重投影之后多加一步像素搜尋,我們在3x3的鄰居內查看每個Reservoir的光線起始位置,找到“光線起點世界坐標”與“當前像素世界坐標”差距最小的歷史幀像素,以此精確地找到重投影后原汁原味的歷史幀像素。
精確地找到歷史幀像素之后,我們從中取出Temporal Reservoir并評估Reservoir與當前幀像素的幾何相似度,以此來決定歷史的有效與否。這將在相機移動時幫助我們快速更新掉已經失效的歷史值,減少了鬼影與延遲。最后我們將Initial Sample產生的新樣本融入進Reservoir中,并更新其RIS權重。
有了時間重采樣,整個采樣器的效率得到了極大地提升,但是還不足以作為最終的圖像輸出。采,就多斂!時間重采樣的20個樣本作為啟動資金,我們已經準備好在空間維度上更進一步,牽扯樣本數目更多的重采樣。
3.3 空間重采樣
空間重采樣的思路和時間重采樣類似,也是將相鄰像素的樣本合并進當前像素的Reservoir,因為我們近鄰搜索的對象是Temporal Reservoir,而每個Temporal Reservoir都是滿載20個樣本的,因此每次合并相當于瀏覽了20個樣本,效率非常之高。
強烈安利一下這個視頻,講解的十分清晰易懂:
https://www.youtube.com/watch?v=gsZiJeaMO48
我們仍然從搜索周圍的像素開始。首先隨機采樣UV Offset拿到周圍像素的Reservoir,根據當前像素的位置和法線計算兩個像素之間的幾何相似度,以此決定是否拒絕鄰居Reservoir的合并。空間上的復用相當于重建了當前像素到鄰居Reservoir樣本點的光路,因此我們還需要小心地檢查光路與采樣點的半球可見性,以此屏蔽一些負值極值的情況,因為那種角度不可能存在對著色點有貢獻的光路。
接著我們根據復用路徑的幾何信息計算一個Jacobian行列式,用來縮放因光路的變換而導致的立體角微元dω的(原論文稱為Measurement Density)變換。筆者這里的理解是,路徑的復用相當于把鄰居像素x'的光照函數L(x')進行了換元,換成了用本地像素x作為自變量輸入的版本L'(x),而Jacobian行列式正是用來衡量兩個坐標系之間進行轉換(換元)所帶來的積分微元的面積變化。
一個直觀的理解是,如下圖所示綠色區塊為2D屏幕上的積分微元dxdy大小,它的面積在坐標系發生線性變換之后相應地被拉伸了,而這個線性變換所對應的Jacobian行列式就描述了其面積的改變。
強烈安利一下這個視頻,講解的十分清晰易懂:
https://www.bilibili.com/video/BV1zq4y1o7hR/
我們復用鄰居像素的Lighting信息計算渲染方程的積分,自然而然地會需要計算∫ L(x) dω,而像素之間的dω并不相通,所以需要Jacobian行列式來進行縮放。
有了Jacobian行列式,我們將鄰居樣本的Target PDF進行縮放,然后根據鄰居Reservoir的樣本數目計算一個合并權重,這相當于重復Reuse了N次近鄰樣本,最后合并Reservoir更新蓄水池樣本數目以及RIS estimator權重。這里要注意限制Jacobian的大小以避免出現Firefly現象。
因為Temporal Reservoir已經看過了20個樣本,假定我們再進行8次空間重采樣,此時每個Reservoir都相當于看過了20x8=160個樣本,采樣質量有了極大的改善。
四、時空降噪器
在時間和空間維度上對光線樣本進行重采樣已經得到了令人滿意的結果,但是其輸出并不能直接用于最終圖像,我們仍然需要常規的降噪器來磨除噪點和抖動。這里筆者直接做了一個最基礎的時空濾波,該部分的代碼非常簡單就不過多展示。
首先出場的是Temporal Filter,它和TAA的計算流程非常類似,采樣當前幀像素3x3范圍顏色,在YCoCg空間下計算顏色包圍盒對歷史幀像素的顏色進行鉗制,校驗像素之間的Geometry Similar決定是否進行歷史混合。
緊接著是Spatial Filter,我們使用3x3的A-Trous濾波器對圖像進行過濾,每一輪次Spatial Filter輸入為上一輪次的Spatial Filter的輸出。我們使用逐步倍增的Filter半徑以覆蓋更大的區域,對每個采樣像素我們使用深度和法線的相似度來作為Edge Stopping Function。
在筆者的實現中通過5次3x3的A-Trous近似了18x18的高斯濾波盒,此時的結果已經足夠平滑作為最終的輸出了。
五、Contact Details
在第4小節中我們用了力大磚飛的Spatial Filter來壓制圖像的噪聲,盡管我們設置了Edge Stopping Function,但由于巨大的Filter半徑,在近距離或者Geometry比較高頻的地方,我們丟失了非常多的細節,包括光照的方向性、間接光照投射的間接陰影等細節全部抹平了。
在本小節中,我們將利用各種手段嘗試恢復一些Contact Details。即使有的方法不是基于物理正確的,但是對于提升最終的圖像質量仍然有幫助。
5.1 空間濾波引導
只有深度、法線作為Edge Stopping Function,我們的Spatial Filter也沒有辦法分辨這些信號是單純的噪聲還是由于幾何的變化引起的自然現象,因此我們需要引入額外的信息作為Guidance。這里筆者參考了Kajiya渲染器的思路,在Spatial Filter的采樣權重中額外考慮中心像素與Sample像素的SSAO的數值差異。
這里SSAO的實現筆者做了一個HBAO上去。在Temporal Filter之前渲染AO,將AO值存儲到Diffuse Irradiance貼圖的A通道,然后用Temporal Filter進行降噪。有了Filter Guidance,我們的Spatial Filter的結果會更加銳利,GI的方向性也越強。
5.2 空間蓄水池校驗
細心的讀者應該注意到了,在第3小節我們實現的Spatial Resampling流程中,筆者在復用周圍像素時忽略了像素彼此之間的可見性,因此Reuse的結果并非無偏。表現在畫面上就是Indirect的遮蔽結果產生了漏光。
漏光的原因在騰訊HSGI中也有提到,周圍鄰居像素的Hit Point,對于當前像素不一定是可見的。如果貿然連接復用(相當于建立和鄰居像素Sample的光路)就會丟失Occlusion信息。
和HSGI一樣,我們在結束了Spatial Reuse之后,對Spatial Reservoir中仍然幸存的那一個天選之子Sample進行可見性測試。出于性能考慮,我們并不發射完整的Hybrid Ray,只在屏幕空間利用半分辨率的Depth Buffer進行簡單的線性Ray March。
對于通過了Visibility Test的鄰居Reservoir我們直接用RIS公式Resolve出Irradiance,否則我們回退到使用Temporal Reservoir的樣本計算Irradiance。我們根據SSR Ray March命中點的距離,和鄰居Sample命中點的距離計算該樣本的置信度,這和DDGI中通過Depth計算Probe遮擋異曲同工。
因為在第3小節我們做了嚴格的重投影,因此Temporal Reservoir能夠嚴格保證產出自同一個像素,所以得到的結果是無偏差的,這極大地緩解了間接遮擋丟失的現象。
5.3 間接陰影
雖然Spatial Reservoir Validation能夠重建正確的間接遮擋,但是我們還有Spatial Filter不加任何驗證地平滑一切顏色。對于一些比較細的物體比如椅子腿投下的細長的Indirect Shadow仍然會被不正確的Filter掉。
幸運的是在5.2小節我們進行了Reservoir Validation,而Spatial Reservoir樣本中存儲的方向代表了當前像素接受光照最強的方向。我們將5.2小節中計算出的Reservoir置信度(針對近鄰像素Sample的可見性測試)直接輸出并認為是Indirect Shadow的信息,它和Color一起在Temporal和Spatial Filter中接受降噪,在最終合成階段運用在Irradiance上。
雖然不是那么物理正確,但是其帶來的視覺效果卻更加能凸顯場景的光照變化關系。并且我們只是通過了開銷極低的屏幕空間步進實現的,實踐表明這是個性價比不錯的路子。
六、Specular Indirect
Specular能夠大大提升場景的真實感和質感,而除了RTX外主流的Specular計算方法或多或少都不那么完美。比如SSR缺少屏幕外的信息,IBL和反射球則依賴烘焙或是只支持Static的場景。對于一個完整的GI方案來說,Specular是不可不品鑒的一個環節。
6.1 總體流程
在筆者的實現中參考了寒霜引擎的方案,不得不感嘆老牌勁旅確實強,差不多十年前的方案到現在都很奏效。因為筆者省略了根據粗燥度做屏幕分塊Trace的部分,但是整個流程基本與原PPT一致。大致分為Initial Sample,Ray Resolve,Temporal和Spatial Filter這四個步驟。
6.2 初始樣本生成
話不多說,我們仍然從本文實現的軟件光追起手(即屏幕空間+距離場的光線追蹤),和Diffuse不同的是我們的射線是按照GGX分布進行重要性采樣。此外筆者根據寒霜PPT的分享也對BRDF進行了截斷,保證在高粗糙度下大部分的光線能收束聚集到鏡面反射方向周圍以減少噪聲,在Screen Trace命中時我們根據命中距離和Roughness從上一幀的Scene Color的Mip中獲取顏色,以彌補BRDF截斷帶來的圖像過于清晰。
6.3 Ray Resolve
緊接著我們進行Ray Resolve,從半分辨率的Initial Sample Texture生成全分辨率的Specular Indirect Texture,核心思路是全分辨率下每個像素隨機地選取周圍UV,根據Jittered UV從半分辨率Texture獲取若干個Ray Sample,將全分辨率像素和Ray的命中點進行重新連接,根據光路的信息估算Irradiance。
對于每個全分辨率下的像素,我們通常選取4個Rray Sample進行連接,假裝我們在進行4SPP的采樣并且產生了4個Sample。這樣我們不僅能夠獲得全分辨率逐像素的Roughness、Normal細節,而且全分辨率每像素4SPP相當于一條Ray我們掰成了16條來用。
值得注意的是,對于Irradiance的估計我們使用的是一個奇怪的加權平均。這在寒霜的分享中也有提到,是一種叫做Ratio Estimator的抽樣方式,它的核心思路是將渲染方程中對Lighting的估計和對BRDF的估計拆分開來,對Lighting仍然使用蒙特卡洛隨機抽樣進行估計,對BRDF則使用有解析解(LUT)進行速查表。
BRDF我們可以通過Image Based Lighting(IBL)中的預積分BRDF圖來近似計算,將其FG項單獨提出來。因為我們拆散了渲染方程,為了保持結果的準確我們要在分母上同時除以一個FG項。這個FG項其實就是預積分的紅綠圖,在最終合成階段我們再乘回去。
理解了數學上的原理,上文代碼中權重計算的代碼就呼之欲出了。其實就是一個BRDF的計算,我們根據全分辨率像素的位置作為光線起點,半分辨率的Ray Sample作為光線終點,連接一條光路并用逐像素的Normal和Roughness評估BRDF,最后加權平均。
經過復用后的圖像已經初具雛形,但是仍然存在一些噪聲。在此基礎上我們也需要進行Temporal和Spatial的Filter來穩定最終的圖像。
6.4 時空降噪
我們首先進行Temporal Filter,值得注意的是重投影時我們不再根據光線起始表面的坐標進行重投影,因為Filter的信號是倒影中的虛像,我們要針對虛像的位置進行重投影。
如果還是使用光線起始表面做重投影,我們會得到非常奇怪的歷史混合。
如果使用虛像位置做重投影,可以看到虛像的拖影指示了正確的Motion。這個拖影我們在Temporal Filter中加上和TAA類似的近鄰顏色包圍盒對歷史顏色進行鉗制就能解決,這里沒有開啟是為了直觀的體現重投影的運動方向。
事實上我們同時采用了兩種不同的重投影策略。對于光滑的表面我們使用虛像位置進行重投影,對于粗糙的表面我們和Diffuse信號類似仍然使用光線起始點進行重投影,最后根據粗糙度來決定兩種重投影混合的比例。
有了Temporal Filter能極大地壓制噪點,現在的結果已經基本可用了。
最后我們再進行Spatial Filter進一步消除噪點和Firefly,這里的Spatial Filter和Diffuse有點不同。我們根據粗糙度來決定filter的范圍,然后在范圍內隨機選取像素,用深度法線和UV距離衰減確定雙邊濾波的權重,然后累加。
代碼非常簡單,唯一值得注意的是我們在計算相鄰像素顏色時要進行ToneMapping,在累加結果之后再反向ToneMapping回去。因為Specular波瓣形狀會產生非常多的尖峰,經過ToneMapping能極大地降低結果的閃爍。我們在上文Ray Reuse Pass加權平均顏色時也用到了這個技巧。
現在的Specular結果已經準備好使用了。
別忘了我們在Ray Reuse Pass中把預積分的BRDF單獨提取出來了,我們在最終合成Pass需要把它加回去,這樣才是完整的渲染方程。
七、性能
我們仍然用EPIC商店的免費場景Modular Asian Medieval City為例,在半開放的室外以保證射向天空的Ray行進了足夠的距離。
筆者的電腦為3060 Laptop(掙韭者),渲染分辨率為1712x1024,光線追蹤分辨率為856x512,此外為了開發方便控制臺變量默認開啟r.Shaders.Optimize=0,沒有試過Cook后的情況。
對于Diffuse流程,性能的大頭主要是三塊:初始樣本生成時的混合光線追蹤,ReSTIR 時空重采樣,以及最后的空間降噪。
對于Specular流程,主要是在初始樣本生成、Ray Reuse和全分辨率的時空降噪。
八、結語
多么吉列的Coding!我們終于完成了Diffuse & Specular的Gather和降噪管線!總結起來就是我們實現了一套,性能不如Lumen、效果細節不如Lumen、響應速度不如Lumen、可伸縮性不如Lumen、降噪穩定性不如Lumen、漏光控制不如Lumen、各種Shading Model、Lighting Feature的支持魯棒性不如Lumen,總之就是不如Lumen的實時GI方案。
當筆者一路攀登以為達到了山頂,卻發現Unreal大神早早就站在了山頂,并給出了完美的GI方案。這種壓迫感與窒息感不禁令我想起英雄聯盟s8 CG中,掌門攀登到了空無一人的山頂,卻發現飛神早已恭候多時。誠然,民科筆者業余時間瞎折騰搗鼓破爛輪子,是不可能與EPIC的全職天才工程師們精雕細琢的工業明珠相提并論。正視差距,但不宜妄自菲薄,更重要的是我們一路走來從中收獲學到了什么。
通過實踐ReSTIR、時空降噪等算法,筆者也深刻理解了“絕知此事要躬行”的道理,真正Coding起來還是有非常多要注意的地方的。同為降噪方案,筆者認為僅對于Indirect Lighting來說,Lumen Screen Probe Gather + SS Bent Normal的策略要好于ReSTIR,相對地ReSTIR在純光追模式下(類似離線渲染用NEE,直接+間接光拉一塊a了)具有更好的表現。最后,令筆者感到意外的是,實際用下來UE引擎的圖形編程體驗也遠非網上討論的那般不堪,在玩熟玩溜了之后會發現這都不是事兒。
UE引擎之路道阻且長。但是踏上取經路,比抵達靈山更加重要。回頭才發現最難的那一步,就是最初的第一步。路途艱辛卻難掩喜悅,筆者將一路走來的所見所聞記錄于此,希望與大伙進步共勉。
九、代碼倉庫
https://github.com/AKGWSB/UnrealEngine/tree/4.27-akgi
引擎部分的代碼位:
Engine\Source\Runtime\Renderer\Private\RealtimeGI
著色器部分位于:
Engine\Shaders\Private\RealtimeGI
十、參考與引用
HSGI: Cross-Platform Hierarchical Surfel Global Illumination:
https://www.gdcvault.com/play/1029169/LIGHTSPEED-STUDIOS-Developer-Summit-HSGI
ReSTIR GI: Path Resampling for Real-Time Path Tracing:
https://research.nvidia.com/publication/2021-06_restir-gi-path-resampling-real-time-path-tracing
kajiya renderer:
https://github.com/EmbarkStudios/kajiya/blob/main/docs/gi-overview.md
渲染中的采樣理論與實踐:
https://zhuanlan.zhihu.com/p/632281317
【論文翻譯】ReSTIR GI: 實時路徑追蹤中的路徑重采樣:
https://zhuanlan.zhihu.com/p/424192784
光線追蹤降噪技術 2020:
https://zhuanlan.zhihu.com/p/267178498
D5 渲染器全局光照解決方案:
https://www.d5render.cn/blog-restir-surfel-gi/
UE5.4 Lumen中的ReSTIR Gather:
https://zhuanlan.zhihu.com/p/703950102
Stochastic Screen-Space Reflections:
https://www.youtube.com/watch?v=AzXEao-WKRc
文末,再次感謝 AKG4e3 的分享, 作者主頁: https://www.zhihu.com/people/long-ruo-li-21, 如果您有任何獨到的見解或者發現也歡迎聯系我們,一起探討。(QQ群: 793972859 )。
近期精彩回顧
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.