游戲性能優(yōu)化之:遮擋剔除
(內(nèi)容有搬運(yùn)其他人的文章,東拼西湊加上自己的理解)用于自己記錄知識點(diǎn) 。
之前有發(fā)一篇性能優(yōu)化專欄,但是實(shí)踐發(fā)現(xiàn)問題還是比較多,目前也在探索中,此專欄專門記錄下。
在游戲開發(fā)中,剔除是非常重要的技術(shù)。剔除可以幫助我們將不需要的渲染開銷降到最低,大幅提升游戲的幀率。
游戲中的剔除可以分成多個部分,比如 遮擋剔除、LOD、視錐剔除、硬件剔除、GPUDrivenPipeline 等。在這些里面,遮擋剔除是最難以實(shí)現(xiàn)的,也是變化最豐富的,這里就來小結(jié)下游戲中常用來實(shí)現(xiàn)遮擋剔除的方式:
1. 預(yù)計(jì)算可見性/Precomputed Visibility
這種方式比較簡單,就是先將場景劃分成一個個Cell,讓后對于每個Cell區(qū)域,預(yù)計(jì)算出攝影機(jī)在這個Cell范圍內(nèi)時,所有可能看到的物體,并將信息保存下來。這樣在運(yùn)行時就可以直接查表來得到所有靜態(tài)物體的可見信息,運(yùn)行時開銷幾乎為0。
UE4中內(nèi)置了這一功能[1],同時推薦在手機(jī)上開啟此功能。

不過這種方式的缺點(diǎn)也很明顯,完全無法剔除動態(tài)物體,靜態(tài)物體可見性全部都是不可變的。
如果我們想要實(shí)現(xiàn)對動態(tài)物體的剔除,可以對該算法進(jìn)行改進(jìn):在每個Cell中,同時保存其他Cell的可見性。對于小型的動態(tài)物體,我們先計(jì)算出動態(tài)物體所在的Cell坐標(biāo),然后根據(jù)Cell之間的可見性信息,判斷可見性。這樣做會額外消耗內(nèi)存,如果動態(tài)的小型物體較多時,可以考慮配合使用。
Precomputed Visibility 已知應(yīng)用:UE4 Mobile、逆水寒[2]。
2. Portal-Culling
這種方式也是將場景劃分成Cell,不同的是,烘焙時保存的是每兩個相鄰Cell之間的連通性。
這樣,在運(yùn)行時,根據(jù)攝影機(jī)所在的位置的Cell和觀察方向,就可以根據(jù)Cell間的連通性信息,快速計(jì)算出目標(biāo)物體是否處于可見范圍內(nèi)。
相對上面完全的預(yù)計(jì)算,這種方式的優(yōu)點(diǎn)就是可以剔除動態(tài)物體,同時提供了些許的靜態(tài)遮擋物體變化的靈活性。比如有扇可以開關(guān)的門,當(dāng)門被打開后,就可以將門兩側(cè)的Cell的連通打開。
使用這種方式最出名的中間件方案就是Umbra[3]。Unity自帶的遮擋剔除[4]就是使用的Umbra。

Portal-Culling已知應(yīng)用:Unity,Control。
3. 基于軟光柵/Software Occlusion
這種方式需要手動標(biāo)記好大型的用來遮擋的物體,在運(yùn)行時,將遮擋物的包圍盒(或最高LOD級別的模型)軟光柵到CPU內(nèi)存中的z-buffer上,然后根據(jù)z-buffer中的深度信息,根據(jù)需要剔除物體的包圍盒,實(shí)時計(jì)算遮擋信息。
這種方式缺點(diǎn)是對CPU端壓力很大,優(yōu)點(diǎn)是非常靈活。
相對前面兩種方案,軟光柵剔除提供了非常大的靈活性,支持完全可變/任意大小的場景,適用于流式關(guān)卡/大世界中的應(yīng)用。
相對GPU實(shí)現(xiàn)的遮擋剔除,這種方案完全不用擔(dān)心硬件兼容問題。
UE4的手機(jī)方案中也提供了這種剔除方式[5],設(shè)置r.mobile.AllowSoftwareOcclusion 1即可打開。

由于CPU的軟光柵計(jì)算壓力非常大,因此我們需要注意,不能在光柵化時計(jì)算太多的物體,不能使用精細(xì)的模型計(jì)算。由于浮點(diǎn)數(shù)計(jì)算很多,因此通常需要配合 SIMD[6]?技術(shù)來使用,比如 PC上的 SSE/AVX 和 ARM 下的 Neon。
Software Occlusion已知應(yīng)用:UE4 Mobile,BattleField3?[7],天涯明月刀端游[8]。
4. 遮擋查詢/Occlusion Query
Occlusion Query首先使用一個簡單的depth-only的pass將深度寫入到z-buffer中,然后使用物體的包圍盒傳入到GPU進(jìn)行遮擋測試,如果測試發(fā)現(xiàn)所有像素都被遮擋,說明這個物體是被遮擋的物體,否則的話認(rèn)為是可見的。
包括OpenGL[9]和Directx[10]在內(nèi)的圖形API都提供了這種遮擋剔除。
由于從GPU回讀數(shù)據(jù)到CPU通常很慢,因此通常會將得到的數(shù)據(jù)放在下一幀中作為剔除數(shù)據(jù)來使用,這樣遮擋剔除其實(shí)是延遲一幀生效的。不過一般來說,延遲一幀的剔除對實(shí)際的渲染影響并不大。
UE4中默認(rèn)也是采用這種方式做剔除[11]。
Occlusion Query已知應(yīng)用:UE4。
5. 基于HZB剔除
HZB即Hierarchical z-buffer,HZB是多Mip層級的z-buffer,每個更高級別Mip的buffer記錄上一級別中周圍四點(diǎn)中最遠(yuǎn)處的深度值。

將HZB生成后,就可以將待剔除物體的包圍盒信息傳入到Computer Shader中進(jìn)行計(jì)算,計(jì)算時會選擇最適合的Mip級別進(jìn)行遮擋測試。在屏幕中占比更大的物體會選擇更高級別Mip的深度進(jìn)行測試,這樣可以降低計(jì)算量。

計(jì)算結(jié)果會存儲到貼圖或者buffer中,然后從GPU回傳到CPU。這種方式和上面提到的方式一樣,會有計(jì)算結(jié)果的延遲。
UE4中可通過r.HZBOcclusion=1切換到這種剔除方式。
HZB剔除已知應(yīng)用:UE4、原神。
參考
^https://docs.unrealengine.com/4.27/en-US/RenderingAndGraphics/VisibilityCulling/PrecomputedVisibilityVolume/
^https://zhuanlan.zhihu.com/p/150448978
^https://www.gamasutra.com/view/feature/164660/sponsored_feature_next_generation_.php
^https://docs.unity3d.com/Manual/OcclusionCulling.html
^https://docs.unrealengine.com/4.27/en-US/RenderingAndGraphics/VisibilityCulling/SoftwareOcclusionQueries/
^https://zhuanlan.zhihu.com/p/55327037
^Culling the Battlefield: Data Oriented Design in Practice?https://www.gamedevs.org/uploads/culling-the-battlefield-battlefield3.pdf
^http://twvideo01.ubm-us.net/o1/vault/gdcchina14/presentations/833779_MiloYip_ADataOrientedCN.pdf
^https://www.khronos.org/opengl/wiki/Query_Object
^https://docs.microsoft.com/en-us/windows/win32/direct3d12/predication-queries
^https://docs.unrealengine.com/4.27/en-US/RenderingAndGraphics/VisibilityCulling/