Unity 中的 DrawCall 与优化核心
✨ 核心概念速览
概念
描述
目的
DrawCall
CPU 准备数据并命令 GPU 渲染的操作14
通知 GPU 绘制物体
批处理
Unity 将多个物体合并后用一次 DrawCall 渲染1
减少 DrawCall 数量
图集
将多个小纹理合并到一个大纹理中38
减少材质切换,促进批处理
动态批处理
对共用相同材质的动态物体自动批处理1
降低动态物体 DrawCall
静态批处理
对标记为 Static 的静止物体进行批处理1
大幅减少静态物体 DrawCall
🔍 什么是 DrawCall 及其增加原因
在 Unity 中,DrawCall 是 CPU 准备好渲染数据(如顶点、纹理、着色器等)后,通知 GPU 开始渲染的命令14。每次 DrawCall 之前,CPU 都需要进行大量准备工作,例如设置渲染状态、准备数据等,这个过程本身就有开销14。
DrawCall 会增加的主要原因包括:
材质切换:每次更改材质通常都会产生一个 DrawCall2。
纹理不同:使用不同纹理的物体一般需要不同的材质,从而增加 DrawCall7。
着色器差异:使用不同着色器的物体无法批量处理。
渲染状态改变:例如深度测试、混合模式等的变化也可能中断批处理。
CPU 需要花费时间处理这些命令并将其提交给 GPU。如果 DrawCall 数量过多,CPU 就可能忙于提交这些命令,导致CPU 过载,进而引发游戏卡顿14。
🤔 为什么要打图集
图集(Texture Atlas) 是将多个小纹理或精灵(Sprite)合并到一张大纹理中的技术38。
打图集的主要目的是:
减少 DrawCall:这是最直接的目的。多个精灵共享同一张大纹理和材质,渲染它们可以减少 DrawCall38。
提升批处理效率:Unity 的批处理系统能更高效地处理共享相同材质的对象。使用图集让多个对象共享同一材质,极大提升了批处理效率3。
减少纹理交换:GPU 频繁加载和卸载不同纹理会产生开销。图集减少了纹理切换,有助于保持较高帧率3。
优化内存与包体:将多张小图(尤其非2的幂次方尺寸)合并为一张2的幂次方大图,便于GPU处理,有时还能减小包体体积8。
📉 打图集如何降低 DrawCall
打图集通过以下方式降低 DrawCall:
材质统一:原本每个不同的纹理都可能需要一个独立的材质。打图集后,多个精灵共享同一张大纹理,从而共享同一个材质。
促进批处理:Unity 的批处理系统会自动尝试将使用相同材质的物体合并渲染1。当多个 UI 元素或精灵使用同一图集(即相同材质)时,它们就更容易被批量处理,从而将原本需要的多次 DrawCall 合并为一次或少数几次3。
这就好比搬运货物:打图集相当于把多个小件货物打包进一个大箱子(大纹理),一次性搬运(一次 DrawCall),而不是来回多次搬运每个小件(多次 DrawCall)。
🧩 图片叠加与 DrawCall 的关系
图片(UI 元素或精灵)叠加时是否增加 DrawCall,不取决于叠加本身,而取决于它们的材质和图层关系。
不同图集/材质叠加:如果叠加的图片来自不同的图集(意味着材质不同),或者纹理、材质、着色器不同,通常会增加 DrawCall。UGUI 根据层级顺序和材质等因素决定合批。当不同材质的元素重叠相交时,容易中断合批,导致 DrawCall 增加59。
同一图集叠加:如果叠加的图片都来自同一图集(即共享同一材质),并且满足合批条件(如层级相邻且无其他中断合批的因素),那么它们通常不会被额外的 DrawCall。UGUI 的合批算法会尝试将这些相同材质的元素合并在一个 DrawCall 中5。
⚠️ 多个按钮叠加的影响
多个按钮(通常是 Image 组件)叠在一起时,是否增加 DrawCall,同样遵循上述原则:
如果这些按钮使用同一图集的精灵,且布局合理(例如在 Hierarchy 中顺序相邻),它们通常可以合批,不会单纯因为叠加而增加 DrawCall。
如果按钮使用了不同图集的精灵,或者叠加方式破坏了合批顺序(例如来自不同图集的元素穿插重叠),就很可能增加 DrawCall9。
按钮上的 TextMeshPro 或 Legacy Text:文本往往是“破坏分子”。即使按钮图像来自同一图集,如果按钮上的文本使用了不同字体纹理(本质上是另一种材质),就极易中断批处理,增加 DrawCall。
📝 文本增加 DrawCall 的原因
文本(包括 Legacy Text 和 TextMeshPro)增加 DrawCall 的主要原因:
独特的材质与纹理:每个字体文件都有对应的纹理图集,用于存储字符。文本渲染时使用自身的材质和字体纹理,这与普通的 Image 组件完全不同。当文本与其他UI元素(如图片)混合时,材质的差异会中断批处理。
频繁更新与动态生成:尤其是动态变化的文本(如得分、血量),其网格可能需要频繁更新,这会阻碍批处理。
轮廓、阴影等效果:这些效果通常通过额外通道或特殊着色器实现,进一步增加了材质的复杂性,使得文本更难与其他元素合批。
💡 优化 DrawCall 的实用建议
充分利用图集:
规划 UI 时,将频繁同时出现的元素放在同一图集3。
使用 Unity 的 Sprite Atlas 进行打包和管理3。
注意 UI 层级与排序:
在 Hierarchy 中,合理安排 UI 元素的顺序,让使用相同材质的元素尽量相邻,有助于合批5。
尽量避免不同图集的元素深度交错重叠。
谨慎使用 Mask 组件:Mask 和 RectMask2D 会中断合批并产生额外 DrawCall,谨慎使用9。考虑使用 RectMask2D 替代 Mask,因为前者对滚动列表内的内容合批更友好。
减少不必要的 Raycast Target:为不需要接收射线检测的 Text 或 Image 取消勾选 Raycast Target,能轻微减少 CPU 开销9。
静态批处理与动态批处理:
对于场景中静止不动的物体,尽量标记为 Static,利用静态批处理1。
了解动态批处理的限制(如顶点数限制),但不要过分依赖,其优化效果有时不明显1。
文本优化:
尽量减少使用过多不同字体的文本。
对于不常更新且内容固定的文本,可以考虑将其转换为图片并入图集(但会失去灵活性)。