Deferred Shading
傳統的渲染過程通常為:1)繪制Mesh;2)指定材質;3)處理光照效果;4)輸出。傳統的過程Mesh越多,光照處理越費時,多光源時就更慢了。
延遲渲染的步驟:1)Pass0先不做光照處理,將Mesh的Position信息和Normal信息繪制到紋理(RenderTargets,D3D支持多向輸出);2)Pass1僅繪制屏幕大小的一個四邊形,利用之前得到的Position紋理和Normal紋理對有效地區域選擇性地進行光照處理,再輸出最后的圖像。
分析:由於延遲渲染將光照的處理量由空間轉換到了平面,減少了光照等效果的計算量,提高了繪制速度,對多光源的繪制優勢更為明顯。
渲染流程
延遲渲染管線可分為四個階段:geometry, lighting, composition, post-processing
Post-processing階段與傳統的forward shading沒有太大差別,這里不提,只說明一下前三個階段。
Geometry階段:將本幀所有的幾何信息光柵化到G-buffer。包括位置,法線,貼圖等。
Lighting階段:以G-buffer作為輸入(位置,法線)進行逐像素的光照計算,將diffuse lighting和specular
Lighting 結果分別保存在兩張RT上作為lighting buffer。
Composition階段:將G-buffer中的貼圖buffer和lighting buffer融合,得到渲染結果。
整體渲染過程並不復雜,但在實際的過程中還是有許多問題需要考慮的,下面一一列舉。
G-buffer
Geometry階段將幾何信息渲染到multi render target上(MRT),當前最多支持4個MRT。並且驅動要求4個MRT必須相同的bit寬度。RT對顯存占用過大會增加帶寬,降低cache命中。而簡單格式的RT又會影響畫質。因此決定使用32bit的RT(如A8R8G8B8,R16G16F)或64bit寬度的RT(如A16R16G16B16F)。需要在畫質和性能間做出折衷。(開發時盡可能可以方便的配置)。[1]中有一些性能比較。
MRT中必須的信息:position(depth), normal, diffuse(texture)
可能需要的信息:specular, power, emissive, ao, material id
這些信息需要在這4個RT上用合理格式,合理的組織。這里還可以就存儲空間和shader的復雜性做折衷。如只保存depth,然后在光照時計算position[12]。以及用球面坐標保存法線[13]。以目前的資料得出的結論是應該盡可能地pack數據,減少內存占用,多出來的若干條shader指令不會明顯影響性能。
光照計算
使用延遲渲染技術最大的好處就是可以渲染光照極為復雜的場景。這里場景中的光照可以分為兩類。
影響整個場景的scene light。如directional light。渲染一個screen quad,逐像素光照計算,沒什么好說的。
另一類是只影響一部分區域的local light。如點光源,聚光燈,以及特效等等。這些local light只影響到屏幕上的某些像素,當然不需要逐像素的進行光照計算。最簡單的方法是繪制這些光源的包圍體(點光源的包圍體是球,聚光燈的包圍體是圓錐),包圍體的大小要大於等於光源的衰減范圍。這些包圍體經過變換投影到屏幕上的對應區域,隨后在pixel shader中計算光照。
優化:
1. 光源包圍體的視錐剔除,遮擋剔除。
2. 光源包圍體投影后很小時剔除;若干個靠的比較近的小光源合並成一個較大的光源[11]
3. 光源包圍體的backface culling
4. 屏幕空間中沒有被光源照到的,或者被更近的物體遮擋住的像素不需要光照計算,因此可以逐像素的深度剔除。
a. 使用正確的stencil light volume。類似shadow volume的方案,將渲染light volume的正反兩面,得到正確的stencil mask,然后光照計算時使用stencil buffer。這種方法可以得到正確的結果,但是需要渲染每盞燈時頻繁改變render state,可能會帶來一定性能上的損失。
b 使用z test,可以得到“一定程度上正確”的結果。
陰影
光照計算的同時計算陰影。使用傳統的shadow map,預先生成一張陰影圖。考慮在編輯場景的時候指定那些重要的光源才會產生陰影。在計算shadow map時要針對光源的binding volume進行剔除。
方向光和聚光燈可以使用基本的shadow map投影(正交投影,透視投影)。點光源會復雜一些,需要使用cubic shadow map。(考慮unwrapping method[14])
半透明
由於在延遲渲染的過程中只計算離屏幕距離最近的那個像素的光照,因此無法處理半透明物體的光照。
方案1
延遲渲染的過程中只處理不透明的物體,將所有半透明的物體放在渲染過程的最后,使用傳統的forward shading渲染。
方案2
在Geometry階段將半透明的物體和背景逐像素的交織起來,將透明度放在一個單獨的通道中。按一般的方法計算光照。隨后在composition階段再根據透明度將透明物體和背景逐像素的混合起來。
優點:
光照一致性。半透明的物體也參加延遲渲染,可以接受多光源的光照。
簡單並且健壯。不需要單獨區分不透明物體和半透明物體,不需要單獨的半透明渲染管道。
速度快。只增加了7到10條ps指令,兩張貼圖,只有約2%的性能損失。
缺點:
模糊。在半透明的物體上會有一點模糊,原因是在交織的過程中會有一定信息損失。
邊緣鋸齒。反交織的過程中半透明物體的邊緣會產生一些鋸齒。
只能有一層半透明。
多種材質
在延遲光照的過程中支持多種材質需要如下方案:
在G-buffer階段輸出材質的ID到G-buffer的一個通道中,隨后在lighting階段和composition階段根據材質ID使用不同的光照函數計算光照。這種方案在sm 3.0中使用動態分支的前提下可以很好的工作。
反鋸齒
Dx9 API不支持反鋸齒的MRT,Dx10支持。
一種方案是使用超采樣,先渲染到大的RT上,再downsample到正常的大小,得到沒有鋸齒的結果。延遲渲染的效率跟分辨率有很大關系,因此這種方法會極大的降低性能,基本不可取。
另一種方案是使用“intelligent blur”,只模糊物體邊緣的像素:
根據相鄰像素的深度和法線提取物體邊界,然后對提取出的邊界進行模糊。模糊時要避免不正確的泄露。如后面物體的顏色泄露到前面的物體上[11]。 總體而言實現會較為復雜。
另一種方案:pre-lighting [8][9][17]
一種pre-z rendering 和deferred rendering的結合。G-buffer階段只保存depth和normal,然后計算光照信息到lighting buffer,格式如下
LightColor.r * N.L * Att
LightColor.g * N.L * Att
LightColor.b * N.L * Att
R.V^n * N.L * Att
最后使用傳統的forward shading再將整個場景渲染一遍,期間查詢lighting buffer。
與普通的deferred shading相比:
優點:
占用帶寬小,第一遍渲染只輸出normal,depth是自動獲得的。
可以用在較老的硬件平台上,不需要MRT支持。
對現有forward shading管道改動較小,比較容易實現。
缺點:
整個場景需要渲染兩遍,相當於在pre-z和forward shading中間加了一個lighting stage。
