最近在項目開發過程當中,無心發現遊戲場景的繪製佔用了大量的Batches,幾乎一個模型顯示就佔用了一個Batch,而Saved by batching數量幾乎爲0,即沒有任何合批渲染優化。這顯然跟預期相去甚遠,由於雖然場景裏有多達上百個模型須要繪製,但大部分都是如出一轍的卡牌模型,引用相同的材質球,按理絕大部分都是能夠被Unity自動dynamic batching,進行合併批處理的。哪究竟是哪裏出了問題?c#
因而翻看Unity Manual,檢查Dynamic Batching的規則,能夠簡單歸納爲如下幾條:app
通常狀況下,Unity僅支持對Meshes小於900頂點的物體進行Dynamic Batching,若是Shader裏使用了頂點位置,法線,UV值,則僅支持300頂點如下的物體;若是使用頂點位置,法線,UV0,UV1和Tangent向量,則僅支持180頂點如下的物體。優化
若是兩個物體的scale恰好是呈鏡像的,如scale分別爲(-1,-1,-1)或(1,1,1),他們不會被Dynamic Batching。this
引用材質實例不一樣的物體不會被Dynamic Batching,即便兩個物體的材質本質上沒有任何不一樣。這句話的理解有點繞,簡單舉例就是說,相同的材質實例化了兩份,分別被A和B引用了,那麼A和B是不會被Dynamic Batching的,由於他們引用的是兩個不一樣的實例。3d
擁用lightmaps的物體將不會被Dynamic Batching,除非他們指向了lightmap的同一部分。code
擁有多Pass通道的Shader的物體不會被Dynamic batching。htm
簡單排查下,模型是達到Unity的1,2,4,5點要求的。因而開始懷疑材質實例引用不一樣致使了問題。blog
查看Inspector,能夠看到Mesh Renderer引用的Material確實就是所須要的材質球,但後面卻加了Instance字眼,單擊圖中紅框處,Project窗口也並無跳轉到材質球文件全部位置,說明這份material是在運行時被實例化的,不是Prafab預設時指向的材質球引用。由此咱們能夠推測,每份模型都各自實例化了一份本身的material實例,致使沒有被Dynamic Batching。知道了問題緣由,還要知道問題出在哪裏。檢查物體上綁定的腳本,查看與material相關的代碼,能夠看到有這麼兩處:接口
private Texture _mainPlaneTex; private Texture _flashPlaneTex; public MeshRenderer m_rendererPlane; private Material _cachedPlaneMaterial = null; private Material cachedPlaneMat { get { if (_cachedPlaneMaterial == null && m_rendererPlane != null) { _cachedPlaneMaterial = m_rendererPlane.material; } return _cachedPlaneMaterial; } }
private void SetGray(bool bGray) { if (bGray) { cachedPlaneMat.SetTexture("_MainTex", _flashPlaneTex); } else { cachedPlaneMat.SetTexture("_MainTex", _mainPlaneTex); } }
這個模型有模型變灰色的需求,實現方式是經過獲取該物體的material,再視狀況將MainTexture設置爲正常/變灰貼圖。出問題的地方十有八九就在這塊代碼。查看MeshRenderer的接口,原來Unity提供了兩個獲取Material的方法接口,分別是material及sharedMaterial。
搞懂這兩個接口的區別,弄清楚Unity在底層到底搞了什麼鬼,估計離真相就不遠了。遊戲
Returns the first instantiated Material assigned to the renderer.
Modifying material will change the material for this object only.
If the material is used by any other renderers, this will clone the shared material and start using it from now on.
Unity官方說當使用Renderer.material獲取Material引用時,會把Render裏Materials列表第一個預設的Material進行實例化,並將返回實例。這樣,當咱們對這個物體的Material進行修改時,只會修改實例,而不會修改到最本來的材質球,好比咱們將同個模型Prafab拖放N多個到場景裏,當咱們對其中一個模型A的材質球進行修改,好比替換貼圖,其餘模型是不會受到影響的,由於A修改的僅僅只是本身實例化出來的材質球。
The shared material of this object.
Modifying sharedMaterial will change the appearance of all objects using this material, and change material settings that are stored in the project too.
It is not recommended to modify materials returned by sharedMaterial. If you want to modify the material of a renderer use material instead.
而若是調用Renderer.sharedMaterial接口,Unity則不會畫蛇添足地幫咱們作實例化,再返回實例了,直接就是返回最本來的材質球。仍是上面那個例子,當咱們給A模型的材質球替換貼圖,會發現場景的全部模型都被替換了貼圖。
看完了兩個接口的差別,也終於明白了問題的根本緣由。爲了實現模型變灰的需求,代碼裏使用Renderer.material來獲取material實例,卻不知每一個物體都各自持有了一份不一樣的material實例,使得Unity沒辦法將這些如出一轍的物體進行合批渲染,當同屏顯示的物體數目多達上百,就意味着多出上百個沒必要要的Drawcall,多了沒必要要的損耗。
而這個問題也不能簡單地將Renderer.material改成調用Renderer.sharedMaterial就解決了,總不能把一個模型變灰,全部模型都要跟着變灰吧?正確的解決方法就是預設正常/灰色兩個材質球,根據不一樣的狀況替換材質。
最後貼下優化先後,場景Drawcall的對比:
優化前
優化後
能夠看到,在同屏模型較多的狀況下,Batch數由近100驟降到2,基本都被Unity動態合批處理了。