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