3D 模型加载与地形贴合
本示例展示如何在 Cesium 中加载 GLTF/GLB 格式的 3D 模型,并确保模型正确贴合真实地形。示例包含两个场景:风车阵列和建筑,通过 dat.gui 控制面板可以切换不同的模型场景。重点演示了地形提供者的使用和模型的精确定位。
核心功能
GLTF 模型格式
GLTF(GL Transmission Format)是 Khronos Group 制定的 3D 模型标准格式:
- 开放标准:跨平台、跨引擎的 3D 资产传输格式
- 高效传输:紧凑的文件格式,支持二进制(GLB)和 JSON 格式
- 完整支持:包含几何、材质、纹理、动画、骨骼等完整信息
- PBR 材质:原生支持基于物理的渲染(PBR)材质
- 动画支持:支持骨骼动画、变形动画等
地形提供者(TerrainProvider)
Cesium 支持多种地形提供者,用于显示真实的地形起伏:
- EllipsoidTerrainProvider:光滑的椭球体地形(默认)
- CesiumTerrainProvider:高精度地形数据(推荐)
- 支持地形贴合:模型高度值可精确匹配地形高度
模型定位与方向
使用 HeadingPitchRoll 和四元数精确控制模型的位置和姿态:
- Heading:方位角(0-360°),顺时针从北开始
- Pitch:俯仰角(-90° 到 90°)
- Roll:横滚角(-180° 到 180°)
关键代码
初始化 Viewer
创建 Cesium Viewer 并配置基础设置(viewer.ts):
export function initViewer(el: HTMLElement) {
const viewer = new Viewer(el, {
baseLayerPicker: false, // 隐藏底图选择器
animation: false, // 隐藏动画控件
timeline: false, // 隐藏时间轴
fullscreenButton: false, // 隐藏全屏按钮
geocoder: false, // 隐藏地理编码搜索框
homeButton: false, // 隐藏主页按钮
infoBox: false, // 隐藏信息框
sceneModePicker: false, // 隐藏场景模式选择器
selectionIndicator: false, // 隐藏选择指示器
navigationHelpButton: false, // 隐藏导航帮助按钮
});
viewer.scene.debugShowFramesPerSecond = true; // 显示帧率
// 配置自定义底图
viewer.imageryLayers.remove(viewer.imageryLayers.get(0));
const xyz = new UrlTemplateImageryProvider({
url: "//data.mars3d.cn/tile/img/{z}/{x}/{y}.jpg",
});
viewer.imageryLayers.addImageryProvider(xyz);
// 移除默认地形(稍后根据场景需要加载真实地形)
viewer.scene.terrainProvider = new EllipsoidTerrainProvider({});
return { viewer, gui };
}
加载风车模型(带地形)
加载多个风车模型并使用真实地形:
async function loadFengcheModel(viewer: Viewer) {
removeEntities(viewer);
// 先加载真实地形
const terrainProvider = await CesiumTerrainProvider.fromUrl(
"//data.mars3d.cn/terrain",
);
viewer.terrainProvider = terrainProvider;
// 设置相机视角
viewer.camera.setView({
destination: Cartesian3.fromDegrees(112.245269, 39.066518, 2913),
orientation: {
heading: CesiumMath.toRadians(226),
pitch: CesiumMath.toRadians(-21),
roll: 0.0,
},
});
// 遍历位置数组,添加风车模型
positions.forEach((item) => {
const position = Cartesian3.fromDegrees(item.lng, item.lat, item.alt);
const heading = CesiumMath.toRadians(135);
const pitch = CesiumMath.toRadians(0);
const roll = CesiumMath.toRadians(0);
const hpr = new HeadingPitchRoll(heading, pitch, roll);
const orientation = Transforms.headingPitchRollQuaternion(position, hpr);
viewer.entities.add({
position: position,
orientation: orientation,
model: {
uri: "/cesium/09/fengche/fengche.gltf",
scale: 40,
runAnimations: true, // 启用模型动画
},
});
});
viewer.clock.shouldAnimate = true; // 启用时钟动画
viewer.flyTo(viewer.entities); // 飞到实体
}
加载建筑模型
加载单个大型建筑模型:
function loadShanghaipudongModel(viewer: Viewer) {
removeEntities(viewer);
const position = Cartesian3.fromDegrees(121.507762, 31.233975, 200);
const heading = CesiumMath.toRadians(215);
const pitch = CesiumMath.toRadians(0);
const roll = CesiumMath.toRadians(0);
const hpr = new HeadingPitchRoll(heading, pitch, roll);
const orientation = Transforms.headingPitchRollQuaternion(position, hpr);
const entity = viewer.entities.add({
position: position,
orientation: orientation,
model: {
uri: "/cesium/09/scene/scene.gltf",
scale: 520,
},
});
viewer.flyTo(entity);
}
清除实体
在切换场景时清除之前的实体:
function removeEntities(viewer: Viewer) {
// 清除所有实体
viewer.entities.removeAll();
}
应用场景
城市建筑可视化
- 城市规划:展示建筑设计方案和城市天际线
- 房地产展示:虚拟看房、楼盘展示
- 智慧城市:建筑信息模型(BIM)与 GIS 融合
- 历史建筑:文物保护、古建筑数字化
风力发电场规划
- 选址规划:风电场地形地貌分析
- 视觉影响评估:模拟风车对景观的影响
- 运维监控:实时监控风机状态和运行数据
- 发电量分析:结合地形和气象数据分析发电效率
基础设施建设
- 道路桥梁:交通设施规划和展示
- 输电线路:电力设施路径规划
- 管线管理:地下管网三维可视化
- 水利工程:水库、大坝、河道的 3D 展示
工业设施
- 工厂布局:工业园区规划
- 设备管理:大型设备的空间定位和管理
- 安全演练:危险场景模拟和应急预案
- 数字孪生:工业设施的数字化镜像
性能优化技巧
模型优化
- 减少多边形数量
// 使用 LOD(Level of Detail)技术
// 远距离使用低精度模型,近距离使用高精度模型
// ✅ 推荐:使用优化过的模型
// - 合并相同材质的网格
// - 移除不可见的面
// - 简化高模为低模
- 纹理优化
// ✅ 压缩纹理(使用 KTX2 格式)
// ✅ 合理的纹理分辨率(2048x2048 通常足够)
// ✅ 使用纹理图集减少绘制调用
- 模型实例化
// 对于相同的模型(如风车),使用实例化可以提高性能
// Cesium 会自动对相同的模型进行实例化渲染
地形性能
- 按需加载地形
// 只在需要精确贴地时才加载地形
if (needTerrain) {
const terrainProvider = await CesiumTerrainProvider.fromUrl(
"//data.mars3d.cn/terrain"
);
viewer.terrainProvider = terrainProvider;
}
- 控制地形精度
// 可以通过调整请求的地形细节层级来平衡性能和精度
viewer.scene.globe.maximumScreenSpaceError = 2; // 默认是 2
视锥体剔除
// Cesium 会自动剔除视锥体外的对象
// 确保不禁用此功能
viewer.scene.globe.enableFrustumCulling = true; // 默认开启
常见问题
模型悬浮在空中
原因:使用 EllipsoidTerrainProvider 时,高度值是相对于椭球体表面的绝对高度。
解决方案:
- 加载真实地形数据(推荐)
- 将高度值设置为 0 或较小值
- 使用
heightReference属性贴地
// 方案 1:加载真实地形(推荐)
const terrainProvider = await CesiumTerrainProvider.fromUrl(
"//data.mars3d.cn/terrain"
);
viewer.terrainProvider = terrainProvider;
// 方案 2:设置高度为 0
const position = Cartesian3.fromDegrees(lng, lat, 0);
// 方案 3:使用 heightReference(对 Entity 有效)
model: {
uri: "model.gltf",
heightReference: HeightReference.CLAMP_TO_GROUND, // 贴地
}
模型加载失败
确保模型文件路径正确,且文件可访问:
// ✅ 使用相对路径
uri: "/cesium/09/scene/scene.gltf"
// ✅ 使用绝对 URL
uri: "https://example.com/models/scene.gltf"
// ❌ 错误的路径
uri: "scene.gltf" // 可能找不到文件
模型方向不对
使用 HeadingPitchRoll 调整模型方向:
// heading: 方位角(0-360°),顺时针从北开始
// pitch: 俯仰角(-90° 到 90°)
// roll: 横滚角(-180° 到 180°)
const heading = CesiumMath.toRadians(135); // 东南方向
const pitch = CesiumMath.toRadians(0); // 水平
const roll = CesiumMath.toRadians(0); // 不倾斜
模型动画不播放
确保启用动画:
model: {
uri: "model.gltf",
runAnimations: true, // 启用模型动画
}
// 启用时钟动画
viewer.clock.shouldAnimate = true;
模型过大或过小
使用 scale 属性调整模型大小:
model: {
uri: "model.gltf",
scale: 40, // 放大 40 倍
}
地形加载慢
地形数据较大,加载需要时间:
// 显示加载进度
async function loadFengcheModel(viewer: Viewer) {
console.log("正在加载地形...");
const terrainProvider = await CesiumTerrainProvider.fromUrl(
"//data.mars3d.cn/terrain"
);
viewer.terrainProvider = terrainProvider;
console.log("地形加载完成");
// 继续加载模型...
}
注意事项
- 坐标系统:确保使用 WGS84 地理坐标系(经纬度)
- 高度基准:明确高度值是相对于什么基准(椭球体、地形还是海平面)
- 模型格式:推荐使用 GLTF 2.0 格式,支持 PBR 材质
- 文件大小:大型模型建议使用 GLB(二进制)格式以减小文件大小
- 异步加载:地形和模型加载都是异步的,注意使用
async/await - 内存管理:切换场景时记得清除不再使用的实体(
removeEntities) - 性能考虑:大量模型时考虑使用 LOD 或实例化技术
- 动画性能:动画会增加 CPU 开销,合理控制同时播放的动画数量