靜態批處理/動態批處理/GPU Instancing /SRP Batcher的詳細剖析


靜態批處理[1]

  • 定義

標明為 Static 的靜態物件,如果在使用相同材質球的條件下,在Build(項目打包)的時候Unity會自動地提取這些共享材質的靜態模型的Vertex buffer和Index buffer。根據其擺放在場景中的位置等最終狀態信息,將這些模型的頂點數據變換到世界空間下,存儲在新構建的大Vertex buffer和Index buffer中。並且記錄每一個子模型的Index buffer數據在構建的大Index buffer中的起始及結束位置。

img

在后續的繪制過程中,一次性提交整個合並模型的頂點數據,根據引擎的場景管理系統判斷各個子模型的可見性。然后設置一次渲染狀態,調用多次Draw call分別繪制每一個子模型。

img

Static batching並不減少Draw call的數量(但是在編輯器時由於計算方法區別Draw call數量是會顯示減少了的[2]),但是由於我們預先把所有的子模型的頂點變換到了世界空間下,所以在運行時cpu不需要再次執行頂點變換操作,節約了少量的計算資源,並且這些子模型共享材質,所以在多次Draw call調用之間並沒有渲染狀態的切換,渲染API(Command Buffer)會緩存繪制命令,起到了渲染優化的目的 。

但Static batching也會帶來一些性能的負面影響。Static batching會導致應用打包之后體積增大,應用運行時所占用的內存體積也會增大。

另外,在很多不同的GameObject引用同一模型的情況下,如果不開啟Static batching,GameObject共享的模型會在應用程序包內或者內存中只存在一份,繪制的時候提交模型頂點信息,然后設置每一個GameObjec的材質信息,分別調用渲染API繪制。開啟Static batching,在Unity執行Build的時候,場景中所有引用相同模型的GameObject都必須將模型頂點信息復制,並經過計算變化到最終在世界空間中,存儲在最終生成的Vertex buffer中。這就導致了打包的體積及運行時內存的占用增大。例如,在茂密的森林級別將樹標記為靜態會嚴重影響內存[3]

  • 無法參與批處理情況
  1. 改變Renderer.material將會造成一份材質的拷貝,因此會打斷批處理,你應該使用Renderer.sharedMaterial來保證材質的共享狀態。
  • 相同材質批處理斷開情況
  1. 位置不相鄰且中間夾雜着不同材質的其他物體,不會進行同批處理,這種情況比較特殊,涉及到批處理的順序,我的另一篇文章有詳解。
  2. 擁有lightmap的物體含有額外(隱藏)的材質屬性,比如:lightmap的偏移和縮放系數等。所以,擁有lightmap的物體將不會進行同批處理(除非他們指向lightmap的同一部分)。
  • 流程原理

img


動態批處理[4]

  • 定義

在使用相同材質球的情況下,Unity會在運行時對於正在視野中的符合條件的動態對象在一個Draw call內繪制,所以會降低Draw Calls的數量。

Dynamic batching的原理也很簡單,在進行場景繪制之前將所有的共享同一材質的模型的頂點信息變換到世界空間中,然后通過一次Draw call繪制多個模型,達到合批的目的。模型頂點變換的操作是由CPU完成的,所以這會帶來一些CPU的性能消耗。並且計算的模型頂點數量不宜太多,否則CPU串行計算耗費的時間太長會造成場景渲染卡頓,所以Dynamic batching只能處理一些小模型。

Dynamic batching在降低Draw call的同時會導致額外的CPU性能消耗,所以僅僅在合批操作的性能消耗小於不合批,Dynamic batching才會有意義。而新一代圖形API( Metal、Vulkan)在批次間的消耗降低了很多,所以在這種情況下使用Dynamic batching很可能不能獲得性能提升。Dynamic batching相對於Static batching不需要預先復制模型頂點,所以在內存占用和發布的程序體積方面要優於Static batching。但是Dynamic batching會帶來一些運行時CPU性能消耗,Static batching在這一點要比Dynamic batching更加高效。

  • 無法參與批處理情況
  1. 物件Mesh大於等於900個面。
  2. 代碼動態改變材質變量后不算同一個材質,會不參與合批。
  3. 如果你的着色器使用頂點位置,法線和UV值三種屬性,那么你只能批處理300頂點以下的物體;如果你的着色器需要使用頂點位置,法線,UV0,UV1和切向量,那你只能批處理180頂點以下的物體,否則都無法參與合批。
  4. 改變Renderer.material將會造成一份材質的拷貝,因此會打斷批處理,你應該使用Renderer.sharedMaterial來保證材質的共享狀態。
  • 批處理中斷情況
  1. 位置不相鄰且中間夾雜着不同材質的其他物體,不會進行同批處理,這種情況比較特殊,涉及到批處理的順序,我的另一篇文章有詳解。
  2. 物體如果都符合條件會優先參與靜態批處理,再是GPU Instancing,然后才到動態批處理,假如物體符合前兩者,此次批處理都會被打斷。
  3. GameObject之間如果有鏡像變換不能進行合批,例如,”GameObject A with +1 scale and GameObject B with –1 scale cannot be batched together”。
  4. 擁有lightmap的物體含有額外(隱藏)的材質屬性,比如:lightmap的偏移和縮放系數等。所以,擁有lightmap的物體將不會進行批處理(除非他們指向lightmap的同一部分)。
  5. 使用Multi-pass Shader的物體會禁用Dynamic batching,因為Multi-pass Shader通常會導致一個物體要連續繪制多次,並切換渲染狀態。這會打破其跟其他物體進行Dynamic batching的機會。
  6. 我們知道能夠進行合批的前提是多個GameObject共享同一材質,但是對於Shadow casters的渲染是個例外。僅管Shadow casters使用不同的材質,但是只要它們的材質中給Shadow Caster Pass使用的參數是相同的,他們也能夠進行Dynamic batching。
  7. Unity的Forward Rendering Path中如果一個GameObject接受多個光照會為每一個per-pixel light產生多余的模型提交和繪制,從而附加了多個Pass導致無法合批,如下圖:

img可以接收多個光源的shader,在受到多個光源是無法合批

  • 流程原理

img


GPU Instancing

  • 定義

在使用相同材質球、相同Mesh(預設體的實例會自動地使用相同的網格模型和材質)的情況下,Unity會在運行時對於正在視野中的符合要求的所有對象使用Constant Buffer[5]將其位置、縮放、uv偏移、lightmapindex等相關信息保存在顯存中的“統一/常量緩沖器”[6]中,然后從中抽取一個對象作為實例送入渲染流程,當在執行DrawCall操作后,從顯存中取出實例的部分共享信息與從GPU常量緩沖器中取出對應對象的相關信息一並傳遞到下一渲染階段,與此同時,不同的着色器階段可以從緩存區中直接獲取到需要的常量,不用設置兩次常量。比起以上兩種批處理,GPU Instancing可以規避合並Mesh導致的內存與性能上升的問題,但是由於場景中所有符合該合批條件的渲染物體的信息每幀都要被重新創建,放入“統一/常量緩沖區”中,而礙於緩存區的大小限制,每一個Constant Buffer的大小要嚴格限制(不得大於64k)。詳細請閱讀:

Testplus:U3D優化批處理-GPU Instancing了解一下zhuanlan.zhihu.com圖標

  • 無法參與加速情況
  1. 縮放為負值的情況下,會不參與加速。
  2. 代碼動態改變材質變量后不算同一個材質,會不參與加速,但可以通過將顏色變化等變量加入常量緩沖器中實現[7]
  3. 受限於常量緩沖區在不同設備上的大小的上限,移動端支持的個數可能較低。
  4. 只支持一盞實時光,要在多個光源的情況下使用實例化,我們別無選擇,只能切換到延遲渲染路徑。為了能夠讓這套機制運作起來,請將所需的編譯器指令添加到我們着色器的延遲渲染通道中。

img當在多個光源開啟GPU Instancing

  • 批處理中斷情況
  1. 位置不相鄰且中間夾雜着不同材質的其他物體,不會進行同批處理,這種情況比較特殊,涉及到批處理的順序,我的另一篇文章有詳解。
  2. 一個批次超過125個物體(受限於常量緩沖區在不同設備上的大小的上限,移動端數量有浮動)的時候會新建另一個加速流程。
  3. 物體如果都符合條件會優先參與靜態批處理,然后才到GPU Instancing,假如物體符合前者,此次加速都會被打斷。
  • 流程原理

img


SRP Batcher[8]

  • 定義

在使用LWRP或者HWRP時,開啟SRP Batcher的情況下,只要物體的Shader中變體一致,就可以啟用SRP Batcher加速。它與上文GPU Instancing實現的原理相近,Unity會在運行時對於正在視野中的符合要求的所有對象使用“Per Object” GPU BUFFER(一個獨立的Buffer) 將其位置、縮放、uv偏移、lightmapindex等相關信息保存在GPU內存中,同時也會將正在視野中的符合要求的所有對象使用Constant Buffer[5]將材質信息保存在保存在顯存中的“統一/常量緩沖器”[6]中。與GPU Instancing相比,因為數據不再每幀被重新創建,而且需要保存進“統一/常量緩沖區”的數據排除了各自的位置、縮放、uv偏移、lightmapindex等相關信息,所以緩沖區內有更多的空間可以動態地存儲場景中所有渲染物體的材質信息。由於數據不再每幀被重新創建,而是動態更新,所以SRP Batcher的本質並不會降低Draw Calls的數量,它只會降低Draw Calls之間的GPU設置成本。

img因為不用重新創建Constant Buffer,所以本質上SRP Batcher不會降低Draw Calls的數量,它只會降低Draw Calls之間的GPU設置成本

  • 無法參與加速情況
  1. 對象不可以是粒子或蒙皮網格。
  2. Shader中變體不一致,如下圖兩個相同Shader的材質,但是因為Surface Options不一致,導致變體不一樣而無法合並。

img變體不同的不同材質

  • 批處理中斷情況
  1. 位置不相鄰且中間夾雜着不同Shader,或者不同變體的其他物體,不會進行同批處理,這種情況比較特殊,涉及到批處理的順序,我的另一篇文章有詳解。
  • 流程原理

img


2020年2月13日-更新: 更改對”統一/常量緩沖器“的描述,對SRP Batcher與GPU Instancing的實現原理進行了比較大的修改。

^ ^ 以上只是我工作中的一些小總結
有什么不正確的地方可以在評論告訴我
我的微信號是:sam2b2b
有想一起進步的小伙伴可以加微信逛逛圈

參考

    1. ^https://gameinstitute.qq.com/community/detail/114323
    2. ^https://forum.unity.com/threads/regression-feature-not-bug-static-dynamic-batching-combining-v-buffers-but-not-draw-calls.360143/
    3. ^https://docs.unity3d.com/Manual/DrawCallBatching.html
    4. ^https://gameinstitute.qq.com/community/detail/114323
    5. ^abConstant Buffer https://zhuanlan.zhihu.com/p/35830868
    6. ^abunity將常量存儲在4M的緩沖池里,並每幀循環池(這個池子被綁定到GPU上,可以在截幀工具比如XCode或者Snapdragon上看到)
    7. ^https://blog.csdn.net/lzhq1982/article/details/88119283
    8. ^SRP Batcher 官方文檔: https://mp.weixin.qq.com/s/-4Bhxtm_L5paFFAv8co4Xw


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM