Cesium 快速入门(四)相机控制完全指南
Cesium 快速入门(四)相机控制完全指南
看过的知识不等于学会。唯有用心总结、系统记录,并通过温故知新反复实践,才能真正掌握一二
作为一名摸爬滚打三年的前端开发,开源社区给了我饭碗,我也将所学的知识体系回馈给大家,助你少走弯路!
OpenLayers、Leaflet 快速入门 ,每周保持更新 2 个案例
Cesium 快速入门,每周保持更新 4 个案例
Cesium 快速入门(一)快速搭建项目
Cesium 快速入门(二)底图更换
Cesium 快速入门(三)Viewer:三维场景的“外壳”
Cesium 快速入门(四)相机控制完全指南
Cesium 快速入门(五)坐标系
Cesium 快速入门(六)实体类型介绍
Cesium 快速入门(七)材质详解
Cesium 快速入门(八)Primitive(图元)系统深度解析
Cesium 快速入门(九)Appearance(外观)系统深度解析
Cesium 快速入门(十) JulianDate(儒略日期)详解
Cesium 快速入门(十一)3D Tiles 大规模三维地理空间数据
Cesium 快速入门(十二)数据加载详解
Cesium 快速入门(十三)事件系统
Cesium 相机控制完全指南
相机(Camera
)是 Cesium 场景交互的核心,负责定义观察者视角和运动方式。
核心概念
相机坐标系
Cesium 相机系统基于右手坐标系:
- 位置(Position):相机在世界坐标系中的三维坐标(Cartesian3)
- 方向(Direction):相机视线方向向量(默认指向-Z 轴)
- 姿态(Orientation):由 heading(方位角)、pitch(俯仰角)、roll(翻滚角)定义
重要区别:
- 本地坐标系:以相机为中心,X 轴向右,Y 轴向上,Z 轴向后
- 世界坐标系:以地球中心为原点的固定坐标系
相机状态参数
参数 | 类型 | 描述 | 取值范围 |
---|---|---|---|
position | Cartesian3 | 世界坐标位置 | 任意三维坐标 |
positionCartographic | Cartographic | 经纬度高度表示 | 经度[-180,180],纬度[-90,90],高度 ≥0 |
heading | Number | 方位角(绕 Y 轴) | [-π, π]弧度,0 为正北 |
pitch | Number | 俯仰角(绕 X 轴) | [-π/2, π/2]弧度,-π/2 为俯视 |
roll | Number | 翻滚角(绕 Z 轴) | [-π, π]弧度,0 为水平 |
基础配置
默认视角设置
相机初始化默认看向 0° 经线、0° 纬线区域,可通过以下方式修改默认视图:
// 在创建Viewer前设置全局默认视图矩形
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(89.99, // 西经度39.9, // 南纬度116.41, // 东经度39.92 // 北纬度
);
注意:
DEFAULT_VIEW_RECTANGLE
必须在 Viewer 创建前设置才有效,适用于需要固定初始范围的场景(如特定区域监控)
相机参数获取
const camera = viewer.camera;// 获取相机位置(笛卡尔坐标)
const position = camera.position;
// 获取相机方向矩阵
const directionMatrix = camera.direction;
// 获取相机方位角
const heading = camera.heading;
// 获取相机俯仰角
const pitch = camera.pitch;
// 获取相机滚动角
const roll = camera.roll;
// 获取相机高度(米)
const height = camera.positionCartographic.height;
相机控制方法
1. 直接定位(setView)
瞬时定位到目标位置,无过渡动画,适用于需要精确定位的场景:
// 方式一:使用heading/pitch/roll定义姿态
viewer.camera.setView({destination: Cesium.Cartesian3.fromDegrees(116.404, // 经度39.915, // 纬度1000 // 高度(米)),orientation: {heading: Cesium.Math.toRadians(0), // 方位角:0°(正北)pitch: Cesium.Math.toRadians(-30), // 俯仰角:-30°(俯视)roll: 0, // 翻滚角:0°(水平)},
});// 方式二:使用方向向量定义姿态
viewer.camera.setView({destination: Cesium.Cartesian3.fromDegrees(116.404, 39.915, 1000),orientation: {direction: new Cesium.Cartesian3(0.1, -0.2, -1), // 视线方向向量up: new Cesium.Cartesian3(0, 1, 0), // 相机上方向向量},
});
应用场景:初始化定位、按钮快速跳转、精确坐标定位
2. 平滑飞行(flyTo)
带过渡动画的相机移动,提供更好的用户体验:
viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(116.404, 39.915, 1000),orientation: {heading: Cesium.Math.toRadians(0),pitch: Cesium.Math.toRadians(-30),roll: 0,},duration: 5, // 动画持续时间(秒),默认3秒easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT, // 缓动函数maximumHeight: 2000, // 飞行路径最高点限制(米)complete: function () {console.log("飞行完成!");},cancel: function () {console.log("飞行被取消!");},endTransform: Cesium.Matrix4.IDENTITY, // 结束时的变换矩阵
});
常用缓动函数对比:
LINEAR_NONE
:匀速运动CUBIC_IN_OUT
:先加速后减速(默认)QUADRATIC_IN
:匀加速ELASTIC_OUT
:弹性效果
3. 锁定目标(lookAt)
固定相机看向目标点,适用于跟踪移动目标:
// 方式一:使用Cartesian3偏移量
viewer.camera.lookAt(Cesium.Cartesian3.fromDegrees(116.404, 39.915, 0), // 目标点坐标new Cesium.Cartesian3(0, -500, 300) // 相对目标点的偏移量(右、后、上)
);// 方式二:使用HeadingPitchRange(推荐)
viewer.camera.lookAt(Cesium.Cartesian3.fromDegrees(116.404, 39.915, 0),new Cesium.HeadingPitchRange(Cesium.Math.toRadians(30), // 方位角:30°(东北方向)Cesium.Math.toRadians(-20), // 俯仰角:-20°(俯视)1000 // 距离目标点的距离(米))
);
注意:使用 lookAt 后,相机将锁定目标点,需调用
camera.lookAtTransform(Cesium.Matrix4.IDENTITY)
恢复自由控制
交互控制
方法 | 描述 | 单位:米 | 类型:Number |
---|---|---|---|
moveForward | 向前移动 | ||
moveBackward | 向后移动 | ||
moveLeft | 向左移动 | ||
moveRight | 向右移动 | ||
moveUp | 向上移动 | ||
moveDown | 向下移动 | ||
---------- | ----- | ------ | --------- |
lookLeft | 向左旋转 | ||
lookRight | 向右旋转 | ||
lookUp | 向上旋转 | ||
lookDown | 向下旋转 | ||
---------- | ----- | ------ | --------- |
twistLeft | 向左倾斜 | ||
twistRight | 向右倾斜 |
键盘控制实现
完整的相机键盘控制方案,支持移动、旋转和倾斜:
<template><div ref="cesiumContainer" class="container"></div>
</template><script setup>
import { ref, onMounted } from "vue";
import * as Cesium from "cesium";
const cesiumContainer = ref(null);
let viewer = null;// 天地图TOKEN
const token = "05be06461004055923091de7f3e51aa6";onMounted(() => {// 初始化Viewerviewer = new Cesium.Viewer(cesiumContainer.value, {geocoder: false, // 关闭地理编码搜索homeButton: false, // 关闭主页按钮sceneModePicker: false, // 关闭场景模式选择器baseLayerPicker: false, // 关闭底图选择器navigationHelpButton: false, // 关闭导航帮助animation: false, // 关闭动画控件timeline: false, // 关闭时间轴fullscreenButton: false, // 关闭全屏按钮baseLayer: false, // 关闭默认地图});// 清空logoviewer.cesiumWidget.creditContainer.style.display = "none";// 监听键盘事件document.addEventListener("keydown", function (event) {const camera = viewer.camera;const distance = 100; // 每次移动的距离switch (event.key) {case "ArrowUp": // 上按键camera.moveForward(distance); // 向前移动break;case "ArrowDown": // 下按键camera.moveBackward(distance); // 向后移动break;case "ArrowLeft": // 左按键camera.moveLeft(distance); // 向左移动break;case "ArrowRight": // 右按键camera.moveRight(distance); // 向右移动break;case "w": // w按键camera.lookUp(distance); // 向上旋转break;case "s": // s按键camera.lookDown(distance); // 向下旋转break;case "a": // a按键camera.lookLeft(distance); // 向左旋转break;case "d": // d按键camera.lookRight(distance); // 向右旋转break;case "q": // q按键camera.twistLeft(distance); // 向左倾斜break;case "e": // e按键camera.twistRight(distance); // 向右倾斜break;default:break;}});initMap();
});// 加载天地图
const initMap = () => {// 以下为天地图及天地图标注加载const tiandituProvider = new Cesium.WebMapTileServiceImageryProvider({url:"http://{s}.tianditu.gov.cn/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=" +token,layer: "img",style: "default",format: "tiles",tileMatrixSetID: "w", // 天地图使用 Web 墨卡托投影(EPSG:3857),需确保 tileMatrixSetID: "w"subdomains: ["t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7"], // 子域名maximumLevel: 18,credit: new Cesium.Credit("天地图影像"),});// 添加地理标注const labelProvider = new Cesium.WebMapTileServiceImageryProvider({url:"http://{s}.tianditu.gov.cn/cia_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=cia&tileMatrixSet=w&tileMatrix={TileMatrix}&tileRow={TileRow}&tileCol={TileCol}&style=default&format=tiles&tk=" +token,layer: "img",style: "default",format: "tiles",tileMatrixSetID: "w",subdomains: ["t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7"], // 子域名轮询maximumLevel: 18,credit: new Cesium.Credit("天地图影像"),});// 天地图影像添加到viewer实例的影像图层集合中viewer.imageryLayers.addImageryProvider(tiandituProvider);// 天地图地理标注(后添加的会覆盖前面的)viewer.imageryLayers.addImageryProvider(labelProvider);
};
</script>
<style scoped>
.container {width: 100vw;height: 100vh;
}
</style>
高级应用
相机事件监听
监听相机状态变化,实现动态响应:
// 相机移动开始事件
viewer.camera.moveStart.addEventListener(function () {console.log("相机开始移动");// 可在此处暂停其他动画或更新UI状态
});// 相机移动结束事件(常用)
viewer.camera.moveEnd.addEventListener(function () {console.log("相机移动结束");// 获取新视域范围const viewRectangle = viewer.camera.computeViewRectangle();if (Cesium.defined(viewRectangle)) {const west = Cesium.Math.toDegrees(viewRectangle.west).toFixed(2);const east = Cesium.Math.toDegrees(viewRectangle.east).toFixed(2);const south = Cesium.Math.toDegrees(viewRectangle.south).toFixed(2);const north = Cesium.Math.toDegrees(viewRectangle.north).toFixed(2);console.log(`当前视域范围: ${west}°E-${east}°E, ${south}°N-${north}°N`);}
});
视角保存与恢复
实现视角状态的保存和快速切换:
// 视角状态管理
const cameraStates = {savedStates: {}, // 存储视角状态的对象// 保存当前视角saveState: function (key) {const camera = viewer.camera;this.savedStates[key] = {position: camera.position.clone(),orientation: {heading: camera.heading,pitch: camera.pitch,roll: camera.roll,},};console.log(`已保存视角: ${key}`);},// 恢复视角restoreState: function (key, duration = 1.5) {const state = this.savedStates[key];if (!state) {console.error(`未找到保存的视角: ${key}`);return;}viewer.camera.flyTo({destination: state.position,orientation: state.orientation,duration: duration,});},// 删除视角deleteState: function (key) {if (this.savedStates[key]) {delete this.savedStates[key];console.log(`已删除视角: ${key}`);}},
};// 使用示例
// cameraStates.saveState("overview"); // 保存概览视角
// cameraStates.restoreState("overview"); // 恢复概览视角
性能优化
相机相关性能建议
使用相机视锥体剔除:自动隐藏视域外对象
viewer.scene.globe.enableLighting = true;
viewer.scene.globe.depthTestAgainstTerrain = true;
避免过度相机移动:高频相机移动会导致帧率下降
// 相机移动节流
let isCameraMoving = false;
viewer.camera.moveEnd.addEventListener(function () {isCameraMoving = false;// 恢复高帧率渲染viewer.scene.maximumRenderTimeChange = 0.0;
});viewer.camera.moveStart.addEventListener(function () {isCameraMoving = true;// 移动时降低渲染频率viewer.scene.maximumRenderTimeChange = 0.2;
});