如何在UE4移動端中實現HZB?

文 | Youwei

騰訊互動娛樂 遊戲客戶端開發


Hierarchical Z-Buffering分層Z緩衝(HZB)對遮擋剔除研究具有重要影響,是GPU Driven Rendering Pipeline的重要剔除手段。目前部分主流商業引擎可能因為某些原因導致該技術無法完全在GPU端工作,但依然是值得探討的。本文先介紹HZB的基本原理以及UE4在PC端的實現方式,然後介紹如何移植到移動端並分析其性能和帶來的價值,以及未來還可以做的工作。


HZB的原理


一般來說,大多數基於HZB的遮擋剔除是這樣的工作的:


1. 使用一些遮擋器生成一個完整的分層Z-金字塔。


如何在UE4移動端中實現HZB?


z-pyramid的最低級別是一個標準的z-buffer。在所有其他層,每個z值都是上一層對應的2×2像素中最遠的z。


如何在UE4移動端中實現HZB?

2. 要測試的對象是否被遮擋,可以將其包圍體投射到屏幕空間,並在z-pyramid中估計mip級別。將對象的包圍體投影到屏幕空間。最長的邊l(像素)用來計算mip等級λ。


如何在UE4移動端中實現HZB?

邊長越長,選取的mip等級越高。


如何在UE4移動端中實現HZB?


3. 根據選定的mip測試遮擋。如果結果不明確,可以繼續使用更細的mip級別進行測試。


這個選擇的原因是它使成本可預測——最多需要讀取和測試四個深度值。此外,這種測試可以被看作是“概率性的”,因為大對象比小對象更容易被看到,所以在這些情況下沒有理由讀取更多的深度值,即節省了帶寬,也增加了Cache命中。


UE4的實現


UE4只在PC端進行了實現,過程大致一樣,不同的是在構建層級Z緩衝上分為ComputeShader和PixelShader兩種方式,然後最終剔除工作主要在CPU端進行,意味著需要回讀GPU的測試結果。以下是UE4的工作流程:


1. 使用SceneDepth作為數據源構建層級Z緩衝。Mip0為第一級,大小為1024*512,總共構建10級。分為兩種方式,PixelShader方式比較簡單,一次構建一級,總共執行十次。ComputeShader則稍微複雜一點,利用GroupMemoryBarrierWithGroupSync,每次同時構建4級,總共只要執行3次,便完成構建。


2. 場景中的物體經過視錐剔除以後,剩下的會被收集起來,存放在一個數組中,並且每個物體會保存自己在數組中的索引值。然後,創建2張RGBA32格式的貼圖,一張存放物體包圍盒的質心座標,一張存放物體包圍盒的大小。每次從數組中取64個物體作為一組,保存到貼圖的64個像素區域中。


3. 採樣第二步中的貼圖,獲取物體的質心座標和包圍盒大小,可以計算出物體包圍盒的八個頂點的世界位置,對這八個頂點進行投影,選取其中最近的Z值。根據投影后的矩形區域,選取最長邊長計算mipmap等級,然後在矩形區域內採樣該mipmap的16個像素,選取其中最遠的Z值。如果包圍盒最近的Z值比它還小,則物體不可見。將結果保存到一張格式為RGBA8的RenderTarget中,作為下一幀讀取。


4. 當前幀讀取上一幀的貼圖數據到一個數組中。每個物體將上一幀保存的數組索引到該數據進行查詢,查詢結果決定了該物體在當前幀的可見性。


以上步驟看出,UE4的HZB實現流程沒有完全放在GPU端執行,在下一幀的時候需要回讀上一幀的結果,然後進行查詢以決定物體當前幀的可見性。另外,在第三步中,計算最遠Z值時,採樣了矩形區域內16個像素,而不是4個像素。

移動端的實現


移動端的HZB方案大部分可以與PC端共用一套邏輯實現,然後針對移動端性能點進行優化。移動端需要解決的第一個首要問題就是SceneDepth的獲取,因為它是構建層級Z緩衝的重要數據來源。


在移動端上,一般來說,如果存在後處理材質需要訪問場景深度信息,UE4會將場景線性深度值保存在SceneColor的Alpha通道。而對於透明材質,如果使用了DepthFade材質節點,則會通過移動設備擴展API來直接提取FrameBuffer中的深度信息。


那我們如果想在移動端上直接訪問深度紋理的話,需要怎麼做了?可以通過設置r.Mobile.ForceDepthResolve為1來始終保留移動端的深度信息。強制深度解析,為設備保留深度紋理。


如何在UE4移動端中實現HZB?


移動端獲取深度紋理


如何在UE4移動端中實現HZB?


獲取了深度紋理,就可以開始構建層級Z緩衝。考慮到移動設備的兼容性,這裡只使用了PixelShader方式,依然構建了10級mipmap。為了保證深度值的精度,這裡將每個深度值編碼到rgba8888格式的貼圖中。


移動端構建層級Z緩衝


如何在UE4移動端中實現HZB?


構建完層級Z緩衝以後,接下來就是進行遮擋測試。算法沿用了PC端的方式,將結果保存在貼圖中,下一幀回讀。移動端上回讀GPU貼圖數據,需要注意的是,UE4會默認處理上下翻轉。因為這裡只是存放遮擋結果的數據貼圖,所以不需要做上下翻轉。


如何在UE4移動端中實現HZB?


最後一步遮擋查詢,過程和PC端一樣,每個物體用上一幀的數組索引去查詢自己當前幀的可見性。

優化讀取性能


移動端回讀GPU數據相當耗費性能,我們可以來看下glReadPixels分別在oppo手機型號為r15和r17上的測試結果:


r15耗時6~8ms


如何在UE4移動端中實現HZB?


r17 耗時 16~20ms


如何在UE4移動端中實現HZB?


直接調用glReadPixels相當慢,不過,好在目前大多數移動設備的opengles已經達到3.0以上,所以,可以考慮使用PBO的方式進行優化,使用 glMapBufferRang進行讀取。過程大致如下:


1.初始化2個buffer。


2.buffer1用於異步glReadPixels讀取,buffer2用於glMapBufferRange讀取。


3.下一幀交換buffer,buffer1用於glMapBufferRange,buffer2用於glReadPixels。


如何在UE4移動端中實現HZB?


再來看下,優化後的測試結果:


優化後,r15耗時0.9ms


如何在UE4移動端中實現HZB?


優化後,r17耗時4~6ms


如何在UE4移動端中實現HZB?


硬件遮擋查詢 和 HZB在oppo r15上的性能對比


經過初步優化,我們來看下,硬件遮擋查詢和HZB各自在手機上的性能對比。測試分為靜態物體和動態物體。新建一個場景,場景內隨機生成10000個物體。在分別只開啟硬件查詢和只開啟HZB的情況下,對幀率和被遮擋物數量的影響。UE4針對硬件查詢做了Batch優化,這樣可以大大降低硬件查詢帶來的DC開銷,不過Batch只對靜態物體有效。所以,需要分開測試。


動態物體:


如圖,只開啟了硬件查詢,因為動態物體無法Batch,Occlusion queries相當高,達到987,而draw call數量達到了1450。被遮擋物體為579,可見物體為411。幀率只有18。由此可見,對於大量動態物體,硬件查詢本身帶來了巨大的DC開銷。


如何在UE4移動端中實現HZB?

如何在UE4移動端中實現HZB?


如圖,只開啟了HZB,硬件查詢的DC開銷已經沒有了,被遮擋物體為570,可見物體為420。Draw call為467,幀率為30。


如何在UE4移動端中實現HZB?

如何在UE4移動端中實現HZB?


結論:對於大量動態物體查詢的場景,從被遮擋物體數量和可見物體數量兩個數據指標來看,硬件查詢和HZB不相上下。性能上,HZB優勢明顯。

靜態物體:


如圖,只開啟硬件查詢,因為靜態物體的原因,硬件查詢發揮了Batch的優勢,Occlusion queries只有58,draw call只有511,幀率達到35。


如何在UE4移動端中實現HZB?

如何在UE4移動端中實現HZB?


如圖,只開啟了HZB,硬件查詢Batch帶來的DC也沒有了,所以draw call略有下降。幀率為33。


如何在UE4移動端中實現HZB?

如何在UE4移動端中實現HZB?


結論:對於大量靜態物體查詢的場景,HZB仍適用,性能與硬件查詢Batch相當。


硬件遮擋查詢 和 HZB在oppo r17上的性能對比


同樣分別測試動態物體和靜態物體,r17和r15表現了完全的不一樣的結果,之前在優化讀取時也發現,r17依然有4~6ms的開銷,這是為什麼了?這跟r15和r17在硬件上不同有關。


R15硬件參數


如何在UE4移動端中實現HZB?


R17硬件參數


如何在UE4移動端中實現HZB?


另外,值得一提的是,UE4針對高通設備,做了硬件查詢的上限限制。最大510次查詢,而其他設備默認是最大4000次查詢。


如何在UE4移動端中實現HZB?

如何在UE4移動端中實現HZB?


這導致了R17在大量動態物體場景下,即便沒有做Batch,也只有不超過250的Occlusion queries數量。這並不是什麼優化,而是UE4直接放棄了超過該數量的硬件查詢,再加上依然存在的回讀耗時,這樣使得HZB的優勢就不那麼明顯了。


如圖,R17上,開啟硬件查詢,關閉HZB。


如何在UE4移動端中實現HZB?


如圖,R17上,關閉硬件查詢,開啟HZB。


如何在UE4移動端中實現HZB?


結論:基於以上原因,在大量動態物體和靜態物體場景下,HZB在R17上都表現不佳。

其他性能開銷


移動端除了回讀耗時以外,在HZB構建以及遮擋測試階段,性能消耗也不能忽視,特別是採樣16次貼圖的操作。如下分別是在R15和R17上的測試結果:


R15在構建和測試階段分別耗時2.6ms和1.6ms。回讀耗時0.9ms


如何在UE4移動端中實現HZB?


R17在構建和測試階段分別耗時0.48ms和0.41ms。回讀耗時5.2ms


如何在UE4移動端中實現HZB?


通過對比發現,R15回讀快,而構建慢。R17回讀慢,而構建快。不同的硬件架構帶來的性能差異很大,關於移動硬件分析已經不屬於本文探討範疇了,在這裡就不展開講了。


總結


關於UE硬件查詢的Batch結論:


1.動態物體不會做Batch,全部一個一個去查詢,帶來巨大的DC開銷。


2.靜態物體在被遮擋的情況下會做batch查詢,DC顯著減少。


3.高通手機最大查詢次數為510,其他為4000,而實際推薦最佳查詢次數是250,2000(分別除了2)。


移動端HZB結論:


大量動態物體查詢,HZB適用於非高通移動設備上。


大量靜態物體查詢,HZB仍適用於非高通移動設備上,性能與硬件查詢Batch相當。

針對移動端未來可以做的優化方案:


1.將物體數據由質心座標+包圍盒範圍改為質心座標+包圍球半徑,可節省一張RGBA32貼圖。


2.將16次採樣改為採樣包圍球表面最近點和包圍盒四個頂點,可減少11次採樣。甚至更保守點,直接將包圍球面最近點作為採樣點進行比較。


3.高通設備可以考慮使用vulkan圖形API進行數據回讀。


4.高通設備在構建HZB的時候可以考慮使用ComputeShader。


分享到:


相關文章: