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

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 方向取反。


http://www.kler.cn/a/585071.html

相关文章:

  • 框架_C语言_数据包解析代码框架
  • 08 | 实现版本号打印功能
  • 《C#上位机开发从门外到门内》2-6:CAN总线通信
  • DAY33 贪心算法Ⅱ
  • 系统架构的评估的系统的质量属性
  • Java学习--Redis
  • 【机器人-基础知识】欧拉角、旋转矩阵和四元数
  • SQL日期处理
  • 如何在PHP中实现OAuth2认证:安全性与可扩展性
  • 【实战ES】实战 Elasticsearch:快速上手与深度实践-6.2.2GDPR数据脱敏处理
  • Visual stdio2022 opencv cude pytroch与yolov8/可视化工具的环境搭建,不搞VIP,我也要当雷锋
  • TDE透明加密:免改造实现SQLServer数据库安全存储
  • Spring Boot集成Mybatis中如何显示日志
  • AutoGen学习笔记系列(十一)Advanced - Magentic-One
  • LeetCode27移除元素
  • 学习路之TP6 --定制workman命令
  • Java×c艹py
  • SpringBoot 根据配置前缀绑定配置:@ConfigurationProperties
  • Python学习第十三天
  • 第三百七十五节 JavaFX教程 - JavaFX表视图