性能优化:在 Cocos Creator 如何通过改变渲染顺序减少 DrawCall
引言:
说到游戏性能优化,DrawCall 都是绝对绕不开的一项。「Cocos Star Writer」夏羊群尝试自定义节点提交渲染的顺序来优化 DrawCall,以下是思路与实操分享,使用引擎为 Cocos Creator 3.3.1,欢迎大家交流探讨!
前几天面试时被抓住不放,问了一系列关于 DrawCall 的问题。可惜我这两年一直在做 3D 游戏,对于 2D 方面 DrawCall 的关注几乎没有,只能说是回答得勉勉强强。所以回来后狠狠补习了一下 Cocos 的源码,并且萌生了绕开节点树的渲染提交顺序,通过自己指定每个节点的提交顺序来最大化合并 DrawCall 的效果。经过一下午的尝试,终于有所收获。
DC 合并的原理
上图是一个游戏中常用的列表,我们先来观察一下这张列表:
conten 是列表的父亲节点
green0、green1、green2 是三个列表的单元格
每个单元格由 green(绿色图片),yellow(黄色图片),label0(一个 bmf 文本构成)
绿色图片和黄色图片已经被打入了同一个贴图集内
那么如果根据 Cocos 传统的节点数的深度访问遍历来提交渲染批次,提交顺序就会是:
Canvas->Camera->content->green0->yellow0->label0->green1->yellow1->label1->green2->yellow2->label2
根据 DC 和批的原理,如果渲染提交的时候,多个连续提交的节点使用了相同的图集,那么这多个节点的 DC 将会合并。所以如下图,会有6个 DC。本来 green0、yellow0 可以合并,然后 label0 打断了这个合并,接着 green1、yellow1 可以合并,label1 又打断了这个合并。也就是说每个单元格会占据2个 DC。如果单元格有 N 个的话,那么这个列表就会有 2N 个 DC。
思路上的改进
此时,已经有聪明的小伙伴想到,如果我提交渲染的顺序是:
green0->green1->green2->yellow0->yellow1->yellow2(到这个时候用了一次 DC)
->label0->label1->label2(此时使用了2个 DC)
那岂不是美滋滋,升职加薪走上人生巅峰也不是梦想。
那我们就需要一个功能,可以打破渲染提交的顺序。同时,如果我不用这个功能,就仍按照传统的节点数去提交。最好的情况是,大部分节点都按照原先的方式去提交批次;而我可以指定某一个节点,让这个节点和它的子节点按照我指定的顺序来渲染。
如上图的一个节点树,其中 ABCDEFG 都是节点团。在渲染的时候,走的顺序还是 A-B->D->C->E->F->G。但是 D、G 这2个节点团渲染的时候,是走自己指定的顺序渲染。如此一来,渲染逻辑上 D 和 G 之间是相互独立的,不会发生错乱,但是 G 节点团的渲染永远是后于 D 节点团的。
经过论坛一位同学的提醒,这里我们还需要考虑透明度的叠加问题。
还是以上图为例,传统的节点树上:
D 看起来的透明度 = A 透明度 x B 透明度 x D 透明度
但是在打乱提交顺序之后,如果还按照上述来计算透明度,代码消耗会增大很多,所以我修改成了:
任意子节点的透明度 = A 节点透明度 x 任意子节点自身透明度
用法上的说明
我先选中 content 节点。
为了满足功能,我修改了 UITransform.ts 的源代码,为它添加了2个属性:
SpanEffected 被勾选上之后表示这个节点和它的所有子节点的已经成为了一个节点团。在这个节点团的内部,所有的渲染提交顺序由用户来自己指定了。
RenderIndex 则是渲染的优先级。在 content 这个节点团下,渲染优先级越小,则越先提交渲染。
接着我们将 content 节点团下的渲染提交优先级设置成如下图所示:
此时我们提交的渲染顺序变成了:
content->green0->green1->green2->yellow0->yellow1->yellow2->label0->label1->label2
那么理论上就只有2个 DC 了。我们用spectorJs来验证一下。
可以看到,确实如此,只有2个 DC。到此,大功告成。
代码修改
UITansform.ts 文件修改如下:
batcher-2d.ts 代码修改行数在610行-658行。我把修改后的源码打包了(已重新加入了计算透明度部分的代码),点击文末【阅读原文】至论坛讨论帖下载查看,开发者可直接替换。如有疑问或其他想法,欢迎在帖子里留言。
后记
在这里笔者只是提供了一个改变渲染顺序的思路,代码写的并不规范。但是我认为这个思路是不错的。另外,这里没有做大规模的测试,无法预估这个修改会不会引发未知的错误。如果要是用到实际中的项目,一定要多测试哦。项目崩了,老板跑路,工作丢了,笔者一概不负责。