[Unity优化]减少DrawCall:批处理

  • Post author:
  • Post category:其他


参考链接:http://game.ceeger.com/Script/Mesh/Mesh.CombineMeshes.html

一、批处理

1.批处理的目的就是为了减少DrawCall。DrawCall即CPU命令GPU去绘制。

2.如果我们需要渲染一千个三角形,那么把它们按一千个单独的网格进行渲染所花费的时间要远大于直接渲染一个包含了一千个三角形的网格。

3.要想使用批处理,需要物体有相同的材质。这是因为,对于使用同一个材质的物体,它们的不同仅仅在于顶点数据的差别,我们可以把这些顶点数据合并在一起,再一起发送给GPU,就可以完成一次批处理。

4.在unity中,有两种批处理:一是动态批处理,二是静态批处理。

对于动态批处理,unity会自动完成,不需要我们进行操作,而且物体是可以移动的,但是动态批处理有许多限制条件。

对于静态批处理,物体不可移动,但是限制条件很少。

二、动态批处理

原理:每一帧把可以进行批处理的模型网格进行合并,再把合并后模型数据传递给GPU,然后使用同一个材质对其渲染。

限制:

1.顶点属性要小于900。例如,如果shader中需要使用顶点位置、法线和纹理坐标这三个顶点属性,那么要想让模型能够被动态批处理,它的顶点数目不能超过300。因此,优化策略就是shader的优化,少使用顶点属性,或者模型顶点数要尽可能少。

2.多Pass的shader会中断批处理。

3.在unity5中,动态批处理对于模型缩放的限制已经不存在了。

4.使用光照纹理的物体需要小心处理。为了让这些物体可以被动态批处理,需要保证它们指向光照纹理中的同一位置。

三、静态批处理

原理:只在运行开始阶段,把需要进行静态批处理的模型合并到一个新的网格中,这意味着这些模型数据不可以在运行时刻被移动。

优点:因为它只需要进行一次合并操作,因此比动态批处理更加高效。

缺点:需要占用更多的内存来存储合并后的几何结构。

操作:将Inspector面板右上角的Static勾选(实际上只需要勾选Batching Static即可)

四、DrawCall

渲染统计窗口


Batches:相当于DrawCall

Saved by batching:通过批处理节省的DrawCall

SetPass calls:跟Shader中Pass的数量有关,Pass越多这个越大

Ps:当对场景中的物体进行修改时,有时需要点击一下Game视图,渲染统计窗口才会刷新。

影响DrawCall的因素:

1.Pass数量

新建一个场景,将天空盒改为纯色,将平行光的阴影类型改为无。此时Batches值为1,当禁用相机时,值为0。


新建一个cube,新建一个材质和三个shader,其中OnePass含一个Pass,TwoPass含两个,ThreePass含三个。将OnePass给材质,材质给cube。



然后依次将TwoPass和ThreePass给材质。



可以看出,

Pass越多DrawCall越大。

可以通过FrameDebugger来查看DrawCall,当然也可以用Profiler,但后者不能查看DrawCall的具体情况。


2.材质

a.在场景中放三个cube,cube材质为一个pass

结论:经过动态批处理后,相同材质的物体只占一个DC


b.将上面的cube改为Capsule

结论:对于顶点数太多的物体,无法进行动态批处理,无法减少DC


c.场景放两个不同材质的cube

结论:不同材质的物体,无法进行动态批处理,无法减少DC


d.针对b情况,勾选Static,然后点击运行(不运行就无法查看静态批处理结果)

结论:对于顶点属性太多的物体(>900),可以使用静态批处理来减少DC


合并之后的网格


e.那么对于d,再添加一个不同材质的Sphere,勾选Static

结论:不同材质的物体,无法通过静态批处理减少DC。但是静态批处理可以通过合并网格来提高性能。



五、合并网格

创建一个空物体,确保缩放为(1,1,1),在空物体下随便创建一些物体。Ps:要使合并后不变形,就要确保根物体的缩放比例为(1,1,1)


添加以下代码到空物体上。

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
public class CombineTest : MonoBehaviour {

	// Use this for initialization
	void Start () 
    {
        //获取材质
        MeshRenderer[] meshRenderers = GetComponentsInChildren<MeshRenderer>();
        Material[] materials = new Material[meshRenderers.Length];

        for (int i = 0; i < meshRenderers.Length; i++)
        {
            materials[i] = meshRenderers[i].sharedMaterial;
        }

        //获取mesh,使用CombineInstance类是因为CombineMeshes方法的需要
        MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
        CombineInstance[] combineInstances = new CombineInstance[meshFilters.Length];

        for (int i = 0; i < meshFilters.Length; i++)
        {
            combineInstances[i].mesh = meshFilters[i].sharedMesh;
            //模型空间坐标转化为世界坐标
            combineInstances[i].transform = meshFilters[i].transform.localToWorldMatrix;
            //隐藏子物体
            meshFilters[i].gameObject.SetActive(false);
        }

        //合并材质
        transform.GetComponent<MeshRenderer>().sharedMaterials = materials;
        //合并网格
        transform.GetComponent<MeshFilter>().mesh.CombineMeshes(combineInstances,false);
        transform.gameObject.SetActive(true);
	}
}


关于CombineMeshes:

void CombineMeshes(CombineInstance[] combine, bool mergeSubMeshes = true, bool useMatrices = true);

结合网格有利于性能最优化。如果mergeSubMeshes为true,所有的网格会被结合成一个单个子网格。否则每一个网格都将变成单个不同的子网格。

如果所有的网格共享同一种材质,设定它为真。

如果useMatrices为false,在CombineInstance结构中的变换矩阵将被忽略。



版权声明:本文为lyh916原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。