UE_Visibility Buffer Deferred Material
一、Visibility Buffer
可見性緩沖區(Visibility Buffer)是延遲渲染的進階處理。
與延遲渲染結合起來更好理解,即:延遲渲染是將材質與光源照明計算進行解耦,基于此種思路將幾何體與材質解耦:
- 每個像素/三角形的材質僅被處理一次,并且在管線其余過程中不會處理到被遮擋的紋理、緩沖區或資源,這種整體的渲染思路便是Visibility Buffer的處理思路。
在此之前我們先來看一下為什么要使用Visibility Buffer,這就得說到延遲渲染的弊處了:
通常來說,延遲渲染管線都需要一組稱之為G-Buffer的Render Target,這些貼圖內存儲了一切光照計算需要的材質信息。當今的3A游戲中,材質種類往往復雜多變,需要存儲的G-Buffer信息也在逐年增加。相同分辨率的情況下,由于材質復雜度和逼真度的提升,G-Buffer需要的帶寬足足提高了一倍,這還不考慮逐年提高的游戲分辨率的因素。
對于overdraw較高的場景,G-Buffer的繪制產生的讀寫帶寬往往會成為性能瓶頸。于是學界提出了Visibility Buffer的新渲染管線。基于Visibility Buffer的算法不再單獨產生臃腫的G-Buffer,而是以帶寬開銷更低的Visibility Buffer作為替代。
比較其兩者的渲染管線區別可見下圖所示:
其中Visibility Buffer管線包含了三個階段:
-
Visibility Passes: 對場景進行光柵化,將Primitive ID和Instance ID(或Material ID)保存到ID Texture里(順手做個Depth Prepass),也就是說只有可見的Primitive才會進入后續的階段;
-
Worklist Pass:構建并Worklist,這一步是為了驅動下一步,將屏幕劃分成很多tile,根據使用到某個Material ID的tile加到該Material ID的Worklist里,作為下一步的索引;
-
Shading Passes : 使用Compute Shader對每個Material ID進行軟光柵,獲取頂點屬性并插值,然后再進行表面著色。
接下來我們來看一個以輸出一個1080P圖像為例不同buffer下的大小:
從上圖中可以明顯看出兩種buffer對內存的消耗情況。
而UE5 Nanite 的一大特色是Visibility Buffer。它是一個 R32G32_UINT 紋理,包含每個像素的三角形和深度信息。此時不存在材料信息,具體可視化標識如下:
一般Visibility Buffer通常需要這些信息:
-
(1)InstanceID,表示當前像素屬于哪個Instance(16~24 bits);
-
(2)PrimitiveID,表示當前像素屬于Instance的哪個三角形(8~16 bits);
-
(3)Barycentric Coord,代表當前像素位于三角形內的位置,用重心坐標表示(16 bits);
-
(4)Depth Buffer,代表當前像素的深度(16~24 bits);
-
(5)MaterialID,表示當前像素屬于哪個材質(8~16 bits);
以上,我們只需要存儲大約8~12 Bytes/Pixel即可表示場景中所有幾何體的材質信息,同時,我們需要維護一個全局的頂點數據和材質貼圖表,表中存儲了當前幀所有幾何體的頂點數據,以及材質參數和貼圖。
在光照著色階段,只需要根據InstanceID和PrimitiveID從全局的Vertex Buffer中索引到相關三角形的信息;進一步地,根據像該素的重心坐標,對Vertex Buffer內的頂點信息(UV,Tangent Space等)進行插值得到逐像素信息;再進一步地,根據MaterialID去索引相關的材質信息,執行貼圖采樣等操作,并輸入到光照計算環節最終完成著色,有時這類方法也被稱為Deferred Texturing。
直觀地看,Visibility Buffer減少了著色所需要信息的儲存帶寬(G-Buffer -> Visibility Buffer);此外,它將光照計算相關的幾何信息和貼圖信息讀取延遲到了著色階段,于是那些屏幕不可見的像素不必再讀取這些數據,而是只需要讀取頂點位置即可?;谶@兩個原因,Visibility Buffer在分辨率較高的復雜場景下,帶寬開銷相比傳統G-Buffer大大降低。但同時維護全局的幾何、材質數據,增加了引擎設計的復雜度,同時也降低了材質系統的靈活度,有時候還需要借助Bindless Texture等尚未全硬件平臺支持的Graphics API,不利于兼容。
二、Deferred Material
我們都知道實際項目場景中會有大量材質,這些材質可能對應不同的 Shader,即使 Shader 相同還有可能會引用不同的 Textures。Visibility Buffer 本質上屬于 Deferred Shading,而 Deferred Shading 最大的缺陷就是難以處理不同材質\Shader。為了解決這個問題,業界也提出了一些解決方案,其中比較有效的是 Deferred Material(也有稱 Deferred Texture)
Deferred Material 的核心思想是將材質分類,找出每個材質對應的像素進行 Shading。因此需要將 Visibility Buffer 像素按材質分類處理。
其實主要就是在屏幕上渲染Material Id這種信息,然后統一的在screenspace把material data讀出來用,之后可以自行做shading或者填充gbuffer。
可以來直觀的看一下UE5 Nanite 生成的Material ID Buffer,它并未存儲在一張UINT類型的貼圖,而是將UINT類型的Material ID轉為float存儲在一張格式為D32S8的Depth/Stencil Target上,理論上最多支持2^32種材質(實際上只有14 bits用于存儲Material ID),而Nanite Mask會被寫入Stencil Buffer中。
由于是屏幕空間處理,最直接的想法是每個材質繪制一個全屏 Quad,找出匹配的像素進行 Shading。如果場景中材質數量為 N,屏幕像素為 M,總計需要 NxM 次的計算開銷,如果場景中有大量的材質,這樣的性能將無法接受。可見UE5 Demo中山谷的材質復雜的如下圖:
另一種比較容易想到的方案是只處理每個材質對應的像素,最終每像素只會計算一次。但是現實很骨感,在實現上為了并行處理,必須用到原子操作,而原子所帶來的同步開銷,最終會抵消掉這個優化,另外還需要保證數據連續存儲,否則無法實現一個 Indirect Dispatch 處理同材質的所有像素,中間要經過幾個 Compute Pass 來回倒騰數據,最終所有這些加起來總開銷更大,顯然這也不是最佳的解決方案。
2.1 Material Culling
UE5 借鑒了幾何處理的方法,很巧妙地來解決這個問題,稱之為 Material Culling 。Visibility 像素經過 Material Culling 之后,實現了每像素只計算一次的效果,并且沒有太多額外的性能和內存開銷,總體上達到了很好的性能平衡。
同幾何處理類似,Material Culling 分為基于 Tile 的粗剔除和基于 Pixel 的細剔除 2 種剔除粒度。從宏觀流程上來看,先根據 Visibility Buffer 生成 Material Depth,接下來將屏幕劃分 Tile,記錄每個材質對應的 Tile 列表,最后每材質繪制對應數量的 Tile,并使用 Material Depth 剔除無效像素,只對有效像素 Shading,從而提升了整體性能。
Nanite在Base Pass繪制階段并不是每種材質一個全屏Pass,而是將屏幕空間分成若干8x8的塊,比如屏幕大小為800x600,則每種材質繪制時生成100x75個塊,每塊對應屏幕位置。為了能夠整塊地剔除,在Emit Targets之后,Nanite會啟動一個CS用于統計每個塊內包含的Material ID的種類。由于Material ID對應的Depth值預先是經過排序的,所以這個CS會統計每個8x8的塊內Material Depth的最大最小值作為Material ID Range存儲在一張R32G32_UINT的貼圖中:
有了這張圖之后,每種材質在其VS階段,都會根據自身塊的位置去采樣這張貼圖對應位置的Material ID Range,若當前材質的Material ID處于Range內,則繼續執行材質的PS;否則表示當前塊內沒有像素使用該材質,則整塊可以剔除,此時只需將VS的頂點位置設置為NaN,GPU就會將對應的三角形剔除。由于通常一個塊內的材質種類不會太多,這種方法可以有效地減少不必要的overdraw。實際上通過分塊分類減少材質分支,進而簡化渲染邏輯的思路也并非第一次被提出,比如《Uncharted 4》在實現他們的延遲光照時,由于材質包含多種Shading Model,為了避免每種Shading Model啟動一個單獨的全屏CS,他們也將屏幕分塊(16x16),并統計了塊內Shading Model的種類,根據塊內Shading Model的Range給每個塊單獨啟動一個CS,取Range內對應的Lighting Shader,以此避免多遍全屏Pass或者一個包含大量分支邏輯的Uber Shader,從而大幅度提高了延遲光照的性能。
在完成了逐塊的剔除后,Material Depth Buffer就派上了用場。在Base Pass PS階段,Material Depth Buffer被設置為Depth/Stencil Target,同時Depth/Stencil Test被打開,Compare Function設置為Equal。只有當前像素的Material ID和待繪制的材質ID相同(Depth Test Pass)且該像素為Nanite Mesh(Stencil Test Pass)時才會真正執行PS,于是借助硬件的Early Z/Stencil我們完成了逐像素的材質ID剔除。
總結
以上是生活随笔為你收集整理的UE_Visibility Buffer Deferred Material的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hive之反斜杠导致Unicode编码字
- 下一篇: Java操作Excel并导出