【实用系列】2.x 循环列表+分层渲染实现。解决多个item drawcall高的问题

前言:

之前写了一篇优化背包的分层渲染,是通过把一个item拆分出来,让item所有节点同一层级,然后通过改zIndex让节点聚在同一层级,达到分层的效果。

但要处理的手尾太多了,特别涉及父节点变换后,子节点也要跟着变换,自己写太麻烦了。

所以这次直接进行引擎修改,把原本的深度渲染,改为层级渲染。

自从用了层级渲染,打开几个界面,dc基本都在100以下。

使用方法:

1:项目初始化时调用CCCExtend.init().通过这个脚本的初始化,进行引擎渲染流的扩展

2:背包等一些滑动面板的content或需要进行分层渲染的节点,挂上LevelRender组件,挂上组件后,该节点下的所有子节点将进行层级渲染

这样,就能愉快的使用层级渲染了,不单只滑动面板,像主界面有很多规律性很强的按钮,如 背包,坐骑,排行榜这类并排,并且都是一个icon和一个label的拼成的(一般这类按钮都没法合批,除非把字体和icon打图集和一些骚操作)。给这些按钮一个父节点,并且挂上LevelRender组件,这样,这些并排按钮就会合批渲染。

像背包滑动面版这种,无论一个item还是多个item,dc都是一个item的dc数量!

原理:

先看引擎render-flow.js一段代码

// 这是孩子节点进入渲染流的部分
_proto._children = function (node) {
    let cullingMask = _cullingMask;
    let batcher = _batcher;

    let parentOpacity = batcher.parentOpacity;
    let opacity = (batcher.parentOpacity *= (node._opacity / 255));

    let worldTransformFlag = batcher.worldMatDirty ? WORLD_TRANSFORM : 0;
    let worldOpacityFlag = batcher.parentOpacityDirty ? OPACITY_COLOR : 0;
    let worldDirtyFlag = worldTransformFlag | worldOpacityFlag; 

    let children = node._children;
    for (let i = 0, l = children.length; i < l; i++) { // 遍历孩子数组
        let c = children[i];

        c._renderFlag |= worldDirtyFlag; //在节点树中如果有进行过 平移、旋转、缩放、透明度 更改,则该孩子需要重新进行渲染
        if (!c._activeInHierarchy || c._opacity === 0) continue;

        _cullingMask = c._cullingMask = c.groupIndex === 0 ? cullingMask : 1 << c.groupIndex;

        let colorVal = c._color._val;
        c._color._fastSetA(c._opacity * opacity);
        flows[c._renderFlag]._func(c); // 孩子进入渲染流
        c._color._val = colorVal;
    }

    batcher.parentOpacity = parentOpacity;

    this._next._func(node); //进入下一个流程
};

上段代码需要关注的地方是,遍历孩子节点数组,如果孩子节点可见,则孩子节点进入渲染流,孩子进入渲染流后,又会继续遍历孩子数组。。。一直递归到没有孩子为止,当孩子的渲染流完成了,之前的父节点才进行下一个流程。现在需要把这个流程改变,碰到孩子节点时,先不进入孩子渲染流,而是把孩子分层存进一个队列中。

举个例子:现在有一百个item,item是由一个背景和一个Label组成,之前引擎的代码是,进入背景的渲染流时,到达孩子流程时,会遍历孩子数组,发现有一个Label,就会进入Label的渲染流程,等Label的流程结束并且没有孩子时,背景才进入下一个流程。现在稍微改一下,遇到孩子节点时,先不进入孩子的流程,而是进入一个队列renderQueue,这个队列把节点分层,如:[[背景1, 背景2…(共100个背景)], [Label1, Label2…(共100个Label)]],这样就把节点分层了。然后通过以下步骤

1.renderQueue出队,获得第一个数组 [背景1, 背景2…(共100个背景)] ,然后逐个出队,调用flows[c._renderFlag]._func©;进入渲染流,这样就会先将100个背景进入渲染流.

2.renderQueue出队,获得第二个数组 [Label1, Label2…(共100个Label)],然后逐个出队,调用flows[c._renderFlag]._func©;这样就会将100个Label进入渲染流.

3.循环往复。直到renderQueue长度为0.

这样,就完成了层级渲染的部分改造,还有一些改造,如父节点变换后,孩子节点也要跟着变换那些就不说了,感兴趣的可以自行看代码,或者与我交流。遇到问题欢迎指正与提出。

项目中深度渲染和层级渲染搭配使用,能让项目dc大量下降!

注意:

  1. label要合批,要符合Label的合批要求,如使用Bitmap或Char模式,或者Bmfont之类的,具体自行了解~

  2. 引擎渲染流要求是v2.x

  3. 多个LevelRender,只会最外层的生效

  4. 使用LevelRender组件的子节点不能有Mask组件

  5. demo示例用上循环列表+分帧加载。假设滑动面板显示5x5=25个item,循环列表则创建6x5=30个item,无论一千个还是一万个item,内存中只占30个item的大小。结合层级渲染,能让多item滑动面板性能巨幅提升

最后附运行截图:


图中7个dc,左下角1个,空scroll2个,scrollItem 2个,角色按钮那些2个。

demo.zip (206.3 KB)

附:最近也搞了3.x版本的,有需求可以看看~ :laughing:

25赞

已经收藏~ 值得借鉴~

顶顶顶,mark~

:+1:赞赞赞

mark mark mark

mark~~~

大佬 列表中的item里有个mask是不能用的吗 应该排行榜中有玩家的圆形头像 用mask做遮罩的

可以让美术出圆形的,假设头像是远端服务器矩形的,但一定要显示圆形,可以考虑通过shader去处理,论坛上有shader处理头像的,而且相同的shader和材质,是符合合批条件的,mask就不行

嗯嗯 如果用你写的这个组件,只是mask之间不能合批吧,不同item之前的其他组件例如sprite还是可以合批的吧?

item节点结构在同一层,只要符合合批条件的都能合批。用mask是直接报错的,具体原因还没探究,等空了再处理这个问题 :rofl:

666,mark,学习下

测试发现在浏览器上是可以的,但是上了微信小游戏测试进度条会渲染不出来,同时按钮点击缩放的时候图片会消失,缩放结束才会变回来。

引擎版本2.4.11

请问你这进度条上是用了mask组件吗,层级渲染组件子节点里目前不能有mask组件。

没有mask组件,昨晚又看了下,表现为打开界面显示正常,滑动后或者修改透明度后就不显示了,怀疑跟_opacity和_worldTransform重写有关系,浏览器是没问题的,微信小游戏不行

好的,微信小游戏用不了就先禁用吧 :joy:,晚点我在微信小游戏复现下看看咋回事。

加了点备注

4赞

大佬 msk的问题好了之后麻烦告诉小弟一声 :joy:

点赞 :+1:~其实我也想标出注释的,但发现内容好像有点多,没时间写完,就没写了 :rofl:

最近新项目缠身,抽不了空~处理了就告诉你哈