第五章:Cesium 1.129 加载与渲染 3D 模型(glTF/glb)

在 Cesium 应用中,3D 模型(如建筑、设备、车辆模型)是实现数字孪生、场景可视化的核心要素。本章针对 Cesium 1.129 版本,详细讲解主流 3D 模型格式(glTF/glb)的加载方法、样式调整、交互配置,以及旧版本常见的兼容性问题解决方案。

5.1 3D 模型格式基础
Cesium 1.129 对 3D 模型的支持以 glTF 2.0 格式为核心,该格式是 Khronos 集团推出的开源 3D 模型标准,具有轻量化、跨平台的特点,主要分为两种后缀:

格式 特点 适用场景
glTF(.gltf) 文本格式,模型数据与纹理、动画文件分离(需单独引用纹理图) 需修改模型参数(如材质、动画)的场景
glb(.glb) 二进制格式,将模型、纹理、动画打包为单个文件 快速部署、减少网络请求的场景(推荐优先使用)
Cesium 1.129 不支持 glTF 1.0 及其他私有格式(如 FBX、OBJ),需提前将模型转换为 glTF 2.0 格式(可使用 Blender、MeshLab 等工具转换)。

5.2 加载单个静态 3D 模型
以加载一个「建筑模型(glb 格式)」为例,演示 Cesium 1.129 中 3D 模型的基础加载流程,包括模型定位、缩放、旋转等核心配置。

5.2.1 准备工作
获取 / 转换 glTF 模型:

可从 Sketchfab(免费开源模型库)下载 glb 格式模型,或用 Blender 将 FBX/OBJ 模型转换为 glTF 2.0;
将模型文件保存至 src/assets/models/building.glb(确保文件大小 < 10MB,避免加载卡顿)。
核心 API 说明:
Cesium 1.129 通过 Cesium.Model.fromGltf() 加载 glTF/glb 模型,返回 Model 实例后添加到场景的 primitives 中(区别于实体 entities,模型属于图元层级)。

5.2.2 完整代码示例

<template>
  <div class="cesium-container">
    <div id="cesiumContainer" class="cesium-viewer"></div>
    <div class="control-panel">
      <button @click="loadSingleModel">加载建筑模型</button>
      <button @click="clearModel">清除模型</button>
      <button @click="rotateModel">旋转模型</button>
    </div>
  </div>
</template>
 
<script>
export default {
  name: 'Cesium3DModelFixed',
  data() {
    return {
      viewer: null,
      buildingModel: null,
      isRotating: false,
      rotateAngle: 0,
    };
  },
  mounted() {
    this.initViewer();
  },
  methods: {
    initViewer() {
      this.viewer = new Cesium.Viewer('cesiumContainer', {
        animation: false,
        baseLayerPicker: false,
        fullscreenButton: false,
        geocoder: false,
        homeButton: false,
        infoBox: true,
        sceneModePicker: false,
        selectionIndicator: true,
        timeline: false,
        navigationHelpButton: false,
        terrainProvider: new Cesium.CesiumTerrainProvider({
          url: 'https://assets.agi.com/stk-terrain/world',
        }),
        imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
          url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer',
        }),
      });
      this.viewer._cesiumWidget._creditContainer.style.display = 'none';
      this.viewer.camera.setView({
        destination: Cesium.Cartesian3.fromDegrees(116.404, 39.915, 500),
        orientation: {
          heading: Cesium.Math.toRadians(0),
          pitch: Cesium.Math.toRadians(-45),
          roll: 0,
        },
      });
    },
 
    loadSingleModel() {
      this.clearModel();
 
      const modelOptions = {
        url: require('@/assets/models/building.glb'),
        modelMatrix: this.getModelMatrix(116.404, 39.915, 0, 10),
        minimumPixelSize: 64,
        maximumScale: 2000,
        customShader: new Cesium.CustomShader({
          uniforms: {
            color: {
              type: Cesium.UniformType.VEC4,
              value: new Cesium.Color(0.8, 0.6, 0.4, 1.0),
            },
          },
          fragmentShaderText: `
            void main() {
              gl_FragColor = czm_material.diffuse * uniforms.color;
            }
          `,
        }),
      };
 
      Cesium.Model.fromGltf(this.viewer.scene.context, modelOptions)
        .then((model) => {
          this.buildingModel = model;
          this.viewer.scene.primitives.add(model);
          this.viewer.flyTo(model, {
            duration: 2,
            offset: new Cesium.HeadingPitchRange(
              0,
              Cesium.Math.toRadians(-30),
              300
            ),
          });
          console.log('3D模型加载成功');
        })
        .catch((error) => {
          console.error('模型加载失败:', error);
          alert('模型加载失败,请检查文件路径和格式(需为glTF 2.0)');
        });
    },
 
    getModelMatrix(lon, lat, height, scale) {
      const position = Cesium.Cartesian3.fromDegrees(lon, lat, height);
      const translateMatrix = Cesium.Matrix4.fromTranslation(position);
      const scaleMatrix = Cesium.Matrix4.fromScale(
        new Cesium.Cartesian3(scale, scale, scale)
      );
      return Cesium.Matrix4.multiply(
        translateMatrix,
        scaleMatrix,
        new Cesium.Matrix4()
      );
    },
 
    rotateModel() {
      if (!this.buildingModel) return;
      this.isRotating = !this.isRotating;
 
      if (this.isRotating) {
        this.viewer.clock.onTick.addEventListener(this.handleRotation);
      } else {
        this.viewer.clock.onTick.removeEventListener(this.handleRotation);
      }
    },
 
    handleRotation: function () {
      if (!this.buildingModel) return;
      this.rotateAngle += 0.01;
      const rotateMatrix = Cesium.Matrix4.fromRotationZ(this.rotateAngle);
      const translateScaleMatrix = this.getModelMatrix(116.404, 39.915, 0, 10);
      const finalMatrix = Cesium.Matrix4.multiply(
        translateScaleMatrix,
        rotateMatrix,
        new Cesium.Matrix4()
      );
      this.buildingModel.modelMatrix = finalMatrix;
    }.bind(this),
 
    clearModel() {
      if (this.buildingModel) {
        this.viewer.scene.primitives.remove(this.buildingModel);
        this.buildingModel = null;
      }
      if (this.isRotating) {
        this.viewer.clock.onTick.removeEventListener(this.handleRotation);
        this.isRotating = false;
        this.rotateAngle = 0;
      }
    },
  },
  beforeDestroy() {
    this.clearModel();
    if (this.viewer) {
      this.viewer.destroy();
    }
  },
};
</script>
 
<style scoped>
.cesium-container {
  width: 100vw;
  height: 100vh;
  position: relative;
  margin: 0;
  padding: 0;
}
 
.cesium-viewer {
  width: 100%;
  height: 100%;
}
 
.control-panel {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  display: flex;
  gap: 12px;
  background: rgba(0, 0, 0, 0.7);
  padding: 12px 20px;
  border-radius: 8px;
}
 
button {
  padding: 8px 16px;
  background: #4caf50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
}
 
button:hover {
  background: #45a049;
}
 
button:disabled {
  background: #cccccc;
  cursor: not-allowed;
}
</style>


一键获取完整项目代码
html

5.2.3 1.129 版本关键注意事项
本地模型路径问题:
Vue2 中加载本地 glb/gltf 模型,必须通过 require() 解析路径(如 require(‘@/assets/models/building.glb’)),直接使用相对路径(./assets/models/building.glb)会导致 Cesium 无法找到文件,报「404 错误」。

模型矩阵配置:

Cesium 1.129 中模型的定位、缩放、旋转需通过 modelMatrix 矩阵实现,不支持新版本的 position/scale 直接配置;
矩阵组合顺序为「先缩放 → 再旋转 → 最后平移」,顺序错误会导致模型位置偏移。
材质修改限制:
1.129 版本的 customShader 仅支持基础颜色修改,不支持复杂纹理替换、PBR 材质参数调整,若需自定义材质,建议在 Blender 中预处理模型纹理,而非在 Cesium 中动态修改。

5.3 加载带动画的 3D 模型
部分 glTF 模型包含动画(如机械臂运动、车辆行驶),Cesium 1.129 支持加载并控制动画播放,以下以「带旋转动画的风机模型(gltf 格式)」为例演示。

5.3.1 核心 API 说明
model.activeAnimations:获取模型的动画集合;
Cesium.ModelAnimation.play():播放指定动画;
Cesium.ModelAnimation.pause():暂停动画;
Cesium.ModelAnimation.stop():停止动画并重置到初始状态。

<template>
  <div class="cesium-container">
    <div id="cesiumContainer" class="cesium-viewer"></div>
    <div class="control-panel">
      <button @click="loadAnimatedModel">加载风机模型(带动画)</button>
      <button @click="playAnimation" :disabled="!isModelLoaded">播放动画</button>
      <button @click="pauseAnimation" :disabled="!isModelLoaded">暂停动画</button>
      <button @click="clearModel">清除模型</button>
    </div>
  </div>
</template>
 
<script>
export default {
  name: 'CesiumAnimatedModelFixed',
  data() {
    return {
      viewer: null,
      windTurbineModel: null,  // 风机模型实例
      isModelLoaded: false,    // 模型加载状态标记
      currentAnimation: null   // 当前播放的动画实例
    };
  },
  mounted() {
    this.initViewer();
  },
  methods: {
    // 初始化Cesium视图
    initViewer() {
      this.viewer = new Cesium.Viewer('cesiumContainer', {
        animation: true,  // 显示动画控件(用于控制时间轴)
        baseLayerPicker: false,
        fullscreenButton: false,
        geocoder: false,
        homeButton: false,
        infoBox: true,    // 点击模型时显示信息框
        sceneModePicker: false,
        selectionIndicator: true,
        timeline: true,   // 显示时间轴(便于观察动画进度)
        navigationHelpButton: false,
        // 地形配置
        terrainProvider: new Cesium.CesiumTerrainProvider({
          url: 'https://assets.agi.com/stk-terrain/world'
        }),
        // 底图配置(高德街道图)
        imageryProvider: new Cesium.UrlTemplateImageryProvider({
          url: 'https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
          subdomains: ['1', '2', '3', '4'],
          maximumLevel: 18
        })
      });
      // 隐藏版权信息
      this.viewer._cesiumWidget._creditContainer.style.display = 'none';
      // 初始定位到适合观察风机的区域(示例:内蒙古草原)
      this.viewer.camera.setView({
        destination: Cesium.Cartesian3.fromDegrees(112.0, 42.0, 1000),
        orientation: {
          heading: Cesium.Math.toRadians(0),
          pitch: Cesium.Math.toRadians(-40),  // 40°俯角
          roll: 0
        }
      });
    },
 
    // 加载带动画的风机模型
    loadAnimatedModel() {
      this.clearModel();  // 先清除已加载的模型
 
      const modelOptions = {
        url: require('@/assets/models/wind-turbine.gltf'),  // 模型路径(Vue2需用require)
        modelMatrix: this.getModelMatrix(112.0, 42.0, 0, 5),  // 定位与缩放矩阵
        minimumPixelSize: 32,  // 最小像素大小(避免过小时消失)
        enableAnimations: true  // 1.129版本需显式启用动画
      };
 
      // 加载模型
      Cesium.Model.fromGltf(this.viewer.scene.context, modelOptions).then(model => {
        this.windTurbineModel = model;
        this.viewer.scene.primitives.add(model);
        this.isModelLoaded = true;
 
        // 定位到模型
        this.viewer.flyTo(model, {
          duration: 2,
          offset: new Cesium.HeadingPitchRange(
            0,
            Cesium.Math.toRadians(-35),  // 35°俯角聚焦模型
            200  // 距离模型200米
          )
        });
 
        // 打印模型包含的动画名称(便于调试)
        const animationNames = model.activeAnimations._animations.map(anim => anim.name);
        console.log('模型包含的动画列表:', animationNames);
 
        // 自动播放第一个动画
        this.playAnimation();
      }).catch(error => {
        console.error('动画模型加载失败:', error);
        alert('模型加载失败,请检查文件是否为带动画的glTF 2.0格式');
      });
    },
 
    // 生成模型矩阵(定位、缩放)
    getModelMatrix(lon, lat, height, scale) {
      // 将经纬度转换为笛卡尔坐标
      const position = Cesium.Cartesian3.fromDegrees(lon, lat, height);
      // 创建平移矩阵(定位)
      const translateMatrix = Cesium.Matrix4.fromTranslation(position);
      // 创建缩放矩阵(放大/缩小)
      const scaleMatrix = Cesium.Matrix4.fromScale(new Cesium.Cartesian3(scale, scale, scale));
      // 合并矩阵(先缩放后平移)
      return Cesium.Matrix4.multiply(translateMatrix, scaleMatrix, new Cesium.Matrix4());
    },
 
    // 播放动画
    playAnimation() {
      if (!this.windTurbineModel) return;
 
      // 停止当前动画(避免叠加)
      if (this.currentAnimation) {
        this.currentAnimation.stop();
      }
 
      // 播放第一个动画轨道(假设模型只有一个动画)
      const firstAnimation = this.windTurbineModel.activeAnimations._animations[0];
      if (firstAnimation) {
        this.currentAnimation = Cesium.ModelAnimation.play(this.windTurbineModel, firstAnimation.name, {
          loop: Cesium.ModelAnimationLoop.REPEAT,  // 循环播放
          speedup: 1.0,  // 播放速度(1.0为正常速度)
          startTime: this.viewer.clock.startTime
        });
      }
    },
 
    // 暂停动画
    pauseAnimation() {
      if (this.currentAnimation) {
        this.currentAnimation.pause();
      }
    },
 
    // 清除模型及动画
    clearModel() {
      if (this.windTurbineModel) {
        // 停止动画
        if (this.currentAnimation) {
          this.currentAnimation.stop();
          this.currentAnimation = null;
        }
        // 移除模型
        this.viewer.scene.primitives.remove(this.windTurbineModel);
        this.windTurbineModel = null;
        this.isModelLoaded = false;
      }
    }
  },
  // 组件销毁时清理资源
  beforeDestroy() {
    this.clearModel();
    if (this.viewer) {
      this.viewer.destroy();
    }
  }
};
</script>
 
<style scoped>
.cesium-container {
  width: 100vw;
  height: 100vh;
  position: relative;
  margin: 0;
  padding: 0;
}
 
.cesium-viewer {
  width: 100%;
  height: 100%;
}
 
.control-panel {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  display: flex;
  gap: 12px;
  background: rgba(0, 0, 0, 0.7);
  padding: 12px 20px;
  border-radius: 8px;
}
 
button {
  padding: 8px 16px;
  background: #2196F3;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
}
 
button:disabled {
  background: #cccccc;
  cursor: not-allowed;
}
 
button:hover:not(:disabled) {
  background: #0b7dda;
}
</style>

5.3.3 1.129 版本动画控制注意事项
动画轨道获取:
1.129 版本需通过 model.activeAnimations._animations 数组获取模型包含的动画(新版本通过 model.animations 获取),建议先打印动画名称,避免因动画名称错误导致控制失效。

时间轴依赖:
模型动画播放依赖 Cesium 的 clock 时间轴,需确保 viewer.clock.shouldAnimate = true(默认启用),否则动画会停滞。

多动画切换:
若模型包含多个动画(如「叶片旋转」「机舱转向」),切换动画时需先调用 currentAnimation.stop() 停止当前动画,再播放新动画,避免动画叠加导致模型姿态异常。

5.3.3 1.129 版本动画控制注意事项
动画轨道获取:
1.129 版本需通过 model.activeAnimations._animations 数组获取模型包含的动画(新版本通过 model.animations 获取),建议先打印动画名称,避免因动画名称错误导致控制失效。

时间轴依赖:
模型动画播放依赖 Cesium 的 clock 时间轴,需确保 viewer.clock.shouldAnimate = true(默认启用),否则动画会停滞。

多动画切换:
若模型包含多个动画(如「叶片旋转」「机舱转向」),切换动画时需先调用 currentAnimation.stop() 停止当前动画,再播放新动画,避免动画叠加导致模型姿态异常。

5.4 3D 模型加载性能优化(1.129 适配)
Cesium 1.129 对复杂 3D 模型(如多边形数量 > 10 万、纹理分辨率 > 2K)的加载性能较弱,易出现卡顿、崩溃,需通过以下方式优化:

模型预处理(推荐):

简化几何面:用 Blender 减少模型多边形数量(保留核心结构,删除冗余细节);
压缩纹理:将纹理图压缩为 WebP 格式(分辨率控制在 1K 以内),用 TexturePacker 合并多张纹理,减少绘制调用;
转换为 glb:将分离的 glTF + 纹理文件打包为单个 glb 文件,减少网络请求次数。
加载策略优化:

延迟加载:仅当相机距离模型 < 5000 米时才加载模型,远距离时用「点标记」替代,示例:// 延迟加载逻辑]

this.viewer.camera.moveEnd.addEventListener(() => {
const modelPosition = Cesium.Cartesian3.fromDegrees(116.404, 39.915);
const distance = Cesium.Cartesian3.distance(this.viewer.camera.position, modelPosition);
// 距离 < 5000 米加载模型,否则清除 if (distance < 5000 && !this.buildingModel) { this.loadSingleModel(); } else if (distance >= 5000 && this.buildingModel) {
this.clearModel();
}
});


————————————————
批量加载限制:同时加载的模型数量 < 5 个,超过时采用「队列加载」,避免一次性占用过多 GPU 资源。
渲染参数调整:

关闭模型抗锯齿:model.antiAliasing = false(牺牲部分画质,提升帧率);
启用视锥体剔除:model.cull = true(默认启用,自动剔除相机视野外的模型);
限制模型最大像素大小:model.maximumPixelSize = 256(避免模型过大导致渲染压力)。
小结
本章针对 Cesium 1.129 版本,系统讲解了 3D 模型加载的核心内容:

静态模型加载:掌握 glTF/glb 模型的定位、缩放、基础材质修改;
动画模型控制:实现动画播放、暂停、循环,解决多动画切换问题;
性能优化:通过模型预处理、延迟加载、渲染参数调整,提升复杂场景的运行流畅度。
————————————————


————————————————
版权声明:本文为CSDN博主「冒气er」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mamapya/article/details/150958187

您可能还喜欢...

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注