cocos creator 自定义渲染

该博客介绍了如何使用自定义渲染组件来创建2D正多边形图片,以避免过多的drawcall导致的性能问题。作者通过创建一个继承自cc.RenderComponent的渲染组件,并实现特定的assembler,详细展示了计算顶点、更新UV坐标、处理颜色等过程,最终达到减少drawcall并实现自定义图形的效果。
摘要由CSDN通过智能技术生成

最近在想虽然shader可以解决2d中的很多特效需求但是会存在一个问题就是drawcall会增加,这样会带来性能隐患,所以看了一下自定义渲染,整了一个正多边形图片的实现:效果如下图:

 具体实现是创建一个渲染组件继承自cc.RenderComponent,并且需要一个自定义的assembler,集成的RenderComponent需要 重写updateRenaderData,updateUvs,updateVerts,updateWorldVerts,fillBuffers,其实可以参考 sprite源码里面的simple.js文件,我也是抄了一部分:

先看看 mySprite.ts:

import MyAssembler from "./myAssembler";

const {ccclass, property,mixins,executeInEditMode} = cc._decorator;

@ccclass
@mixins(cc.BlendFunc)
@executeInEditMode
export default class MySprite extends cc.RenderComponent {

    @property(cc.Texture2D)
    _texture: cc.Texture2D = null;

    @property(cc.Integer)
    _eadgeCount: number = 6;

    @property(cc.Integer)
    public get eadgeCount() {
        return this._eadgeCount;
    }

    public set eadgeCount(value: number) {
        this._eadgeCount = value;
        this.calculatePoints();
        this.setVertsDirty();
    }

    private _assembler: cc.Assembler = null;

    public spriteFrame: cc.SpriteFrame = null;

    public points: cc.Vec2[] = [];

    @property(cc.Texture2D)
    public get texture() {
        return this._texture;
    }
    public set texture(tex: cc.Texture2D) {
        this._texture = tex;

        this.spriteFrame = new cc.SpriteFrame();
        this.spriteFrame.setTexture(tex);
        this.node.width = this._texture.width;
        this.node.height = this._texture.height;
        
        if(CC_EDITOR) {
            Editor.log('farme is ',this.spriteFrame);
        }

        this._updateMaterial();
    }

    public calculatePoints() {
        const points = [];

        const r = this.node.width > this.node.height ? this.node.height / 2 : this.node.width / 2;

        const angle = Math.PI * 2 / this.eadgeCount;
        let startPoint = cc.v2(-r * Math.cos(angle),-r * Math.sin(angle));
        points.push(startPoint);
        let startp = startPoint
        for(let i = 1; i < this.eadgeCount; i++) {
            let point = startp.rotate(angle * i);
            points.push(point);
        }

        this.points = points;
    }

    

    @property({type: cc.Enum(cc.macro.BlendFactor), override: true})
    srcBlendFactor: cc.macro.BlendFactor = cc.macro.BlendFactor.SRC_ALPHA;

    @property({type: cc.Enum(cc.macro.BlendFactor), override: true})
    dstBlendFactor: cc.macro.BlendFactor = cc.macro.BlendFactor.ONE_MINUS_SRC_ALPHA;

    // __preload会调用
    public _resetAssembler() {
        let assembler = this._assembler = new MyAssembler();
        assembler.init(this);
        this._updateColor();
        this.setVertsDirty();
    }

    public _updateMaterial() {
        let texture = this._texture;
        let material = this.getMaterial(0);
        if(material) {
            if(material.getDefine("USE_TEXTURE") !== undefined) {
                material.define("USE_TEXTURE", true);
            }
            material.setProperty("texture", texture);
        }
        // this['__proto__']._updateBlendFunc.call(this);
        cc.BlendFunc.prototype._updateMaterial.call(this); 
        this.setVertsDirty();        
    }
    

    start () {
        let p1 = cc.v2(-100,200);
        let p3 = cc.v2(300,200);
        let p2 = cc.v2(0,0);

        let p4 = cc.v2(300,0);

        const isIn = this.isInTriangle(p4,p1,p2,p3);
        console.log('isIn is ',isIn);
    }

    /**
     * 监测一个点是否在三角形内
     * @param  {cc.Vec2} point
     * @param  {cc.Vec2} a
     * @param  {cc.Vec2} b
     * @param  {cc.Vec2} c
     */
     isInTriangle(point: cc.Vec2,a: cc.Vec2,b: cc.Vec2,c: cc.Vec2) {
        const isEqualSign = (n1,n2) => {
            return (n1 >= 0 && n2 >= 0) || (n1 <= 0 && n2 <= 0);
        }

        const ca = a.sub(c);
        const cb = b.sub(c);
        const cd = point.sub(c);

        const ac = c.sub(a);
        const ab = b.sub(a);
        const ad = point.sub(a);

        const ba = a.sub(b);
        const bc = c.sub(b);
        const bd = point.sub(b);

        const cCross = isEqualSign(ca.cross(cb),ca.cross(cd));
        const aCross = isEqualSign(ab.cross(ac),ab.cross(ad));
        const bCross = isEqualSign(bc.cross(ba),bc.cross(bd));

        return cCross && aCross && bCross;
    }

    update (dt) {
        // this._updateMaterial();
    }
}

myAssembler.ts:

import MySprite from "./mySprite";

const {ccclass, property} = cc._decorator;

const gfx = cc['gfx'];

// 顶点格式 -> 位置 UV, 颜色
let vfmtPosUvColor = new gfx.VertexFormat([
    { name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 2 },
    { name: gfx.ATTR_UV0, type: gfx.ATTR_TYPE_FLOAT32, num: 2 },
    { name: gfx.ATTR_COLOR, type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true },
]);

@ccclass
export default class MyAssembler extends cc.Assembler {

    public floatsPerVert = 5;

    public verticesCount = 4;
    public indicesCount = 6;

    public uvOffset = 2;
    public colorOffset = 4;

    public _local = [];

    public _renderData: cc.RenderData = null;

    public calculatePoints(comp: MySprite) {
        const points = [];

        const r = comp.node.width > comp.node.height ? comp.node.height / 2 : comp.node.width / 2;

        const angle = Math.PI * 2 / comp.eadgeCount;
        let startPoint = cc.v2(-r * Math.cos(angle),-r * Math.sin(angle));
        let startp = startPoint;
        points.push(startp);
        for(let i = 1; i < comp.eadgeCount; i++) {
            let point = startp.rotate(angle);
            points.push(point);
            startp = point;
        }

        console.log('points is ',points);
        // this.points = points;
        return points;
    }


    constructor() {
        super();
        this._renderData = new cc.RenderData();
        this._renderData.init(this);

        this.initData();
        this.initLocal();
    }

    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;
    }

    updateUVs(comp: MySprite) {
        const points = this.calculatePoints(comp);
        const uvs = this.computeUv(points,comp.node.width,comp.node.height);

        // const uv = [
        //     0,1,
        //     1,1,
        //     0,0,
        //     1,0,
        // ];
        // 更新uv数据
        console.log('uvs is ',uvs);
        const verts = this._renderData.vDatas[0];
        for(let i = 0; i < uvs.length; i++) {
            let dstOffset = this.floatsPerVert * i + this.uvOffset;

            verts[dstOffset] = uvs[i].x;
            verts[dstOffset + 1] = uvs[i].y;
        }

    }

    computeUv(points: cc.Vec2[],width: number,height: number) {
        let uvs: cc.Vec2[] = [];
        for(let p of points) {
            let x = this.clamp(0,1,(p.x + width / 2) / width);
            let y = this.clamp(0,1,1 - (p.y + height / 2) / height);
            uvs.push(cc.v2(x,y));
        }
        return uvs;
    }

    clamp(min,max,value) {
        if(value < min) {
            return min;
        } else if(value > max) {
            return max;
        }
        return value;
    }

    getIndicesByPoints(points: cc.Vec2[]) {
        if(points.length === 3) return [0,1,2];
        
        let pointIndexMap: {[key: string]: number} = {};

        for(let i = 0; i < points.length; i++) {
            let p = points[i];
            pointIndexMap[`${p.x.toFixed(2)}-${p.y.toFixed(2)}`] = i;
        }

        let index = 0;

        let indexs = [];

        points = points.concat([]);

        while(points.length > 3) {
            // 点的长度大于3继续分割
            let p1 = points[index % points.length];
            let p2 = points[(index + 1) % points.length];
            let p3 = points[(index + 2) % points.length];

            let p21 = p1.sub(p2);
            let p23 = p3.sub(p2);
            // 分割点
            let splitPoint = p2;
            if(p23.cross(p21) < 0) {
                // p2点是凹点
                index = (index + 1) % points.length;
                continue;
            }

            let isIn = false;
            for(let p of points) {
                if(p !== p1 && p !== p2 && p !== p3 && this.isInTriangle(p,p1,p2,p3)) {
                    isIn = true;
                    break;
                }
            }
            if(isIn) {
                index = (index + 1) % points.length;
                continue;
            }

            const splitPointIndex = (index + 1) % points.length;
            // 切耳
            points.splice(splitPointIndex,1);

            indexs.push(pointIndexMap[`${p1.x.toFixed(2)}-${p1.y.toFixed(2)}`]);
            indexs.push(pointIndexMap[`${p2.x.toFixed(2)}-${p2.y.toFixed(2)}`]);
            indexs.push(pointIndexMap[`${p3.x.toFixed(2)}-${p3.y.toFixed(2)}`]);

        }

        for(let i = 0; i < points.length; i++) {
            let p = points[i];
            indexs.push(pointIndexMap[`${p.x.toFixed(2)}-${p.y.toFixed(2)}`]);
        }

        return indexs;
    }

    /**
     * 监测一个点是否在三角形内
     * @param  {cc.Vec2} point
     * @param  {cc.Vec2} a
     * @param  {cc.Vec2} b
     * @param  {cc.Vec2} c
     */
    isInTriangle(point: cc.Vec2,a: cc.Vec2,b: cc.Vec2,c: cc.Vec2) {
        const isEqualSign = (n1,n2) => {
            return (n1 > 0 && n2 > 0) || (n1 < 0 && n2 < 0);
        }

        const ca = a.sub(c);
        const cb = b.sub(c);
        const cd = point.sub(c);

        const ac = c.sub(a);
        const ab = b.sub(a);
        const ad = point.sub(a);

        const ba = a.sub(b);
        const bc = c.sub(b);
        const bd = point.sub(b);

        const cCross = isEqualSign(ca.cross(cb),ca.cross(cd));
        const aCross = isEqualSign(ab.cross(ac),ab.cross(ad));
        const bCross = isEqualSign(bc.cross(ba),bc.cross(bd));

        return cCross && aCross && bCross;
    }

    public initQuadIndices(indices: number[], arr: number[]) {
        for(let i = 0; i < arr.length; i++) {
            indices[i] = arr[i];
        }
    }

    updateVerts(comp: MySprite) {
        const points = this.calculatePoints(comp);
        const indicesArr = this.getIndicesByPoints(points);
        console.log('indicesArr is ',indicesArr);

        this.initQuadIndices(this._renderData.iDatas[0], indicesArr);
        this.updateWorldVerts(comp);
    }

    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;
        }
    }

    updateWorldVerts (comp: MySprite) {
        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 floatsPerVert = this.floatsPerVert;
        let vertexOffset = 0;
        let justTranslate = a === 1 && b === 0 && c === 0 && d === 1;

        const points = this.calculatePoints(comp);
        console.log('points is ',points);

        if (justTranslate) {
            for(let i = 0; i < points.length; i++) {
                verts[i * floatsPerVert] = points[i].x + tx;
                verts[i * floatsPerVert + 1] = points[i].y + ty;
            }
        } else {
            for(let i = 0; i < points.length; i++) {
                verts[i * floatsPerVert] = a * points[i].x + c * points[i].y + tx;
                verts[i * floatsPerVert + 1] = b * points[i].x +  d * points[i].y + ty;
            }
        }
    }

    getBuffer () {
        return cc.renderer._handle.getBuffer('mesh',vfmtPosUvColor);
    }

    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
        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
        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];
        }
    }

    public resetData(comp: MySprite) {
        let points = this.calculatePoints(comp);
        if(!points || points.length < 3) return;

        // 顶点个数根据points的长度
        this.verticesCount = points.length;
        // 索引个数
        this.indicesCount = this.verticesCount + (this.verticesCount - 3) * 2;
        this._renderData['clear']();
        this.initData();
    }

    packToDynamicAtlas(comp: MySprite, frame: any) {
        if (CC_TEST) return;
        
        if (!frame._original && cc.dynamicAtlasManager && frame._texture.packable) {
            let packedFrame = cc.dynamicAtlasManager.insertSpriteFrame(frame);            
            if (packedFrame) {
                frame._setDynamicAtlasFrame(packedFrame);
            }
        }
        let material = comp['_materials'][0];
        if (!material) return;
        
        if (material.getProperty('texture') !== frame._texture) {
            // texture was packed to dynamic atlas, should update uvs
            comp._vertsDirty = true;
            comp._updateMaterial();
        }
    }

    updateRenderData (comp) {
        if(comp._vertsDirty) {
            this.resetData(comp);
            this.updateUVs(comp);
            this.updateVerts(comp);
            this.updateColor(comp,null);
            comp._vertsDirty = false;
        }
    }
}

Cocos Creator 中,你可以使用自定义材质来实现顶点着色渲染。顶点着色渲染是一种在渲染管线中对模型的每个顶点进行颜色计算的技术,通过改变顶点的颜色来实现特定效果。下面是一个简单的示例代码: ```javascript // 在节点上添加一个脚本组件 VertexColor.js cc.Class({ extends: cc.Component, properties: { color: cc.Color.WHITE, // 自定义颜色 }, // 在渲染组件的 updateMaterial 方法中设置材质的属性 updateMaterial: function() { var material = this.getComponent(cc.RenderComponent).getMaterial(0); // 获取渲染组件的材质 material.setProperty('u_color', this.color); // 设置自定义颜色属性 }, // 在 onLoad 方法中注册 updateMaterial 方法到渲染组件的 updateMaterial 方法中 onLoad: function() { this.getComponent(cc.RenderComponent).updateMaterial = this.updateMaterial.bind(this); }, }); ``` 在上述代码中,我们假设节点上有一个渲染组件(如 Sprite、Mesh 等),我们通过自定义脚本组件 `VertexColor.js` 来实现顶点着色渲染。脚本组件中定义了一个 `color` 属性,用于设置自定义颜色。在 `updateMaterial` 方法中,我们获取渲染组件的材质,并设置自定义颜色属性。最后,在 `onLoad` 方法中将 `updateMaterial` 方法注册到渲染组件的 `updateMaterial` 方法中,以便在每帧更新时调用。 然后,你可以在 Cocos Creator 编辑器中将该脚本组件 `VertexColor.js` 添加到需要进行顶点着色渲染的节点上。在脚本组件的属性面板中,可以设置自定义的颜色值。 需要注意的是,顶点着色渲染需要使用支持顶点着色的材质和渲染组件,例如使用自定义的 Shader 或在材质中设置相应的 Uniform 属性等。具体的实现方式和效果可以根据你的需求和场景进行自定义调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值