先看效果图

一、引言
在现代地理信息系统 (GIS) 和 3D 可视化应用中,如何高效地在地图上加载和展示 3D 模型是一个关键需求。Cesium 作为一款强大的开源 JavaScript 库,为我们提供了丰富的 API 和工具,使我们能够轻松实现基于 Web 的 3D 地理可视化。本文将详细介绍如何使用 Cesium 加载 GLTF 格式的 3D 模型,并结合高德地图底图创建一个完整的 3D 可视化应用。
二、Cesium 与 GLTF 基础知识
2.1 Cesium 简介
Cesium 是一个用于创建基于 Web 的 3D 地理信息系统的开源 JavaScript 库,由美国国家航空航天局 (NASA) 和美国地质调查局 (USGS) 支持开发。它支持多种地理数据格式,提供了强大的 3D 渲染能力,能够在浏览器中实现高性能的地球和地图可视化。
2.2 GLTF 格式
GLTF (OpenGL Transmission Format) 是一种用于高效传输和加载 3D 模型的开放格式,由 Khronos Group 开发。它以 JSON 格式存储 3D 模型的结构信息,同时支持二进制数据存储顶点、纹理等资源,具有体积小、加载快的特点,非常适合 Web 端 3D 应用。
三、环境搭建与项目初始化
首先,我们需要创建一个 Vue 项目并安装 Cesium 库:
# 创建Vue项目
vue create cesium-gltf-demo
# 进入项目目录
cd cesium-gltf-demo
# 安装Cesium
npm install cesium
接下来,配置 Vue 项目以正确加载 Cesium 资源。在 vue.config.js 中添加以下配置:
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
module.exports = {
configureWebpack: {
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: path.join(__dirname, 'node_modules/cesium/Build/Cesium'),
to: 'cesium'
}
]
})
],
resolve: {
fallback: {
fs: false,
path: false
}
}
},
chainWebpack: config => {
config.module
.rule('cesium')
.test(/\.js$/)
.include.add(path.join(__dirname, 'node_modules/cesium/Source'))
.end()
.use('babel-loader')
.loader('babel-loader')
.options({
presets: ['@babel/preset-env']
});
},
transpileDependencies: [
'cesium'
]
};
四、实现 Cesium 地图与 3D 模型加载
下面是完整的 Vue 组件代码,实现了 Cesium 地图初始化、高德地图底图加载和 GLTF 模型加载功能:
<template>
<div class="cesium-container" id="cesiumContainer"></div>
<div v-if="loading" class="loading-overlay">
<div class="spinner"></div>
<p>加载中...</p>
</div>
<div v-if="errorMessage" class="error-message">
<p>{{ errorMessage }}</p>
<button @click="retry">重试</button>
</div>
</template>
<script>
import { mapConfig } from "../../config/mapConfig";
export default {
data() {
return {
viewer: null,
loading: true,
errorMessage: null
};
},
mounted() {
this.initCesium();
},
beforeDestroy() {
if (this.viewer) {
this.viewer.destroy();
}
},
methods: {
async initCesium() {
try {
// 引入Cesium样式
require('cesium/Build/Cesium/Widgets/widgets.css');
// 初始化Viewer
this.viewer = new Cesium.Viewer("cesiumContainer", {
animation: false,
baseLayerPicker: false,
fullscreenButton: false,
geocoder: false,
homeButton: false,
infoBox: false,
sceneModePicker: false,
scene3DOnly: true,
selectionIndicator: false,
timeline: false,
navigationHelpButton: false,
shadows: true,
shouldAnimate: true,
sceneMode: Cesium.SceneMode.SCENE3D
});
// 初始定位 - 更靠近模型位置
this.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(104.0744619, 30.0503706, 5000),
duration: 3.0,
orientation: {
heading: Cesium.Math.toRadians(0), // 朝北
pitch: Cesium.Math.toRadians(-30), // 俯视
roll: 0.0
},
complete: () => {
console.log("飞行动画完成");
}
});
// 移除默认影像图层
this.viewer.scene.imageryLayers.remove(
this.viewer.scene.imageryLayers.get(0)
);
// 去除版权信息
this.viewer._cesiumWidget._creditContainer.style.display = "none";
// 添加高德地图影像
await this.addGaodeMap();
// 加载模型
await this.loadModel();
} catch (error) {
console.error("初始化失败:", error);
this.errorMessage = "加载失败,请重试";
} finally {
this.loading = false;
}
},
async addGaodeMap() {
try {
const mapOption = {
url: mapConfig.gaode.url2,
minimumLevel: 3,
maximumLevel: 18,
credit: new Cesium.Credit({
text: "高德地图",
link: "https://www.amap.com/"
})
};
const tdtLayer = new Cesium.UrlTemplateImageryProvider(mapOption);
this.viewer.scene.imageryLayers.addImageryProvider(tdtLayer);
} catch (error) {
console.error("高德地图加载失败:", error);
throw new Error("地图加载失败");
}
},
async loadModel() {
try {
// 模型位置与朝向
const position = Cesium.Cartesian3.fromDegrees(
104.0744619,
30.0503706,
100 // 降低高度以便更好观察
);
// 调整模型方向,使其朝上
const heading = Cesium.Math.toRadians(0); // 朝北
const pitch = Cesium.Math.toRadians(0); // 水平
const roll = 0;
const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
const orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
// 加载模型
const entity = await this.viewer.entities.add({
name: "3D Model",
position: position,
orientation: orientation,
model: {
uri: "/scene.gltf",
scale: 50.0, // 调整缩放比例
minimumPixelSize: 128,
maximumScale: 20000,
incrementallyLoadTextures: true,
runAnimations: true,
clampAnimations: true,
shadows: Cesium.ShadowMode.ENABLED,
heightReference: Cesium.HeightReference.NONE,
onLoad: (model) => {
console.log("模型加载成功", model);
// 添加成功提示
setTimeout(() => {
alert("模型加载成功!可以使用鼠标拖动来浏览场景");
}, 1000);
},
onError: (error) => {
console.error("模型加载失败", error);
throw new Error("模型加载失败");
}
}
});
// 聚焦模型
this.viewer.trackedEntity = entity;
this.viewer.zoomTo(entity);
// 添加视图控制说明
this.addViewerControls();
} catch (error) {
console.error("模型加载过程出错:", error);
throw new Error("模型加载过程出错");
}
},
addViewerControls() {
// 添加简单的控制说明
const controls = document.createElement('div');
controls.className = 'viewer-controls';
controls.innerHTML = `
<div class="control-item">
<span>鼠标左键:</span> 旋转视角
</div>
<div class="control-item">
<span>鼠标右键:</span> 平移
</div>
<div class="control-item">
<span>滚轮:</span> 缩放
</div>
<button id="resetView">重置视图</button>
`;
document.body.appendChild(controls);
// 添加重置视图功能
document.getElementById('resetView').addEventListener('click', () => {
this.viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(104.0744619, 30.0503706, 5000),
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-30),
roll: 0.0
}
});
});
},
retry() {
this.errorMessage = null;
this.loading = true;
this.initCesium();
}
}
};
</script>
<style lang="scss" scoped>
.cesium-container {
width: 100%;
height: 100vh;
touch-action: none;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
z-index: 1000;
.spinner {
border: 4px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top: 4px solid white;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin-bottom: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
}
.error-message {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(255, 0, 0, 0.8);
color: white;
padding: 10px 20px;
border-radius: 5px;
z-index: 1000;
display: flex;
align-items: center;
button {
margin-left: 10px;
padding: 5px 10px;
background-color: white;
color: red;
border: none;
border-radius: 3px;
cursor: pointer;
}
}
.viewer-controls {
position: absolute;
bottom: 20px;
left: 20px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px;
border-radius: 5px;
z-index: 1000;
.control-item {
margin-bottom: 5px;
}
button {
margin-top: 10px;
padding: 5px 10px;
background-color: white;
color: black;
border: none;
border-radius: 3px;
cursor: pointer;
}
}
</style>
export const mapConfig = {
gaode: {
url1: 'http://webst02.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8', //'高德路网中文注记'
url2: 'https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}', //高德影像
url3: 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}', //高德矢量
},
};
五、关键技术点解析
5.1 模型位置与方向控制
在 Cesium 中,模型的位置和方向是通过position和orientation属性控制的:
// 设置模型位置(经纬度和高度)
const position = Cesium.Cartesian3.fromDegrees(104.0744619, 30.0503706, 100);
// 设置模型方向(航向、俯仰、翻滚)
const heading = Cesium.Math.toRadians(0);
const pitch = Cesium.Math.toRadians(0);
const roll = 0;
const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
const orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
5.2 相机控制
相机控制是 3D 可视化中的重要部分,我们可以通过以下方式设置相机位置和方向:
// 设置相机位置和方向
this.viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(longitude, latitude, height),
orientation: {
heading: Cesium.Math.toRadians(headingAngle),
pitch: Cesium.Math.toRadians(pitchAngle),
roll: 0.0
}
});
5.3 错误处理与用户体验
为了提高应用的健壮性,我们添加了全面的错误处理和加载状态提示:
try {
// 执行可能出错的代码
} catch (error) {
console.error("错误:", error);
this.errorMessage = "加载失败,请重试";
} finally {
this.loading = false;
}
六、常见问题与解决方案
-
模型加载失败:
- 检查模型文件路径是否正确,模型文件建议放再public文件夹下
- 确保模型文件格式正确(.gltf 或.glb)
- 检查网络请求是否有 404 或其他错误
-
模型显示异常:
- 调整模型的 scale 参数
- 检查模型的原始坐标系,调整 heading/pitch/roll 参数
- 尝试使用不同的 heightReference 设置
-
性能问题:
- 对于复杂模型,考虑使用简化版本
- 启用
incrementallyLoadTextures以渐进加载纹理 - 使用
scene3DOnly: true优化性能

732

被折叠的 条评论
为什么被折叠?



