基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

KillerAery發表於2021-10-31

所謂基於螢幕,就是指利用的資訊來源於“螢幕”,例如:frame buffer、depth buffer、G-buffer都記錄著螢幕所看到的各 pixel 的資訊。

Reflective Shadow Maps(RSM)


Reflective Shadow Maps(RSM):主要是利用了 shadow map 思想的GI技術,但 shadow map 嚴格意義上不屬於使用者的“螢幕”資訊,而是屬於光源的“螢幕”資訊,因此我還是將其歸納為 screen space 的技術。

RSM的思路:將受到直接光照的地方都視為次級光源,那麼 shading point x 所受的次級光照便是來源於各個次級光源的反射。

次級光照 = bounce為1的間接光照,RSM演算法只能支援bounce為1的間接光照效果

然後,假定次級光源均是 diffuse 物體,那麼一小塊次級光源 patch(這塊次級光源面積位於點 \(x_p\) )對 shading point x 的 irradiance 貢獻是:

\(E_{p}(x) = \Phi_p \frac{\max (\mathbf{n_p} \cdot normalize(x-x_p),0)\max (\mathbf{n} \cdot normalize(x_{p}-x),0)}{\left\|x-x_{p}\right\|^{2}}\)

\(\Phi\) 是次級光源 patch 的 power,\(\mathbf{n_p}\)\(x_p\) 的法線 ,\(\mathbf{n}\)\(x\) 的法線

所有的次級光源 patch 對 \(x\) 的貢獻加起來便是 \(x\) 的間接光照 irradiance:

\(E(x) =\sum \ E_{p}(x)\)

那麼,怎麼找到這些次級光源呢?這就用到了 shadow map 的思想:

  1. 陰影生成 pass:在光源攝像機渲染 shadow map (往往只記錄了深度)的時候,順便額外記錄 世界座標 \(x_p\) 、法線 \(n_p\)、 接受的直接光源Power \(\Phi_p\)。那麼就可以認為 shadow map 的一個 texel 對應一塊patch ,從而這張 shadow map 就包含了所有次級光照 patch 的資訊了 。

實際上,世界座標也可以通過uv座標、遮擋深度來推算得到,好處是可以節省空間,壞處是在後面的pass需要渲染 pixel 時,大量對紋理的取樣會導致大量的座標變換計算(而且很多計算都是重複的),因此在RSM演算法中,不推薦這種壓縮做法。

此外,計算一個 texel (或者說一塊patch)的 \(\Phi_p\) 時,無論光源是directional light還是spot light,都不必計算 cosine 或者 距離衰減,而直接用光源強度與物體 albedo 相乘

$\Phi_p=\Phi(u_p,v_{p})= I * c_p $

\(u_p、v_p\)\(x_p\) 在 shadow map 上的紋理座標。

  1. 主渲染pass:在 pixel shader 階段,計算出 \(x\) 對應的 shadow map uv座標,並取該座標周圍若干個 texel (這些正是我們要取樣的次級光源點)對應的 世界座標 \(x_p\) 、法線 \(n_p\)、 接受的直接光源Power \(\Phi_p\) ,它們將對 \(x\) 的渲染造成間接光照影響:

\(L_{indirect}(x,\mathbf{v}) = \frac{E(x)}{\pi} = \frac{{\sum_{\text {texels p}} \ E_{p}(x)}}{\pi}\)

RSM效果圖:

RSM 的重要性取樣

理論上,為了實現最好的RSM效果,應當取整張 shadow map 的所有 texel 作為次級光源點,因為整張shadow map 意味著包含了整個光源照到的資訊。但這樣所需的取樣數就相當於 shadow map 的解析度,代價太高。

因此我們應當使用少量的取樣數來保證效能,同時也要保證RSM的間接光源質量能夠接受,那麼就容易想到用 Importance Sampling 來加速取樣的收斂。那麼哪些地方的次級光源點比較重要呢?

RSM 假定,離 shading point x 近的點更可能給 x 的光照貢獻大,而遠的點給 x 的光照貢獻小。

因此這個用於RSM的 Importance Sampling 將給近的的地方更多的取樣點(當然權重更小),遠的地方更少的取樣點(權重更大),用視覺化取樣點數量和權重大概就是這個樣子:

因此,選取一個隨機取樣點座標 \((u,v)\) 和對應的權重 \(importance\)

\((u,v)=\left(s+r_{\max } \xi_{1} \sin \left(2 \pi \xi_{2}\right), t+r_{\max } \xi_{1} \cos \left(2 \pi \xi_{2}\right)\right)\)

\(importance = (\xi_{1})^2\)

其中,\(s、t\) 為 shading point x 在 shadow map 的紋理座標,\(\xi_{1}、\xi_{2}\) 為隨機數

RSM 的應用與缺陷

缺陷:

  • 效能開銷與燈光數量成正比,有點昂貴(意味著需要同樣數量的 shadow map、在多張 shadow map 取樣等...)
  • 由於 shadow map 記錄的是光源攝像機螢幕上的表面幾何資訊,因此在計算 patch 對 shading point 的貢獻時很難做到檢查 visibility:
基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)
  • RSM 假設次級光源面均是 diffuse 的,這會影響影像 visibility 的正確性(當然大部分情況下,)

應用:

  • 作為廉價的GI方法,常被用於做單個重要光源的GI效果(例如手電筒)
基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

Screen Space Ambient Occulsion(SSAO)


螢幕空間環境光遮蔽(Screen Space Ambient Occulusion,SSAO):是一類遊戲工業界很常用且廉價的螢幕空間GI方法。

所謂環境光遮蔽(AO),就是某個 shading point 因為被其它幾何表面所遮擋,從而降低了接受外界環境光的比例(這種遮蔽常常發生在凹處表面):

一種計算AO的經典方法就是通過蒙特卡洛+ray casting 預計算模型上各點的AO,然後做成 AO 紋理可以執行時像普通紋理一樣取樣並與顏色相乘(AO map 存的是 visibility 值)。

SSAO 將要用到的螢幕資訊是:color、depth

SSAO 不需要預計算過程,只需要通過螢幕空間資訊就能做到還算不錯的AO效果:

  1. 在第一個 pass 只渲染整個場景的直接光照,得到包含直接光照結果的 color buffer 和 depth buffer。

  2. 在第二個 pass 對整個螢幕渲染,對於某個 shading point ,在該點周圍隨機取樣一些點,然後這些點與 depth buffer 對應的深度作比較:若取樣點的深度小於 depth buffer 對應位置的深度,則說明該取樣點被遮蔽了。而這些取樣點的遮蔽率便是該 shading point 的遮蔽率。遮蔽率將乘於 color 得到該 shading point 最終的渲染結果。

當然也有不正確的遮蔽現象,例如下圖中間點的取樣,有個紅色取樣點實際上沒有被遮蔽。但是該取樣點的深度小於depth buffer的對應深度,因此被 SSAO 判定為遮蔽了。

SSAO 效果圖(左為關閉SSAO效果,右為開啟SSAO效果,可以看到物體交界處等地方多了更多的暗部細節):

基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

SSAO Blur

實踐中由於效能限制,SSAO 一般僅使用16個取樣點,那麼 AO 的結果將會是 noisy 的:

基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

這時候就稍微修改下 SSAO 的演算法流程,在計算 shading point 的 AO 時,不再直接乘於 color。而是先寫入到一個 AO buffer 上,之後用一個螢幕後處理 pass 對 AO buffer 資訊進行邊緣保留濾波演算法(其實就是保持邊緣感的模糊操作,例如雙邊濾波演算法),那麼得到將是不那麼 noisy 的 AO 結果:

基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

Horizon Based Ambient Occlusion(HBAO)

實際上,shading point 的 SSAO 取樣範圍不應該是一個球型,而應當是基於該點的法線為中心的半球形取樣範圍(因為渲染方程本就是上半球的積分,下半球的光線不會照到 shading point )。

HBAO 就是取樣上半球取樣範圍的 SSAO 改進方法,得到該範圍的取樣點演算法也很簡單:

vec3 rand;				// 在球形上的隨機座標
vec3 n;					// shading point法線
rand = sign(dot(n,rand))*rand;	// 在半球上的隨機座標
基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

SSAO 的應用與缺陷

缺陷:

  • 僅包含螢幕表面的幾何資訊不能表示完全正確的 visibility,因此 AO 效果不那麼準確(相對於預計算AO貼圖)

應用:

  • 廉價的GI效果,提升畫面的暗部細節,大部分遊戲都會將其納入一種畫面增強選項。

Screen Space Directional Occlusion(SSDO)


Screen Space Directional Occlusion(SSDO) 也是一類與 SSAO 極其相似的螢幕空間GI方法,區別在於它們看待光線遮蔽的角度是相反的:

  • AO 認為 shading point 朝外的光線打到物體幾何表面時,相當於外部的直接環境光被這個表面遮擋了,因此(對於下面這幅圖) AO 將紅色部分視為間接光照來源,黃色部分視為損失的間接光照

  • 而 DO 認為 shading point 朝外的光線打到物體幾何表面時,相當於受到了間接光照(光照來源於打到的表面),因此(對於下面這幅圖) DO 會將黃色部分視為間接光照來源,紅色部分視為損失的間接光照

用渲染方程去表示兩種GI就是:

\(L_{\mathrm{SSAO}}\left(\mathrm{p}, \omega_{o}\right)=\int_{\Omega^{+}} L_{\mathrm{environment}}\left(\mathrm{p}, \omega_{i}\right) * f_{r}\left(\mathrm{p}, \omega_{i}, \omega_{o}\right) \cdot V(p) \cdot \cos \theta_{i} \mathrm{~d} \omega_{i}\)

\(L_{\mathrm{SSDO}}\left(\mathrm{p}, \omega_{o}\right)=\int_{\Omega^{+}} L_{\mathrm{indirect}}\left(\mathrm{p}, \omega_{i}\right)* f_{r}\left(\mathrm{p}, \omega_{i}, \omega_{o}\right) \cdot (1-V(p)) \cdot \cos \theta_{i} \mathrm{~d} \omega_{i}\)

其中,\(V(p)\) 代表 \(p\) 周圍取樣點被 depth buffer 深度遮擋的概率。

因此 SSAO 往往增加的是明暗細節,而 SSDO 往往增加的是周圍物體表面的顏色影響(或者說增加color bleeding效果)

SSAO 將要用到的螢幕資訊是:color、depth

SSDO 演算法流程:

  1. 在第一個 pass 只渲染整個場景的直接光照,得到包含直接光照結果的 color buffer 和 depth buffer。

  2. 在第二個 pass 對整個螢幕渲染,對於某個 shading point ,在該點周圍隨機取樣一些點,然後這些點與 depth buffer 對應的深度作比較:若取樣點的深度大於 depth buffer 對應位置的深度,則說明該取樣點將提供間接光照。而這些取樣點的間接光照按權重加起來便是該 shading point 的間接光照結果。間接光照結果將直接疊加 color 得到該 shading point 最終的渲染結果。

SSDO 效果圖:

SSDO 的應用與缺陷

缺陷:

  • 僅包含螢幕表面的幾何資訊仍然不能表示完全正確的 visibility
  • 僅支援短距離GI效果,而無法展示長距離的GI
基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)
  • 會缺失螢幕看不到的平面資訊(對於有顏色的GI效果很容易看出artifact)

Screen Space Reflection(SSR)/Screen Space Ray Tracing(SSRT)


Screen Space Reflection(SSR),一類與 ray tracing 思路非常相似的螢幕空間GI方法,因此也有被叫為 Screen Space Ray Tracing(SSRT)

它的想法是,將螢幕所看到的表面幾何資訊當成一個場景,然後計算間接光照時,往半球範圍若干個方向投射射線,看看能和這個場景的哪個螢幕畫素點相交,這些便可以相交的畫素點便是提供間接光照的來源。

基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

SSR 需要用到的螢幕資訊:color、normal、depth

SSR 的演算法流程:

  1. 在第一個 pass 只渲染整個場景的直接光照,得到包含直接光照結果的 color buffer 、normal buffer、 depth buffer。

  2. 在第二個 pass 對整個螢幕渲染,對於某個 shading point ,在該點往半球隨機方向投射若干條射線(使用 ray marching演算法),然後將與射線相交的點 \(\mathrm{p'}\) 將對 shading point 的間接光照做出貢獻(這與渲染方程是一致的):

    $ L_{\mathrm{indirect}}\left(\mathrm{p}, \omega_{o}\right) = \int_{\Omega^{+}} L_{}\left(\mathrm{p'}, \omega_{i}\right) * f_{r}\left(\mathrm{p}, \omega_{i}, \omega_{o}\right) \cdot V(p') \cdot \cos \theta_{i} \mathrm{~d} \omega_{i}$

    其中當射線命中時, \(V(p') = 1\) ;否則,\(V(p') = 0\)

    為了減少計算,這裡仍然假設次級光源點是 diffuse 的,這樣式子實際可以寫成:

    \(L_{\mathrm{indirect}}\left(\mathrm{p}, \omega_{o}\right) = \int_{\Omega^{+}} \frac{E(\mathrm{p'})}{\pi} * f_{r}\left(\mathrm{p}, \omega_{i}, \omega_{o}\right) \cdot V(p') \cdot \cos \theta_{i} \mathrm{~d} \omega_{i}\)

基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

此外,SSR 還可以通過使用不同的 brdf 來實現不同的反射效果:

基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

SSR 效果圖:

基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

SSR的 Ray Marching

得益於帶 depth buffer,SSR 可以實現比較廉價的 Ray Marching 效果。Ray Marching 的精度和效能之間的平衡將取決於 march 的步長。

演算法先從 start point 開始,

  1. 每次往射線方向走一個步長得到一個測試點,將該測試點變換成螢幕座標 \((u,v,z)\)
  2. 根據uv座標取 depth buffer 對應的深度 \(d\)\(z\) 比較:若 \(z>d\) ,則說明射線碰到該uv位置上畫素點的“柱條”,返還該測試點;否則,重複上述步驟
基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)
bool RayMarch(vec3 ori, vec3 dir, out vec3 hitPos) {
  float step = 1.0;
  vec3 lastPoint = ori;
  for(int i=0;i<10;++i){
    // 往射線方向走一步得到測試點深度
    vec3 testPoint = lastPoint + step * dir;
    float testDepth = GetDepth(testPoint);
    // 測試點的uv位置對應在depth buffer的深度
    vec2 testScreenUV = GetScreenCoordinate(testPoint);
    float bufferDepth = GetGBufferDepth(testScreenUV);
    // 若測試點深度 > depth buffer深度,則說明光線相交於該測試點位置所在的畫素柱條
    if(testDepth-bufferDepth > -1e-6){
      hitPos = testPoint;
      return true;
    }
    // 繼續下一次 March
    lastPoint = testPoint;
  }
  return false;
}

Depth Mipmap 加速 Ray Marching

在 SSR 的 ray marching 中,步長短了會導致要走很多步,消耗很多效能;而步長長了則可能會導致越過原本應該相交的地方後面,導致錯誤的相交。

為了優化這一過程,我們可以對 depth buffer 做成特殊的 mipmap,低層級的將取高層級若干個 texel 的最大值,而不是傳統 mimap 所取的平均值。這樣我們可以先在底層級的 mipmap 進行大步的 march:若沒碰到,則說明不在當前這塊 texel 的任何子畫素,可以繼續下一大步;若碰到了,則說明可能與在這塊 texel 裡的某個子畫素相交,因此需要降低層級,進行更小步的 march。

這個 mipmap 加速方法實際上和 BVH 方法是相似的,mipmap 每個 texel 相當於每個AABB包圍盒,層級越低則包圍盒越大

mip = 0;
while(level>-1)
    step through current cell;
	if(above Z plane) ++level;
	if(below Z plane) --level;

Edge Fading

由於 screen space 的方法天生丟失了螢幕以外的資訊,在某些時候的渲染可能會看到反射物比較突兀的斷掉了螢幕外的資訊:

基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

為了掩蓋這一突兀的artifact,可以使用基於畫素uv座標的間接光照權重貢獻,即uv座標越接近邊界(例如接近u=0、u=1、v=0、v=1),則權重貢獻應當越小:

基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

BRDF 重要性取樣

為了讓 SSR 的取樣更容易收斂,我們可以根據不同的 BRDF lobe 在進行 importance sampling:

基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

射線結果重用

當 pixel 的 ray marching 得出一個相交點時,不僅計算出對該 pixel 的間接光照貢獻,還可以將計算該點與原 pixel 附近的 pixel 的間接光照貢獻並賦給相應的 pixel :

基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

預過濾取樣結果

每個方向取樣得到的結果將根據不同的 BRDF lobe 來決定這個結果的權重,從而最終綜合得到一個過濾後的間接光照結果,減少了取樣的 noise 問題:

基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)

SSR/SSRT 的應用與缺陷

缺陷:

  • screen space 方法仍然缺失了螢幕所看不到的幾何資訊
基於螢幕空間的實時全域性光照(Real-time Global Illumination Based On Screen Space)
  • diffuse 情況下,由於要往半球範圍均勻取樣(不能像specular/glossy那樣用importance sampling極大優化取樣),容易造成nosiy結果,這時候可能需要犧牲更多的效能來取樣更多

應用:

  • SSR 的渲染效果非常好(前面的方案看起來總像是增強部分的影像效果)
  • 通過不同的 brdf 函式,可以自由調成各種反射效果(specular/glossy/diffuse)

參考


相關文章