最近在项目开发过程当中,无心发现游戏场景的绘制占用了大量的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动态合批处理了。