Cesium模型没有动画怎么办?手把手教你通过代码给GLB模型添加动画!

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 提供的 nodeTransformationsNodeTransformationPropertyCallbackPropertyQuaternion,就能够直接通过代码控制模型内部节点,实现:

  • 螺旋桨旋转
  • 雷达扫描
  • 风机转动
  • 机械臂运动
  • 云台旋转

这种方式最大的优点是:

text 复制代码
动画逻辑完全由代码控制
无需重新导出模型
运行时可动态调整速度和方向

对于工业数字孪生、无人机仿真、设备监控等场景,非常实用。

相关推荐
用户83134859306982 天前
Vue3 + Cesium 实现城市 3D 场景下雪特效(按钮开关控制下雪启停)
cesium
BJ-Giser8 天前
CesiumJS升级全新VFX特效粒子系统
前端·可视化·cesium
白嫖叫上我8 天前
Cesium抗锯齿处理
cesium
白嫖叫上我8 天前
Cesium地球风格切换、昼夜交替效果
cesium
用户83134859306989 天前
Vue3 + Cesium 实现热气球第一人称自动飞行(支持手机端)
cesium
青山Coding10 天前
Cesium应用(六):三维地形中坡度分析的实现过程
前端·cesium
爱喝铁观音的谷力景辉13 天前
在Cesium中实现带箭头方向路线样式的技术详解
javascript·cesium
Nian.Baikal14 天前
Cesium 3D Tiles 加载与优化实战
前端·cesium
青山Coding18 天前
Cesium应用(五):通视分析,解锁三维场景的”无遮挡“视野
前端·cesium