Skip to content

Lesson 042|Assembler 如何生成顶点、UV、颜色和索引数据? ​

Tags: #Creator2.x #Stage04 #Assembler #Vertex #UVDifficulty: ⭐⭐⭐⭐☆


真实问题:圆形进度条每帧变化,为什么比移动节点贵 ​

技能冷却使用 Filled Sprite,每帧更新 fillRange。另一组图标只改变父节点位置。节点数相同,前者的 CPU 数据准备成本却可能更高。

移动节点主要改变变换结果;Filled 几何的可见扇区发生变化,Assembler 可能需要重新计算顶点位置、UV 和索引范围。

Assembler 可以理解成:

把组件语义翻译成 GPU 能消费的几何数据。

🎯 本课目标 ​

Renderer 不能直接理解“这是一张头像”。它需要结构化的几何数据。本课解释:

  • Assembler 的职责。
  • 顶点、UV、颜色和索引分别是什么。
  • 为什么 Sprite 类型会影响几何生成。
  • 顶点数据变化和材质变化有什么区别。

二、Assembler 是什么 ​

可以把 Assembler 理解为:

把渲染组件的业务属性转换成 Renderer 能提交的几何数据。

输入:

text
SpriteFrame
节点尺寸和 Anchor
颜色和透明度
Fill / Sliced / Tiled 参数
世界变换

输出:

text
顶点
UV
颜色
索引

三、四边形需要哪些数据 ​

一个普通 Sprite 可以用四个顶点表示:

text
v0 ───── v1
│       │
│       │
v3 ───── v2

两个三角形可以通过索引连接:

text
0, 1, 2
0, 2, 3

每个顶点通常还带有:

text
position
uv
color

四、顶点位置 ​

顶点位置通常先在 Sprite 的本地空间生成:

text
左下:(-width * anchor.x, -height * anchor.y)
右上:( width * (1-anchor.x), height * (1-anchor.y))

之后再由世界矩阵和 Camera 变换到渲染空间。

这样同一套本地几何可以被不同 Node Transform 复用。


五、UV 坐标 ​

UV 告诉 Shader:

从纹理的哪个位置采样像素。

text
纹理左下:uv = (0, 0)
纹理右上:uv = (1, 1)

SpriteFrame 如果只是图集中的一块区域,Assembler 需要把区域矩形换算成对应 UV,而不是总是使用完整纹理范围。

图像上下方向、纹理旋转和平台坐标约定都可能影响 UV 计算。


六、顶点颜色 ​

顶点颜色可以携带:

text
Node.color
Opacity
组件颜色
渐变或局部颜色

Shader 通常会把顶点颜色和采样到的纹理颜色相乘或按材质规则组合。

因此改颜色不一定要重新上传一张纹理,但可能需要更新顶点缓冲数据。


七、索引为什么存在 ​

索引告诉 GPU 如何复用顶点组成三角形:

text
顶点数组:v0、v1、v2、v3
索引:0,1,2, 0,2,3

复用顶点可以减少重复数据。复杂的 Filled、Tiled 和 Mask 几何会产生更多顶点和索引。


八、不同 Sprite 类型的生成差异 ​

text
Simple:固定矩形
Sliced:按边界拆分多个区域
Tiled:重复 UV 或生成重复几何
Filled:根据比例裁剪几何

Assembler 不只是复制一份四边形,它需要根据组件属性生成对应形状。


九、更新成本从哪里来 ​

可能触发 Assembler 更新:

  • 尺寸改变。
  • Anchor 改变。
  • SpriteFrame 改变。
  • Fill 参数改变。
  • 顶点颜色改变。
  • 自定义渲染属性改变。

仅仅移动 Node 通常主要影响变换,但具体是否重建几何还要看组件和引擎缓存策略。


十、案例:Filled 进度条 ​

ts
this.progressBar.fillRange = value;

可能发生:

text
Fill 参数改变
    ↓
Assembler 重新计算可见区域
    ↓
更新顶点或索引
    ↓
Renderer 使用新的几何

如果进度条数量很大且每帧都更新,需要评估更新频率和显示数量。


十一、常见误区 ​

误区一:顶点就是屏幕像素 ​

顶点还要经过矩阵、光栅化和 Fragment Shader 才形成像素。

误区二:UV 表示世界坐标 ​

UV 是纹理采样坐标。

误区三:改变颜色一定重新生成完整几何 ​

可能只更新顶点颜色或材质输入,具体看实现。

误区四:DrawCall 数量等于顶点数量 ​

一个 DrawCall 可以提交很多顶点,二者是不同指标。


十二、深入推导:用一个四边形理解顶点与索引 ​

假设一个宽 100、高 50、Anchor 在中心的 Simple Sprite,不考虑旋转缩放,它的局部顶点可以写成:

text
v0 = (-50, -25)
v1 = ( 50, -25)
v2 = ( 50,  25)
v3 = (-50,  25)

如果 Frame 占据独立 Texture 的完整区域,概念 UV 是:

text
uv0 = (0, 0)
uv1 = (1, 0)
uv2 = (1, 1)
uv3 = (0, 1)

两个三角形可以使用索引:

text
0, 1, 2
0, 2, 3

索引中的 0 和 2 被复用,所以不必为第二个三角形再复制两个完全相同的顶点。实际顶点顺序、坐标原点和 UV 方向以 Creator 2.4.x 当前 Assembler 为准,这个例子用于理解数据关系,而不是要求硬编码内部数组。

如果 Anchor 改成左下角,局部顶点范围会从 [-50, 50] × [-25, 25] 变为接近 [0, 100] × [0, 50]。Node Position 没变,几何相对 Position 的分布却变了。

Simple Sprite 通常可用四个顶点和两个三角形表达:

text
v3 ----- v2
|       / |
|     /   |
|   /     |
v0 ----- v1

一种索引排列:

text
triangle 1: 0, 1, 2
triangle 2: 0, 2, 3

每个顶点至少需要关联:

text
position:画在哪里
uv:采样纹理哪里
color:怎样调制颜色/透明度

具体顶点格式、分量和内存布局以 Creator 2.4.x 当前渲染实现为准。

十三、为什么索引可以复用顶点 ​

如果两个三角形各自保存三个顶点,需要六份顶点;索引让它们共享对角线端点,只保存四份顶点和六个较小索引。

对复杂网格:

text
顶点缓冲:属性数据
索引缓冲:三角形如何引用顶点

减少重复只是一个收益;索引还明确拓扑与绘制顺序。

十四、Creator 2.4.x 实验:用 Sprite Type 观察几何变化 ​

实验不访问私有 Assembler 字段,只通过公开组件行为和 Profiler 推断数据更新。

场景 ​

创建三组各 100 个 Sprite:

text
SimpleGroup
SlicedGroup
FilledGroup

三组使用同一 Atlas/材质,尺寸相近。

AssemblerCostProbe.ts ​

ts
const { ccclass, property } = cc._decorator;

@ccclass
export default class AssemblerCostProbe extends cc.Component {
    @property([cc.Sprite])
    filledSprites: cc.Sprite[] = [];

    @property(cc.Node)
    movingRoot: cc.Node = null;

    private animateFill = false;
    private animateMove = false;
    private time = 0;

    toggleFill() {
        this.animateFill = !this.animateFill;
    }

    toggleMove() {
        this.animateMove = !this.animateMove;
    }

    update(dt: number) {
        this.time += dt;

        if (this.animateFill) {
            const value = (Math.sin(this.time) + 1) * 0.5;
            for (const sprite of this.filledSprites) {
                sprite.fillRange = value;
            }
        }

        if (this.animateMove) {
            this.movingRoot.x = Math.sin(this.time) * 50;
        }
    }
}

对照方法 ​

  1. 全静止,记录基线。
  2. 只动画一个父节点位置。
  3. 只动画 100 个 Filled Sprite。
  4. 降低 Filled 更新频率,再比较视觉与帧时间。
  5. 保持 DrawCall 相近,观察 CPU 数据准备是否不同。

实验不能推出所有设备固定倍率;它用于证明“同 DrawCall、同节点数也可能有不同几何更新成本”。

十五、Sliced、Tiled、Filled 怎样改变几何 ​

三者解决的问题不同:

text
Sliced
→ 保持四角不拉伸,中间区域伸缩
→ 需要把矩形划成九个区域

Tiled
→ 按原块重复铺满目标尺寸
→ 尺寸越大,重复单元和几何可能越多

Filled
→ 只显示一定比例或角度
→ fillStart / fillRange 改变可见边界

这就是为什么“所有 Sprite 都是四个顶点”不成立。Simple 常能用四顶点表达,Sliced、Tiled 和 Filled 可能根据类型和参数生成不同几何。

圆形 Filled 每帧更新时,问题也不只是赋值:

text
fillRange 改变
→ 重新求圆弧边界
→ 更新顶点/UV/索引
→ 写入批次缓冲

如果 200 个冷却图标同时更新,即使 DrawCall 仍然很少,CPU 几何更新也可能成为瓶颈。

Sliced ​

九宫格把矩形拆成多个区域,角不拉伸、边单轴拉伸、中间拉伸。需要比 Simple 更多顶点和索引。

Tiled ​

按区域重复纹理,节点越大、tile 越小,可能需要更多格子和几何;实际实现和限制以版本为准。

Filled ​

根据 Horizontal、Vertical 或 Radial 等模式改变可见几何/UV。Radial 的边界穿过哪些角会影响生成结果。

这解释了组件语义如何进入 Assembler,而不是“所有 Sprite 都只是四个点”。

十六、顶点颜色如何进入最终像素 ​

四个顶点可以携带不同颜色。Vertex Shader 把颜色传给光栅化阶段,三角形内部的颜色被插值,再交给 Fragment Shader 与纹理采样结果组合:

text
顶点颜色
→ 光栅化插值
→ 每个片元得到颜色
→ textureColor × vertexColor
→ Blend 到目标

因此多个 Sprite 颜色不同,不必然为每个对象复制 Material。如果颜色可以进入兼容的顶点格式,它们仍可能共享 Shader、Texture 和 Render State。

Node/Sprite 颜色通常被编码到顶点属性,Fragment Shader 再与纹理采样相乘:

text
finalColor ≈ textureColor × vertexColor

实际公式还受材质、预乘 Alpha、颜色空间和 shader 影响。

大量节点每帧改颜色,可能更新顶点数据;这与只在 shader 中使用统一参数又是不同成本模型。

十七、真实项目故障:200 个圆形冷却同时更新 ​

现象 ​

团战界面有 200 个单位技能图标,每帧设置 Radial Filled fillRange。DrawCall 不高,CPU 却明显上升。

根因 ​

每个图标都持续要求几何/UV 更新,即使剩余时间在两帧之间的视觉差异几乎不可见。

修复 ​

  • 冷却显示以 10~20 Hz 更新,而不是强制每渲染帧;
  • 只更新屏幕内或重要单位;
  • 数值未跨显示阈值时不写属性;
  • 评估 shader 方案,但同时考虑材质实例与合批;
  • 对比 CPU、DrawCall 和视觉误差。

十八、三种错误优化 ​

直接改私有顶点数组 ​

版本升级或组件刷新会覆盖,且绕过 dirty/批次协议。

认为减少顶点一定减少 DrawCall ​

顶点数与批次数是两个维度。大量小批次即使每批四顶点,CPU 提交仍可能高。

为避免重建永远缓存所有几何 ​

缓存会占内存,并且在尺寸、Frame、颜色或材质变化时需要失效策略。

十九、练习、推导答案与版本边界 ​

Assembler 是 Creator 2.4.x 渲染内部扩展点,课程主线只讲职责。自定义 Assembler 必须固定引擎版本并阅读对应源码,不能把私有字段当跨版本 API。

  1. 四边形为何通常用六个索引? 两个三角形各需要三个索引,并共享四个顶点。
  2. UV 与 position 分别解决什么? position 决定几何位置,UV 决定该顶点对应 Texture 的采样位置。
  3. Filled 每帧更新为何可能比移动父节点贵? Filled 可能重建每个组件的局部几何/UV,父移动更多是共享变换链更新。
  4. 顶点少能否保证 GPU 快? 不能。还受片元覆盖、shader、纹理带宽、状态切换和 DrawCall 影响。

能力验收 ​

你应该能手写一个四边形的四个顶点与六个索引,解释位置、UV、颜色如何进入 Shader,并通过 Sprite Type 与更新频率实验区分几何重建成本和 DrawCall。

本课总结 ​

text
Assembler 是组件和 Renderer 之间的翻译层。
顶点描述几何位置。
UV 描述纹理采样位置。
颜色携带顶点表现参数。
索引描述三角形连接关系。

十四、下一课预告 ​

text
Lesson043|Label 为什么通常比 Sprite 更复杂?