Cesium模型没有动画怎么办?手把手教你通过代码给GLB模型添加动画
前言
在实际项目开发中,我们经常会遇到这样一种情况:
- 拿到一个
.glb模型; - 模型本身没有动画;
- 但是需要让模型中的某些部件运动起来。
例如:
- 无人机螺旋桨旋转
- 雷达天线扫描
- 风机叶片转动
- 机械臂关节运动
- 炮塔旋转
很多人第一反应是:
重新找建模人员制作动画。
实际上,如果模型节点结构合理,Cesium 本身就提供了节点变换能力,我们完全可以通过代码动态控制模型节点,实现动画效果。
本文以无人机螺旋桨旋转 为例,讲解如何使用 nodeTransformations 给一个原本没有动画的 GLB 模型添加动画。

最终效果
实现效果如下:
- 无人机机身保持静止
- 四个螺旋桨持续旋转
- 两两反向旋转
- 不依赖模型自带动画

一、实现原理
Cesium 在加载模型时,可以单独控制模型内部节点。
核心属性:
js
nodeTransformations
官方说明:
text
允许开发者动态修改模型内部节点的
平移(Translation)
旋转(Rotation)
缩放(Scale)
因此我们可以:
text
找到螺旋桨节点
↓
持续修改节点旋转
↓
形成旋转动画
整个过程:
text
GLB模型
↓
获取节点名称
↓
NodeTransformationProperty
↓
CallbackProperty实时计算旋转角度
↓
Quaternion旋转
↓
形成动画
二、前提条件
1. 模型节点必须独立
例如模型结构:
text
Drone
├── Body
├── shell_8-DC_Shell
├── shell_9-DC_Shell
├── shell_10-DC_Shell
└── shell_11-DC_Shell
每个螺旋桨都必须是单独节点。
如果整个模型只有一个 Mesh:
text
Drone
那么无法单独控制螺旋桨。
2. 旋转轴必须正确
这是很多人踩坑最多的地方。
错误情况:
text
节点原点在模型中心
旋转后:
text
螺旋桨绕飞机中心公转
而不是:
text
绕自身中心旋转
因此需要提前调整模型节点 Pivot。
例如:
text
shell_8-DC_Shell
其节点原点必须位于:
text
螺旋桨中心
否则动画一定不对。
三、确定需要控制的节点
先定义四个螺旋桨名称:
js
const propellerNames = [
"shell_8-DC_Shell",
"shell_10-DC_Shell",
"shell_11-DC_Shell",
"shell_9-DC_Shell"
];
这些名称来自 GLB 模型内部节点名称。
例如:
text
shell_8-DC_Shell
shell_9-DC_Shell
shell_10-DC_Shell
shell_11-DC_Shell
四、创建节点动画配置
先创建一个对象:
js
const nodeTransformations = {};
用于保存所有节点动画。
结构类似:
js
{
node1: xxx,
node2: xxx,
node3: xxx
}
后面直接传给:
js
model.nodeTransformations
即可。
五、为每个节点创建旋转动画
遍历节点
js
propellerNames.forEach((name, i) => {
});
设置旋转方向
无人机通常需要相邻螺旋桨反向旋转。
例如:
js
const dir = i % 2 === 0 ? 1 : -1;
结果:
text
第1个 顺时针
第2个 逆时针
第3个 顺时针
第4个 逆时针
创建节点变换属性
js
nodeTransformations[name] =
new Cesium.NodeTransformationProperty({
rotation: ...
});
这里表示:
text
仅修改旋转
不修改位置
不修改缩放
六、实时计算旋转角度
为什么使用 CallbackProperty
动画需要每一帧都更新。
因此不能写死:
js
rotation: xxx
而应该:
js
new Cesium.CallbackProperty(...)
让 Cesium 每一帧重新计算。
获取运行时间
js
const t =
Cesium.JulianDate.secondsDifference(
time,
viewer.clock.startTime
);
作用:
text
当前时间 - 开始时间
得到:
text
已经运行了多少秒
例如:
text
0秒
1秒
2秒
3秒
...
根据时间计算旋转角度
js
dir * angularSpeed * t
例如:
js
const angularSpeed = 12;
则:
text
1秒 12弧度
2秒 24弧度
3秒 36弧度
随着时间增长不断旋转。
七、生成四元数旋转
Cesium 内部使用 Quaternion 表示旋转。
代码:
js
Cesium.Quaternion.fromAxisAngle(
Cesium.Cartesian3.UNIT_Z,
dir * angularSpeed * t
);
含义:
text
绕Z轴旋转
旋转角度 = dir * angularSpeed * t
UNIT_Z是什么
js
Cesium.Cartesian3.UNIT_Z
等价于:
js
(0,0,1)
表示:
text
绕Z轴旋转
如果模型方向不同怎么办
有些模型:
text
X轴朝上
则应该:
js
Cesium.Cartesian3.UNIT_X
有些模型:
text
Y轴朝上
则应该:
js
Cesium.Cartesian3.UNIT_Y
需要根据实际模型测试。
八、加载模型
最后创建实体:
js
const vehicleEntity =
viewer.entities.add({
position:
Cesium.Cartesian3.fromDegrees(
116.397,
39.907,
100
),
model: {
uri: "./air_pivot_fixed.glb",
runAnimations: false,
nodeTransformations:
nodeTransformations,
minimumPixelSize: 100,
scale: 10
}
});
关键参数说明
runAnimations
js
runAnimations: false
关闭模型自带动画。
因为这里:
text
动画完全由代码驱动
nodeTransformations
js
nodeTransformations
将前面配置好的节点动画绑定到模型。
scale
js
scale: 10
模型缩放倍数。
minimumPixelSize
js
minimumPixelSize: 100
保证远距离模型不会太小。
九、锁定视角观察效果
js
viewer.trackedEntity =
vehicleEntity;
作用:
text
相机跟随模型
方便观察动画效果。
十、性能分析
很多人担心:
text
每帧更新会不会卡?
实际上:
text
4个节点
每帧计算4个四元数
开销极小。
即使:
text
几十个节点
也基本不会成为性能瓶颈。
真正影响性能的通常是:
- 模型面数
- 贴图大小
- 实体数量
而不是节点旋转。
常见问题
问题1:螺旋桨围着飞机转
原因:
text
节点Pivot位置错误
解决:
text
将节点原点移动到螺旋桨中心
问题2:螺旋桨不转
检查:
js
runAnimations
和:
js
nodeTransformations
是否正确配置。
同时确认:
text
节点名称是否正确
问题3:旋转方向不对
修改:
js
UNIT_X
UNIT_Y
UNIT_Z
尝试不同轴向。
完整代码
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script src="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Widgets/widgets.css" rel="stylesheet" />
<style>
html,
body,
#map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
Cesium.Ion.defaultAccessToken =
"YOUR_TOKEN";
const viewer = new Cesium.Viewer("map", {
shouldAnimate: true,
});
const propellerNames = [
"shell_8-DC_Shell",
"shell_10-DC_Shell",
"shell_11-DC_Shell",
"shell_9-DC_Shell"
];
const angularSpeed = 12;
const nodeTransformations = {};
propellerNames.forEach((name, i) => {
const dir = i % 2 === 0 ? 1 : -1;
nodeTransformations[name] =
new Cesium.NodeTransformationProperty({
rotation:
new Cesium.CallbackProperty(
(time) => {
const t =
Cesium.JulianDate.secondsDifference(
time,
viewer.clock.startTime
);
return Cesium.Quaternion.fromAxisAngle(
Cesium.Cartesian3.UNIT_Z,
dir * angularSpeed * t
);
},
false
),
});
});
const vehicleEntity =
viewer.entities.add({
position:
Cesium.Cartesian3.fromDegrees(
116.397,
39.907,
100
),
model: {
uri: "./air_pivot_fixed.glb",
runAnimations: false,
nodeTransformations:
nodeTransformations,
minimumPixelSize: 100,
scale: 10.0
}
});
viewer.trackedEntity =
vehicleEntity;
</script>
</body>
</html>
无人机模型
总结
当 GLB 模型没有动画时,不一定要重新制作动画文件。
利用 Cesium 提供的 nodeTransformations、NodeTransformationProperty、CallbackProperty 和 Quaternion,就能够直接通过代码控制模型内部节点,实现:
- 螺旋桨旋转
- 雷达扫描
- 风机转动
- 机械臂运动
- 云台旋转
这种方式最大的优点是:
text
动画逻辑完全由代码控制
无需重新导出模型
运行时可动态调整速度和方向
对于工业数字孪生、无人机仿真、设备监控等场景,非常实用。