Fork me on GitHub

CocosCreator优化DrawCall漫谈

本文首发于我的个人Blog阿西BUG,欢迎大家批评指正

前言

在游戏开发中,DrawCall 作为一个非常重要的性能指标,直接影响游戏的整体性能表现。
无论是 Cocos Creator、Unity、Unreal 还是其他游戏引擎,只要说到游戏性能优化,DrawCall 都是绝对少不了的一项。
本文将会介绍什么是 DrawCall,为什么要减少 DrawCall 以及在 Cocos Creator 项目中如何减少 DrawCall 来提升游戏性能。

正文

什么是 DrawCall

DrawCall就是CPU调用图形库(比如DirectX或OpenGL)的图形绘制接口,来命令GPU进行渲染的操作。

DrawCall 是如何影响性能的呢?

先举个栗子:
http形式从服务器拉取1024个大小1kb的文件和单个大小为1M的文件,哪个耗时更短?
答:肯定是拉取单个1M的更快。
原因就是每个请求之前,http 都需要做许多的准备工作来保证文件能够正常传输。而正是这些额外工作,造成了很多时间和性能开销。

而每一次绘制CPU都要调用DrawCall,而在调动DrawCall前,CPU还要进行很多准备工作:检测渲染状态、提交渲染所需要的数据、提交渲染所需要的状态。
而GPU本身具有很强大的计算能力,可以很快就处理完渲染任务。一般来说,绘制 100 个三角形和绘制 1000 个三角形所消耗的时间没差多少。
但是,当 DrawCall 过多,CPU就会很多额外开销用于准备工作,CPU本身负载,而这时GPU可能闲置了。

也就是说,正是因为每次渲染前,CPU 都需要做一系列准备工作,而 CPU 的每一次内存显存读写、数据处理和渲染状态切换都会带来一定的性能和时间消耗,积少成多,而 GPU 大部分时间都在摸鱼。
所以才造成了我们认知中的,DrawCall 数量过多导致了卡顿

如何减少 DrawCall

如上所说,我们可以通过一次多给 CPU 分配点工作,让一次渲染的内容多一些,减少分配次数,来达到我们的目的。
话是这么说,可是我们应该怎么实际操作呢?往下看

针对图片资源

静态合图

就是讲碎图整合成一张大图,即我们常说的 打图集。
但是图集也不是随便打的,并不是一张大图容纳越多的碎图越好。这里面也是有一定的门道的。

尽量将处于同一界面(UI)下的相邻且渲染状态相同的碎图打包成图集

对于Creator来说,在游戏运行时引擎是按照节点层级顺序从上往下由浅到深进行渲染的,理论上 每渲染一张图像(文本最终也是图像)都需要一次 DrawCall。
渲染状态是指:纹理状态(预乘、循环模式和过滤模式)或 Material(材质)、Blend(混合模式)等等,所以使用自定义 Shader 也会打断合批

所以 相邻且渲染状态相同 是关键点。
tip: 不建议任何图片资源的尺寸超过2048*2048,否则可能会出现加载相关的问题。

下面介绍2种打图集的方式

自动图集资源(Auto Atlas)

利用 Cocos Creator 内置的自动图集资源来将碎图打包成图集。
在项目构建时,编辑器会将所有自动图集资源所在文件夹下的所有符合要求的图像分别根据配置打包成一个或多个图集。
自动图集资源使用起来很灵活,编辑器在打包图集时会自动递归子目录,若子目录下也有自动图集资源(即 .pac 文件)则会跳过该目录,所以我们可以对同一目录下的不同部分的碎图配置不同的参数。

关于自动图集的几点建议

  • 合理控制图集最大尺寸,避免单个图像加载时间过长。
  • 尺寸太大的图像没有必要打进图集(如背景图)。
  • 善用九宫格(Sliced)可以节省很多空间(这一点需要美术大佬配合)。
  • 间距保持默认的 2 并保持勾选扩边选项,避免图像裁剪错误和出现黑边的情况。
  • 勾选不包含未被引用资源选项,自动排除没有用到的图像以节省空间(该选项预览时无效)。
  • 开发时预览图集,根据结果进行调整,以达到最好的优化效果。
TexturePacker

使用 TexturePacker 打包图集时需要注意配置「形状填充(Shape Padding,对应 Auto Atlas 中的间距)」,避免某张图像出现相邻图像的像素的情况。

对比一下

Auto Atlas

  • Cocos Creator 内置,方便
  • 功能不多但是该有的都有
  • 项目构建时才生成图集,开发时任意修改无压力
  • 图集尺寸在生成时自适应,节省空间
  • 支持自动纹理压缩

TexturePacker

  • 第三方软件需自行安装,不够方便
  • 收费功能很多很专业但是基本用不着,免费功能也够用
  • 先生成图集再使用,更换图像又要重新生成图集
  • 尺寸固定需要自己设置
  • 不支持自动纹理压缩

动态合图

Creator 官方说明:

Cocos Creator 提供了在项目构建时的静态合图方法 —— 「自动合图」(Auto Atlas)。但是当项目日益壮大的时候贴图会变得非常多,很难将贴图打包到一张大贴图中,这时静态合图就比较难以满足降低 DrawCall 的需求。
所以 Cocos Creator 在 v2.0 中加入了 「动态合图」(Dynamic Atlas)的功能,它能在项目运行时动态的将贴图合并到一张大贴图中。当渲染一张贴图的时候,动态合图系统会自动检测这张贴图是否已经被合并到了图集(图片集合)中,如果没有,并且此贴图又符合动态合图的条件,就会将此贴图合并到图集中。
动态合图官方文档:
https://docs.cocos.com/creator/manual/zh/advanced-topics/dynamic-atlas.html

动态图集有2个限制:

  • 动态图集尺寸最大是 2048 * 2048
  • 碎图的尺寸默认不能超过 512,可通过 API 进行修改:cc.dynamicAtlasManager.maxFrameSize = 512

tips: 启用动态合图会增大内存消耗,不同平台占用内存不一致。小游戏和原生平台默认禁止动态合图。可以通过 api 自行开启:cc.macro.CLEANUP_IMAGE_CACHE = false; cc.dynamicAtlasManager.enabled = true;
还需要保证纹理的 Premulyiply Alpha(预乘)、Wrap Mode(循环模式) 和 Filter Mode(过滤模式) 等信息与动态图集一致才能够动态合批。

另外,静态图集也能参与动态合图,只要满足动态合图的要求即可。
tip1: 自动图集资源(Auto Atlas)需要在其属性检查器面板中开启 Texture 栏下的 Packable 选项,该选项默认是禁用的
tip2: 精灵(Sprite)也是需要开启 Packable 选项才能动态合图。该选项默认是开启的
tip3: 如果要使用了 shader ,那么需要禁用该精灵的 Packable 选项。

针对 Label

位图字体(BMFont)

在 Creator 中使用系统字体或 TTF 字体的 Label 会打断渲染合批,特别是 Label 和 Sprite 层叠交错的情况,每一个 Label 都会打断合批增加一个 DrawCall。

因此建议使用 BMFont 来代替 TTF 或系统字体,并且将 BMFont 与 UI 碎图打包到同一图集中(或「开启动态合图」),可以免除大部分文本导致的 DrawCall增加。

文本缓存模式(Cache Mode)

Creator 2.0.9 版本在 Label 组件上增加了 Cache Mode 选项,来解决系统字体和 TTF 字体带来的性能问题。
CacheMode有三种选项:

  1. NONE(默认)
    每一个 Label 都会生成为一张单独的位图,且不会参与动态合图,所以每一个 Label 都会打断渲染合批
  2. BITMAP
    开启 BITMAP 模式后,文本同样会生成为一张位图,但是只要符合动态合图要求就可以参与动态合图,和周围的精灵合并 DrawCall 。
    一定要注意 BITMAP 模式只适用于不频繁更改的文本,否则内存会爆炸。
  3. CHAR
    开启 CHAR 模式后,引擎会将该 Label 中出现的所有字符缓存到一张全局共享的位图中,相当于是生成了一个 BMFont 。
    适用于文本频繁更改的情况,对性能和内存最友好。
    tip: 该模式 只能用于字体样式和字号固定,并且不会频繁出现巨量未使用过的字符 的 Label。因为共享位图的最大尺寸为 2048*2048,占满了之后就没办法再渲染新的字符,需要切换场景才会清除共享位图。

    总结:对于大量频繁更改的文本,使用 CHAR 模式带来的性能提升是非常明显的。
    同时 CHAR 模式的局限也很明显,一般用于场景中出现大量数字文本,类似于经验值增加、血量减少之类的特效的情况

必经之路–调整UI层级顺序

原则:

  • 分离图像节点和文本节点
  • 文本使用 BMFont 或 Cache Mode 选项,尽量出现避免文本打断渲染合批的情况
  • FBI WARNING: 一个 Mask 组件及其控制的渲染节点,需要至少三次 Draw call。第一次开启模板测试并调用一次 Draw call,刷新模板缓冲。第二次绘制对需要通过模板测试的区域进行设置。第三次再进行实际的子节点内容绘制,绘制结束再关闭模板测试。因此 使用 Mask 组件就无法与其他相邻节点进行批次处理 ,但是 Mask 组件内部的连续节点在满足合并规则的情况下还是会进行合批。

总结

  • 改变渲染状态会打断渲染合批,例如改变纹理状态(预乘、循环模式和过滤模式)或改变 Material(材质)、Blend(混合模式)等等,所以使用自定义 Shader 也会打断合批。
  • 图集默认不参与动态合图,手动开启自动图集资源的 Packable 选项后如果最终图集符合动态合图要求也可以参与动态合图。
  • 纹理开启 Packable 选项参与动态合图后无法使用自定义 Shader,因为动态合图会修改原始贴图的 UV 坐标。
  • 使用 Cache Mode 的 BITMAP 模式需要注意内存情况,CHAR 模式需要注意文本内容不宜过多。
  • Cocos Creator 2.0.7 之前的版本中,改变节点的颜色或透明度、Sprite 组件使用九宫格(Sliced)都会打断渲染合批

以上内容整理自 CocosCreator 官方文档,点击这里查看

阿西BUG wechat
福利干货铺(YM-TALK)-- 互联网福利干货大集合
Enjoy it ? Donate for it ! 欣赏此文?求鼓励,求支持!
>