合批是什么?為什么可以減少Drawcall?有什么合批方法?


發現自己只是知道合批怎么用,是可以減少drawcall,但卻不知道這些底層的機制是怎么樣的,為什么可以減少drawcall?這可是犯了大忌!決定潛心學習研究一下,在此記錄。

首先什么是合批?

合批,也可以叫做批量渲染。合批就是通過減少CPU向GPU發送渲染命令(DrawCall)的次數,以及減少GPU切換渲染狀態的次數,盡量讓GPU一次多做一些事情,來提升邏輯線和渲染線的整體效率

但參與合批有個前提,就是參與合批的材質必須相同!

那么為什么合批可以優化drawcall?

讓我們先看一個drawcall所走過的流程需要哪些消耗:

Application -> Runtime -> Driver -> 顯卡(GPU)

Runtime會將所有API調用先轉換成與設備無關的命令(這里為什么說是與設備無關的命令,是因為這樣我們寫的程序就可以運行在任何特性兼容的硬件上了),而Drawcall性能消耗原因是命令從Runtime到Driver的過程中,CPU要發生從用戶模式到內核模式的切換,這個模式切換對於CPU來說是個非常耗時的工作。

那么如果Runtime中有一個Command Buffer可以將一些沒必要馬上發送給Driver的命令緩存起來,在適當的時機一起發送給Driver,再在GPU里執行。這樣是不是就減少了CPU的模式切換,從而提升了效率呢?沒錯,這就是合批可以干的事!

讓我們來看看有什么合批

離線合批

利用美術工具例如Maya把相關資源做合批處理,以減輕引擎實時合批的負擔。比如靜態模型和場景物件。如場景地表裝飾面:石頭/磚塊等等

靜態合批

對標記為static的Mesh自動合批。以空間換時間的策略來提升渲染效率。以存儲更多網格數據為代價的

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

在后續的繪制過程中,一次性提交整個合並模型的頂點數據,根據引擎的場景管理系統判斷各個子模型的可見性。然后設置一次渲染狀態,調用多次Draw call分別繪制每一個子模型。所以其實static batching是不減少Draw call的數量(但是在編輯器時由於計算方法區別Draw call數量是會顯示減少了的),但是由於我們預先把所有的子模型的頂點變換到了世界空間下,所以在運行時cpu不需要再次執行頂點變換操作,節約了少量的計算資源,並且這些子模型共享材質,所以在多次Draw call調用之間並沒有渲染狀態的切換,渲染API(Command Buffer)會緩存繪制命令,起到了渲染優化的目的 。

但靜態合並有個很大的缺點就是打包之后會導致應用體積增大,應用運行時所占用的內存體積也會增大。例如,在茂密的森林級別將樹標記為靜態會嚴重影響內存,因為場景中所有引用相同模型的GameObject都必須將模型頂點信息復制,並經過計算變化到最終在世界空間中,存儲在最終生成的Vertex buffer中,這個時候的vertex buffer會特別大。

動態合批

將數份Mesh的數據復制粘貼到一起,也就是實時的,每一幀都合並,但不適用於網格數據太多的物體(比如球)

在進行場景繪制之前將所有的共享同一材質的模型的頂點信息變換到世界空間中,然后通過一次Draw call繪制多個模型,達到合批的目的。模型頂點變換的操作是由CPU完成的,所以這會帶來一些CPU的性能消耗。並且計算的模型頂點數量不宜太多,否則CPU串行計算耗費的時間太長會造成場景渲染卡頓,所以Dynamic batching只能處理一些小模型。所以僅僅在合批操作的性能消耗小於不合批,Dynamic batching才會有意義,雖然在內存占用和發布的程序體積方面要優於Static batching。

無法參加dynamic batching的情況:

  1. 物件Mesh大於等於900個面
  2. 代碼動態改變材質變量后不算同一個材質,會不參與合批
  3. 如果你的着色器使用頂點位置,法線和UV值三種屬性,那么你只能批處理300頂點以下的物體;如果你的着色器需要使用頂點位置,法線,UV0,UV1和切向量,那你只能批處理180頂點以下的物體,否則都無法參與合批

 這個與static batching的區別主要網格信息是合並在一起了,command buffer只有一次drawcall了

GPU Instancing

使用一個渲染調用來繪制多個物體,來節省每次繪制物體時CPU -> GPU的通信。也就是,讓GPU一次性渲染同一網格多次,每次繪制的網格屬性都可以不一樣:包括縮放、位置、顏色等等,即材質球雖然相同但屬性可以各有各的區別。

在使用相同材質球相同Mesh(預設體的實例會自動地使用相同的網格模型和材質)的情況下,Unity會在運行時對於正在視野中的符合要求的所有對象使用Constant Buffer將其位置、縮放、uv偏移、lightmapindex等相關信息保存在顯存中的“統一/常量緩沖器”中,然后從中抽取一個對象作為實例送入渲染流程,當在執行DrawCall操作后,從顯存中取出實例的部分共享信息與從GPU常量緩沖器中取出對應對象的相關信息一並傳遞到下一渲染階段,與此同時,不同的着色器階段可以從緩存區中直接獲取到需要的常量,不用設置兩次常量

GPU Instancing可以規避合並Mesh導致的內存與性能上升的問題,但是由於場景中所有符合該合批條件的渲染物體的信息每幀都要被重新創建,放入“統一/常量緩沖區”中,而礙於緩存區的大小限制,每一個Constant Buffer的大小要嚴格限制(不得大於64k) 

動態合批與靜態合批的區別:

  1. 動態合批不會創建常駐內存的“合並后網格”,也就是說它不會在運行時造成內存的顯著增長也不會影響打包時的包體大小;
  2. 動態合批在繪制前會先將頂點轉換到世界坐標系下,然后再填充進頂點、索引緩沖區;靜態合批后子網格不接受任何變換操作,僅手動合批后的Root節點可被操作,因此靜態合批的頂點、索引緩沖區中的信息不會被修改(Root的變換信息則會通過Constant Buffer傳入);
  3. 因為2的原因,動態合批的主要開銷在於遍歷頂點進行空間變換時的對CPU性能的開銷;靜態合批沒有這個操作,所以也沒有這個開銷;
  4. 動態合批使用根據渲染器類型分配的公共緩沖區,而靜態合批使用自己專用的緩沖區。

 

Reference

  1. https://blog.csdn.net/chenweiyu11962/article/details/121340711
  2. https://zhuanlan.zhihu.com/p/98642798
  3. https://zhuanlan.zhihu.com/p/356211912


免責聲明!

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



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