不同幾種剔除(Culling)在渲染流程中的使用總結

前提

最近在閱讀《Real-Time Rendering》Third Edition時,發現對於渲染管線中不同剔除部分的具體含義和生效階段不甚明瞭,遂做了一點研究,在這裏做一個記錄。

涉及到的剔除方法包括:

  • 視椎體剔除
  • 遮擋剔除
  • 視口剔除
  • 背面剔除
  • 深度剔除

以下對於這幾種剔除方法分別進行分析:

視椎體剔除

發生在應用程序階段(Application Stage),一般由遊戲引擎內部實現或者自己編寫對應的算法來實現,運行在CPU上。裁剪的依據主要是根據攝像機的視野(field of view)以及近裁減面和遠裁剪面的距離,將可視範圍外的物體排除出渲染,被剔除的物體將不會進入渲染的幾何階段(Geometry Stage)。視椎體剔除是減少渲染消耗的最有效手段之一,可以在不影響渲染效果的情況下大幅減少渲染涉及到的頂點數和麪數。

根據不同引擎的的實現,實際時候時可能有一些需要注意的地方。經過測試,Unity中默認ShadowMap的生成會大幅影響視椎體剔除的範圍,對於移動平臺不使用實時陰影的情況下,可以嘗試關閉燈的陰影投射或者去掉簡單sheder中的FallBack(通常會含有ShadowCaster),就會使同屏渲染面數大幅減小。

遮擋剔除

發生在應用程序階段(Application Stage),由遊戲引擎實現,運行在CPU上。以Unity爲例,需要根據場景中Static物體的位置預先生成場景OcclusionCulling數據,運行時就可以剔除對應靜態物體之後的其他物體。遮擋剔除是減少渲染消耗的有效手段之一,可以和視椎體剔除同時生效,進一步減少渲染的消耗。

針對Unity中遮擋剔除中的使用和研究,https://blog.csdn.net/cartzhang/article/details/52684127 中總結的很好。

隨着硬件技術的提升,目前GPU已經可以支持在沒有應用程序階段額外數據的支持下在硬件側面實現遮擋剔除(在光柵化之後,像素着色器之前,優於渲染管線末尾才進行的深度剔除)
https://blog.csdn.net/cartzhang/article/details/72420731 一文中提到

現代GPU中運用了Early-Z的技術,在Vertex階段和Fragment階段之間(光柵化之後,fragment之前)進行一次深度測試,如果深度測試失敗,就不必進行fragment階段的計算了,因此在性能上會有很大的提升。但是最終的ZTest仍然需要進行,以保證最終的遮擋關係結果正確。

視口剔除

發生在幾何階段(Geometry Stage)後期,投影變換之後屏幕映射之前,是渲染管線的必要一環。只有當圖元完全或部分存在於規範立方體內部的時候,纔將其返送到光柵化階段。其中,對於完全位於規範立方體內部的圖元,則直接進行下一階段;完全處於規範立方外部的圖元則完全被捨棄;部分處於規範立方體內部圖元,則會根據視口進行對應的裁剪,在這一過程中可能會產生新的頂點。

通過視口剔除可以將視口外的圖元捨棄掉,減小光柵化階段的消耗。

背面剔除

背面剔除即是將背向視點的圖元剔除,因爲它們對最終渲染的圖像沒有貢獻。這是一種簡單直觀的操作,一次對一個多邊形進行操作。剔除的基本原理是先判定多邊形的朝向,並和當前的觀察方向進行比較

更爲詳細的說明可以在《Real-Time Rendering》第14章加速算法找到,也可參照淺墨大大的總結文章
https://blog.csdn.net/poem_qianmo/article/details/78884513

Unity中的背面剔除在光柵化階段進行,執行在Vertex Shader 之後,在Fragment Shader片元着色器之前,通過Shader中的Cull指令來控制背面剔除的開啓和關閉,
Unity手冊上的圖示顯示的比較清晰:
image
https://docs.unity3d.com/Manual/SL-CullAndDepth.html

深度剔除

在Fragment Shader之後,光柵化階段末期的融合階段執行,又叫深度檢測(或Z緩存檢測)。每次將一個圖元回執爲相應的像素時,都會計算像素位置處圖元的深度值,和深度緩存中對應像素的值進行比較,如果新計算出的深度小於緩存中的深度,則更新深度緩存中的值;如果深度值大於深度緩衝中的值,則計算結果被捨棄,深度緩衝的值也無需更新。

在執行深度剔除時,已經位於渲染管線末尾,所有的顏色計算都已經完成,因此意義只在處理圖元的可見性問題,對於渲染的消耗基本沒有影響,不是優化時需要重點考察的問題。

總結

  • 在考慮渲染優化的問題上,應當優先考察”視椎體剔除”和”遮擋剔除”是否在項目中正常運作,這兩項會極大的影響到實際渲染時的消耗。
  • 其次需要考慮背面剔除的開啓,在沒有大量使用單面的模型情況下,背面剔除可以在不影響顯示的前提下少處理一半的像素着色運算。
  • 視口剔除和深度剔除是渲染管線的固有部分,不在優化的主要考察範圍內。

一些有趣的問題

  1. Unity在shader中如果要進行深度相關的計算(景深,軟粒子等相關效果時),需要取到當前像素的深度值,因而需要使用ShadowCaster通道來保證對應物體出現在讀取的深度圖上(Camera額外生成),爲什麼不直接使用當前繪製過程中深度緩存中的值?

    因爲深度緩存中的值在光柵化的融合階段纔開始寫入,Fragment Shader中自然無法讀取到。這邊Unity的實現是在正式渲染之前由Camera預先渲染一張深度圖,並設置到ShaderLab全局變量中,這裏只有擁有ShaderCaster Pass的物體纔會被繪製在這張深度圖中(這裏比較迷,將陰影投射和深度圖生成綁定)。在正式渲染時就可以通過圖元在視口中的位置從全局變量中獲取對應的深度值。

  2. 視椎體裁剪和遮擋剔除是否會同時生效,有必要一起使用嗎?

    視椎體裁剪是將視椎體外的物件排除在渲染之外,由引擎默認處理(關聯到一些特殊情況可能會效果不理想,如上面提到的ShadowMap生成),而遮擋剔除是將攝像機看不到的物體剔除,這包括視椎體範圍內的一些物體,因此這兩項可以同時生效,在大部分情況下也應當一起使用。
    RTR3中的一張圖描述的很清晰:
    image

另外推薦一下《Real-Time Rendering》Third Edition這本書,對於渲染流程的總結非常到位,是渲染入門的必備書目,我讀後感覺受益匪淺,推薦有興趣的同學都能找對應的資源來讀一讀,另外推薦一下淺墨大大的RTR3讀書總結 https://blog.csdn.net/poem_qianmo?viewmode=contents

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章