前言
在游戏开发中,Draw call 作为一个非常重要的性能指标,直接影响游戏的整体性能表现。Draw call 就是 CPU 调用图形 API,比如 OpenGL,命令 GPU 进行图形绘制。一次 Draw call 就代表一次图形绘制命令,由于 Draw call 带来的 CPU 及 GPU 的渲染状态切换消耗,往往需要通过批次合并来降低 Draw call 的调用次数。批次合并的本质就是在一帧的渲染过程中,保证连续节点的渲染状态一致,将尽可能多的节点数据合并一次性提交,从而减少绘图指令的调用次数,降低图形 API 调用带来的性能消耗,同时也可以避免 GPU 进行频繁的渲染状态切换。渲染状态就包括:纹理状态,Blend 模式,Stencil 状态,Depth Test 状态等等
为了保证节点使用的纹理图片资源一致,引擎提供两种合图方式:静态合图和动态合图。
静态合图
静态合图即为编辑器提供的自动图集功能,以及其他第三方图集打包工具如:TexturePacker 等。在资源层面进行散图合并,保证UI节点使用的都是同一张贴图,因为同一张图集的纹理状态都是一致的,所以能够达到渲染批次合并对纹理状态的要求。
对于 Label 组件,为了保证所有的 Label 节点使用相同的纹理,通常会使用 BMFont 将要使用的 UI 文字提前进行打包,并使用引擎的自动图集与散图一起合并进一张大的纹理,即可与其他相邻的 Sprite 节点进行批次合并。自动图集的创建与设置可以参考 自动图集资源,然后新建一个自动图集资源配置,把所有你希望进行合图的 UI 图片,BMFont 和艺术数字都拖到自动图集资源所在的目录即可。BMFont 的文本制作可以通过 BMFont 字体制作工具,将常用的美术字或者文本制作生成一张字体图片及其字体映射文件。然后直接拖入编辑器中即可使用。
动态合图
静态合图的局限性还常常体现在动态文本的渲染过程,如 Label 组件在使用系统文本时,文本贴图是依据文本内容通过 Canvas 绘制动态生成,不能提前进行图集打包。所以,除了静态合图,引擎也提供了动态合图的功能。
在运行时,引擎通过将散图添加到动态图集中,来保证节点使用的纹理一致。由于动态图集使用的是默认纹理状态,所以只有当散图的纹理状态与动态图集的状态一致,才可参与到引擎的动态合图中。
Label 组件目前提供三种 Cache Mode:NONE、BITMAP、CHAR。
NONE 模式即 Label 的整个文本内容会进行一次绘制,并进行提交,但是并不参与动态合图。
BITMAP 模式即 Label 的整个文本内容会进行一次绘制,并加入到动态图集中,以便进行批次合并。
CHAR 模式即 Label 会将文本内容进行拆分,然后对单个字符进行绘制,并将字符缓存到一张单独的字符图集中。下次遇到相同字符不再重新绘制。
目前引擎的动态图集主要有两种:
一种是为散图及使用 BITMAP 模式的文本提供的动态图集,最大数量为 5 张,尺寸为 2048 * 2048。
另外一种是为使用 CHAR 模式的文本提供的字符图集,单个场景只有 1 张,尺寸为 2048 * 2048
这两种动态图集在切换场景时会进行清理释放。由于动态图集空间有限,因此需要最佳化的利用:
对于一些 不常变化的静态文本,例如 UI 界面的标题,属性栏的固定文本,如果使用系统文本,可以设置为 BITMAP 模式,缓存到动态图集中,这样连续的 UI 节点即可进行动态合批。由于图片一般会打包为静态图集,而为了最大限度的把界面中的 Label 进行合批,可以将界面中的这些静态文本节点层级放在最上层,并保证这些 Label 节点连续即可避免 Label 节点打断批次,同时合并连续的 Label 节点降低 Draw call。
对于一些 频繁变化的文本,例如游戏中常用的倒计时,如果使用 BITMAP 模式,会导致大量的数值文本占用动态图集空间,但是其使用的字符数量有限,只有数字 0 - 9 这 10 个字符。为了避免频繁绘制,可设置为 CHAR 模式,进行字符缓存,将单个字符文本添加到字符图集中。这样缓存一次之后,后续所有的数字组合都可以从已缓存的字符中获取,提高性能。如果连续的 Label 节点使用的都是 CHAR 模式,因为它们使用的是同一张字符图集,所以也可以保证这些节点能够进行批次合并。
注意事项
1、由于 Creator 的自动图集功能是在项目导出的时候进行的,所以应该在发布后的项目中进行合批测试。
2、在导入 BMFont 的资源的时候,需要把 .fnt 和相应的 png 图片放在同一个目录下面。
3、LabelAtlas 底层渲染采用的跟 BMFont 一样的机制,所以也可以和 BMFont 及其它 UI 元素一起合图来实现批次渲染。
4、微信小游戏平台由于 Image 的内存占用原因,默认禁用了动态图集功能,如果对内存占用要求不高的游戏,可以自行通过 cc.dynamicAtlasManager.enabled = true 打开该功能,并且设置 cc.macro.CLEANUP_IMAGE_CACHE = false 禁止清理 Image 缓存。具体可参考启用、禁用动态合图。
5、默认 Spine 的合批是关闭的,需要勾选enableBatch选项开启,Spine 必须是同个 Spine 资源创建的对象,且每个 Spine 只有一种混合模式、一张贴图,才能进行批次合并,Dragonbones 同理。
6、单次 Draw call 的 Buffer 数据有限,当数据超过 Buffer 长度限制时,会重新申请新的 Buffer,不同的 Buffer 也会是不同的批次。