Cesium 相机控制完全指南
相机(Camera)是 Cesium 场景中的"眼睛",负责定义观察者在 3D 世界中的位置和视角方向。所有渲染到屏幕上的内容都由相机的当前状态决定,包括:
- 观察点位置
- 视线方向
- 视野范围
- 透视效果
核心概念
相机坐标系
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 为水平 |
frustum | PerspectiveFrustum , PerspectiveOffCenterFrustum , OrthographicFrustum | 视锥体参数 | - |
基础配置
默认视角设置
js
// 在创建Viewer前设置全局默认视图矩形
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(
89.99, // 西经度
39.9, // 南纬度
116.41, // 东经度
39.92 // 北纬度
);
注意:
DEFAULT_VIEW_RECTANGLE
必须在 Viewer 创建前设置才有效,适用于需要固定初始范围的场景(如特定区域监控)
相机参数获取
js
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)
瞬时定位到目标位置,无过渡动画
,适用于需要精确定位的场景:
js
// 方式一:使用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)
带过渡动画的相机移动,提供更好的用户体验:
js
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)
固定相机看向目标点,适用于跟踪移动目标:
js
// 方式一:使用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 // 距离目标点的距离(米)
)
);
// 方式二:使用Cartesian3偏移量
viewer.camera.lookAt(
Cesium.Cartesian3.fromDegrees(116.404, 39.915, 0), // 目标点坐标
new Cesium.Cartesian3(0, -500, 300) // 相对目标点的偏移量(右、后、上)
);
// 恢复自由控制
viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
注意:使用 lookAt 后,相机将锁定目标点,需调用
camera.lookAtTransform(Cesium.Matrix4.IDENTITY)
恢复自由控制
交互控制
禁用默认交互
代表地图无法实现拖拽、缩放、旋转等交互
js
const controller = viewer.scene.screenSpaceCameraController;
// 禁用默认控制
controller.enableRotate = false;
controller.enableTranslate = false;
controller.enableZoom = false;
自定义交互控制
方法 | 描述 | 单位:米 |
---|---|---|
moveForward | 向前移动 | |
moveBackward | 向后移动 | |
moveLeft | 向左移动 | |
moveRight | 向右移动 | |
moveUp | 向上移动 | |
moveDown | 向下移动 | |
---------- | ----- | ------ |
lookLeft | 向左旋转 | |
lookRight | 向右旋转 | |
lookUp | 向上旋转 | |
lookDown | 向下旋转 | |
---------- | ----- | ------ |
twistLeft | 向左倾斜 | |
twistRight | 向右倾斜 |
下面是完整的相机键盘控制方案,支持移动、旋转和倾斜:
展开代码
vue
<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(() => {
// 初始化Viewer
viewer = new Cesium.Viewer(cesiumContainer.value, {
geocoder: false, // 关闭地理编码搜索
homeButton: false, // 关闭主页按钮
sceneModePicker: false, // 关闭场景模式选择器
baseLayerPicker: false, // 关闭底图选择器
navigationHelpButton: false, // 关闭导航帮助
animation: false, // 关闭动画控件
timeline: false, // 关闭时间轴
fullscreenButton: false, // 关闭全屏按钮
baseLayer: false, // 关闭默认地图
});
// 清空logo
viewer.cesiumWidget.creditContainer.style.display = "none";
initMap();
// 定位到北京
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(116.404, 39.915, 1000),
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-90),
roll: 0.0,
},
});
// 键盘控制相机交互
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;
}
});
});
// 加载天地图
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",
subdomains: ["t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7"], // 子域名
maximumLevel: 18,
credit: new Cesium.Credit("天地图影像"),
});
// 天地图影像添加到viewer实例的影像图层集合中
viewer.imageryLayers.addImageryProvider(tiandituProvider);
};
</script>
<style scoped>
.container {
width: 100vw;
height: 100vh;
}
</style>
高级应用
相机事件监听
监听相机状态变化,实现动态响应:
js
// 相机移动开始事件
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`);
}
});
解决相机移动时的画面抖动
js
// 调整惯性参数
const controller = viewer.scene.screenSpaceCameraController;
controller.inertiaSpin = 0.8;
controller.inertiaTranslate = 0.8;
controller.inertiaZoom = 0.8;
// 启用平滑相机移动
viewer.camera.constrainedAxis = Cesium.Cartesian3.UNIT_Z;
碰撞检测
防止相机穿过地形或模型
javascript
// 启用地形碰撞检测
viewer.scene.screenSpaceCameraController.enableCollisionDetection = true;
// 设置最小碰撞高度
viewer.scene.screenSpaceCameraController.minimumCollisionTerrainHeight = 2.0;
视角保存与恢复
实现视角状态的保存和快速切换:
js
// 视角状态管理
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"); // 恢复概览视角
性能优化
相机相关性能建议
使用相机视锥体剔除:自动隐藏视域外对象
js
viewer.scene.globe.enableLighting = true;
viewer.scene.globe.depthTestAgainstTerrain = true;
避免过度相机移动:高频相机移动会导致帧率下降
js
// 相机移动节流
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;
});