之前搞了个2.x版本的层级渲染,然后感觉早晚都要用3.x版本的了。所以再搞个3.x的层级渲染,后面新项目就直接用3.x的了
3.x的改起来轻松了好多,2.x的还要处理父节点进行变换时,子节点也跟着变换。
使用方式
- 项目启动时调用CCCExtend.ts的init方法,进行引擎代码扩展。
- 需要层级渲染的节点,挂上LevelRender组件即可
挂上LevelRender的节点下所有孩子节点将进行层级渲染
原理
3.x和2.x版本的渲染代码并不一样,3.x的2d渲染代码是在batcher-2d.ts里。虽说渲染代码不一样,但分层处理手法是一样的,甚至直接拷贝2.x那写好的分层方法过来用,都不需要修改。
下面看看batcher-2d.ts的部分渲染代码, 因为要子节点分层,所以,看遍历子节点的方法,也就是batcher-2d.ts里的walk方法。
/** 遍历子节点渲染的方法 */
public walk (node: Node, level = 0) {
if (!node.activeInHierarchy) { //如果节点在节点树中没有被激活,则不用渲染
return;
}
const children = node.children;
const uiProps = node._uiProps;//ui的一些属性放在这里,如透明度
const render = uiProps.uiComp as UIRenderer;//节点的渲染组件
// Save opacity
const parentOpacity = this._pOpacity;//父节点透明度(假设父节不透明度50%,就算子节点不透明度100%,最终显示也只有50%的不透明度)
let opacity = parentOpacity;
// TODO Always cascade ui property's local opacity before remove it
const selfOpacity = render && render.color ? render.color.a / 255 : 1;
this._pOpacity = opacity *= selfOpacity * uiProps.localOpacity;
// TODO Set opacity to ui property's opacity before remove it
uiProps.setOpacity(opacity);
if (!approx(opacity, 0, EPSILON)) {
if (uiProps.colorDirty) {
// Cascade color dirty state
this._opacityDirty++;
}
// Render assembler update logic
if (render && render.enabledInHierarchy) { //开始填充渲染数据
render.fillBuffers(this);// for rendering
}
// Update cascaded opacity to vertex buffer
if (this._opacityDirty && render && !render.useVertexOpacity && render.renderData && render.renderData.vertexCount > 0) {
// HARD COUPLING
updateOpacity(render.renderData, opacity);
const buffer = render.renderData.getMeshBuffer();
if (buffer) {
buffer.setDirty();
}
}
//开始遍历子节点,进行递归渲染,这部分就是重点修改部分
if (children.length > 0 && !node._static) {
for (let i = 0; i < children.length; ++i) {
const child = children[i];
this.walk(child, level);
}
}
if (uiProps.colorDirty) {
// Reduce cascaded color dirty state
this._opacityDirty--;
// Reset color dirty
uiProps.colorDirty = false;
}
}
// Restore opacity
this._pOpacity = parentOpacity;
// Post render assembler update logic
// ATTENTION: Will also reset colorDirty inside postUpdateAssembler
if (render && render.enabledInHierarchy) { //后置渲染,像mask组件,需要等子节点渲染完毕,然后mask才能根据子节点内容,遮罩出来。
render.postUpdateAssembler(this);
if ((render.stencilStage === Stage.ENTER_LEVEL || render.stencilStage === Stage.ENTER_LEVEL_INVERTED)
&& (StencilManager.sharedManager!.getMaskStackSize() > 0)) {
this.autoMergeBatches(this._currComponent!);
this.resetRenderStates();
StencilManager.sharedManager!.exitMask();
}
}
level += 1;
}
上述代码重点关注的部分如下
if (children.length > 0 && !node._static) {
for (let i = 0; i < children.length; ++i) {
const child = children[i];
this.walk(child, level); //遍历孩子节点,然后递归调用walk进行渲染数据处理。
}
}
按照层级渲染思路,遇到孩子先别急着进行渲染,先分层放进一个渲染队列(renderQueue)。然后逐个出队渲染,即可,具体可自行查看代码
注意:
- 写这个组件是在3.8.0版本引擎上写的,其他3.x版本不一定有用,还没进行测试。
- 层级渲染的子节点,mask组件只能遮罩一层,多层失效(最好不要在层级渲染下用mask组件)。
- 分层渲染合批需要符合合批条件。
- 如有bug,欢迎反馈,我这边空了会修复
运行时截图
共7个drawcall,左下角性能分析面板1个,空scroll2个,滑动item2个,角色按钮等2个。
demo.zip (20.9 KB)