Unity的靜態批處理和動態批處理

什麼是批處理

爲了將物體繪製到屏幕上,引擎必須向圖像API(例如OpenGL、Direct3D)發送一個draw call指令。每一次draw call就可以大致理解爲一個渲染批次(batch)。Draw call屬於資源密集型的指令,圖形API要爲每個Draw call做大量的工作。造成CPU性能消耗的主要是渲染狀態的切換導致的,例如切換到不同的材質,這會導致在圖形驅動中產生密集的資源驗證和切換。爲了減少draw call的調用,Unity使用了兩種技巧來優化這個問題:

  • 動態批處理:對於足夠小的mesh,動態批處理通過將他們的頂點整合到一個批次中進行繪製。
  • 靜態批處理:通過將不會移動的靜態物體合併到更大的mesh中,以提升渲染速度。

與手動合併相比,內置的批處理方式最大的有點在於,每一個物體仍然可以被單獨剔除。但是他們也有缺點,靜態批處理會消耗更多的內存空間,動態批處理會產生CPU開銷。

材質方面的要求

只有使用了相同材質的物體才能夠實現批處理。如果兩個不同的物體,使用的兩個材質,只是紋理上的差別,那就把他們的紋理合併到一起,這樣就可以使用同一個材質。腳本中使用Render.material屬性時,會重新生成一個原來材質的拷貝,所以用Render.sharedMaterial可以保持材質的一致性(但是使用同一個材質的物體,都會被改變)。關於陰影,只要材質中使用的是相同的Shadow Pass,就可以實現批處理,即便他們不是同一個材質。

靜態批處理

靜態批處理因爲不需要再CPU中進行頂點轉換,所以要比動態批處理快的多,但是會佔用更多內存。如果多個物體共享同一個材質,使用靜態批處理時,會爲每個幾何體生成一份拷貝,不管是在編輯器還是運行時。所以在內存緊張的環境下,就需要損失一定的渲染性能來避免靜態批帶來的內存開銷。

靜態批處理比較簡單,只需要將場景種不會動的靜態物品設置Batching Static,剩下的就交給引擎了。

靜態批處理的原理是將物體轉換到世界空間,併爲他們建立一個共享的頂點和索引緩存。在Player Setting中勾選了Opeimized Mesh Ddata以後,Unity在構建緩存時,會移除那些在所有shader變體中都沒有被用到的頂點的elements(官方文檔裏是這麼說的,不知道該翻譯成什麼,頂點的屬性?)。Unity通過一些特殊的關鍵字檢查來實現這個操作,比如如果沒有包含LIGHTMAP_ON,該批處理的頂點輸入中就會刪除lightmap UVs。

官方文檔裏有這樣一句話:Then, for visible GameObjects in the same batch, Unity performs a series of simple draw calls, with almost no state changes in between each one. 

在同一批次的可見物體中,unity會執行一系列的簡單的draw call,且調用之間幾乎不會發生狀態變化。

可能是我的驗證方式有問題,對這句話我表示存疑。

靜態批處理,並不是所有的模型都合併到同一個Mesh裏,合併的時候仍然受限於mesh的頂點數要求,在大多數平臺上,批處理限制是64k個頂點和64k個索引(OpenGLES上48k個索引,macOS上32k個索引)。如果幾個模型的頂點總數超過了這個限制,就會被拆分成多個draw call,但是這樣的拆分,並不會造成setpass call的變化,因爲渲染狀態沒有變化,所以這些被分開的渲染批次仍然執行的很快。

 

動態批處理

需要重點說的是動態批處理,動態批處理不需要我們做設置,引擎會在條件滿足時自動處理,但是動態批處理的條件非常苛刻,任何一點不滿足就不能實現批處理。究竟有多麼苛刻呢?

注意,接下來的一部分內容將會與你在其他貼子中看到的有相當大的出入。鑑於此,先貼上官方文檔(Version:2019.2):

Unity can automatically batch moving GameObjects into the same draw call if they share the same Material and fulfill other criteria. Dynamic batching is done automatically and does not require any additional effort on your side.

  • Batching dynamic GameObjects has certain overhead per vertex, so batching is applied only to Meshes containing no more than 900 vertex attributes, and no more than 300 vertices.
    • If your Shader
       is using Vertex Position, Normal and single UV, then you can batch up to 300 verts, while if your Shader is using Vertex Position, Normal, UV0, UV1 and Tangent, then only 180 verts.
    • Note: attribute count limit might be changed in future.
  • GameObjects are not batched if they contain mirroring on the transform (for example GameObject A with +1 scale and GameObject B with –1 scale cannot be batched together).
  • Using different Material instances causes GameObjects not to batch together, even if they are essentially the same. The exception is shadow caster rendering.
  • GameObjects with lightmaps
     have additional renderer parameters: lightmap index and offset/scale into the lightmap. Generally, dynamic lightmapped GameObjects should point to exactly the same lightmap location to be batched.
  • Multi-pass Shaders break batching.
    • Almost all Unity Shaders support several Lights in forward rendering
      , effectively doing additional passes for them. The draw calls for “additional per-pixel lights” are not batched.
    • The Legacy Deferred (light pre-pass) rendering path
       has dynamic batching disabled, because it has to draw GameObjects twice.

Dynamic batching works by transforming all GameObject vertices into world space on the CPU, so it is only an advantage if that work is smaller than doing a draw call. The resource requirements of a draw call depends on many factors, primarily the graphics API used. For example, on consoles or modern APIs like Apple Metal, the draw call overhead is generally much lower, and often dynamic batching cannot be an advantage at all.

Unity會自動爲你完成動態批處理,只要你的東西符合它的要求,要求如下:

  • 對於頂點的要求只有兩個:第一單個mesh的頂點數必須小於300,第二:單個mesh全部頂點的屬性總個數必須小於900至於300,和180,那只是爲了解釋這兩個要求舉的例子!換一種說法就是,如果你的頂點輸入中有三個屬性,那麼就可以支持300個頂點,如果輸入中有5個屬性,那就只能支持180個頂點,因爲180*5=900。至於提到的頂點位置,法線切線UV等等,都只是舉個例子。
  • 如果物體做了鏡像,也就是縮放中出現了負值,則不會進行批處理。至於都爲正數的縮放,測試過程中同時觀察了SetPass call和Draw call,都可以正常合併。如果使用較老的版本,建議自行測試。
  • 使用了光照貼圖的對象,光照貼圖的相關參數必須一致。
  • 多通道(Multi-pass)的材質,如果兩個對象的渲染使用的通道(pass)不一致,也會中斷批處理。

動態批處理會在每幀都會在CPU中將模型的頂點轉換到世界座標系,這部分的消耗也要權衡。

對於在運行時能夠生成動態模型的組件(Particle Systems,Line Renderers,Trail Renderers),動態批處理的方式相對於Mesh有所不同。

  • For each compatible renderer type, Unity builds all batchable content into 1 large Vertex Buffer.
  • The renderer sets up the Material state for the batch.
  • Unity binds the Vertex Buffer to the Graphics Device.
  • For each Renderer in the batch, Unity updates the offset into the Vertex Buffer, and then submits a new draw call.
  • 對於每個兼容的渲染器類型,Unity將所有可批處理的內容構建到一個大的頂點緩存中。
  • Renderer爲批處理設置材質狀態。
  • Unity將頂點緩存綁定到圖形設備(顯卡?)。
  • 對於批處理中的每個Renderer,Unity會將偏移更新到頂點緩存中,然後提交一個新的繪製調用。

在計算渲染成本時,渲染組件最慢的部分是材質狀態的設置。相比之下,將不同偏移量的draw call提交到共享頂點緩存會非常快。這種方法更像是Unity在使用靜態批處理時如何提交draw call。

 

還有幾點需要注意的:

  • 能夠批處理的包括:Mesh Renderers,Trail Renderers,Line Renderers,Particle Systems,Sprite Renderers。其他的如Skinned Meshes,Cloth等渲染組件並能批處理。
  • 半透明物體在渲染時,使用的是從遠到近的順序,所以批處理對它的支持不如Opaque。
  • 手動合併(通過建模軟件,或者Mesh.CombineMeshes方法)臨近的物體,有時候也是不錯的優化方式。

 

 

關於這部分內容的官方文檔鏈接:https://docs.unity3d.com/Manual/DrawCallBatching.html

 

 

 

 

 

 

 

 

 

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