Cocos Creator 中的assembler那点事

一:Assembler的来龙去脉

二:自定义一个渲染组件(使用自定义的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);
};

 讲了这么多是时候拿个例子来看看了。

二:自定义一个渲染组件(使用自定义的assembler挂载)

有这么个需求就是防止节点结构合批被打乱,自己实现一个渲染组件来改善这种情况。我之前的做法是图片和图片放到一起,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);
    }
}

至此一个广度优先遍历的节点合批方案完成,希望给给为水友一点启发 

你可能感兴趣的:(cocos,creator,cocos2d,游戏引擎)