先上效果,用了六个参数不同的 RoateSprite,DC总共为4,分别是:背景 + 文字 + RotateSprite + Sprite。RotateSprite 合批成功!
前言
为啥要合批
减少DC
什么是自定义顶点参数
通过 几何体实例化 特性(GPU Instancing)可使 GPU 批量绘制模型相同且材质相同的渲染对象。如果我们想在不打破这一特性的情况下单独修改某个对象的显示效果,就需要通过自定义几何体实例化属性。
UI(Sprite) 怎么你了?
按照文档中的说法,需要用到 MeshRenderer
来设置自定义属性。
但 UI(Sprite) 并没有这个组件,也就无法按照文档来实现自定义 UI shader 的合批。
但事情还没完,根据社区成员的分享,我们有更 hardcore 一点的方式来完成这些事情,接下来是具体的操作步骤。
具体操作步骤
接下来以一个制造旋转效果的 shader 为例子,提供了这些参数的设置:
- 旋转速度 float
- 旋转中心位置 vec2
- 逆时针/顺时针 bool
- 扭曲度 float
并在使用的贴图一致的前提下并且参数不同的值都能够合批。
最终项目可以从 GITHUB 获取。
CCC版本:3.8.0
深入了解可以阅读后续的 参考资料 及 源码阅读。
第一步、shader(.effect)
1. 将 builtin-sprite.effect 复制一份出来,重命名为 rotate-sprite.effect.
- builtin-sprite.effect 是 Sprite 组件默认使用的 shader(.effect)。
在 Assets面板 中搜索 builtin-sprite 即可找到 builtin-sprite.effect。
复制一份到项目的 assets 中。
重命名为 rotate-sprite.effect。
2. 打开 rotate-sprite.effect,在 顶点着色器 sprite-vs 上定义顶点参数,并传递给 片元着色器 sprite-fs。
顶点着色器 sprite-vs 如下,其中如 in vec3 a_position
这类以 a_
就是使用的顶点参数。
CCProgram sprite-vs %{
...
// 😀 使用的 顶点参数
in vec3 a_position;
in vec2 a_texCoord;
in vec4 a_color;
// 😀 传递给 片元着色器 的变量
out vec4 color;
out vec2 uv0;
vec4 vert () {
...
// 😀 给变量赋值
uv0 = a_texCoord;
#if SAMPLE_FROM_RT
CC_HANDLE_RT_SAMPLE_FLIP(uv0);
#endif
color = a_color;
return pos;
}
}%
添加我们要用到的顶点参数后。
CCProgram sprite-vs %{
...
in vec3 a_position;
in vec2 a_texCoord;
in vec4 a_color;
// 😀😀😀 开始 😀😀😀
// 旋转速度
in float a_rotateSpeed;
// 旋转中心
in vec2 a_rotateCenter;
// 是否顺时针旋转
in float a_clockwise;
// 扭曲度
in float a_distort;
// 😀😀😀 结束 😀😀😀
...
}%
因为旋转效果要在 片元着色器 sprite-fs 中实现,因此我们要把这些 顶点参数的值 传递给 片元着色器 sprite-fs。
在 顶点着色器 sprite-vs 中定义对应的 out
输出变量。
CCProgram sprite-vs %{
...
out vec4 color;
out vec2 uv0;
// 😀😀😀 开始 😀😀😀
// 旋转速度
out float rotateSpeed;
// 旋转中心
out vec2 rotateCenter;
// 是否顺时针旋转
out float clockwise;
// 扭曲度
out float distort;
// 😀😀😀 结束 😀😀😀
...
}%
在 顶点着色器 sprite-vs 的函数中完成对 out
输出变量 的赋值。
CCProgram sprite-vs %{
...
vec4 vert () {
...
uv0 = a_texCoord;
#if SAMPLE_FROM_RT
CC_HANDLE_RT_SAMPLE_FLIP(uv0);
#endif
color = a_color;
// 😀😀😀 开始 😀😀😀
rotateSpeed = a_rotateSpeed;
rotateCenter = a_rotateCenter;
clockwise = a_clockwise;
distort = a_distort;
// 😀😀😀 结束 😀😀😀
return pos;
}
}%
以上步骤,就完成了在 顶点着色器 sprite-vs 上的需要编辑内容。
3. 在 片元着色器 sprite-fs 上接收从 顶点着色器 sprite-vs 传递过来的顶点参数。
片元着色器 sprite-fs 如下。其中 in vec4 color
这样的 in
输入变量,从 顶点着色器 sprite-vs 中接收了对应变量。
CCProgram sprite-fs %{
precision highp float;
#include <builtin/internal/embedded-alpha>
#include <builtin/internal/alpha-test>
// 😀 从 sprite-vs 中接收的变量 part1
in vec4 color;
#if USE_TEXTURE
// 😀 从 sprite-vs 中接收的变量 part2
in vec2 uv0;
#pragma builtin(local)
layout(set = 2, binding = 12) uniform sampler2D cc_spriteTexture;
#endif
vec4 frag () {
vec4 o = vec4(1, 1, 1, 1);
#if USE_TEXTURE
o *= CCSampleWithAlphaSeparated(cc_spriteTexture, uv0);
#if IS_GRAY
float gray = 0.2126 * o.r + 0.7152 * o.g + 0.0722 * o.b;
o.r = o.g = o.b = gray;
#endif
#endif
o *= color;
ALPHA_TEST(o);
return o;
}
}%
增加对应我们新增顶点参数的 in
输入变量。
CCProgram sprite-fs %{
...
in vec4 color;
// 😀😀😀 开始 😀😀😀
// 旋转速度
in float rotateSpeed;
// 旋转中心
in vec2 rotateCenter;
// 是否顺时针旋转
in float clockwise;
// 扭曲度
in float distort;
// 😀😀😀 结束 😀😀😀
#if USE_TEXTURE
in vec2 uv0;
#pragma builtin(local)
layout(set = 2, binding = 12) uniform sampler2D cc_spriteTexture;
#endif
...
}%
4. 使用参数并实现效果。
如何实现不是本文关注点,这里直接给出完成后的 片元着色器 sprite-fs 的代码。
CCProgram sprite-fs %{
precision highp float;
#include <builtin/uniforms/cc-global>
#include <builtin/internal/embedded-alpha>
#include <builtin/internal/alpha-test>
in vec4 color;
// 旋转速度
in float rotateSpeed;
// 旋转中心
in vec2 rotateCenter;
// 是否顺时针旋转
in float clockwise;
// 扭曲度
in float distort;
#define PI 3.1415926535897932384626433832795
#if USE_TEXTURE
in vec2 uv0;
#pragma builtin(local)
layout(set = 2, binding = 12) uniform sampler2D cc_spriteTexture;
#endif
float yOflineOnX(float k, float b, float x) {
return k * x + b;
}
float xOflineOnY(float k, float b, float y) {
return (y - b) / k;
}
bool isBetween(float value, float min, float max) {
return value >= min && value <= max;
}
vec2 findFarthestFittingPoint(vec2 dir, vec2 rotateCenter) {
vec2 farFitPoint = vec2(0.0);
float len4fit = 0.0;
float xSign = sign(dir.x);
float slope = dir.y / (xSign * max(abs(dir.x), 0.00000001));
slope = clamp(slope, -9999999999.9, 9999999999.9);
float yIntercept = rotateCenter.y - slope * rotateCenter.x;
yIntercept = clamp(yIntercept, -9999999999.9, 9999999999.9);
vec2 checkVal = vec2(0.0, yOflineOnX(slope, yIntercept, 0.0));
vec2 check2center = checkVal - rotateCenter;
if (isBetween(checkVal.y, 0.0, 1.0) && dot(dir, check2center) > 0.0) {
farFitPoint = checkVal;
len4fit = length(check2center);
}
checkVal = vec2(1.0, yOflineOnX(slope, yIntercept, 1.0));
check2center = checkVal - rotateCenter;
float len4check = length(check2center);
if (isBetween(checkVal.y, 0.0, 1.0) && dot(dir, check2center) > 0.0 && len4check > len4fit) {
farFitPoint = checkVal;
len4fit = len4check;
}
checkVal = vec2(xOflineOnY(slope, yIntercept, 0.0), 0.0);
check2center = checkVal - rotateCenter;
len4check = length(check2center);
if (isBetween(checkVal.x, 0.0, 1.0) && dot(dir, check2center) > 0.0 && len4check > len4fit) {
farFitPoint = checkVal;
len4fit = len4check;
}
checkVal = vec2(xOflineOnY(slope, yIntercept, 1.0), 1.0);
check2center = checkVal - rotateCenter;
len4check = length(check2center);
if (isBetween(checkVal.x, 0.0, 1.0) && dot(dir, check2center) > 0.0 && len4check > len4fit) {
farFitPoint = checkVal;
len4fit = len4check;
}
return farFitPoint;
}
vec2 rotateVector(vec2 vec, float angle) {
return vec2(
vec.x * cos(angle) - vec.y * sin(angle),
vec.x * sin(angle) + vec.y * cos(angle)
);
}
float easeOutBounce(float x){
float n1 = 7.5625 * distort;
float d1 = 2.75;
if (x < 1.0 / d1) {
return n1 * x * x;
} else if (x < 2.0 / d1) {
return n1 * (x -= 1.5 / d1) * x + 0.75;
} else if (x < 2.5 / d1) {
return n1 * (x -= 2.25 / d1) * x + 0.9375;
} else {
return n1 * (x -= 2.625 / d1) * x + 0.984375;
}
}
float easeInCirc(float x) {
return 1.0 - sqrt(1.0 - pow(x, 2.0 * distort));
}
vec4 frag () {
vec4 o = vec4(1.0);
#if USE_TEXTURE
float rotateRad = sign(clockwise) * cc_time.x * PI * rotateSpeed;
// 通过 uv转换 来实现旋转
vec2 dir = uv0 - rotateCenter;
vec2 farFitPoint = findFarthestFittingPoint(dir, rotateCenter);
float percent = length(dir) / length(farFitPoint - rotateCenter);
vec2 dirRotated = rotateVector(dir, rotateRad);
farFitPoint = findFarthestFittingPoint(dirRotated, rotateCenter);
vec2 uvRotated = rotateCenter + (farFitPoint - rotateCenter) * easeInCirc(percent);
o *= CCSampleWithAlphaSeparated(cc_spriteTexture, uvRotated);
#if IS_GRAY
float gray = 0.2126 * o.r + 0.7152 * o.g + 0.0722 * o.b;
o.r = o.g = o.b = gray;
#endif
#endif
o *= color;
ALPHA_TEST(o);
return o;
}
}%
5. 创建 rotate-sprite.mat,并使用 rotate-sprite.effect。
创建新的 material。
重命名为 rotate-sprite.mat。
修改材质所使用的 shader(.effect),选中 rotate-sprite.effect。
并激活 “USE TEXTURE”,然后保存设置即可。
第二步、编写 RotateSprite.ts (一)
1. 新建一个 ts 脚本,命名为 RotateSprite.ts。
2. 删掉 start 和 update,并其继承 Sprite
3. 编写顶点参数相关逻辑
回顾 shader(.effect) 定义的顶点参数。
对应列出如下表,其中 gfx.Format
的值可以 查表 得。
字段 | glsl类型 | gfx.Format |
---|---|---|
in vec3 a_position | vec3 | RGB32F |
in vec2 a_texCoord | vec2 | RG32F |
in vec4 a_color | vec4 | RGBA32F |
in float a_rotateSpeed | float | R32F |
in vec2 a_rotateCenter | vec2 | RG32F |
in float a_clockwise | float | R32F |
in float a_distort | float | R32F |
参考源码,对照该表,复写 requestRenderData
。(请按需 引入 import)
@ccclass('RotateSprite')
export class RotateSprite extends Sprite {
public requestRenderData(drawInfoType?: __private._cocos_2d_renderer_render_draw_info__RenderDrawInfoType): RenderData {
// 😀😀😀 开始 😀😀😀
const data = RenderData.add([
new gfx.Attribute(gfx.AttributeName.ATTR_POSITION, gfx.Format.RGB32F),
new gfx.Attribute(gfx.AttributeName.ATTR_TEX_COORD, gfx.Format.RG32F),
new gfx.Attribute(gfx.AttributeName.ATTR_COLOR, gfx.Format.RGBA32F),
new gfx.Attribute("a_rotateSpeed", gfx.Format.R32F),
new gfx.Attribute("a_rotateCenter", gfx.Format.RG32F),
new gfx.Attribute("a_clockwise", gfx.Format.R32F),
new gfx.Attribute("a_distort", gfx.Format.R32F),
]);
// 😀😀😀 结束 😀😀😀
data.initRenderDrawInfo(this, drawInfoType);
this._renderData = data;
return data;
}
}
增加 顶点参数 对应的成员和属性。(请按需 引入 import)
@ccclass('RotateSprite')
export class RotateSprite extends Sprite {
@property({ type: CCFloat })
private _rotateSpeed: number = 1;
@property({ type: Vec2 })
private _rotateCenter: Vec2 = new Vec2(0.5, 0.5);
@property({ type: CCBoolean })
private _isClockWise: boolean = true;
@property({ type: CCFloat })
private _distort: number = 1;
public get rotateSpeed(): number {
return this._rotateSpeed;
}
@property({ type: CCFloat })
public set rotateSpeed(value: number) {
if (this._rotateSpeed == value) return;
this._rotateSpeed = value;
}
public get rotateCenter(): Vec2 {
return this._rotateCenter;
}
@property({ type: Vec2 })
public set rotateCenter(value: Vec2) {
if (this._rotateCenter.equals(value)) return;
this._rotateCenter.set(value);
}
public get isClockWise(): boolean {
return this._isClockWise;
}
@property({ type: CCBoolean })
public set isClockWise(value: boolean) {
if (this._isClockWise == value) return;
this._isClockWise = value;
}
public get distort(): number {
return this._distort;
}
@property({ type: CCFloat })
public set distort(value: number) {
if (this._distort == value) return;
this._distort = value;
}
...
}
到此为止,针对 RotateSprite
的编写暂告一段落,待我们完成组装器 rotateAssembler.ts 的编写后再来补充后续。
第三步、编写 rotateAssembler.ts
1. 将 simple Assembler 复制出来。
打开 CCC 源码文件夹。
在编辑器中打开 resources 文件夹。
准确导航到 Sprite 使用的 simple.ts。注意参考下图确定路径。
将文件复制到项目 assets 中。
重命名为 rotateAssembler.ts。
2. 修正代码报错。
首先将变量名从 simple
修改为 rotateAssembler
首先将原本的 引入 import 全部注释掉
然后增加如下 引入 import
import { DynamicAtlasManager, IAssembler, IRenderData, RenderData, Sprite } from "cc";
针对 dynamicAtlasManager.packToDynamicAtlas(sprite, frame);
的报错。
将其改为 DynamicAtlasManager.instance.packToDynamicAtlas(sprite, frame);
即可。
针对这种 类型定义 的报错,直接把它们改成 any
。
至此应该就没有报错了。
3. 按照 rotate-sprite.effect 的 顶点参数定义 补充代码。
再次回顾 shader(.effect) 定义的顶点参数。
对应列出表。
字段 | 占位 | 偏移 |
---|---|---|
in vec3 a_position | 3 | 0 |
in vec2 a_texCoord | 2 | 3 |
in vec4 a_color | 4 | 5 |
in float a_rotateSpeed | 1 | 9 |
in vec2 a_rotateCenter | 2 | 10 |
in float a_clockwise | 1 | 12 |
in float a_distort | 1 | 13 |
首先,如果 updateUVs
在设置顶点参数值时写死了偏移值。
那么我们需要修改 updateUVs
成下面的样子。
updateUVs(sprite: Sprite) {
if (!sprite.spriteFrame) return;
const renderData = sprite.renderData!;
const vData = renderData.chunk.vb;
const uv = sprite.spriteFrame.uv;
// 😀😀😀 开始 😀😀😀
let offset = 3;
let count = 0;
for (let i = 0; i < 4; i++, offset += renderData.floatStride) {
vData[offset] = uv[count++];
vData[offset + 1] = uv[count++];
}
// 😀😀😀 结束 😀😀😀
},
然后是 in float a_rotateSpeed
旋转速度,占位 1,偏移位 9。
增加对应函数 updateRotateSpeed(sprite: RotateSprite)
完成对 a_rotateSpeed
的赋值。
updateRotateSpeed(sprite: RotateSprite) {
const renderData = sprite.renderData!;
const vData = renderData.chunk.vb;
let offset = 9;
for (let i = 0; i < 4; i++, offset += renderData.floatStride) {
vData[offset] = sprite.rotateSpeed;
}
}
然后是 in vec2 a_rotateCenter
旋转中心,占位 2,偏移位 10。
增加对应函数 updateRotateCenter(sprite: RotateSprite)
完成对 a_rotateCenter
的赋值。
updateRotateCenter(sprite: RotateSprite) {
const renderData = sprite.renderData!;
const vData = renderData.chunk.vb;
let offset = 10;
for (let i = 0; i < 4; i++, offset += renderData.floatStride) {
vData[offset] = sprite.rotateCenter.x;
vData[offset + 1] = sprite.rotateCenter.y;
}
},
然后是 in float a_clockwise
是否顺时针旋转,占位 1,偏移位 12。
增加对应函数 updateaClockwise(sprite: RotateSprite)
完成对 a_clockwise
的赋值。
updateaClockwise(sprite: RotateSprite) {
const renderData = sprite.renderData!;
const vData = renderData.chunk.vb;
let offset = 12;
for (let i = 0; i < 4; i++, offset += renderData.floatStride) {
vData[offset] = sprite.isClockWise ? 1 : -1;
}
},
然后是 in float a_distort
扰乱程度,占位 1,偏移位 13。
增加对应函数 updateaDistort(sprite: RotateSprite)
完成对 a_distort
的赋值。
updateaDistort(sprite: RotateSprite) {
const renderData = sprite.renderData!;
const vData = renderData.chunk.vb;
let offset = 13;
for (let i = 0; i < 4; i++, offset += renderData.floatStride) {
vData[offset] = sprite.distort;
}
},
4. 增加 updateCustomVertexData
方法
export const rotateAssembler: IAssembler = {
...
updateCustomVertexData(sprite: Sprite) {
this.updateRotateSpeed(sprite);
this.updateRotateCenter(sprite);
this.updateaClockwise(sprite);
this.updateaDistort(sprite);
},
...
}
5. 修改 updateRenderData
方法 和 fillBuffers
方法
export const rotateAssembler: IAssembler = {
...
updateRenderData(sprite: Sprite) {
const frame = sprite.spriteFrame;
// dynamicAtlasManager.packToDynamicAtlas(sprite, frame);
DynamicAtlasManager.instance.packToDynamicAtlas(sprite, frame);
this.updateUVs(sprite);// dirty need
//this.updateColor(sprite);// dirty need
const renderData = sprite.renderData;
if (renderData && frame) {
if (renderData.vertDirty) {
this.updateVertexData(sprite);
// 😀😀😀 开始 😀😀😀
this.updateCustomVertexData(sprite);
// 😀😀😀 结束 😀😀😀
}
renderData.updateRenderData(sprite, frame);
}
},
...
fillBuffers(sprite: Sprite, renderer: any) {
if (sprite === null) {
return;
}
const renderData = sprite.renderData!;
const chunk = renderData.chunk;
if (sprite.node.hasChangedFlags || renderData.vertDirty) {
// const vb = chunk.vertexAccessor.getVertexBuffer(chunk.bufferId);
this.updateWorldVerts(sprite, chunk);
// 😀😀😀 开始 😀😀😀
this.updateCustomVertexData(sprite);
// 😀😀😀 结束 😀😀😀
renderData.vertDirty = false;
}
...
}
...
}
到此为止,针对 rotateAssembler.ts 的编写结束,接下来我们继续回到 RotateSprite
进行编写。
第四步、编写 RotateSprite.ts (二)
再次回顾 shader(.effect) 定义的顶点参数。
1. 增加自定义顶点参数的对应更新方法。
@ccclass('RotateSprite')
export class RotateSprite extends Sprite {
@property({ type: CCFloat })
public set rotateSpeed(value: number) {
if (this._rotateSpeed == value) return;
this._rotateSpeed = value;
// 😀😀😀 开始 😀😀😀
this._updateRotateSpeed();
// 😀😀😀 结束 😀😀😀
}
...
@property({ type: Vec2 })
public set rotateCenter(value: Vec2) {
if (this._rotateCenter.equals(value)) return;
this._rotateCenter.set(value);
// 😀😀😀 开始 😀😀😀
this._updateRotateCenter();
// 😀😀😀 结束 😀😀😀
}
...
@property({ type: CCBoolean })
public set isClockWise(value: boolean) {
if (this._isClockWise == value) return;
this._isClockWise = value;
// 😀😀😀 开始 😀😀😀
this._updateaClockwise();
// 😀😀😀 结束 😀😀😀
}
...
@property({ type: CCFloat })
public set distort(value: number) {
if (this._distort == value) return;
this._distort = value;
// 😀😀😀 开始 😀😀😀
this._updateDistort();
// 😀😀😀 结束 😀😀😀
}
...
private _updateRotateSpeed() {
if (this._assembler) {
this._assembler.updateRotateSpeed(this);
this.markForUpdateRenderData();
}
}
private _updateRotateCenter() {
if (this._assembler) {
this._assembler.updateRotateCenter(this);
this.markForUpdateRenderData();
}
}
private _updateaClockwise() {
if (this._assembler) {
this._assembler.updateaClockwise(this);
this.markForUpdateRenderData();
}
}
private _updateDistort() {
if (this._assembler) {
this._assembler.updateaDistort(this);
this.markForUpdateRenderData();
}
}
}
2. 增加 _updateCustomVertexData
方法。
@ccclass('RotateSprite')
export class RotateSprite extends Sprite {
...
private _updateCustomVertexData() {
this._updateRotateSpeed();
this._updateRotateCenter();
this._updateaClockwise();
this._updateDistort();
}
...
}
3. 复写 _flushAssembler
方法。
参考源码,对照该表,复写 _flushAssembler
。(请按需 引入 import)
@ccclass('RotateSprite')
export class RotateSprite extends Sprite {
...
protected _flushAssembler() {
// 😀😀😀 开始 😀😀😀
const assembler = rotateAssembler;
// 😀😀😀 结束 😀😀😀
if (this._assembler !== assembler) {
this.destroyRenderData();
this._assembler = assembler;
}
if (!this._renderData) {
if (this._assembler && this._assembler.createData) {
this._renderData = this._assembler.createData(this);
this._renderData!.material = this.getRenderMaterial(0);
this.markForUpdateRenderData();
if (this.spriteFrame) {
this._assembler.updateUVs(this);
}
this._updateColor();
// 😀😀😀 开始 😀😀😀
this._updateCustomVertexData();
// 😀😀😀 结束 😀😀😀
}
}
// 😀😀😀 注释掉下面几行 😀😀😀
// // Only Sliced type need update uv when sprite frame insets changed
// if (this._spriteFrame) {
// if (this._type === SpriteType.SLICED) {
// this._spriteFrame.on(SpriteFrame.EVENT_UV_UPDATED, this._updateUVs, this);
// } else {
// this._spriteFrame.off(SpriteFrame.EVENT_UV_UPDATED, this._updateUVs, this);
// }
// }
}
...
}
4. 增加一个小处理,否则在移动端上会显示错误。
@ccclass('RotateSprite')
export class RotateSprite extends Sprite {
...
public start(): void {
this.scheduleOnce(() => {
this._updateCustomVertexData();
}, 0);
}
...
}
第五步、测试
1. 使用 RotateSprite
。
新建一个 Sprite 对象。
删掉原有的 Sprite 组件。
添加 RotateSprite 组件。
将 CustomMaterial 设置为 rotate-sprite.mtl;
并将 SpriteFrame 改成要用的图片,这里我使用一个叫 door 的图片。
自定义参数对外接口。
能在 Scene 中看到这个。
2. 关闭所用图片的 Packable
关掉这个 RotateSprite 所用图片的 Packable。
否则针对 uv 的转换会因为合图逻辑变得异常。
3. 玩起来吧!
旋转速度2
旋转速度3
调整扰乱度
4. DC测试
搭了一个这样的环境
其中文字的 CacheMode 用了 CHAR
测试,用了六个参数不同的 RoateSprite,DC总共为4,分别是:背景 + 文字 + RotateSprite + Sprite(原图)。RotateSprite 合批成功!
参考资料
资料1
来源:动态生成图片的需求
用户:homym(tkhoi01281)
3.x 版自定参数我是利用createMesh方法去生成ui,因为createMesh就有自定义顶点参数的方法
这个改动其实是可以弄一个新sprite 来继承老spirte, 然后把引擎里的simple.ts,splice.ts 等assembler 拷一份出来, 放到自己项目, 然后新sprite 复写一下assembler的初始化改为取你魔改的simple.ts,splice.ts就能弄一个新的sprite跟老的sprite一起用又不用改动引擎, 唯一缺点是新sprite和老sprite不能共同一个material
针对提问 “您好, 我也是想在3.x中使用自定义顶点合批。你是使用的 utils.createMesh 方法吗。”
不是,3.x版以sprite为例,需先继续Sprite,然后复写函数requestRenderData,这个父函数可以查看引擎源码的ui-renderer.ts ,其中RenderData.add([添加你想要的参数]) 比如:
RenderData.add([
new gfx.Attribute(gfx.AttributeName.ATTR_POSITION, gfx.Format.RGB32F),
new gfx.Attribute(gfx.AttributeName.ATTR_TEX_COORD, gfx.Format.RG32F),
new gfx.Attribute(gfx.AttributeName.ATTR_COLOR, gfx.Format.RGBA32F)
])
最后就是我上面回复的处理,把需要值塞到buffer里。
资料2
来源:【分享】自定义渲染合批之自定义顶点格式(附 Demo 和引擎源码解读)
用户:GT(caogtaa)
介绍了在 CocosCreator2.x 中怎么完成如题需求。