Cesium快速入门到精通系列教程二:添加地形与添加自定义地形、相机控制 - 指南
一、添加地形与添加自定义地形
在 Cesium 1.93 中添加地形可以通过配置terrainProvider实现。Cesium 支持多种地形数据源,包括 Cesium Ion 提供的全球地形、自定义地形服务以及开源地形数据。下面介绍几种常见的添加地形的方法:
使用 Cesium Ion 全球地形服务
这是最简单的方式,需要一个 Cesium Ion 账户和访问令牌:
// 设置Cesium Ion访问令牌Cesium.Ion.defaultAccessToken = '你的Cesium Ion令牌'; // 初始化Viewer并启用全球地形const viewer = new Cesium.Viewer('cesiumContainer', { terrainProvider: Cesium.createWorldTerrain({ requestVertexNormals: true, // 启用地形光照 requestWaterMask: true // 启用水面效果 }), baseLayerPicker: false, // 可选:禁用默认图层选择器});
添加自定义地形
1、从地理空间数据云下载数据:
数据资源->公开数据->DEM 数字高程数据
2、从cesiumlab下载工具进行数据转换:
安装下载的工具,比如当前版本cesiumlab4_4.0.8.exe;
打开工具,安装以下方式设置提交即可:
将以上生成的瓦片本地部署,部署的方式很多种,只要保证能通过url在线访问即可:
在代码中加载:
const viewer = new Cesium.Viewer('cesiumContainer', { terrainProvider: new Cesium.CesiumTerrainProvider({ url: 'http://localhost:3000', // 替换为你的服务器地址 requestVertexNormals: true, // 请求法线以支持地形光照 requestWaterMask: true // 请求水掩码以支持水面效果 })});
// 配置自定义地形服务const customTerrainProvider = new Cesium.CesiumTerrainProvider({ url: 'http://localhost:3000', // 替换为你的服务器地址 requestVertexNormals: true, requestWaterMask: true, isSct: true // 若为 SuperMap iServer 服务需设为 true [6](@ref)}); // 应用自定义地形viewer.terrainProvider = customTerrainProvider;
常见问题排查
问题现象 | 解决方案 |
---|---|
地形加载失败 | 检查网络连接和 Cesium Ion 令牌 |
水体效果未显示 | 确认 requestWaterMask: true |
地形贴图模糊 | 增大 viewer.scene.maximumScreenSpaceError 值 |
内存泄漏 | 限制 viewer.scene.globe.tileCacheSize |
二、相机的方向和位置
在Cesium 1.93中,相机的方向和位置控制是三维场景交互的核心。
1、相机坐标系与关键概念
1.1 相机坐标系基础
将相机比喻成直立行走的人,镜头好比人的视野。
- 位置(Position):相机在三维空间中的笛卡尔坐标(Cartesian3),以地球质心为原点。
- 方向(Direction):相机的朝向,由视线向量(View Vector)表示,指向场景中的目标点。
- 上方向(Up Vector):相机的 “上方” 方向,默认与地球表面垂直(Z 轴正方向)。
- heading:绕Y轴旋转(正北为0°,向东为正方向)。
- pitch:绕X轴旋转(-90°为俯视地面,0°为平视,正值为仰视)。
- roll:绕Z轴旋转(默认0°,正值为右倾)。
- 参考系(Reference Frame):相机运动的参考坐标系,通常为ENU(东 - 北 - 上)或ECF(地心地固坐标系)。
const orientation = { heading: Cesium.Math.toRadians(0), // 正北 pitch: Cesium.Math.toRadians(-90), // 俯视地面 roll: 0.0};
2、相机控制的核心方法
2.1 设置默认视角
// 设置Cesium默认视角Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees( 89.5, // 西边经度 20.4, // 南边维度 110.4, // 东边经度 61.2) // 北边维度
2.2 setView:直接设置视角
特点:无动画,立即切换到目标位置和方向。
viewer.camera.setView({ destination: position, // 目标位置(Cartesian3) orientation: orientation // 方向参数});
const position = Cesium.Cartesian3.fromDegrees(116.3907917, 39.9158389, 500); // 故宫 const orientation = { heading: Cesium.Math.toRadians(0), // 正北 pitch: Cesium.Math.toRadians(-90), // 俯视地面 roll: 0.0 }; viewer.camera.setView({ destination: position, orientation });
2.3 flyTo:动画飞行至目标
特点:支持平滑过渡,可设置飞行时长、视角偏移等。
关键参数:
- duration:动画时间(秒)。
- pitchAdjustHeight:高度超过此值时自动调整俯仰角。
viewer.camera.flyTo({ destination: position, orientation: orientation, duration: 5, // 5秒动画 pitchAdjustHeight: -90 // 强制俯视地面});
2.4 lookAt:视角锁定目标点
特点:相机位置固定,始终朝向目标点。
参数:target(目标点)和offset(偏移量,支持HeadingPitchRange)。
const center = Cesium.Cartesian3.fromDegrees(116.4, 39.9);viewer.camera.lookAt(center, new Cesium.HeadingPitchRange(0, -Math.PI/2, 1000));
2.5 viewBoundingSphere:环绕目标区域
适用场景:室内或小范围模型浏览。
const boundingSphere = new Cesium.BoundingSphere(center, radius);viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0, 0, 0));
2.6 方向控制的进阶应用
2.6.1 局部坐标系转换
使用Transforms.eastNorthUpToFixedFrame将局部坐标转换为全局坐标系:
const localPosition = new Cesium.Cartesian3(10, 20, 0);const transform = Cesium.Transforms.eastNorthUpToFixedFrame(localPosition);const globalPosition = Cesium.Matrix4.multiplyByPoint(transform, localPosition);
2.6.2 动态方向控制
通过事件监听实时更新相机方向:
viewer.scene.preRender.addEventListener(() => { const heading = viewer.camera.heading; const pitch = viewer.camera.pitch; console.log(`当前航向:${Cesium.Math.toDegrees(heading).toFixed(2)}°`);});
2.6.3 实体跟随模式
使用trackedEntity让相机自动跟随移动目标:
viewer.trackedEntity = entity; // 实体ID或对象
2.6.4 多阶段飞行
viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(116.39, 39.90, 1000000), duration: 3, orientation: { heading: 0, pitch: -Math.PI/2, roll: 0 }, complete: () => { // 第一阶段完成后触发第二阶段 viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(116.40, 39.91, 500000), duration: 2, easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT }); }});
效果:分阶段飞行,首阶段俯冲至地面,第二阶段缓升至目标点。
2.7 常见问题与注意事项
- 坐标系一致性
确保位置和方向参数在同一坐标系下(如WGS84)。若使用局部坐标,需通过变换矩阵转换。
- 俯仰角限制
默认俯仰角范围为[-π/2, π/2],超出可能导致视角异常。可通过viewer.camera.pitchLimits调整。
- 性能优化
频繁调用flyTo或setView时,建议合并连续操作,避免卡顿。
2.8 完整示例:相机环绕目标点
// 定义目标点(北京天安门)const target = Cesium.Cartesian3.fromDegrees(116.397, 39.908, 50); // 设置相机初始位置和方向viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(116.397, 39.908, 1000), orientation: { heading: Cesium.Math.toRadians(0), pitch: Cesium.Math.toRadians(-30), roll: 0 }}); // 启动环绕动画(每5秒绕目标一圈)viewer.clock.onTick.addEventListener(() => { const time = Cesium.JulianDate.now(viewer.clock.currentTime); const angle = (time.secondsOfDay * 360) / 5; // 每5秒旋转360° viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees( 116.397 + 10 * Math.cos(Cesium.Math.toRadians(angle)), 39.908 + 10 * Math.sin(Cesium.Math.toRadians(angle)), 1000 ), orientation: { heading: Cesium.Math.toRadians(angle), pitch: Cesium.Math.toRadians(-30), roll: 0 } });});
2.9 相机动画与相机动态交互
Cesium 1.93 实现镜头飞向故宫的完整示例,包含了基础的场景设置、相机飞行动画以及简单的交互控制。
Cesium.Ion.defaultAccessToken = 'Cesium defaultAccessToken'import { onMounted } from "vue";import * as Cesium from "cesium";import "./Widgets/widgets.css"; window.CESIUM_BASE_URL = "/"; // 设置Cesium静态资源路径(public目录) onMounted(() => { // 初始化Viewer const viewer = new Cesium.Viewer('cesiumContainer', { geocoder: false, //设置搜索框是否可见 homeButton: false, // 返回初始位置键是否可见 sceneModePicker: false, // 查看器选择模式选择键是否可见 baseLayerPicker: false, // 图层选择键是否可见 navigationHelpButton: false, // 帮助按钮是否可见 animation: false, // 播放控制按钮是否可见 timeline: false, // 时间轴是否可见 fullscreenButton: false, // 全屏按钮是否可见 terrainProvider: Cesium.createWorldTerrain() }); // 故宫位置(经纬度和高度) const palacePosition = { destination: Cesium.Cartesian3.fromDegrees(116.3907917, 39.9158389, 500), // 经度、纬度、高度(米) orientation: { heading: Cesium.Math.toRadians(0.0), // 偏航角(向东) pitch: Cesium.Math.toRadians(-30.0), // 俯仰角(向下倾斜) roll: 0.0 // 翻滚角 }, duration: 5, // 飞行持续时间(秒) maximumHeight: 2000, // 飞行过程中最大高度(米) curveAmount: 0.5 // 飞行曲线弯曲程度(0-1) }; // 长城位置(慕田峪段) const greatWallPosition = { destination: Cesium.Cartesian3.fromDegrees(116.6558, 40.4139, 500), orientation: { heading: Cesium.Math.toRadians(90.0), pitch: Cesium.Math.toRadians(-20.0), roll: 0.0 }, duration: 5, maximumHeight: 3000 }; // 初始视角 const initialView = { destination: Cesium.Cartesian3.fromDegrees(116.3907917, 39.9158389, 15000), orientation: { heading: Cesium.Math.toRadians(0.0), pitch: Cesium.Math.toRadians(-30.0), roll: 0.0 } }; // 设置初始视角 viewer.camera.setView(initialView); // 飞向故宫按钮事件 document.getElementById('flyToPalaceBtn').addEventListener('click', function () { viewer.camera.flyTo(palacePosition); }); // 飞向长城按钮事件 document.getElementById('flyToGreatWallBtn').addEventListener('click', function () { viewer.camera.flyTo(greatWallPosition); }); // 重置视角按钮事件 document.getElementById('resetViewBtn').addEventListener('click', function () { viewer.camera.setView(initialView); });}) * { margin: 0; padding: 0;} #cesiumContainer { width: 100wh; height: 100vh;} .controls { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 10px; z-index: 100;} button { padding: 8px 16px; background-color: #007BFF; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;} button:hover { background-color: #0056b3;}