深度解析 Cesium 测试资产 AnimatedMorphCube:glTF 2.0 Morph Targets 变形动画的完整技术拆解
【免费下载链接】cesiumAn open-source JavaScript library for world-class 3D globes and maps :earth_americas:项目地址: https://gitcode.com/GitHub_Trending/ce/cesium
本篇文章以 Cesium 仓库内的 glTF 2.0 测试资产 Animated Morph Cube 为核心,逐层拆解其"薄片(thin)与斜坡(angle)两个 Morph Target 之间循环变形"的动画实现,并延伸到 Cesium 引擎对 Morph Targets 的加载、权重驱动与 GPU 顶点着色管线,帮助你同时掌握"模型资产结构"与"引擎底层原理"两个层面的完整知识。
一、模型资产概述:两个 Morph Target 的变形动画
AnimatedMorphCube 是 Cesium 仓库 Specs/Data/Models/glTF-2.0/AnimatedMorphCube 下的标准 glTF 2.0 测试模型。根据其 README 的描述,这个立方体模型内置了两个 Morph Target(形态目标):
thin(薄片):将立方体的顶点沿特定方向移动,使其在视觉上变成"超薄"的扁平形态;angle(斜坡):将立方体的顶点移动,使其构成一个斜坡(ramp)形状。
模型的动画循环在这两种状态之间平滑地来回变形。它不依赖骨骼动画(Skinning),而是完全通过 Morph Target 权重(weights)驱动顶点位移实现,是验证 glTF 2.0 Morph Target 与权重动画支持的标准样本。
该模型资产在仓库中以两种变体存在:
| 变体 | 目录 | 生成工具 | 二进制大小 |
|---|---|---|---|
| 标准版 | glTF/AnimatedMorphCube.gltf+AnimatedMorphCube.bin | glTF Tools for Unity | 4284 字节 |
| 量化版 | glTF-Quantized/AnimatedMorphCube.gltf+AnimatedMorphCube.bin | gltfpack 0.13 | 1700 字节 |
二、glTF 标准资产的结构拆解:从 Accessor 到 Animation
标准版 glTF/AnimatedMorphCube.gltf 是一个自包含引用外部.bin缓冲文件的 JSON 描述,我们可以按 glTF 2.0 的层级逐层分析。
2.1 顶点数据与 Accessor 布局
模型的基础网格(mesh,名为Cube)包含 24 个顶点(count: 24),三角形索引数量为 36(count: 36,类型SCALAR,componentType 5123 即UNSIGNED_SHORT)。primitive 的属性(attributes)引用了三个 Accessor:
| 语义 | Accessor 索引 | 类型 | 说明 |
|---|---|---|---|
POSITION | 2 | VEC3 / FLOAT | 基础位置,min/max 约为 ±0.01,即边长为 0.02 的单位立方体 |
NORMAL | 0 | VEC3 / FLOAT | 法线 |
TANGENT | 1 | VEC4 / FLOAT | 切线(含 W 分量) |
关键的 Morph Target 数据位于 primitive 的targets数组中,glTF 2.0 规定 targets 中的每个对象是与基础属性同结构的增量数据——即每个 target 存储的是"顶点需要被偏移的量",而不是目标位置的绝对值:
"targets": [ { "NORMAL": 3, "POSITION": 4, "TANGENT": 5 }, { "NORMAL": 6, "POSITION": 7, "TANGENT": 8 } ]- 第一个 target(
thin)的 POSITION 增量 Accessor(索引 4)的 min 为[0, 0, 0],max 为[0, 0.01893253, 0]——所有增量仅出现在Y 轴方向,说明"薄片"形态是沿 Y 轴方向压缩/拉伸顶点产生的; - 第二个 target(
angle)的 POSITION 增量 Accessor(索引 7)的 min 为[0, 0, 0],max 为[0, 0.0198908355, 0]——同样只有 Y 轴增量,但各顶点的增量幅值分布不同,从而把顶面推成斜坡状。
每个 target 同时携带对应的NORMAL与TANGENT增量,保证变形过程中光照法线能够同步更新,这是高质量 Morph Target 资产的必要设计。
2.2 Mesh 默认权重与 Animation 动画
mesh 层级声明了默认权重:
"weights": [0.0, 0.0]即模型初始状态完全处于基础形态。真正的变形由动画驱动,动画名为Square:
- channels:一个 channel,
sampler: 0,target.node: 0,target.path: "weights"——即动画目标是对节点 0 的 Morph Target 权重属性进行关键帧驱动; - samplers:
interpolation: "LINEAR"(线性插值),input为 accessor 10(127 个时间采样点,min 0.0,max 约 4.2 秒),output为 accessor 11(254 个值,即 127 帧 × 2 个权重,恰好与两个 Morph Target 一一对应)。
因此整个动画循环就是:thin权重从 0→1 拉出薄片形态,再切换到angle权重 0→1 拉出斜坡形态,再回到原点,如此循环。动画作用于节点 0(名为AnimatedMorphCube),该节点还携带了旋转四元数[0, 0.7071067, -0.7071068, 0](绕 X 轴旋转 90°)和 100 倍的等比缩放,使立方体在场景中以合适的朝向与尺寸展示。
2.3 材质与元信息
材质(Material)使用 PBR 金属粗糙度模型:baseColorFactor为[0.6038274, 0.6038274, 0.6038274, 1.0](中性灰),metallicFactor: 0,roughnessFactor: 0.5。资产头部标注generator: "glTF Tools for Unity"、version: "2.0",说明这是由 Unity 的 glTF 导出工具生成的官方标准样本。
三、Morph Target 变形原理:权重加权求和
Morph Target 的核心数学原理非常简单——最终顶点位置等于基础位置加上每个 target 增量按权重加权求和的结果。这一点在 Cesium 引擎源码中体现得淋漓尽致。
在 MorphTargetsPipelineStage.js 中,引擎为每个 Morph Target 属性生成如下 GLSL 累加代码(见addMorphTargetAttributeDeclarationAndFunctionLine,第 179-189 行):
morphedPosition += u_morphWeights[i] * a_targetPosition_i;其中:
a_targetPosition_i是第 i 个 Morph Target 的 POSITION 增量属性;u_morphWeights[i]是第 i 个权重 uniform;morphedPosition初始值为基础position。
法线、切线同理分别累加a_targetNormal_i、a_targetTangent_i。这一实现与 glTF 2.0 规范中"最终顶点 = 基础顶点 + Σ(权重 × target 增量)"的定义完全一致。
四、Cesium 渲染管线中的 Morph Target 处理
4.1 管线阶段:MorphTargetsPipelineStage
Cesium 采用管线(Pipeline Stage)方式组织模型加载与渲染资源构建。当 glTF 的 primitive 携带targets时,MorphTargetsPipelineStage.js 会执行如下工作(见process方法,第 45-103 行):
- 在顶点着色器中定义宏
HAS_MORPH_TARGETS; - 遍历 primitive 的所有 Morph Target 及其属性,把每个增量属性注册为顶点属性(
a_targetPosition_i等); - 只支持 POSITION、NORMAL、TANGENT 三种语义,其余属性直接跳过(见第 65-72 行注释 "Cesium only supports morph targets for positions, normals, and tangents");
- 声明 uniform
u_morphWeights[N](N 为 Morph Target 数量)并注册到 uniformMap; - 注入
MorphTargetsStageVS顶点着色器代码。
这些注入的 GLSL 函数(getMorphedPosition/getMorphedNormal/getMorphedTangent)被 MorphTargetsStageVS.glsl 中的morphTargetsStage函数调用,在顶点着色阶段替换positionMC、normalMC、tangentMC,从而在 GPU 上完成变形计算:
void morphTargetsStage(inout ProcessedAttributes attributes) { vec3 positionMC = attributes.positionMC; attributes.positionMC = getMorphedPosition(positionMC); #ifdef HAS_NORMALS vec3 normalMC = attributes.normalMC; attributes.normalMC = getMorphedNormal(normalMC); #endif #ifdef HAS_TANGENTS vec3 tangentMC = attributes.tangentMC; attributes.tangentMC = getMorphedTangent(tangentMC); #endif }4.2 运行时节点:ModelRuntimeNode
动画运行期间,权重值存储在运行时节点上。ModelRuntimeNode.js 中定义了morphWeights属性的 setter(第 445-462 行):它要求新数组长度与原始权重数组完全一致,否则抛出DeveloperError("value must have the same length as the original weights array."),随后逐元素拷贝。这一校验确保了动画采样器输出的权重数量与 Morph Target 数量严格匹配(AnimatedMorphCube 为 2 个),从机制上避免了越界访问。
在 glTF 加载时(ModelRuntimeNode.js第 514-516 行),若节点声明了morphWeights,则直接slice()拷贝为运行时初始权重,对应本资产 mesh 上的"weights": [0.0, 0.0]。
五、测试用例:仓库如何验证 Morph Target 能力
AnimatedMorphCube 在 Cesium 仓库中承担着双重测试职责:
GltfLoaderSpec.js 第 1044-1047 行:"loads AnimatedMorphCube" 测试用例通过
loadGltf加载该资产并断言组件加载成功,验证了完整 glTF 结构(含 Morph Targets 与权重动画)的解析路径。MorphTargetsPipelineStageSpec.js 第 153 行起:"processes morph target with POSITION, NORMAL, and TANGENT" 测试用例直接加载
AnimatedMorphCube/glTF/AnimatedMorphCube.gltf,并断言:- 顶点着色器生成了 3 个属性(POSITION、NORMAL、TANGENT 各一的 Morph Target 增量);
- 生成了顶点函数
getMorphedPosition,函数体内包含morphedPosition += u_morphWeights[0] * a_targetPosition_0;与return morphedPosition;; - 定义了宏
HAS_MORPH_TARGETS; - 声明了 uniform
u_morphWeights[1](该用例只取第一个 primitive,因此为 1),且 uniformMap 能正确返回运行时权重。
这正是验证本资产"两个 target 各带 POSITION/NORMAL/TANGENT"结构与 Cesium 管线输出对应关系的直接证据。
PrimitiveStatisticsPipelineStageSpec.js也引用了该资产用于统计管线相关断言。
六、量化版本:KHR_mesh_quantization 与 gltfpack 优化
glTF-Quantized/AnimatedMorphCube.gltf 由 gltfpack 0.13 生成,声明了扩展KHR_mesh_quantization(同时列入extensionsUsed与extensionsRequired),展示了真实的量化优化手法:
| 数据 | 标准版 | 量化版 |
|---|---|---|
| POSITION / NORMAL 分量 | FLOAT(5126) | 5120(BYTE,normalized: true) |
| Morph Target POSITION 增量 | FLOAT(5126) | 5122(SHORT,min/max 保留量化范围) |
| 权重输出 | FLOAT(5126) | 5121(UNSIGNED_BYTE,normalized: true) |
| 索引 | 5123(UNSIGNED_SHORT) | 5123(UNSIGNED_SHORT) |
| 二进制体积 | 4284 字节 | 1700 字节(压缩约 60%) |
量化版将浮点属性替换为整型 + 归一化(normalized)表示,并在 accessor 的 min/max 中保留量化前的取值范围(例如 Morph Target 增量 max 从[0, 0.0189, 0]变为[0, 5188, 0],配合节点 scale 与 min/max 完成反量化还原),将顶点数据从 12 字节/分量压缩到 1~2 字节/分量。同时量化版把节点的 100 倍缩放拆分为两层(父节点AnimatedMorphCube旋转 + 子节点携带微缩比例与平移),属于 gltfpack 的常见重组优化。由于KHR_mesh_quantization被声明为extensionsRequired,加载端(如 Cesium)必须支持该扩展才能正确渲染此变体。
七、在 Cesium 中实际使用 Morph Target 资产
在 Cesium 中加载 AnimatedMorphCube 这类模型,标准方式是通过Model.fromGltf或实体系统的model图形(如Cesium.ModelGraphics)指定 glTF/glb 资源路径。加载后:
- 动画自动播放:glTF 内嵌的权重动画(本资产中的
Square动画)会随模型动画系统播放,即model.activeAnimations.add({ name: "Square" })一类接口即可驱动 thin/angle 之间的循环变形; - 手动驱动权重:可以通过运行时节点接口直接设置
morphWeights数组(如[1.0, 0.0]完全展示薄片形态、[0.0, 1.0]完全展示斜坡形态),注意新数组长度必须与 Morph Target 数量一致,否则ModelRuntimeNode会抛出 DeveloperError; - 光照同步:由于资产为每个 target 提供了 NORMAL/TANGENT 增量,变形过程中法线与切线会同步更新,光照与法线贴图效果保持正确。
八、许可信息
根据模型目录内 README 的声明,该资产由 Microsoft 在知识共享 CC0(公有领域)许可下发布:在法律允许的最大范围内,Microsoft 放弃对该资产的所有版权及相关邻接权利,因此它可被自由用于测试、二次分发与商业项目,这也是它作为 Cesium 测试样本能够随仓库分发的许可基础。
总结
AnimatedMorphCube 虽然只是一个"立方体",却是理解 glTF 2.0 Morph Target 全链路的最佳样本:从资产侧看,它完整展示了targets增量数据结构、默认weights、weights路径动画与 LINEAR 插值;从引擎侧看,Cesium 通过MorphTargetsPipelineStage将其转化为u_morphWeightsuniform 与顶点着色器加权累加,通过ModelRuntimeNode.morphWeights做运行时驱动与长度校验,并通过 MorphTargetsPipelineStageSpec.js 与 GltfLoaderSpec.js 两条测试路径反复验证。掌握这一样本,你便能举一反三地理解人脸表情、布料褶皱、地形过渡等一切基于 Morph Target 的 glTF 资产在 Cesium 中的工作方式。
【免费下载链接】cesiumAn open-source JavaScript library for world-class 3D globes and maps :earth_americas:项目地址: https://gitcode.com/GitHub_Trending/ce/cesium
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考