一:绘制原理

为了将物体绘制到屏幕上,引擎必须向图像API(例如OpenGL、Direct3D)发送一个DrawCall指令,每一次发送DrawCall指令的过程为一个渲染批次(Batch),而这个过程分为两大部分:设置渲染状态(setPass)和调用DrawCall(Batches),其中设置渲染状态属于比较重的分工,对于加载到游戏中的资源和对象等,CPU需要计算其顶点相关的矩阵,渲染所用的贴图,渲染所用到的材质和shader,渲染所用到的灯光等,如果每个物体的材质和贴图等都不一样,此时CPU的主要工作就是设置这些物体的渲染状态后调用DrawCall,从而造成CPU性能的开销,简单来说也就是CPU向GPU发送指令并由GPU进行绘制


二:什么是批处理

因为每一次发送DrawCall指令都会造成CPU的性能开销,而CPU的处理速度比GPU慢多了,所以可以将绘制的压力移交给GPU
Unity可以将一些物体进行合并,从而用一次DrawCall来渲染他们。这一操作,称为批处理,批处理能得到更好的渲染性能

例如渲染一千个三角形,如果把它们按一千个单独的网格进行渲染则需要请求1000次DrawCall,而如果直接渲染一个包含了一千个三角形的网格则只需要请求1次DrawCall,这样减轻了CPU的开销在性能上会有很大的提升


三:渲染统计窗口

Unity中的动态批处理和静态批处理_数据
——Batches:相当于DrawCall次数
——Saved by batching:通过批处理节省的DrawCall次数
——SetPass calls:设置渲染状态的次数


四:动态批处理

动态批处理默认是关闭的,需要手动开启:Project Setting—Player—勾上Dynamic Batching
Unity中的动态批处理和静态批处理_批处理_02

条件限制
——必须使用相同材质(相同材质的物体之间的差别只在于顶点数据不同,可以将顶点数据合并在一起,再一起发送给GPU)
——Mesh不能超过900个顶点(带uv的模型不能超过300个顶点)
——transform的scale属性不能有负值
——多个pass的shader会破坏批处理


五:静态批处理

静态批处理需要在Inspector面板上勾选上Static
Unity中的动态批处理和静态批处理_贴图_03

只要勾选上Static并拥有相同材质的物体即可在运行时进行静态批处理
但是静态批处理会为每一个物体创建一个合并后的Combined Mesh,需要额外的内存空间来存储合并后的Combined Mesh,所以使用时要慎用(例如一片浓密的草地,如果使用静态批处理则会造成严重的内存开销)


六:总结

——静态批处理和动态批处理都能降低DrawCall,最主要的前提是物体使用同一个材质
——静态批处理适用于场景中位置不会改变并且单对象不能太多的物体,像树林,草地这种用动态批处理更适合
——动态批处理限制较多,顶点数要求,材质要求等等。静态批处理限制较少,是用内存换性能的方法,具体情况具体分析


七:合并网格

静态批处理会增加额外的内存消耗去存储合并后的Mesh,但原先没有用的Mesh仍保留在内存中,所以可以通过代码动态合并网格后将原先的网格销毁或隐藏

using UnityEngine;

/// <summary>
/// 合并网格(挂载到要合并网格所有物体的父物体身上)
/// </summary>
public class CombineMesh : MonoBehaviour
{
    /// <summary>
    /// 合并网格
    /// </summary>
    public void Combine()
    {
        MeshFilter[] filter = GetComponentsInChildren<MeshFilter>();
        MeshRenderer[] renderer = GetComponentsInChildren<MeshRenderer>();
        Material[] mat = new Material[renderer.Length];
        CombineInstance[] combine = new CombineInstance[renderer.Length];

        for (int i = 0; i < renderer.Length; i++)
        {
            mat[i] = renderer[i].sharedMaterial;//获得共享材质

            combine[i].mesh = filter[i].sharedMesh;
            combine[i].transform = filter[i].transform.localToWorldMatrix;

            filter[i].gameObject.SetActive(false);
        }

        GameObject mesh = new GameObject("CombinedMesh");
        mesh.AddComponent<MeshFilter>().mesh.CombineMeshes(combine, false);
        mesh.AddComponent<MeshRenderer>().sharedMaterials = mat;
    }
}