如何在 UE4 移動端中實現 HZB?
HZB 的原理
一般來說,大多數基於 HZB 的遮擋剔除是這樣的工作的:
1. 使用一些遮擋器生成一個完整的分層 Z-金字塔。
z-pyramid 的最低階別是一個標準的 z-buffer。在所有其他層,每個 z 值都是上一層對應的 2×2 畫素中最遠的 z。
2. 要測試的物件是否被遮擋,可以將其包圍體投射到螢幕空間,並在 z-pyramid 中估計 mip 級別。將物件的包圍體投影到螢幕空間。最長的邊 l(畫素)用來計算 mip 等級λ。
邊長越長,選取的 mip 等級越高。
3. 根據選定的 mip 測試遮擋。如果結果不明確,可以繼續使用更細的 mip 級別進行測試。
這個選擇的原因是它使成本可預測——最多需要讀取和測試四個深度值。此外,這種測試可以被看作是“概率性的”,因為大物件比小物件更容易被看到,所以在這些情況下沒有理由讀取更多的深度值,即節省了頻寬,也增加了 Cache 命中。
UE4 的實現
UE4 只在 PC 端進行了實現,過程大致一樣,不同的是在構建層級 Z 緩衝上分為 ComputeShader 和 PixelShader 兩種方式,然後最終剔除工作主要在 CPU 端進行,意味著需要回讀 GPU 的測試結果。以下是 UE4 的工作流程:
使用 SceneDepth 作為資料來源構建層級 Z 緩衝。Mip0 為第一級,大小為 1024*512,總共構建 10 級。分為兩種方式,PixelShader 方式比較簡單,一次構建一級,總共執行十次。ComputeShader 則稍微複雜一點,利用 GroupMemoryBarrierWithGroupSync,每次同時構建 4 級,總共只要執行 3 次,便完成構建。
場景中的物體經過視錐剔除以後,剩下的會被收集起來,存放在一個陣列中,並且每個物體會儲存自己在陣列中的索引值。然後,建立 2 張 RGBA32 格式的貼圖,一張存放物體包圍盒的質心座標,一張存放物體包圍盒的大小。每次從陣列中取 64 個物體作為一組,儲存到貼圖的 64 個畫素區域中。
取樣第二步中的貼圖,獲取物體的質心座標和包圍盒大小,可以計算出物體包圍盒的八個頂點的世界位置,對這八個頂點進行投影,選取其中最近的 Z 值。根據投影后的矩形區域,選取最長邊長計算 mipmap 等級,然後在矩形區域內取樣該 mipmap 的 16 個畫素,選取其中最遠的 Z 值。如果包圍盒最近的 Z 值比它還小,則物體不可見。將結果儲存到一張格式為 RGBA8 的 RenderTarget 中,作為下一幀讀取。
當前幀讀取上一幀的貼圖資料到一個陣列中。每個物體將上一幀儲存的陣列索引到該資料進行查詢,查詢結果決定了該物體在當前幀的可見性。
以上步驟看出,UE4 的 HZB 實現流程沒有完全放在 GPU 端執行,在下一幀的時候需要回讀上一幀的結果,然後進行查詢以決定物體當前幀的可見性。另外,在第三步中,計算最遠 Z 值時,取樣了矩形區域內 16 個畫素,而不是 4 個畫素。
移動端的實現
移動端的 HZB 方案大部分可以與 PC 端共用一套邏輯實現,然後針對移動端效能點進行優化。移動端需要解決的第一個首要問題就是 SceneDepth 的獲取,因為它是構建層級 Z 緩衝的重要資料來源。
在移動端上,一般來說,如果存在後處理材質需要訪問場景深度資訊,UE4 會將場景線性深度值儲存在 SceneColor 的 Alpha 通道。而對於透明材質,如果使用了 DepthFade 材質節點,則會通過移動裝置擴充套件 API 來直接提取 FrameBuffer 中的深度資訊。
那我們如果想在移動端上直接訪問深度紋理的話,需要怎麼做了?可以通過設定 r.Mobile.ForceDepthResolve 為 1 來始終保留移動端的深度資訊。強制深度解析,為裝置保留深度紋理。
移動端獲取深度紋理
獲取了深度紋理,就可以開始構建層級 Z 緩衝。考慮到移動裝置的相容性,這裡只使用了 PixelShader 方式,依然構建了 10 級 mipmap。為了保證深度值的精度,這裡將每個深度值編碼到 rgba8888 格式的貼圖中。
移動端構建層級 Z 緩衝
構建完層級 Z 緩衝以後,接下來就是進行遮擋測試。演算法沿用了 PC 端的方式,將結果儲存在貼圖中,下一幀回讀。移動端上回讀 GPU 貼圖資料,需要注意的是,UE4 會預設處理上下翻轉。因為這裡只是存放遮擋結果的資料貼圖,所以不需要做上下翻轉。
最後一步遮擋查詢,過程和 PC 端一樣,每個物體用上一幀的陣列索引去查詢自己當前幀的可見性。
優化讀取效能
移動端回讀 GPU 資料相當耗費效能,我們可以來看下 glReadPixels 分別在 oppo 手機型號為 r15 和 r17 上的測試結果:
r15 耗時 6~8ms
r17 耗時 16~20ms
直接呼叫 glReadPixels 相當慢,不過,好在目前大多數移動裝置的 opengles 已經達到 3.0 以上,所以,可以考慮使用 PBO 的方式進行優化,使用 glMapBufferRang 進行讀取。過程大致如下:
初始化 2 個 buffer。
buffer1 用於非同步 glReadPixels 讀取,buffer2 用於 glMapBufferRange 讀取。
下一幀交換 buffer,buffer1 用於 glMapBufferRange,buffer2 用於 glReadPixels。
再來看下,優化後的測試結果:
優化後,r15 耗時 0.9ms
優化後,r17 耗時 4~6ms
硬體遮擋查詢 和 HZB 在 oppo r15 上的效能對比
經過初步優化,我們來看下,硬體遮擋查詢和 HZB 各自在手機上的效能對比。測試分為靜態物體和動態物體。新建一個場景,場景內隨機生成 10000 個物體。在分別只開啟硬體查詢和只開啟 HZB 的情況下,對幀率和被遮擋物數量的影響。UE4 針對硬體查詢做了 Batch 優化,這樣可以大大降低硬體查詢帶來的 DC 開銷,不過 Batch 只對靜態物體有效。所以,需要分開測試。
動態物體:
如圖,只開啟了硬體查詢,因為動態物體無法 Batch,Occlusion queries 相當高,達到 987,而 draw call 數量達到了 1450。被遮擋物體為 579,可見物體為 411。幀率只有 18。由此可見,對於大量動態物體,硬體查詢本身帶來了巨大的 DC 開銷。
如圖,只開啟了 HZB,硬體查詢的 DC 開銷已經沒有了,被遮擋物體為 570,可見物體為 420。Draw call 為 467,幀率為 30。
結論:對於大量動態物體查詢的場景,從被遮擋物體數量和可見物體數量兩個資料指標來看,硬體查詢和 HZB 不相上下。效能上,HZB 優勢明顯。
靜態物體:
如圖,只開啟硬體查詢,因為靜態物體的原因,硬體查詢發揮了 Batch 的優勢,Occlusion queries 只有 58,draw call 只有 511,幀率達到 35。
如圖,只開啟了 HZB,硬體查詢 Batch 帶來的 DC 也沒有了,所以 draw call 略有下降。幀率為 33。
結論:對於大量靜態物體查詢的場景,HZB 仍適用,效能與硬體查詢 Batch 相當。
硬體遮擋查詢 和 HZB 在 oppo r17 上的效能對比
同樣分別測試動態物體和靜態物體,r17 和 r15 表現了完全的不一樣的結果,之前在優化讀取時也發現,r17 依然有 4~6ms 的開銷,這是為什麼了?這跟 r15 和 r17 在硬體上不同有關。
R15 硬體引數
R17 硬體引數
另外,值得一提的是,UE4 針對高通裝置,做了硬體查詢的上限限制。最大 510 次查詢,而其他裝置預設是最大 4000 次查詢。
這導致了 R17 在大量動態物體場景下,即便沒有做 Batch,也只有不超過 250 的 Occlusion queries 數量。這並不是什麼優化,而是 UE4 直接放棄了超過該數量的硬體查詢,再加上依然存在的回讀耗時,這樣使得 HZB 的優勢就不那麼明顯了。
如圖,R17 上,開啟硬體查詢,關閉 HZB。
如圖,R17 上,關閉硬體查詢,開啟 HZB。
結論:基於以上原因,在大量動態物體和靜態物體場景下,HZB 在 R17 上都表現不佳。
其他效能開銷
移動端除了回讀耗時以外,在 HZB 構建以及遮擋測試階段,效能消耗也不能忽視,特別是取樣 16 次貼圖的操作。如下分別是在 R15 和 R17 上的測試結果:
R15 在構建和測試階段分別耗時 2.6ms 和 1.6ms。回讀耗時 0.9ms
R17 在構建和測試階段分別耗時 0.48ms 和 0.41ms。回讀耗時 5.2ms
通過對比發現,R15 回讀快,而構建慢。R17 回讀慢,而構建快。不同的硬體架構帶來的效能差異很大,關於移動硬體分析已經不屬於本文探討範疇了,在這裡就不展開講了。
總結
關於 UE 硬體查詢的 Batch 結論:
動態物體不會做 Batch,全部一個一個去查詢,帶來巨大的 DC 開銷。
靜態物體在被遮擋的情況下會做 batch 查詢,DC 顯著減少。
高通手機最大查詢次數為 510,其他為 4000,而實際推薦最佳查詢次數是 250,2000(分別除了 2)。
移動端 HZB 結論:
大量動態物體查詢,HZB 適用於非高通移動裝置上。
大量靜態物體查詢,HZB 仍適用於非高通移動裝置上,效能與硬體查詢 Batch 相當。
針對移動端未來可以做的優化方案:
將物體資料由質心座標+包圍盒範圍改為質心座標+包圍球半徑,可節省一張 RGBA32 貼圖。
將 16 次取樣改為取樣包圍球表面最近點和包圍盒四個頂點,可減少 11 次取樣。甚至更保守點,直接將包圍球面最近點作為取樣點進行比較。
高通裝置可以考慮使用 vulkan 圖形 API 進行資料回讀。
高通裝置在構建 HZB 的時候可以考慮使用 ComputeShader。
來源:GWB-騰訊創意遊戲合作計劃
相關文章
- 如何在UE4中實現植物風場效果?
- 如何在移動應用中實現AI畫圖?AI
- 如何在移動端資料視覺化大屏實現分析?視覺化
- React 服務端渲染實現 Gank 移動端React服務端
- 如何讓你的網站在移動端健步如飛網站
- 移動機器人如何在陌生環境中實現智慧導航?機器人
- Html5實現移動端、PC端 刮刮卡效果HTML
- js實現移動端字型響應式JS
- 如何在移動端app中應用字型圖示icon fontsAPP
- 原生 js 實現移動端 Touch 滑動反彈JS
- 移動端實現內滾動的4種方案
- Html5實現移動端、PC端 刮刮卡效果薦HTML
- 如何在Unity中實現水體互動?Unity
- 原生JS實現移動端線上籤協議JS協議
- 實現基於React的移動端Swiper元件React元件
- HTML+CSS實現時間軸(移動端)HTMLCSS
- 移動端日曆元件設計與實現元件
- H5移動端彈幕動畫實現H5動畫
- 移動端的車牌識別如何實現
- 移動端實現1px的邊框
- 移動端無限滾動載入 js實現原理JS
- 如何在iPhone移動端設定海外HTTP代理?iPhoneHTTP
- UE4純C++實現遊戲中快捷欄C++遊戲
- UE4 Dash功能實現
- H5實現移動端複製文字功能H5
- 移動端輪播圖實現方法(dGun.js)JS
- 移動遠端辦公是怎樣實現的?
- vue全家桶實現移動端電商web-appVueWebAPP
- react實現移動端PDF線上預覽外掛React
- 移動端H5實現圖片上傳H5
- 如何在移動應用中整合美顏SDK實現人臉識別和美化功能
- UE4 C++(11):移動元件和碰撞C++元件
- 如何在DataWindow中實現列的自動折行
- 如何在create-react-app專案中使用vw實現手淘vw移動端適配佈局ReactAPP
- H5移動端獲獎無縫滾動動畫實現H5動畫
- 如何在Java後端中實現事件驅動架構:從事件匯流排到事件溯源Java後端事件架構
- H5實現移動端語音錄製功能H5
- Ts + React + Mobx 實現移動端瀏覽器控制檯React瀏覽器