一:Assembler的来龙去脉
二:自定义一个渲染组件(使用自定义的assembler挂载)
之前的文章中提到和使用过assembler相关的技术,但是现在看来还是理解的不是很到位,
1: 首先看看那assembler是如何绑定到渲染组件的,随便找到一个渲染组件,比如说老生常谈的Sprite组件
cocos2d/cocos-engine-2.4.8/cocos2d/core/renderer/webgl/assemblers/sprite/index.js
import Assembler from '../../../assembler';
import { Type, FillType } from '../../../../components/CCSprite';
import Simple from "./2d/simple";
import Sliced from "./2d/sliced";
import Tiled from "./2d/tiled";
import RadialFilled from "./2d/radial-filled";
import BarFilled from "./2d/bar-filled";
import Mesh from './2d/mesh';
import Simple3D from "./3d/simple";
import Sliced3D from "./3d/sliced";
import Tiled3D from "./3d/tiled";
import RadialFilled3D from "./3d/radial-filled";
import BarFilled3D from "./3d/bar-filled";
import Mesh3D from './3d/mesh';
let ctor = {
getConstructor(sprite) {
let is3DNode = sprite.node.is3DNode;
// sprite节点默认绑定的是SimpleAssembler2D
let ctor = is3DNode ? Simple3D : Simple;
switch (sprite.type) {
case Type.SLICED:
ctor = is3DNode ? Sliced3D : Sliced;
break;
case Type.TILED:
ctor = is3DNode ? Tiled3D : Tiled;
break;
case Type.FILLED:
if (sprite._fillType === FillType.RADIAL) {
ctor = is3DNode ? RadialFilled3D : RadialFilled;
} else {
ctor = is3DNode ? BarFilled3D : BarFilled;
}
break;
case Type.MESH:
ctor = is3DNode ? Mesh3D : Mesh;
break;
}
return ctor;
},
Simple,
Sliced,
Tiled,
RadialFilled,
BarFilled,
Mesh,
Simple3D,
Sliced3D,
Tiled3D,
RadialFilled3D,
BarFilled3D,
Mesh3D,
};
Assembler.register(cc.Sprite, ctor);
可以看到sprite组件会根据type选择对应的assembler来组装渲染数据。
Assembler.register = function (renderCompCtor, assembler) {
renderCompCtor.__assembler__ = assembler;
};
register函数是初始化渲染组件的构造函数的__assembler__属性,以便后来的init函数赋值renderComp._assembler ,你可能会问Assembler的init函数在哪里执行的呢,不错这个init函数是在_resetAssembler函数里面执行的。
_resetAssembler() {
Assembler.init(this);
this._updateColor();
this.setVertsDirty();
},
2:Assembler的执行顺序是怎样的
聊完了注册和初始化,接下来看看一个Assembler它的执行顺序是怎么样的,想要了解这些之前有必要知道Assembler都有哪些跟渲染有关的方法:
export default class Assembler {
constructor () {
this._extendNative && this._extendNative();
}
init (renderComp) {
this._renderComp = renderComp;
}
updateRenderData (comp) {
}
fillBuffers (comp, renderer) {
}
getVfmt () {
return vfmtPosUvColor;
}
}
import Assembler from './assembler';
import dynamicAtlasManager from './utils/dynamic-atlas/manager';
import RenderData from './webgl/render-data';
export default class Assembler2D extends Assembler {
constructor() {
super();
this._renderData = new RenderData();
this._renderData.init(this);
this.initData();
this.initLocal();
}
get verticesFloats() {
// verticesCount 顶点个数
// 每个顶点数据几个float数据 5个 x,y,u,v,
return this.verticesCount * this.floatsPerVert;
}
initData() {
let data = this._renderData;
data.createQuadData(0, this.verticesFloats, this.indicesCount);
}
initLocal() {
this._local = [];
this._local.length = 4;
}
updateColor(comp, color) {
let uintVerts = this._renderData.uintVDatas[0];
if (!uintVerts) return;
color = color != null ? color : comp.node.color._val;
let floatsPerVert = this.floatsPerVert;
let colorOffset = this.colorOffset;
for (let i = colorOffset, l = uintVerts.length; i < l; i += floatsPerVert) {
uintVerts[i] = color;
}
}
getBuffer() {
return cc.renderer._handle._meshBuffer;
}
updateWorldVerts(comp) {
let local = this._local;
let verts = this._renderData.vDatas[0];
let matrix = comp.node._worldMatrix;
let matrixm = matrix.m,
a = matrixm[0], b = matrixm[1], c = matrixm[4], d = matrixm[5],
tx = matrixm[12], ty = matrixm[13];
let vl = local[0], vr = local[2],
vb = local[1], vt = local[3];
let floatsPerVert = this.floatsPerVert;
let vertexOffset = 0;
let justTranslate = a === 1 && b === 0 && c === 0 && d === 1;
if (justTranslate) {
// left bottom
verts[vertexOffset] = vl + tx;
verts[vertexOffset + 1] = vb + ty;
vertexOffset += floatsPerVert;
// right bottom
verts[vertexOffset] = vr + tx;
verts[vertexOffset + 1] = vb + ty;
vertexOffset += floatsPerVert;
// left top
verts[vertexOffset] = vl + tx;
verts[vertexOffset + 1] = vt + ty;
vertexOffset += floatsPerVert;
// right top
verts[vertexOffset] = vr + tx;
verts[vertexOffset + 1] = vt + ty;
} else {
let al = a * vl, ar = a * vr,
bl = b * vl, br = b * vr,
cb = c * vb, ct = c * vt,
db = d * vb, dt = d * vt;
// left bottom
verts[vertexOffset] = al + cb + tx;
verts[vertexOffset + 1] = bl + db + ty;
vertexOffset += floatsPerVert;
// right bottom
verts[vertexOffset] = ar + cb + tx;
verts[vertexOffset + 1] = br + db + ty;
vertexOffset += floatsPerVert;
// left top
verts[vertexOffset] = al + ct + tx;
verts[vertexOffset + 1] = bl + dt + ty;
vertexOffset += floatsPerVert;
// right top
verts[vertexOffset] = ar + ct + tx;
verts[vertexOffset + 1] = br + dt + ty;
}
}
// 填充 meshBuffer
fillBuffers(comp, renderer) {
if (renderer.worldMatDirty) {
this.updateWorldVerts(comp);
}
let renderData = this._renderData;
let vData = renderData.vDatas[0];
let iData = renderData.iDatas[0];
let buffer = this.getBuffer(renderer);
let offsetInfo = buffer.request(this.verticesCount, this.indicesCount);
// buffer data may be realloc, need get reference after request.
// fill vertices 填充meshBuffer中的顶点数据
let vertexOffset = offsetInfo.byteOffset >> 2,
vbuf = buffer._vData;
if (vData.length + vertexOffset > vbuf.length) {
vbuf.set(vData.subarray(0, vbuf.length - vertexOffset), vertexOffset);
} else {
vbuf.set(vData, vertexOffset);
}
// fill indices 填充meshBuffer中的顶点索引数组
let ibuf = buffer._iData,
indiceOffset = offsetInfo.indiceOffset,
vertexId = offsetInfo.vertexOffset;
for (let i = 0, l = iData.length; i < l; i++) {
ibuf[indiceOffset++] = vertexId + iData[i];
}
}
packToDynamicAtlas(comp, frame) {
if (CC_TEST) return;
if (!frame._original && dynamicAtlasManager && frame._texture.packable && frame._texture.loaded) {
let packedFrame = dynamicAtlasManager.insertSpriteFrame(frame);
if (packedFrame) {
frame._setDynamicAtlasFrame(packedFrame);
}
}
let material = comp._materials[0];
if (!material) return;
if (material.getProperty('texture') !== frame._texture._texture) {
// texture was packed to dynamic atlas, should update uvs
comp._vertsDirty = true;
comp._updateMaterial();
}
}
}
cc.js.addon(Assembler2D.prototype, {
floatsPerVert: 5,
verticesCount: 4,
indicesCount: 6,
uvOffset: 2,
colorOffset: 4,
});
cc.Assembler2D = Assembler2D;
Assembler的主要几个方法:updateUVs,updateRenderData ,updateVerts,updateWorldVerts,fillBuffers,updateColor,initData,initLocal
那么接下来看看他们的作用,
a: initData: 是创建渲染所需要的数据
分为4个顶点数据和6个顶点索引数据,并且初始化6个顶点数据的值一般是(0,1,2)(1,3,2),如果想要个性化修改图片的边数可以再这里进行重写修改
b:initLocal: 初始化节点的本地坐标(4个坐标,分别是左下,右下,左上,右上的坐标)数组
initData,initLocal在Assembler构造函数中完成。
更新顶点数据和uv数据是在什么时候调用的呢,首先想一想什么时候才能触发更新这些数据的时候,如果一个节点渲染数据没有变化肯定不会触发这些操作,只有需要更新渲染数据的时候才会触发。updateRenderData是触发的条件
protected updateRenderData(comp: cc.RenderComponent) {
if (comp._vertsDirty) {
this.updateUVs(comp);
this.updateVerts(comp);
comp._vertsDirty = false;
}
}
接着就是updateRenderData的调用时机,说起这个的调用时机它跟render-flow有着千丝万缕的联系,讲到render-flow之前 可以看看我之前写的render-flow渲染流的章节,简单的说就是根据二进制的标志位进行渲染的切换,一般的渲染链条一般是:null -> Final -> postRender->children->render -> Opacity_color -> color -> Opacity -> updateRenderData -> Transform -> WorldTransform -> localTransform,这里的updateRenderData标志位为1的时候就会触发 上述的updateRenderData函数
_proto._updateRenderData = function (node) {
let comp = node._renderComponent;
// 这个就是assembler的更新渲染函数了
comp._assembler.updateRenderData(comp);
// 渲染之前的_renderFlag是需要update_render_data,这里的操作是执行完updateRenderData函数之后需要将之前的标志位取反也就是证明该操作执行完毕了
node._renderFlag &= ~UPDATE_RENDER_DATA;
// 转入下一个流程
this._next._func(node);
};
举一反三类似的updateColor是在color标志位为1的时候触发:
_proto._color = function (node) {
let comp = node._renderComponent;
if (comp) {
comp._updateColor();
}
node._renderFlag &= ~COLOR;
this._next._func(node);
};
相应的fillBuffers的执行时机:render标志位为1的时候触发,每帧执行
_proto._render = function (node) {
let comp = node._renderComponent;
// 检查合批
comp._checkBacth(_batcher, node._cullingMask);
// 填充缓冲区 meshBuffer
comp._assembler.fillBuffers(comp, _batcher);
// 转入下一个流程 children 或者是postRender
this._next._func(node);
};
提交渲染数据流程时机:
_proto._postRender = function (node) {
let comp = node._renderComponent;
// 检查是否可以合批
comp._checkBacth(_batcher, node._cullingMask);
comp._assembler.postFillBuffers(comp, _batcher);
this._next._func(node);
};
讲了这么多是时候拿个例子来看看了。
有这么个需求就是防止节点结构合批被打乱,自己实现一个渲染组件来改善这种情况。我之前的做法是图片和图片放到一起,label和label放到一起,这样就能达到合批的目的,但是这样做的结果就是代码组织就变得很没有结构性,最好的办法是既能保持节点结构又能达到轻松合批的效果。之所以合批被打乱的原因就是节点遍历cocos默认是深度优先遍历,很容易就会造成节点合批被打乱。此次优化方案(节点采用广度优先遍历的方法)也是论坛的大神提供的思路,在此搬砖分享给各位水友。
1:定义自己的MyAssembler2D,理解这个之前一定要知道渲染数据的构成,注释里面详细的解释了
/**
*
* 自定义渲染数据
* 每个2d渲染数据里面有
* 4个顶点属性数据 为什么是4个是因为一个四边形由四个顶点
* 6个顶点索引 -> (一个四边形由两个三角形组成)
*
*
* 每个顶点数据格式:
* | x | y | u | v | r | g | b | a | ....
* 4 4 4 4 1 1 1 1 (一个顶点总共20个字节的数据)
*
* 其中uv数据偏移是2个单位,color的偏移是4个单位
*
*/
export default class MyAssembler2D extends cc.Assembler {
/** 顶点数据个数 */
public verticesCount = 4;
/** 顶点索引个数 */
public indicesCount = 6;
/** 每个顶点数据有几个float32位的值 */
public floatsPerVert = 5;
/** uv偏移量位2个单位 */
public uvOffset = 2;
public colorOffset = 4;
protected _renderData: cc.RenderData;
protected _local: any = null;
init(comp: cc.RenderComponent) {
super.init(comp);
this._renderData = new cc.RenderData();
/** 这里的初始化? */
this._renderData.init(this);
this.initLocal();
this.initData();
}
get verticesFloats() {
return this.verticesCount * this.floatsPerVert;
}
initData() {
let data = this._renderData;
data.createQuadData(0, this.verticesFloats, this.indicesCount);
}
initLocal() {
this._local = [];
this._local.length = 4;
}
/**
* 更新节点的颜色
* @param {cc.RenderComponent} comp
* @param {any} color
*/
updateColor(comp: cc.RenderComponent, color: any) {
if (!this._renderData.uintVDatas) {
if (CC_EDITOR) {
Editor.log("uintVDatas is undefined");
}
return;
}
if (CC_EDITOR) {
Editor.log("unitVDatas is ", this._renderData.uintVDatas);
}
let uintVerts = this._renderData.uintVDatas[0];
if (!uintVerts) {
if (CC_EDITOR) {
Editor.warn("uintVerts is null");
return;
}
};
// @ts-ignore
color = color != null ? color : comp.node.color._val;
let floatsPerVert = this.floatsPerVert;
let colorOffset = this.colorOffset;
for (let i = colorOffset, l = uintVerts.length; i < l; i += floatsPerVert) {
uintVerts[i] = color;
}
if (CC_EDITOR) {
Editor.log("verts is ", uintVerts);
}
}
getBuffer() {
// @ts-ignore
return cc.renderer._handle._meshBuffer;
}
/** 更新顶点数据到世界坐标系 */
updateWorldVerts(comp) {
if (CC_NATIVERENDERER) {
this.updateWorldVertsNative(comp);
} else {
this.updateWorldVertsWebGL(comp);
}
}
updateWorldVertsNative(comp: cc.RenderComponent) {
let local = this._local;
let verts = this._renderData.vDatas[0];
let floatsPerVert = this.floatsPerVert;
let l = local[0], r = local[1], b = local[2], t = local[3];
let index = 0;
// left bottom
verts[index] = l;
verts[index + 1] = b;
index += floatsPerVert;
// right bottom
verts[index] = r;
verts[index + 1] = b;
index += floatsPerVert;
// left top
verts[index] = l;
verts[index + 1] = t;
index += floatsPerVert;
// right top
verts[index] = r;
verts[index + 1] = t;
}
/**
* 更新webGL平台世界坐标 cocos 采用行向量的方式(优点: 直接数组操作)来与矩阵运算的 M * V(列式向量) V(行式向量) * M
* @param {cc.RenderComponent} comp
*/
updateWorldVertsWebGL(comp: cc.RenderComponent) {
if (CC_EDITOR) {
Editor.warn("updateWorldVerts");
}
let local = this._local;
// 顶点数据
let verts = this._renderData.vDatas[0];
// @ts-ignore 节点的世界矩阵
let matrix = comp.node._worldMatrix;
let m = matrix.m,
a = m[0], b = m[1], c = m[4], d = m[5],
tx = m[12], ty = m[13];
// a, d 缩放因子参数 b,c 斜切因子参数
let vl = local[0], vr = local[2],
vb = local[1], vt = local[3];
/*
- -
m00 = 1, m01 = 0, m02 = 0, m03 = 0, | 1 0 0 0 |
m04 = 0, m05 = 1, m06 = 0, m07 = 0, | 0 1 0 0 |
m08 = 0, m09 = 0, m10 = 1, m11 = 0, | 0 0 1 0 |
m12 = 0, m13 = 0, m14 = 0, m15 = 1 | 0 0 0 1 |
- -
*/
let justTranslate = a === 1 && d === 1 && b === 0 && c === 0;
let index = 0;
let floatsPerVert = this.floatsPerVert;
if (CC_EDITOR) {
Editor.warn("justTranslate is ", justTranslate);
}
if (justTranslate) {
// left bottom
verts[index] = vl + tx;
verts[index + 1] = vb + ty;
index += floatsPerVert;
// right bottom
verts[index] = vr + tx;
verts[index + 1] = vb + ty;
index += floatsPerVert;
// left top
verts[index] = vl + tx;
verts[index + 1] = vt + ty;
index += floatsPerVert;
// right top
verts[index] = vr + tx;
verts[index + 1] = vt + ty;
} else {
// 根据矩阵运算规则 【x,y,z,w】* M可以得到以下结果
/** - -
* | a b 0 0 |
* | c d 0 0 |
*[x,y,z,1] * | 0 0 1 0 | = 【a*x + cy + tx,bx + dy + ty,0,1】
* | tx ty 0 1 |
* - -
*/
// left bottom
verts[index] = vl * a + c * vb + tx;
verts[index + 1] = vl * b + d * vb + ty;
index += floatsPerVert;
// right bottom
verts[index] = vr * a + c * vb + tx;
verts[index + 1] = vr * b + d * vb + ty;
index += floatsPerVert;
// left top
verts[index] = vl * a + c * vt + tx;
verts[index + 1] = vl * b + d * vt + ty;
index += floatsPerVert;
// right top
verts[index] = vr * a + c * vt + tx;
verts[index + 1] = vr * b + d * vt * ty;
}
}
packToDynamicAtlas(comp: cc.RenderComponent, frame: any) {
if (CC_TEST) return;
if (!frame._original && cc.dynamicAtlasManager && frame._texture.packable) {
let packedFrame = cc.dynamicAtlasManager.insertSpriteFrame(frame);
// @ts-ignore
if (packedFrame) {
frame._setDynamicAtlasFrame(packedFrame);
}
}
// @ts-ignore
let material = comp._materials[0];
if (!material) return;
if (material.getProperty('texture') !== frame._texture) {
// @ts-ignore
comp._vertsDirty = true;
// @ts-ignore
comp._updateMaterial();
}
}
protected updateUVs(comp: cc.RenderComponent) {
if (CC_EDITOR) {
Editor.warn("updateUVS...");
}
// 4个顶点的uv坐标 对应左下,右下,左上,右上
let uv = [0, 0, 1, 0, 0, 1, 1, 1];
// if (comp instanceof cc.Sprite) {
// // @ts-ignore
// uv = comp._spriteFrame.uv;
// }
let uvOffset = this.uvOffset;
let floatsPerVert = this.floatsPerVert;
let verts = this._renderData.vDatas[0];
for (let i = 0; i < 4; i++) {
let srcOffset = i * 2;
let dstOffset = floatsPerVert * i + uvOffset;
verts[dstOffset] = uv[srcOffset];
verts[dstOffset + 1] = uv[srcOffset + 1];
}
}
// 每帧调用
public fillBuffers(comp: cc.RenderComponent, render: cc.ModelBatcher) {
if (CC_EDITOR) {
// Editor.warn("fillBuffers...");
}
// if(r)
if (render.worldMatDirty) {
this.updateWorldVerts(comp);
}
let renderData = this._renderData;
let vData = renderData.vDatas[0];
let iData = renderData.iDatas[0];
// 获取meshBuffer
let buffer = this.getBuffer();
let offsetInfo = buffer.request(this.verticesCount, this.indicesCount);
let vertexOffset = offsetInfo.byteOffset >> 2,
vbuf = buffer._vData;
if (vData.length + vertexOffset > vbuf.length) {
vbuf.set(vData.subarray(0, vbuf.length - vertexOffset), vertexOffset);
} else {
vbuf.set(vData, vertexOffset);
}
let ibuf = buffer._iData, indiceOffset = offsetInfo.indiceOffset, vertexId = offsetInfo.vertexOffset;
for (let i = 0, l = iData.length; i < l; i++) {
ibuf[indiceOffset++] = vertexId + iData[i];
}
}
protected updateVerts(comp: cc.RenderComponent) {
if (CC_EDITOR) {
Editor.warn("updateVerts...");
}
let node: cc.Node = comp.node,
cw = node.width,
ch = node.height,
// (锚点x * cw,锚点y * ch)
appx = node.anchorX * cw,
appy = node.anchorY * ch,
l, b, r, t;
// 左边界
l = -appx;
// 下边界
b = -appy;
// 右边界
r = cw - appx;
// 上边界
t = ch - appy;
this._local[0] = l;
this._local[1] = b;
this._local[2] = r;
this._local[3] = t;
this.updateWorldVerts(comp);
}
protected updateRenderData(comp: cc.RenderComponent) {
if (comp._vertsDirty) {
this.updateUVs(comp);
this.updateVerts(comp);
comp._vertsDirty = false;
}
}
}
2:定义自己的MySpriteAssembler2D
import MyAssembler2D from "./MyAssembler2D";
export default class MySimpleSpriteAssembler2D extends MyAssembler2D {
protected updateRenderData(sprite: cc.Sprite) {
// @ts-ignore
this.packToDynamicAtlas(sprite, sprite._spriteFrame);
super.updateRenderData(sprite);
}
/**
* 将图片的uv数据复制到 renderData.vData中
* @param {cc.Sprite} sprite
* @returns void
*/
updateUVs(sprite: cc.Sprite): void {
if (CC_EDITOR) {
Editor.log("updateUVs...");
}
// @ts-ignore
let uv = sprite._spriteFrame.uv;
let uvOffset = this.uvOffset;
let floatsPerVert = this.floatsPerVert;
let verts = this._renderData.vDatas[0];
for (let i = 0; i < 4; i++) {
let srcOffset = i * 2;
let dstOffset = floatsPerVert * i + uvOffset;
verts[dstOffset] = uv[srcOffset];
verts[dstOffset + 1] = uv[srcOffset + 1];
}
}
/**
* 更新图片的渲染顶点数据
* @param {cc.Sprite} sprite
* @returns void
*/
protected updateVerts(sprite: cc.Sprite): void {
if (CC_EDITOR) {
Editor.log("updateVerts ");
}
let node = sprite.node,
cw = node.width, ch = node.height,
appx = node.anchorX * cw, appy = node.anchorY * ch, l, b, r, t;
if (sprite.trim) {
l = -appx;
b = -appy;
r = cw - appx;
t = ch - appy;
} else {
let frame = sprite.spriteFrame,
// @ts-ignore
ow = frame._originalSize.width, oh = frame._originalSize.height,
// @ts-ignore
rw = frame._rect.width, rh = frame._rect.height,
// @ts-ignore
offset = frame._offset, scaleX = cw / ow, scaleY = ch / oh;
let trimLeft = offset.x + (ow - rw) / 2;
let trimRight = offset.x - (ow - rw) / 2;
let trimBottom = offset.y + (oh - rh) / 2;
let trimTop = offset.y - (oh - rh) / 2;
l = trimLeft * scaleX - appx;
b = trimBottom * scaleY - appy;
r = trimRight * scaleX + cw - appx;
t = trimTop * scaleY + ch - appy;
}
this._local[0] = l;
this._local[1] = b;
this._local[2] = r;
this._local[3] = t;
this.updateWorldVerts(sprite);
}
}
3:定义自己的合批Assembler: BatchAssembler2D
import { recordData } from "../../bingoslots/scripts/server_data/MockData";
import MySimpleSpriteAssembler2D from "./MySimpleSpriteAssembler2D";
/**
*
* 分层合批处理assembler(引擎内置为深度遍历的方式不方便合批处理)
*
*/
// @ts-ignore 渲染掩码用于检测当前节点的渲染标志位是否含有 render和postRender标志位
const RENDER_MASK = cc.RenderFlow.FLAG_RENDER | cc.RenderFlow.FLAG_POST_RENDER;
// @ts-ignore 透明度掩码用于检测当前节点的渲染标志位是否含有 Opacity 和WorldTransform标志位
const PROP_DIRTY_MASK = cc.RenderFlow.FLAG_OPACITY | cc.RenderFlow.FLAG_WORLD_TRANSFORM;
let BATCH_OPTIMIZE_SWITCH: boolean = true;
export default class BatchingAssembler2D extends MySimpleSpriteAssembler2D {
protected _layers: any[];
fillBuffers(comp: cc.RenderComponent, render: cc.ModelBatcher) {
super.fillBuffers(comp, render);
if (CC_EDITOR || CC_NATIVERENDERER) return;
if (!BATCH_OPTIMIZE_SWITCH) return;
let layer = [];
this._layers = [layer];
// @ts-ignore
let worldTransformFlag = render.worldMatDirty ? cc.RenderFlow.FLAG_WORLD_TRANSFORM : 0;
// @ts-ignore
let worldOpacityFlag = render.parentOpacityDirty ? cc.RenderFlow.FLAG_OPACITY_COLOR : 0;
let dirtyFlag = worldTransformFlag | worldOpacityFlag;
comp.node['__myDirtyFlag'] = dirtyFlag;
// 广度优先遍历
let queue: cc.Node[] = [];
queue.push(comp.node);
let depth = 0, end = 1, iter = 0;
let myRenderFlag;
while (iter < queue.length) {
let node = queue[iter++];
dirtyFlag = node['__myDirtyFlag'];
for (let c of node.children) {
// @ts-ignore
if (!c._activeInHierarchy || c.opacity == 0) {
continue;
}
// @ts-ignore
myRenderFlag = c._renderFlag & RENDER_MASK;
if (myRenderFlag > 0) {
// 携带了渲染的标志位
// 移除子节点的renderFlag,是renderFlow不执行它的render函数
c["__myRenderFlag"] = myRenderFlag;
// @ts-ignore 移除之前有的渲染标志位
c._renderFlag &= ~myRenderFlag;
// 将本来有渲染标志位的节点给加入到层级队列里面
layer.push(c);
}
// @ts-ignore 逐层传递父节点的dirtyFlag
c["__myDirtyFlag"] = dirtyFlag | (c._renderFlag & PROP_DIRTY_MASK);
// 将遍历的子节点推入队列
queue.push(c);
}
if (iter == end) {
++depth;
end = queue.length;
layer = [];
this._layers.push(layer);
}
}
if (CC_EDITOR) {
Editor.log("layers is ", this._layers);
}
console.log("layers is ", this._layers);
}
postFillBuffers(comp: cc.RenderComponent, render: cc.ModelBatcher) {
if (CC_EDITOR) {
// Editor.log("postFillbuffers");
}
let originWorldMatDirty = render.worldMatDirty;
if (!BATCH_OPTIMIZE_SWITCH || !this._layers) return;
BATCH_OPTIMIZE_SWITCH = false;
let myRenderFlag, myDirtyFlag;
for (let layer of this._layers) {
if (layer.length == 0) {
continue;
}
for (let c of layer) {
myRenderFlag = c['__myRenderFlag'];
myDirtyFlag = c['__myDirtyFlag'];
render.worldMatDirty = myDirtyFlag > 0 ? 1 : 0;
// 还原之前被修改的标志位
c._renderFlag |= myRenderFlag;
// @ts-ignore
cc.RenderFlow.flows[myRenderFlag]._func(c);
}
}
this._layers = null;
BATCH_OPTIMIZE_SWITCH = true;
render.worldMatDirty = originWorldMatDirty;
}
}
4:定义自己的合批渲染组件:LayeredBatchingSprite
import BatchingAssembler2D from "./BatchingAssembler2D";
import MySimpleSpriteAssembler2D from "./MySimpleSpriteAssembler2D";
const { ccclass } = cc._decorator;
@ccclass
export default class LayeredBatchingSprite extends cc.Sprite {
protected onEnable(): void {
super.onEnable();
if (!CC_EDITOR || !CC_NATIVERENDERER) {
// @ts-ignore 添加一个postRender 渲染标志位 来执行 postFillBuffers
this.node._renderFlag |= cc.RenderFlow.FLAG_POST_RENDER;
}
}
_resetAssembler() {
this.setVertsDirty();
let assembler = this._assembler = new BatchingAssembler2D();
assembler.init(this);
}
}
至此一个广度优先遍历的节点合批方案完成,希望给给为水友一点启发