当前位置: 首页 > news >正文

cocos creator使用mesh修改图片为圆形,减少使用mask,j减少drawcall,优化性能

cocos creator版本2.4.11

一个mask占用drawcall 3个以上,针对游戏中技能图标,cd,以及多玩家头像,是有很大优化空间

1.上代码,只适合单独图片的,不适合在图集中的图片

const { ccclass, property } = cc._decorator;

const gfx = cc.gfx;
cc.Class({
    extends: cc.Component,

    properties: {
        radius: 100, // 圆的半径
        segments: 32, // 圆的细分段数(顶点数)
        /**
         * !#en The sprite frame of the sprite.
         * !#zh 精灵的精灵帧
         * @property spriteFrame
         * @type {SpriteFrame}
         * @example
         * sprite.spriteFrame = newSpriteFrame;
         */
          
        spriteFrame: {
            default: null,
            type: cc.SpriteFrame
        },
    },

    onLoad() {
        let renderer = this.node.getComponent(cc.MeshRenderer);
        if (!renderer) {
            renderer = this.node.addComponent(cc.MeshRenderer);
        }

        renderer.mesh = null;
        this.renderer = renderer;
        let builtinMaterial = cc.MaterialVariant.createWithBuiltin("unlit");
        renderer.setMaterial(0, builtinMaterial);
        this._applySpriteFrame();
        this.setMesh();
    },

    setMesh(){
         // 创建 Mesh
         let mesh = new cc.Mesh();
         // 计算顶点和 UV
         let positions = [];
         let uvs = [];
         let indices = [];
         let colors = [];
 
         // 圆心顶点
         positions.push(cc.v2(0, 0)); // 圆心
         uvs.push(cc.v2(0.5, 0.5));  // 圆心 UV
         colors.push(cc.Color.WHITE); // 圆心颜色
 
         // 圆边缘顶点
         for (let i = 0; i <= this.segments; i++) {
             let angle = (i / this.segments) * Math.PI * 2; // 计算角度
             let x = Math.cos(angle) * this.radius; // 计算 x 坐标
             let y = Math.sin(angle) * this.radius; // 计算 y 坐标
 
             positions.push(cc.v2(x, y)); // 添加顶点
             uvs.push(cc.v2((x / this.radius + 1) / 2, 1-(y / this.radius + 1) / 2)); // 添加 UV
             colors.push(cc.Color.WHITE); // 添加颜色
         }
 
         // 设置索引(三角形扇)
         for (let i = 1; i <= this.segments; i++) {
             indices.push(0); // 圆心
             indices.push(i); // 当前顶点
             indices.push(i + 1); // 下一个顶点
         }
 
         mesh.init(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 },
         ]), positions.length, true);
 
         mesh.setVertices(gfx.ATTR_POSITION, positions);
         mesh.setVertices(gfx.ATTR_UV0, uvs);
         mesh.setIndices(indices);
         this.renderer.mesh = mesh;
    },
      // 更新图片
      _applySpriteFrame() {
        // cc.log('_applySpriteFrame');
        if (this.spriteFrame) {
            const renderer = this.renderer;
            let material = renderer._materials[0];
            // Reset material
            let texture = this.spriteFrame.getTexture();
            material.define("USE_DIFFUSE_TEXTURE", true);
            material.setProperty('diffuseTexture', texture);
        }
    }
});

这个js组件,绑定到节点上,把要渲染的spriteFrame挂在上面,运行就可以了,这种方式只适合单独图片,不适合图集中的图片

运行效果,下面是对比了这个图片

说明:这种方式是直接修改图片的mesh网格结构,使用meshRenderer组件,不能挂载sprite组件,使用shader也可以达到效果,但是shader是在Gpu层修改显示,图片形状没有变,这个是运行的时候直接修改形状,而且shader修改的话会有问题,例如打断动态合批,如果项目勾选了动态合批或者图片在图集中,shader修改是无效的

这种方式可以降低mask增加的drawcall

2.工具式的,直接调用,升级版,可以修改图集中的某个图片的显示

const { ccclass, property } = cc._decorator;

const gfx = cc.gfx;
cc.Class({
    extends: cc.Component,

    properties: {
        radius: 100, // 圆的半径
        segments: 32, // 圆的细分段数(顶点数)
        /**
         * !#en The sprite frame of the sprite.
         * !#zh 精灵的精灵帧
         * @property spriteFrame
         * @type {spriteFrame}
         */

        spriteFrame: {
            default: null,
            type: cc.spriteFrame,
        },
    },

    /**设置数据显示 需要等spriteFrame加载完成后调用,可以拿到实际的图片
     * radius: 半径
     * segments: 圆细分段数,越多会越圆滑,但是性能消耗会更大
     * node:节点,这里需要使用mesheRenderer组件,所以需要把sprite剔除
     * isAtlas:是否是图集中的图片
     */
    setDataShow(node, radius, segments, isAtlas) {
        // MeshRenderer
        let renderer = this.node.getComponent(cc.MeshRenderer);
        if (!renderer) {
            renderer = this.node.addComponent(cc.MeshRenderer);
        }
        renderer.mesh = null;
        this.renderer = renderer;
        let builtinMaterial = cc.MaterialVariant.createWithBuiltin("unlit");
        renderer.setMaterial(0, builtinMaterial);
        renderer.enabled = false;

        this.radius = radius;
        this.segments = segments;
        let sp = node.getComponent(cc.Sprite);
        if (sp) {
            this.spriteFrame = sp.spriteFrame;

            node.removeComponent(cc.Sprite);
        }

        // 把图片加载到renderer上的材质
        this.applySpriteFrame();
        // 设置mesh
        if (isAtlas) {// 大图集中的texture
            this.setMeshByAtlas();
        } else {// 单个图片
            this.setMesh();
        }
        // 这里必须延迟一帧,不然不会刷新mesh,显示不出来图片
        setTimeout(() => {
            if(cc.isValid(renderer)){
                renderer.enabled = true;
            }
        }, 100);
    },

    /**更新mesh,在图集中的 */
    setMeshByAtlas() {
        let uv = this.spriteFrame.uv;

        // 创建 Mesh
        let mesh = new cc.Mesh();

        // 计算顶点和 UV
        let positions = [];
        let uvs = [];
        let indices = [];
        let colors = [];

        // 圆心顶点
        positions.push(cc.v2(0, 0)); // 圆心
        uvs.push(cc.v2((uv[6] + uv[0]) / 2, (uv[7] + uv[1]) / 2)); // 圆心 UV(取中心点)
        colors.push(cc.Color.WHITE); // 圆心颜色

        // 圆边缘顶点
        for (let i = 0; i <= this.segments; i++) {
            let angle = (i / this.segments) * Math.PI * 2; // 计算角度
            let x = Math.cos(angle) * this.radius; // 计算 x 坐标
            let y = Math.sin(angle) * this.radius; // 计算 y 坐标

            positions.push(cc.v2(x, y)); // 添加顶点

            // 计算 UV 坐标(根据图集的 UV 信息进行映射)
            let u = (x / this.radius + 1) / 2; // 归一化到 [0, 1]
            let v = (y / this.radius + 1) / 2; // 归一化到 [0, 1]
            let uvX = uv[0] + (uv[2] - uv[0]) * u; // 根据图集 UV 计算实际 UV
            let uvY = uv[1] + (uv[5] - uv[1]) * v; // 根据图集 UV 计算实际 UV
            uvs.push(cc.v2(uvX, uvY)); // 添加 UV

            colors.push(cc.Color.WHITE); // 添加颜色
        }

        // 设置索引(三角形扇)
        for (let i = 1; i <= this.segments; i++) {
            indices.push(0); // 圆心
            indices.push(i); // 当前顶点
            indices.push(i + 1); // 下一个顶点
        }

        mesh.init(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 },
        ]), positions.length, true);

        mesh.setVertices(gfx.ATTR_POSITION, positions);
        mesh.setVertices(gfx.ATTR_UV0, uvs);
        mesh.setIndices(indices);
        this.renderer.mesh = mesh;
    },

    // 更新mesh,单独图片的
    setMesh() {
        // 创建 Mesh
        let mesh = new cc.Mesh();
        // 计算顶点和 UV
        let positions = [];
        let uvs = [];
        let indices = [];
        let colors = [];

        // 圆心顶点
        positions.push(cc.v2(0, 0)); // 圆心
        uvs.push(cc.v2(0.5, 0.5));  // 圆心 UV
        colors.push(cc.Color.WHITE); // 圆心颜色

        // 圆边缘顶点
        for (let i = 0; i <= this.segments; i++) {
            let angle = (i / this.segments) * Math.PI * 2; // 计算角度
            let x = Math.cos(angle) * this.radius; // 计算 x 坐标
            let y = Math.sin(angle) * this.radius; // 计算 y 坐标

            positions.push(cc.v2(x, y)); // 添加顶点
            uvs.push(cc.v2((x / this.radius + 1) / 2, (y / this.radius + 1) / 2)); // 添加 UV
            colors.push(cc.Color.WHITE); // 添加颜色
        }

        // 设置索引(三角形扇)
        for (let i = 1; i <= this.segments; i++) {
            indices.push(0); // 圆心
            indices.push(i); // 当前顶点
            indices.push(i + 1); // 下一个顶点
        }

        mesh.init(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 },
        ]), positions.length, true);

        mesh.setVertices(gfx.ATTR_POSITION, positions);
        mesh.setVertices(gfx.ATTR_UV0, uvs);
        mesh.setIndices(indices);
        this.renderer.mesh = mesh;
    },

    // 更新图片
    applySpriteFrame() {
        // cc.log('_applySpriteFrame');
        if (this.spriteFrame) {
            const renderer = this.renderer;
            let material = renderer._materials[0];
            // Reset material
            material.define("USE_DIFFUSE_TEXTURE", true);
            material.setProperty('diffuseTexture', this.spriteFrame.getTexture());
        }
    },

});

外部调用这个组件的方法,setDataShow传对应的参数就可以,节点上需要挂sprite组件,sprite更新图片或者初始化加载的时候,调用这个方法setDataShow,同时兼容删除节点的sprite组件,如果不想挂载sprite组件,默认直接挂上meshRenderer组件,需要自己修改下代码,把参数node直接改成传对应的spriteFrame图片 

Cocos Creator 的纹理坐标系(UV 坐标系)的 Y 轴方向是 从上到下 的,如果结果图片y是反向的,可以设代码修改uvs中的y的取值

  • 将 v 的计算改为 1 - (y / radius + 1) / 2,即对 Y 方向取反。

相关文章:

  • Linux 进程信息查看
  • docker私有仓库配置
  • π0源码剖析——从π0模型架构的实现(如何基于PaLI-Gemma和扩散策略去噪生成动作),到基于C/S架构下的模型训练与部署
  • 深度学习数值精度详细对比:BF16、FP16、FP32
  • 【商城实战(18)】后台管理系统基础搭建:从0到1构建电商中枢
  • 大空间多人互动技术、大空间LBE、VR大空间什么意思?如何实现?
  • from psbody.mesh import MeshModuleNotFoundError: No module named ‘psbody‘
  • AI算法与应用 全栈开发 前端开发 后端开发 测试开发 运维开发
  • Ubuntu22.04修改root用户并安装cuda
  • 解锁「3D格式转换SDK」HOOPS Exchange高质量B-REP功能的三大应用场景
  • 基于单片机的智慧音乐播放系统研究
  • Java多线程与高并发专题——阻塞队列常用方法与区别
  • 推动人工智能从“通用”向“专用”转变:GAI认证如何助力个人职业生涯
  • 1688店铺所有商品数据接口详解
  • Android 源码下载以及编译指南
  • MongoDB(二) - MongoDB命令详解
  • 【从零开始学习计算机科学】计算机体系结构(一)计算机体系结构、指令、指令集(ISA)与量化评估
  • Vue中vfor循环创建DOM时Key的理解之Vue中的diff算法
  • Android OKHttp缓存模块原理分析
  • 【HarmonyOS Next】鸿蒙应用弹框和提示气泡详解(一)
  • 武汉警方通报一起故意伤害案件:1人死亡,嫌疑人已被抓获
  • 大学2025丨专访西湖大学副校长邓力:如何才能培养“不惧未知”的创新者
  • 俄媒:俄乌代表团抵达谈判会场
  • 高新波任西安电子科技大学校长
  • 南昌上饶领导干部任前公示:2人拟提名为县(市、区)长候选人
  • 美国关税压力下,日本经济一年来首次萎缩