外观
Lesson 052|Mask、Graphics、Spine、Particle 的特殊渲染成本
Tags: #Creator2.x #Stage04 #Mask #Graphics #Spine #ParticleDifficulty: ⭐⭐⭐⭐☆
真实问题:活动页只有 30 个节点,为什么比 300 个普通 Sprite 更慢
页面节点不多,却同时包含:
text
圆形头像 Mask
动态 Graphics 折线
两套 Spine 角色
全屏 Particle节点计数不能描述这些组件的工作量。普通 Sprite 常是固定四边形;特殊组件可能有额外 Pass、动态网格、骨骼求值、大量粒子和状态切换。
🎯 本课目标
这些组件都能“显示东西”,但渲染路径不同。本课建立快速判断能力:
text
组件看起来是什么
≠
组件实际如何渲染二、Mask
以 Stencil Mask 为例,实际不是“超出范围就不画”一句话:
text
绘制 Mask 形状
→ 写入模板标记
→ 切换 Stencil Test
→ 绘制子树内容
→ 退出时恢复上一级模板状态如果列表中每个头像都有独立 Mask,Renderer 会在普通 UI 之间反复进入和退出模板上下文。即使头像很小,批次也可能被切成许多段。
Mask 通常需要定义裁剪区域,可能使用:
text
Stencil
额外绘制
裁剪几何
独立状态Mask 嵌套和大面积裁剪会增加状态切换与像素处理。
三、Graphics
lineTo、圆弧和贝塞尔曲线只是业务描述,GPU 最终仍需要三角形。描边宽度、连接方式和曲线精度会影响三角化结果:
text
更多路径点
→ 更多线段和连接
→ 更多顶点/索引
→ 更多 CPU 生成与缓冲写入静态路径只生成一次和每帧 clear → 重画 的视觉结果可以相同,CPU 成本却完全不同。
Graphics 运行时生成线段、矩形、圆和路径:
text
业务命令
↓
路径解析
↓
顶点和索引生成
↓
上传或更新几何每帧清空并重画复杂路径会造成 CPU 和顶点更新成本。静态图形应尽量缓存或减少重建。
四、Spine
Spine 一帧至少可以拆成两段:
text
动画更新
→ 时间线采样、骨骼世界变换、插槽、附件、事件
渲染准备
→ 网格顶点/蒙皮、裁剪、Texture、Blend、绘制顺序暂停渲染但继续动画,和暂停动画但保留最后一帧渲染,是区分 CPU 动画成本与 Renderer/GPU 成本的有效对照。只把节点移出屏幕不一定停止骨骼更新。
Spine 可能包含:
- 骨骼层级计算。
- 动画采样。
- 插槽和附件切换。
- 多纹理或多材质。
- 蒙皮顶点更新。
骨骼动画的 CPU 更新和 Renderer 提交都需要测量,不能只看骨骼数量。
五、Particle
粒子成本不能只数“发射了多少个”,还要看:
text
活跃粒子数
× 每粒子顶点数
× 每秒更新频率
→ CPU 与几何规模
活跃粒子数
× 单粒子屏幕面积
× 平均重叠层数
→ Fragment 与 Blend 压力100 个 16×16 小粒子和 100 个 512×512 半透明粒子,DrawCall 可能相近,GPU 像素成本却相差几个数量级。
粒子系统通常带来:
text
粒子生命周期更新
发射和回收
顶点生成
透明混合
大量屏幕覆盖粒子数量不一定等于 DrawCall 数量,但会影响 CPU 更新、顶点量和 Overdraw。
六、为什么这些组件容易打断合批
“特殊”不是固定性能标签,而是这些组件更容易改变普通 Sprite 批次所依赖的条件:
text
Mask:改变 Stencil 上下文
Graphics:产生不同或动态几何
Spine:多附件、纹理、Blend 和网格
Particle:特殊顶点、透明材质和大面积覆盖某个具体组件是否昂贵,仍取决于资源、更新频率、屏幕面积和平台。一个静态小 Graphics 可能完全可接受,一百个每帧重画的复杂路径则是另一回事。
text
特殊顶点格式
不同 Shader
不同纹理
Blend / Stencil
独立更新阶段因此特殊组件应在性能分析中单独归类,而不是和普通 Sprite 混在一起平均计算。
七、案例:滚动列表里的 Mask
text
ScrollView
└── View
└── Content
└── 多个 Item优化方向:
- 减少嵌套 Mask。
- 控制可见 Item 数量。
- 复用 Item,避免不断创建。
- 评估是否可用边界裁剪替代复杂 Mask。
- 真机测量模板、填充和 DrawCall。
八、案例:粒子特效覆盖 UI
粒子可能使用透明材质覆盖大面积屏幕:
text
粒子数量不多
但每个粒子很大
↓
大量 Fragment 和混合
↓
GPU 仍然超时这类问题要看 Overdraw 和 GPU 时间,不要只数粒子对象。
九、常见误区
误区一:Mask 只是限制显示区域,不会增加绘制
实现裁剪通常需要额外状态或处理。
误区二:Graphics 画一条线很便宜,重复画也一样
每帧重建大量路径会带来 CPU 和顶点成本。
误区三:Spine 只需要播放一张图片
它可能持续计算骨骼、附件和蒙皮数据。
误区四:粒子卡顿只看粒子数量
粒子面积、透明层数、Shader 和更新频率同样重要。
十、深入推导:四类成本必须拆开看
Mask
可能需要先绘制遮罩形状、写 Stencil,再绘制子内容并恢复状态。嵌套层级进一步增加状态复杂度。
Graphics
路径、描边、连接和填充需要在 CPU 侧三角化。每帧 clear 后重画复杂路径,会反复生成几何。
Spine
每帧进行动画采样、骨骼/插槽更新、网格变形,再按贴图与 Blend 组织渲染。剪裁、网格附件、多纹理和复杂混合会增加成本。
Particle
包含发射、生命周期、位置/颜色/尺寸更新和大量 Billboard/顶点;大粒子还会制造 Overdraw。
它们分别可能压到 CPU、DrawCall、顶点或 Fragment 阶段。
不要用“这个组件贵不贵”作为最终问题,要拆成四个轴:
| 组件 | CPU 更新 | 几何/顶点 | 提交与状态 | 片元覆盖 |
|---|---|---|---|---|
| Mask | 层级与裁剪管理 | 遮罩几何 | Stencil 进入/退出 | 遮罩与内容覆盖 |
| Graphics | 路径解析、三角化 | 随路径复杂度变化 | 材质与批次边界 | 取决于填充面积 |
| Spine | 动画、骨骼、蒙皮 | 网格附件变化 | 纹理、Blend、剪裁切换 | 取决于角色面积与重叠 |
| Particle | 发射与生命周期模拟 | 活跃粒子顶点 | Texture、Blend、批次 | 粒子尺寸和层叠常主导 |
同一种组件在不同项目里可能落在不同瓶颈。例如静态小 Mask 主要是状态边界,而全屏复杂 Mask 还可能带来明显像素成本;必须先关掉更新、再关掉渲染做对照。
十一、Creator 2.4.x 实验:四组独立开关
组件和资源通过 Creator 编辑器创建/绑定,不手工修改动画、材质、Prefab 或 Effect 序列化文件。
场景
text
MaskGroup
GraphicsGroup
SpineGroup(若项目已有合法测试资源)
ParticleGroup
ControlPanel没有 Spine 测试资源时,不新造或伪造资源;只完成其他三组,并在已有正式项目资源中测 Spine。
SpecialRenderProbe.ts
ts
const { ccclass, property } = cc._decorator;
@ccclass
export default class SpecialRenderProbe extends cc.Component {
@property([cc.Node])
groups: cc.Node[] = [];
setOnly(index: number) {
for (let i = 0; i < this.groups.length; i++) {
this.groups[i].active = i === index;
}
}
setAll(active: boolean) {
for (const group of this.groups) {
group.active = active;
}
}
}Graphics 动态路径探针
ts
const { ccclass, property } = cc._decorator;
@ccclass
export class GraphicsProbe extends cc.Component {
@property(cc.Graphics)
graphics: cc.Graphics = null;
private dynamic = false;
private time = 0;
toggleDynamic() {
this.dynamic = !this.dynamic;
}
update(dt: number) {
if (!this.dynamic) {
return;
}
this.time += dt;
const g = this.graphics;
g.clear();
g.moveTo(-300, 0);
for (let i = 0; i < 100; i++) {
g.lineTo(
-300 + i * 6,
Math.sin(this.time * 3 + i * 0.2) * 80
);
}
g.stroke();
}
}比较静态绘制一次与每帧重绘,记录 CPU 差异。
实验记录
| 组 | CPU Frame | DrawCall | 视觉覆盖 | 特殊变量 |
|---|---|---|---|---|
| Mask | 实测 | 实测 | 小/中 | 嵌套层 |
| Graphics | 实测 | 实测 | 小 | 路径点数/更新频率 |
| Spine | 实测 | 实测 | 中 | 骨骼/插槽/贴图 |
| Particle | 实测 | 实测 | 大 | 活跃粒子/尺寸 |
十二、真实项目案例:滚动列表里每个头像一个 Mask
100 个 Item 均带圆形 Mask,即使屏幕只看到 12 个,项目仍保留全部节点并参与部分更新。
改进组合:
- 使用虚拟列表只创建可见 Item;
- 头像源图提供圆形透明边缘;
- 评估统一 shader 裁剪方案;
- 对不可见 Item 停止动画和粒子;
- 只在确有动态形状需求时保留 Mask;
- 验证替代方案的 Overdraw 和边缘质量。
十三、真实项目案例:Spine 换装后 DrawCall 激增
多个插槽引用不同 Atlas/材质与 Blend Mode,绘制顺序不断切换。优化需要从资源和骨骼内容入手:
- 合并兼容纹理;
- 减少不需要的插槽与透明附件;
- 控制剪裁附件;
- 远处/不可见角色降低更新频率;
- 不播放时暂停动画更新;
- 以目标 Spine 运行时版本验证。
十四、故障诊断
Graphics 静止仍高 CPU
检查是否每帧 clear/stroke、路径数据是否变化、脚本是否重复构造点数组。静态图形应绘制一次或缓存为资源。
Particle DrawCall 不高但 GPU 慢
检查粒子尺寸、数量、重叠、纹理透明留白和 Fragment Shader。
Spine CPU 高
分别关动画更新和渲染,区分骨骼求值与 GPU;检查活跃实例、骨骼/网格复杂度和监听回调。
Mask 引发批次碎片
检查嵌套层级、Mask 子树范围和相邻普通 Sprite 是否被反复切开。
十五、练习、推导答案与版本边界
Spine、Particle、Graphics 和 Mask 的内部实现随 Creator 2.4.x 小版本与原生运行时变化。必须固定资源和设备实测。
- 为什么节点少不代表便宜? 一个节点可生成大量几何、多个 Pass、骨骼计算或海量片元。
- 静态 Graphics 应怎样优化? 避免每帧重建,可绘制一次、缓存或预制为 Sprite 资源。
- Particle 优化为什么同时看 CPU/GPU? CPU 模拟粒子,GPU 处理顶点和重叠片元,两端都可能瓶颈。
- Mask 替换为透明 PNG 一定更快吗? 不一定,可能增加透明像素 Overdraw 和纹理内存;要按目标设备测量。
能力验收
你应该能把四类组件分别放到 CPU 更新、几何生成、提交状态和片元覆盖四个维度中分析;能用独立开关区分成本,并避免用节点数或 DrawCall 单一指标替代证据。
本课总结
text
Mask 可能增加裁剪和状态成本。
Graphics 的动态路径会产生几何更新。
Spine 有骨骼、附件和蒙皮成本。
Particle 同时影响更新、顶点和像素覆盖。
特殊组件应单独测量。十一、下一课预告
text
Lesson053|Overdraw 与 Fill Rate:DrawCall 不高为什么仍然会卡?