目錄
- Forward Rendering(前向渲染)
- Deferred Rendering(延遲渲染)
- Light Pre-Pass/Deferred Lighting(延遲光照)
- Tiled Shading
- Tiled-based Deferred Rendering
- Forward+ Rendering
- Clustered Shading
- Clustered-based Deferred Rendering
- Clustered-based Forward Rendering
- 參考
Forward Rendering(前向渲染)
Forward Rendering/Forward Shading(前向渲染/前向著色):傳統的光柵化渲染流程中,場景中每個三角形都會被光柵化成若干個片元后進行著色計算,

但是由于無法確保這些三角形繪制的先后順序,相當多的經過著色計算后的片元會被覆寫(由于深度測驗),這就造成了計算的浪費,在大量光源的情況下,這種計算的浪費更是致命的,因為三角形光柵化后的每個片元要經過與光源數量相等次數的著色計算,
Rendering 流程:
- 繪制所有不透明的幾何體:一次 Pass,先進行著色,再進行深度測驗
- 繪制所有半透明的物體:同上,但關閉深度寫入
Forward Rendering 缺點:
- 場景復雜度與光源數量相關,假設有 n 個物體,m 個光源,則渲染復雜度為 \(O(n\cdot m)\)
- 會導致大量的 overdraw ,一些最侄訓被深度測驗所覆寫/丟棄的片元仍需要先經過著色計算,浪費了性能
Deferred Rendering(延遲渲染)
Deferred Rendering/Deferred Shading(延遲渲染/延遲著色):相比于傳統的 Forward Rendering,它的核心目標是讓場景復雜度和光源數量解耦,

它的主要思路就是將著色計算延遲到深度測驗之后,這樣就可以僅對最終留在螢屏上的片元進行著色計算而不是對每個三角形片元都進行著色計算(也就是說最終需要著色計算的片元僅與螢屏像素數有關,與場景有多少個三角形無關),
而為了不丟失像素點的幾何屬性等資訊,Deferred Rendering 需要額外借助一個名為 G-buffer 的幀快取區(FBO)來保存之,這樣在延遲后的著色計算階段就能讀取出像素點對應的資訊來作為引數,然后進行著色計算,
Rendering 流程:
-
Geometry Pass(幾何處理階段):繪制場景中所有的不透明幾何體,并將每個片元對應的法線、diffuse 反射率、specular 擴散系數等屬性寫入到 G-buffer 中,G-buffer 其實就是一個龐大的幀緩沖區物件(FBO),其一個元素包含相當多個屬性,下圖是一個 G-buffer 單個像素的布局例子:

G-buffer 部分屬性可視化:

此程序需要在片元著色器中輸出多個顏色值,因此要用到圖形處理器介面中的 MRT 特性
-
Lighting Pass(光照處理階段):繪制出一個螢屏大小的2D矩形,在對螢屏每個片元著色時,對應的 G-buffer 元素則作為光照計算的輸入引數,光源對每個片元的顏色計算結果被累加到?個累積快取(Accumulate buffer),大體公式如下:
\(L_{o}(\mathbf{v})=\sum_{k=1}^{n}\left(\mathbf{c}_{d i f f} \otimes f_{d i f f}\left(B_{L_{k}}, \mathbf{l}_{k}, \mathbf{n}\right)+\mathbf{c}_{s p e c} \otimes f_{s p e c}\left(B_{L_{k}}, \mathbf{l}_{k}, \mathbf{n}, \mathbf{v}, m\right)\right)\)
其中,\(n\) 為光源數量,\(c_{diff}\) 和 \(c_{spec}\) 代表表面的 diffuse 反射率和 specular 反射率,\(f_{diff}\) 和 \(f_{spec}\) 分別為 diffuse 和 specular 的光照貢獻
公式的由來:渲染方程為
\(L_{o}(\mathbf{p}, \mathbf{v})=L_{e}(\mathbf{p}, \mathbf{v})+\int_{\Omega} f(\mathbf{l}, \mathbf{v}) \otimes L_{i}(\mathbf{p}, \mathbf{l}) \cos \theta_{i} d \omega_{i}\),其中 \(f\) 為 BRDF
如果僅考慮光源的直接光照,便可以簡化成如下(即多個直接光源的貢獻之和)
\(L_{o}(\mathbf{v})=\sum_{k=1}^{n} f_{\text {shade }}\left(E_{L_{k}}, \mathbf{l}_{k}, \mathbf{v}, \mathbf{n}, \mathbf{c}_{d i f f}, \mathbf{c}_{s p e c}, m\right)\)
再具體劃分一下,光照一般由 diffuse 和 specular 兩種貢獻組成,并通過 diffuse 反射率和 specular 反射率來控制貢獻率(材質的屬性),也就有了上面的公式
-
繪制所有半透明的物體(按傳統的前向渲染管線方法)
Deferred Rendering 優點:
- 渲染性能不再與場景的復雜度像耦合(支持數量巨大的光源),渲染復雜度變為 \(O(m+n)\),從而保證穩定的幀率,在大量光源的場景優勢非常明顯
- 避免了 forward rendering 的 overdraw 問題,前向著色階段后,剩下的就是被深度測驗剔除之后的片元及其著色引數,因此它只對螢屏中最終可見的片元進行著色計算,避免計算浪費
Deferred Rendering 缺陷:
- G-buffer 占用了巨大存盤空間,而且其讀寫也需占用大量顯存帶寬:
- 通常 G-Buffer 中每個紋素可以占用多達 128bits 甚至更多的顯存占用,當使用多重采樣時更是會占用巨大的記憶體
- 為了保證多個光源累加結果的精確性,顏色累積快取還必須使用較高精度的變數
- 不支持半透明物體:?個包含半透明表面的像素點的顏色值是兩個(或多個)表面點顏色值混合的結果,而 G-Buffer 只保存每個像素點的?個表面點的值,無法正確表現半透明表面,因此即使是 deferred rendering ,也必須對半透明物體單獨采用傳統的渲染管線來處理
- 只能使用同一shader,很難自定義多種不同的shader:延遲著色階段會對螢屏區域的像素點進行著色計算,而不是像 forward rendering 那樣根據每個物體的型別來繪制物體
- 一種解決方法:G-buffer 額外引入物體 ID 屬性,從而在延遲著色階段可以讀取出像素點對應的物體 ID 來呼叫不同的 shader,但開銷大且不能支持太多種(分支計算開銷大)
- 需硬體支持 MRT
- 對硬體 MSAA 的支持不友好
Light Pre-Pass/Deferred Lighting(延遲光照)
Light Pre-Pass,又叫 Deferred Lighting,Light Pre-Pass 和 Deferred Rendering 是不同的概念,簡單來說,Light Pre-Pass 則是 Deferred Rendering 的一種改進流程,
核心目標是,減少 G-buffer 單個元素的大小,從而使得顯存讀寫帶寬大大減輕壓力甚至于無需開啟 MRT 特性也可實作的 deferred rendering 流程
主要思路是,首先第一個階段將傳統 deferred rendering 中 G-buffer 的部分屬性舍棄掉(也就是不寫入這些屬性),然后第二個階段根據已有的部分屬性計算出光照著色的中間結果,最后階段通過一個額外的幾何 Pass 來重新計算出這些被舍棄掉的屬性,從而得到完整的光照引數,計算出光照著色的最終結果
注:Deferred Rendering 是在2004年的GDC上被提出的,而 Light Pre-Pass 則是在2008年被提出,而目前主流的游戲引擎中, Light Pre-Pass 方法被大量使用,
Rendering 流程
-
**第一個 Geometry Pass **:繪制所有不透明幾何體,相比于 Deferred Rendering,它僅將法線向量 \(n\) 和高光擴展系數(specular spread factor) \(m\) 寫入到 n/m-buffer(類似于 G-Buffer 的緩沖區,但包含的資訊更少,更輕量)
由于法線向量占據 3 個分量且高光擴展系數是?個很小的整數,所以它們可以被合進?個 9 分量的顏色快取即可,可不需要 MRT 的支持
-
Lighting Pass:繪制每個光源影響范圍的包圍盒,并對該包圍盒覆寫的每個螢屏像素點計算漫反射和鏡面著色方程(相比于 Deferred Rendering,這個計算缺少了 \(c_{diff}\) 和 \(c_{spec}\) 的參與),并將結果寫入不同的漫反射和鏡面反射累積快取區:
\(\begin{aligned} g_{d i f f} &=\sum_{k=1}^{n} f_{d i f f}\left(B_{L_{k}}, \mathbf{l}_{k}, \mathbf{n}\right) \\ g_{s p e c} &=\sum_{k=1}^{n} f_{s p e c}\left(B_{L_{k}}, \mathbf{l}_{k}, \mathbf{n}, \mathbf{v}, m\right) \end{aligned}\)
光源包圍盒不是真實存在的場景幾何體,因此該階段需要關閉深度測驗;下圖是一些光源范圍幾何體網格示例:
當攝像機位于光源包圍幾何體內部時,應該只繪制該幾何體的背面,否則只需要繪制正面
對于?些體積特別大的光源(如直線光源或者光源影響范圍同時包含了視錐體近平面和遠平面),可以直接繪制?個 2D 的全屏平面
-
**第二個 Geometry Pass **:再次繪制所有不透明幾何體,但是此時并不需要對表面進行光照計算,而僅需要從前?階段的兩個顏色累積快取中讀取值并一般執行下列運算,將最終的著色結果寫入到幀快取中:
\[L_{o}=\mathbf{c}_{diff}\otimes g_{diff}+\mathbf{c}_{spec} \otimes g_{spec} \] -
繪制所有半透明的物體
優化:
可優化的地方主要集中于延遲光照計算階段輸出的兩個輻射照度量的存盤表述(原始方法需要兩個 3 通道的顏色快取物件來存盤)
- CryEngine3 引擎使用?個 4 通道的顏色快取 (A16R16G16B16f 或 A8R8G8B8)來同時記錄漫反射和高光反射的 irradiance ;其中前 3 個通道表?漫反射值 \(diffuse\),第 4 個通道表示高光的強度 \(strength\),所以高光顏色值可以由 \(diffuse*strength\) 計算得出,這種方式僅保留了高光的亮度丟棄了色度(chrominance),但人眼對亮度的感應比色度更為明顯,因此這種效果雖然不物理但仍能接受,
- YCoCg 壓縮幀快取則使用一個 4 通道的顏色快取來強行壓縮存盤 diffuse 和 specular 的色度和亮度,效果也尚可接受
Light Pre-Pass 優點(與傳統的 Deferred Rendering 相比):
- 減少 G-buffer 存盤空間,大大降低了顯存帶寬的占用,甚至可以不需要硬體支持 MRT
- Lighting Pass 是遍歷每個光源,增加了光源設定的靈活性
- 第二個 Geometry Pass 可以在繪制每個幾何體時使用不同的 shader 進行渲染(例如讓某個特定模型的邊緣變紅變亮),有更大的靈活性
- 第二個 Geometry Pass 使得可以很方便支持 MSAA(傳統的G-buffer由于是離散的網格存盤,必不能展現完整的三角形資訊,而新的 Geometry Pass 可以在遍歷三角形時重新得到完整的三角形資訊)
Light Pre-Pass 缺陷(與傳統的 Deferred Rendering 相比):
-
Light Pre-Pass 重復讀寫多次 buffer,可能造成另一種顯存帶寬壓力
傳統 Deferred Rendering 的 Lighting Pass 直接對螢屏每個像素著色,保證 G-buffer 的元素只被讀一次,frame buffer 只寫一次:
for each pixel read G-buffer for each affecting light compute shading write frame buffer而 Light Pre-Pass 由于需要遍歷光源范圍包圍盒,導致同一像素可能要讀寫多次 buffer :
for each affecting light for each pixel read n/m-buffer compute shading write frame buffer實際上無論是傳統的 Deferred Rendering 還是 Light Pre-Pass,它們的 Lighting Pass 都可以采用先遍歷螢屏像素的策略或者先遍歷光源范圍包圍盒的策略:
- 先遍歷螢屏像素的策略,一個浪費性能的點在于:一些光源可能由于距離太遠,本就不可能影響到某個像素,但對該像素著色時卻又不得不遍歷所有的光源并進行光照計算,造成了計算浪費
- 而先遍歷光源范圍包圍盒的策略可以避免這種計算浪費但讀寫帶寬不友好
我們可以想辦法盡可能優化先遍歷螢屏像素的策略,著色某個像素時可以盡量剔除掉無意義的光源,這樣就能盡量減少計算浪費還能對讀寫帶寬友好,
Tiled Shading
Tiled Shading 將螢屏區分劃分為多個塊(tile),每個 tile 覆寫的像素區域為32×32(也可以是別的解析度),且擁有?個光源串列(包含所有在該 tile 內有影響的光源 ID ),這樣就可以在對某個片元著色時,得到該片元所在的 tile,然后根據 tile 光源串列中的所有 ID 呼叫對應光源的著色計算,而不必呼叫全部光源的著色計算

Tiled Shading 用到的具體資料結構如下:
- 全域光源串列:存盤著各光源的屬性(如 radiant intensty 等)
- 塊光源索引串列:存盤著各 tile 對應的光源 ID 串列,以陣列的形式拼接在一起
- 光源表格:用于查詢某個 tile 的光源 ID 串列位于 tile 光源索引串列的哪里到哪里(陣列中下標多少到多少)
Tiled Shading 的 Light Culling 流程:
實際上就是填充上述資料結構,一般用 GPU compute shader 來實作,有很多種方法,如:
-
光源包圍盒(光源影響范圍包圍盒)相交檢測:一個 compute shader pass,對每個 tile,做以下步驟
-
根據 depth range 確定出該 tile 的包圍盒
tile 的 depth range:只需要求在 tile 中各個像素的 max depth(往往借助 Z-buffer)
-
讓這個 tile 包圍盒和各個光源包圍盒進行相交檢測,得到對這個 tile 有貢獻的光源 ID 并插入到塊光源索引串列尾部(陣列尾部)
-
更新光源表格中對應位置的 offset 和 size
-
Tiled Shading 優點 :
- 減少了相當部分的無關光源的計算
Tiled Shading 缺陷:
-
tile 的光源串列資訊與攝像機的變換、光源的變換相關,也就是說當攝像機(或者光源)位置和方向發生改變,tile 光源索引串列和光源表格都需要重新計算,帶來一定性能開銷
-
基于螢屏空間的 tile 劃分仍然粗糙,沒有考慮到深度值(離攝像機遠近的距離)劃分
如下圖黑色線段物體,每個 shading point 本應該最多同時受到一個光源的影響,由于 tile 劃分沒有考慮 z 值,從而使得實際每個 shading point 都有三個光源的著色計算:

Tiled-based Deferred Rendering
將 Tiled Shading 應用到 Deferred Rendering 時,只需要在 Lighting Pass 做稍微的改動(在填充好 Tiled Shading 資料結構的情況下)就可以了,
rendering 流程:
- Geometry Pass
- Light Culling Pass
- Lighting Pass :片元著色時根據該片元所在 tile 的光源串列來累加計算著色(其實就是根據光源串列中的所有 ID 呼叫對應光源的著色計算)
- (Light Pre-Pass 可選)Geometry Pass
Forward+ Rendering
所謂 Forward+ Rendering,其實就是將 Tiled Shading 應用到 Forward Rendering
rendering 流程:
-
Z-prepass(很多 forward rendering 都會用這個作為優化,而在 forward+ 中變成了必然步驟)
-
Light Culling Pass
-
Forward Rendering Pass:繪制所有幾何體,片元著色時根據該片元所在 tile 的光源串列來累加計算著色
Forward+ 優點(相比 Deferred Rendering):
- 著色時可以通過 tile 方法過濾相當多不必要計算的光源
- 由于增加了 Z-prepass,可以過濾掉相當多不必要計算著色的片元
- forward rendering 天然支持各類不同復雜的材質
- 不需要占用較高的帶寬利用率
- 可以支持透明物體
- 可以很方便支持硬體 MSAA
- 幾乎所有主流移動平臺的 GPU 是 tile-based 的,可以減少 tile-based 部分的演算法代價
Forward+ 缺陷:
- 相比傳統 forward rendering,增加了 Z-prepass + Lighting Culling Pass 的額外開銷
- 需要支持 compute shader 等高級特性,部分手機GPU并不支持
在一些PC性能實驗中,憑借過濾了相當的光源及片元計算量和低帶寬開銷,Forward+ 性能 > Deferred Rendering 性能(甚至是 > 帶寬需求最小的 deferred lighting 方法):

Clustered Shading
為了進?步剔除光源數量,clustered shading 在 tiled shading 的基礎上,將像素分組的劃分從 2D 的螢屏空間擴展到 3D 的觀察空間,
每個 3D 的塊稱為?個 cluster,從而使每個光源真正做到僅影響其區域區域:
空間劃分:
由于攝像機的透視投影,對于同樣大小的物體,較遠物體在螢屏上所占的空間更小;因此每個 cluster 不應該是均勻劃分的,而是可以以指數形式劃分深度方向(即更遠的 cluster 體積更大)
那么為了索引到一個 cluster,就必須有一個三維坐標來表示 \((i,j,k)\) ,\(i,j\) 其實和 tiled shading 的索引映射沒有區別,而重點在于,給定一個深度值 \(z\),其在深度方向的索引值(即 \(k\) )如何計算,
\(near_k\) 為 \(Z\) 方向上第 \(k\) 層 cluster 的近平面在 \(Z\) 方向上的值:
\(near_{k}= near_{k-1}+h_{k-1}\)
\(h_k\) 為在 \(Z\) 方向上第 \(k\) 層其中一個 cluster 的近平面長度
其中第 0 層 cluster(即朝攝像機的那面位于近平面的 cluster)有:
\(near_0 = near\), \(h_{0}=\frac{2 \text { near } \tan \theta}{S_{y}}\)
其中,視錐體的 \(Y\) 方向上的張角為 \(2\theta\), \(S_y\) 為螢屏空間 \(Y\) 方向劃分的數量
\(h_k = h_{k-1}*2*tan\theta/S_y+d_{k-1}\)
又 \(d_{k-1}=h_{k-1}\)
則 \(\operatorname{near}_{k}=\operatorname{near}\left(1+\frac{2 \tan \theta}{S_{y}}\right)^{k}\)
此時,索引值 \(k\) 可被解出來:
\(k=\left\lfloor\frac{\log \left(-z\ / \text { near }\right)}{\log \left(1+\frac{2 \tan \theta}{S_{y}}\right)}\right\rfloor\)
法線分布劃分(可選):
為了讓 cluster 進一步剔除 light,還可以從背面剔除的角度出發,例如 cluster 內的各像素法線均背朝著 light ,那么就可以剔除之,
為此可以為 cluster 指定一個法線分布,索引鍵值額外增加 6 位(\(2^6=64\) 種法線分布)用來表示法線分布,這樣 cluster 的索引就剛好湊成 32 位 \((i,j,k,normal)\) 了
可以假定一個類似于魔方(一面均分成3*3個格子)的立方體包圍住 cluster 中心,然后該中心往各個格子的方向投射出一個錐形,一個錐形的范圍便作為一種法線分布的范圍(共3*3*6=54種錐形范圍的法線分布,而索引鍵值的6位足以滿足表示):
可能有些 cluster 的像素法線相差很大,一個錐形范圍不能容納所有的法線,此時可以調整錐形的半角來讓錐形范圍變得更大,但這也會讓法線分布剔除光源的效率有所降低,在 Battlefield 中稱這種技術為法線剔除(normal culling)

如果入射光方向與法線錐形中心軸的夾角 \(\omega > \frac{\pi}{2}+\alpha+\delta\) ,則應該在該 cluster 剔除掉該光源
\(\alpha\) 是法線錐形的半角;\(\delta\) 時從光源發出的錐形半角,且這個錐形恰好包圍住了 cluster 的AABB包圍盒
Clustered Shading 的 Light Culling 流程:
1. 找出所有參與計算的 cluster
因為螢屏上的所有像素(或片元)所涉及到的 cluster 數量一般遠遠小于所有 cluster 的總數量(換句話說實際上能用上場的 cluster 是稀疏的),我們只需要對會參與著色計算的 cluster 進行光源分配
方法一:
- 每個像素點計算出 cluster index(cluster 索引值)后插入到一個陣列
- 對該陣列進行 GPU 排序
GPU 排序是非常影響性能的,因此可以先對2D螢屏空間的單個 tile 內像素進行排序,而不是對整個螢屏所有像素進行排序,這樣每個 tile 可以各自獨立進行區域排序,剛好就可以充分利用 GPU 并行計算,且每個 tile 內的資料可以寫入到本地共享快取,而不是全域記憶體進行計算,
- 對該有序陣列去重,得到唯一 cluster index 串列(即包含所有參與著色計算的 cluster index)

方法二:
- 使用 virtual texture 來存盤巨大記憶體的稀疏資料(通過動態分配頁表來映射索引到稠密的物理記憶體去)
2. 對每個 cluster 分配光源
得到 cluster index 串列后,就可以遍歷 cluster 了,
方法一(基于相交測驗):
- 對每個 cluster ,與所有光源包圍盒進行相交測驗,然后填充光源串列相關資料結構,其實方法與 tiled shading 差不多
方法二(基于DX12的保守光柵化技術):
所謂保守光柵化(conservative rasterization),就是光柵化時只要片元存在圖元面積,哪怕只有一點點面積,也視為有效片元

DirectX12 中通過創建一個管線狀態物件時設定 ConservativeRaster 的標識為 D3D12_CONSERVATIVE_RASTERIZATION_MODE_ON 來開啟保守光柵化
- Shell Pass:繪制所有光源范圍幾何體,利用保守光柵化技術,繪制到 tile 解析度的 buffer 上,并記錄該光源在每個 tile 上的最大深度值和最小深度值
- Fill Pass:是一個 compute shader Pass,利用 shell pass 產生的最大最小深度值來填充范圍內 cluster 的光源串列(tile和最大最小深度值組成了一段空間,這段空間范圍里包含的所有 cluster 都會被該光源填充)
3. 著色計算
這部分基本和 tiled shading 的著色計算差不多,取決于 forward rendering 還是 deferred rendering,只不過現在擁有了更加具有區域性的 cluster 光源串列
Clustered Shading 優點:
- 進一步剔除了更多無關光源(相比 tiled shading 多考慮了深度乃至于法線分布的劃分)
Clustered Shading 缺點:
- light culling 流程變得更加復雜,可能需要多個 pass 才能完成 clustered shading 的資料結構填充,會相當占據性能
Clustered-based Deferred Rendering
rendering 流程:
-
Geometry Pass
-
Light Culling:
-
計算出所有參與運算的 cluster 集合
-
對集合中每個 cluster 進行光源分配
-
-
Lighting Pass :繪制整個螢屏的2D矩形,片元著色時根據該片元所在 cluster 的光源串列來累加計算著色
-
(Light Pre-Pass 可選)Geometry Pass
Clustered-based Forward Rendering
rendering 流程:
-
Z-prepass
-
Light Culling:
- 計算出所有參與運算的 cluster 集合
- 對集合中每個 cluster 進行光源分配
-
Forward Rendering Pass:繪制所有幾何體,片元著色時根據該片元所在 cluster 的光源串列來累加計算著色
參考
- [1] 《Real-time Rendering 4th》
- [2] 《全域光照技術》
- [3] forward框架的逆襲:決議forward渲染 | KlayGE
- [4] 《GPU Pro7》
作者:KillerAery
出處:http://www.cnblogs.com/KillerAery/
本文著作權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/436977.html
標籤:其他
下一篇:The type or namespace name 'MenuItemAttribute' could not be found
