第五章: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