Unity的绘制调用批处理(Draw call batching)

概述

要在屏幕上绘制一个物体,引擎必须发送一个绘制命令(Draw Call)给图形的API,比如OpenGL或者Direct3D的接口。由于每次Draw Call,CPU要准备大量的数据,往往会引起较大的CPU的性能消耗。因此我们需要想办法减少Draw Call的数量。

一般情况下,Unity有两种比较方便使用的减少Draw Call的方法:动态批处理和静态批处理。
动态批处理:适用于面数比较小的物体,Unity会动态的将这些小物体的网格合并在一起,然后一次性绘制
静态批处理:将静态的物体的网格合并成一个大网格,然后一次性绘制

相比手动的去合并Mesh,Unity的合批有一个最大的优点就是仍然可以进行单个对象的剔除,当然也有缺点,比如静态批处理会增加内存的消耗并且需要额外的存储,动态批处理由于需要动态的合并Mesh,需要额外的CPU消耗。

我们可以通过Unity的设置开关批处理
在这里插入图片描述

批处理的材质设置(一些注意点)

只有用相同材质球的物体才能够被合批处理,所以我们想要更好的批处理效果,就需要尽可能的让不同的物体使用相同的材质球。

如果我们的材质球之间的区别仅仅是需要使用不同的贴图,那么我们就可以将这些贴图合并到一张大贴图中来实现使用同一个材质球的需求。我们一般用的UI图集就是这么处理的,所以我们的UI有那么多的绘制对象却只需要较少Draw Call。

如果我们需要在代码里面修改材质,要注意不能用Renderer.material接口,这个会导致复制一份材质实例出来,要用Renderer.sharedMaterial,这样材质球才是被共用的。

阴影的绘制会比较特殊,即使材质不一样,也基本上能合批,只要在阴影的Pass中用到的材质中的参数值是一样的,比如物体用了不同的贴图,但由于阴影Pass中不需要使用贴图,所以它们的阴影还是可以合批的
(我觉得Unity的文档有点坑,讲的一点都不清楚,很可能写文档的人自己都没搞明白,阴影的合批依然的分静态合批和动态合批来讨论,这样子说个大概有啥用)

未设置静态合批时,会尝试阴影动态合批,但仍然有诸如顶点数量的限制,如下:
静态合批
在这里插入图片描述
将这些物体全部设置为静态后,有部分使用了不同ShadowPass的物体依然无法合批
在这里插入图片描述

动态合批(Dynamic batching)

Unity可以对满足条件的相同材质的移动物体进行动态的合批,并且这个合批只需要开启即可,不需要我们做额外的工作。
当然,看起来这么好的事情肯定会有很多限制

动态合批的限制

顶点数量限制

由于要动态的将物体进行合并为一个Mesh,所以合并的物体的顶点数量肯定不能很多,否则合并的性能消耗太大,反而得不偿失
顶点数量不超过300
顶点包含的属性数量不超过900
举个例子:
如果我们的Shader使用到了顶点位置、法线和一套UV,那这里会有三份属性,900/3 = 300,因此顶点数量不能超过300
如果我们的Shader使用到了顶点位置,法线,UV0,UV1,切线,总共五份属性,900/5 = 180,因此顶点数量不能超过180

Scale不允许有负值

其他条件满足后,Scale只要是正值,是多少都可以合批,但只要有任意的负值,这个物体就不再能被合并
所以为了动态合批,我们应该尽量避免设置负的scale
Position和Rotation不会有影响
我们可以简单做个测试

  1. 创建4个Cube
    在这里插入图片描述
  2. 对他们分别做下调整
    左侧两个Y轴是-1
    右侧两个scale都是正值,只是x轴和y轴进行放大
    在这里插入图片描述
    我们来看合批的结果
    我们看到虽然两个scale负值都是-1,但两个都无法合批
    而另外两个虽然scale值不同,但是都是正值,所以也可以合批
    在这里插入图片描述

必须是同一个材质实例

如果是不同的材质实例,即使他们所有的参数都一模一样,也会导致无法动态合批,所以在修改材质时不能用Renderer.material,而是要用Renderer.sharedMaterial
但阴影的合批会比较特殊,主要看用到的参数是否一致

Unity 渲染过程中,Draw Call 是指 CPU 向 GPU 发送渲染指令的次数。每次 Draw Call 都伴随着数据传输和状态切换,过多的 Draw Call 会显著增加 CPU 的负担,从而影响游戏的整体性能[^3]。以下是一些降低 Draw Call 的方法: ### 材质与网格优化 - **并材质**:尽量使用相同的材质进行渲染,避免频繁切换材质。Unity 提供了自动批处理(Static Batching)和动态批处理(Dynamic Batching)功能,可以将多个物体并为一个 Draw Call 进行渲染[^1]。 - **并网格**:对于静态对象,可以手动或通过脚本将多个网格并为一个大网格,以减少 Draw Call 次数[^1]。 ### 批处理策略 - **静态批处理**:适用于不会移动的对象,例如建筑物、地形等。Unity 在构建时会将这些对象的网格并,从而减少运行时的 Draw Call 数量[^2]。 - **动态批处理**:适用于小型且不断变化的对象,例如粒子效果、小道具等。Unity 在运行时会自动将它们并渲染,但有一定的性能开销和限制。 ### GPU 实例化(GPU Instancing) - 对于大量相同材质和网格的对象(如树木、士兵等),启用 GPU Instancing 可以将它们一次性提交给 GPU 进行渲染,大幅减少 Draw Call 次数。 ### UGUI 优化 - **减少 Mask 使用**:Mask 组件会导致额外的 Draw Call 和 Overdraw。可以通过 Custom Image 直接裁剪 UV 值来替代 Mask,从而减少不必要的渲染调用[^4]。 - **优化 UI 层级结构**:理组织 Canvas 和子元素层级,避免嵌套过深导致频繁重建网格和排序操作[^4]。 ### 着色器优化 - **简化着色器复杂度**:过于复杂的着色器不仅会影响 GPU 性能,还可能导致批处理失败。建议使用轻量级着色器,尤其是针对低端设备。 - **共享材质实例**:确保多个对象尽可能共享相同的材质实例,而不是创建多个独立材质,这样有助于提高批处理效率。 ### 动态加载与卸载 - **LOD(Level of Detail)技术**:根据摄像机距离动态切换不同精度的模型,减少远距离物体的绘制开销。 - **资源动态加载/卸载**:及时释放不再使用的资源,避免冗余数据驻留在内存中,从而减少不必要的渲染请求。 ### 示例代码:并网格 ```csharp using UnityEngine; public class MeshCombiner : MonoBehaviour { public GameObject[] objectsToCombine; public bool combineStatic = true; void Start() { CombineMeshes(); } void CombineMeshes() { MeshFilter[] meshFilters = new MeshFilter[objectsToCombine.Length]; for (int i = 0; i < objectsToCombine.Length; i++) { meshFilters[i] = objectsToCombine[i].GetComponent<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); } Mesh combinedMesh = new Mesh(); combinedMesh.CombineMeshes(combineInstances, true, false); MeshFilter combinedMeshFilter = gameObject.AddComponent<MeshFilter>(); combinedMeshFilter.mesh = combinedMesh; MeshRenderer combinedRenderer = gameObject.AddComponent<MeshRenderer>(); combinedRenderer.material = new Material(Shader.Find("Standard")); } } ``` 通过上述方法,开发者可以在不同场景下选择适的优化策略,从而有效降低 Draw Call,提升 Unity 游戏的整体性能表现。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值