什么是 Camera
Camera 是 Cesium 中控制"从哪里看、往哪里看、怎么看"的核心对象。
它主要由三部分组成:
- 位置:相机在三维世界中的坐标。
- 方向:相机朝向哪里。
- 视锥体:相机能看到的空间范围。
text
Camera
├─ position 相机位置(Cartesian3,ECEF 坐标)
├─ direction 朝向方向(单位向量)
├─ up 相机上方向(单位向量)
├─ right 相机右方向(单位向量)
└─ frustum 视锥体
可以把 Camera 理解成真实世界里的摄像机:
text
position = 摄像机放在哪里
direction = 摄像机镜头朝哪里
frustum = 摄像机能拍到多大范围
Camera 在 Viewer 中的位置
创建 Viewer 后,通过 viewer.camera 或 viewer.scene.camera 拿到相机对象(两者等价)。
js
const viewer = new Cesium.Viewer('cesiumContainer')
const camera = viewer.camera
// 等价于
const camera = viewer.scene.camera
层级关系:
text
Viewer
└─ scene
└─ camera
视锥体 Frustum
视锥体(Frustum)是相机能看到的空间范围,由 6 个平面围成:
left/right/top/bottom:四个侧面near:近平面far:远平面
text
far
/-------/
/ /
/-------/
near
|
Camera
Cesium 用视锥体做裁剪(Frustum Culling):
text
在视锥体内的对象 → 可能渲染
不在视锥体内的对象 → 直接剔除,不消耗性能
这是 3D Tiles、地形、影像和 Primitive 能高性能渲染的重要原因之一。
视锥体类型
| 类型 | 说明 |
|---|---|
PerspectiveFrustum |
透视投影,近大远小,默认 3D 模式使用 |
OrthographicFrustum |
正交投影,无透视效果,常用于 2D 模式 |
js
// 查看当前视锥体类型
console.log(viewer.camera.frustum)
读取当前相机状态
实际业务中经常需要保存或同步当前视角,可以直接读取相机属性。
读取位置(经纬度 + 高度)
js
const cartographic = viewer.camera.positionCartographic
const longitude = Cesium.Math.toDegrees(cartographic.longitude)
const latitude = Cesium.Math.toDegrees(cartographic.latitude)
const height = cartographic.height
console.log(longitude, latitude, height)
读取姿态角
js
const heading = Cesium.Math.toDegrees(viewer.camera.heading)
const pitch = Cesium.Math.toDegrees(viewer.camera.pitch)
const roll = Cesium.Math.toDegrees(viewer.camera.roll)
console.log(heading, pitch, roll)
读取 Cartesian3 位置
js
const position = viewer.camera.position // Cartesian3,ECEF 坐标
保存 / 恢复视角
js
// 保存
const savedView = {
destination: viewer.camera.position.clone(),
orientation: {
heading: viewer.camera.heading,
pitch: viewer.camera.pitch,
roll: viewer.camera.roll
}
}
// 恢复
viewer.camera.setView(savedView)
Heading / Pitch / Roll 详解
heading
heading 表示水平朝向,可以理解为指南针方向。
text
0° → 朝北
90° → 朝东
180° → 朝南
270° → 朝西
pitch
pitch 表示俯仰角。
text
0° → 平视(水平)
-45° → 斜向下看
-90° → 垂直向下俯视
Cesium 里俯视地图通常用负数:
js
pitch: Cesium.Math.toRadians(-45)
roll
roll 表示镜头翻滚,绕朝向轴旋转。一般业务场景很少使用,通常设为 0。
Cesium 的角度参数统一使用弧度 ,需要用
Cesium.Math.toRadians()将角度值转换。
destination 与 orientation 参数
控制相机时最常见的两个参数:
text
destination = 相机位置
orientation = 相机姿态
destination
destination 通常是 Cartesian3 坐标:
js
const destination = Cesium.Cartesian3.fromDegrees(
116.39, // 经度
39.9, // 纬度
1000 // 椭球高 HAE(单位:米)
)
注意:这里的高度是椭球高(HAE),不是海拔高(ASL),两者在有地形时有差异。
orientation
orientation 包含三个角度(均为弧度):
| 参数 | 含义 | 常见值 |
|---|---|---|
heading |
水平方向角 | 0 表示朝北 |
pitch |
俯仰角 | 负值向下看 |
roll |
翻滚角 | 通常为 0 |
js
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-45),
roll: 0
}
控制相机视角
setView ------ 立即跳转
立即切换视角,无动画过程,适合初始化视角、重置视角、快速定位。
js
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 1000),
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-45),
roll: 0
}
})
flyTo ------ 飞行动画
带动画的飞行切换,适合点击列表定位、从一个城市飞到另一个城市。
js
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 1000),
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-45),
roll: 0
},
duration: 2, // 动画时长(秒),默认 3
complete: () => { // 飞行完成回调
console.log('arrived')
},
cancel: () => { // 被取消时回调
console.log('cancelled')
}
})
viewer.flyTo vs camera.flyTo
两者都能触发飞行动画,但适用场景不同:
| API | 目标类型 | 说明 |
|---|---|---|
viewer.flyTo(target) |
Entity、Primitive、Tileset、DataSource 等 | 自动计算目标包围球,高层封装 |
camera.flyTo(options) |
具体的 destination 坐标 |
底层,需要手动指定坐标和姿态 |
js
// 飞到 tileset(推荐方式,自动适配位置)
viewer.flyTo(tileset)
// 飞到某个 Entity
viewer.flyTo(entity, { duration: 2 })
// 飞到具体坐标(需要自己算好位置)
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 500)
})
实际开发中,有 Entity 或 Tileset 对象时优先用 viewer.flyTo(),只知道坐标时用 camera.flyTo()。
flyToBoundingSphere
当有一个模型、Tileset 或一组对象时,可以用包围球定位。
js
viewer.camera.flyToBoundingSphere(tileset.boundingSphere, {
duration: 2,
offset: new Cesium.HeadingPitchRange(
Cesium.Math.toRadians(0), // heading
Cesium.Math.toRadians(-30), // pitch
500 // 与目标点的距离(米)
)
})
HeadingPitchRange 三个参数:
heading:绕目标点水平旋转角度pitch:俯仰角range:相机与目标点的距离
lookAt
让相机锁定围绕一个目标点观察,适合查看建筑、模型、设备。
js
const target = Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0)
viewer.camera.lookAt(
target,
new Cesium.HeadingPitchRange(
Cesium.Math.toRadians(0),
Cesium.Math.toRadians(-45),
1000
)
)
使用
lookAt后,相机会进入以目标点为参考的锁定状态,鼠标旋转会绕目标点转。如果想恢复自由相机,调用:
jsviewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY)
zoomIn / zoomOut
沿相机朝向方向前进或后退一段距离,适合程序化调整远近。
js
viewer.camera.zoomIn(500) // 向前移动 500 米
viewer.camera.zoomOut(500) // 向后移动 500 米
相机位移与旋转方法族
Cesium 提供了一组细粒度的相机控制方法,常用于自定义交互或动画。
位移
js
viewer.camera.moveForward(100) // 沿朝向前进
viewer.camera.moveBackward(100) // 沿朝向后退
viewer.camera.moveLeft(100) // 向左平移
viewer.camera.moveRight(100) // 向右平移
viewer.camera.moveUp(100) // 向上移动
viewer.camera.moveDown(100) // 向下移动
旋转
js
viewer.camera.rotateLeft(0.1) // 向左旋转(弧度)
viewer.camera.rotateRight(0.1) // 向右旋转
viewer.camera.rotateUp(0.1) // 向上旋转
viewer.camera.rotateDown(0.1) // 向下旋转
视角调整
js
viewer.camera.twistLeft(0.1) // 逆时针翻滚
viewer.camera.twistRight(0.1) // 顺时针翻滚
viewer.camera.lookLeft(0.1) // 原地向左看
viewer.camera.lookRight(0.1) // 原地向右看
viewer.camera.lookUp(0.1) // 原地向上看
viewer.camera.lookDown(0.1) // 原地向下看
cancelFlight ------ 取消飞行
如果 flyTo 或 flyToBoundingSphere 正在执行,可以随时取消:
js
viewer.camera.cancelFlight()
常见用途:用户在飞行过程中点击了另一个目标,需要先取消当前飞行再发起新的。
js
// 先取消当前飞行,再飞向新目标
viewer.camera.cancelFlight()
viewer.camera.flyTo({ destination: newDestination })
坐标转换
屏幕坐标转地球坐标
方式一:pickPosition(有深度信息时)
适合有 3D Tiles、模型或开启深度检测的场景。
js
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
handler.setInputAction((movement) => {
const cartesian = viewer.scene.pickPosition(movement.position)
if (!Cesium.defined(cartesian)) return
const cartographic = Cesium.Cartographic.fromCartesian(cartesian)
const longitude = Cesium.Math.toDegrees(cartographic.longitude)
const latitude = Cesium.Math.toDegrees(cartographic.latitude)
const height = cartographic.height
console.log(longitude, latitude, height)
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
方式二:pickEllipsoid(拾取椭球表面)
当场景没有 3D Tiles 或模型时,pickPosition 可能拿不到结果。此时用 pickEllipsoid 拾取椭球面(忽略地形高度):
js
const cartesian = viewer.camera.pickEllipsoid(
movement.position,
viewer.scene.globe.ellipsoid
)
两者对比
| 方法 | 适用场景 | 注意 |
|---|---|---|
scene.pickPosition() |
有模型/3D Tiles/深度信息 | 需要开启深度测试,结果受深度影响 |
camera.pickEllipsoid() |
纯地球表面 | 忽略地形起伏,只返回椭球面坐标 |
世界坐标转屏幕坐标
js
const position = Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100)
const windowPosition = Cesium.SceneTransforms.worldToWindowCoordinates(
viewer.scene,
position
)
if (Cesium.defined(windowPosition)) {
console.log(windowPosition.x, windowPosition.y)
}
常用于 HTML 标注、弹窗跟随、DOM 面板贴合三维坐标。
注意:当目标点在相机背后或屏幕外时,
worldToWindowCoordinates可能返回undefined或屏幕外坐标,使用前需判断。
射线拾取
camera.getPickRay 可以获取屏幕某点投射出的射线,配合 globe.pick 拾取地形表面(包含地形高度)。
js
const ray = viewer.camera.getPickRay(movement.position)
const cartesian = viewer.scene.globe.pick(ray, viewer.scene)
if (Cesium.defined(cartesian)) {
const cartographic = Cesium.Cartographic.fromCartesian(cartesian)
const longitude = Cesium.Math.toDegrees(cartographic.longitude)
const latitude = Cesium.Math.toDegrees(cartographic.latitude)
const height = cartographic.height
console.log(longitude, latitude, height)
}
与 pickPosition 的区别:globe.pick 专门用于地形表面拾取,结果更稳定。
获取当前视野范围
js
const rectangle = viewer.camera.computeViewRectangle()
if (Cesium.defined(rectangle)) {
const west = Cesium.Math.toDegrees(rectangle.west)
const south = Cesium.Math.toDegrees(rectangle.south)
const east = Cesium.Math.toDegrees(rectangle.east)
const north = Cesium.Math.toDegrees(rectangle.north)
console.log(west, south, east, north)
}
常用场景:相机停止后用当前视野范围请求接口数据,或同步鹰眼地图范围。
相机交互控制
Cesium 默认允许用户用鼠标控制相机:
| 操作 | 效果 |
|---|---|
| 左键拖拽 | 旋转 / 平移 |
| 右键拖拽 | 缩放 |
| 滚轮 | 缩放 |
| 中键拖拽 | 倾斜视角 |
这些交互由 screenSpaceCameraController 控制。
开启 / 关闭交互
js
const controller = viewer.scene.screenSpaceCameraController
controller.enableRotate = true // 旋转
controller.enableTranslate = true // 平移
controller.enableZoom = true // 缩放
controller.enableTilt = true // 倾斜
controller.enableLook = true // 自由观察
禁止缩放
js
viewer.scene.screenSpaceCameraController.enableZoom = false
限制相机高度
js
const controller = viewer.scene.screenSpaceCameraController
controller.minimumZoomDistance = 100 // 最近距离(米)
controller.maximumZoomDistance = 50000 // 最远距离(米)
适合限制用户不能钻到地下,或者不能无限拉远。
监听相机变化
moveStart / moveEnd
相机开始移动和停止移动时触发。
js
viewer.camera.moveStart.addEventListener(() => {
console.log('相机开始移动')
})
viewer.camera.moveEnd.addEventListener(() => {
console.log('相机停止移动')
// 适合在这里请求接口
})
常见用途:
- 相机停止后加载业务数据
- 根据视野范围请求接口
- 同步鹰眼地图
- 保存用户当前视角
camera.changed
camera.changed 是一个更细粒度的相机变化事件,每当相机属性变化超过一定比例时触发。
js
// percentageChanged 控制灵敏度,默认 0.5,范围 0~1
// 值越小,越灵敏(越小的变化也会触发)
viewer.camera.percentageChanged = 0.01
viewer.camera.changed.addEventListener(() => {
const cartographic = viewer.camera.positionCartographic
const height = cartographic.height
console.log('相机高度变化到', height)
})
与 moveEnd 的对比:
| 事件 | 触发时机 | 适用场景 |
|---|---|---|
moveEnd |
相机完全停止后 | 停止后请求接口、保存视角 |
camera.changed |
相机属性变化超过阈值时 | 实时同步视野、动态更新 UI |
2D / 3D / Columbus View
Cesium 支持三种场景模式:
| 模式 | 含义 |
|---|---|
SCENE3D |
三维地球 |
SCENE2D |
二维平面地图 |
COLUMBUS_VIEW |
2.5D 哥伦布视图 |
js
viewer.scene.morphTo3D()
viewer.scene.morphTo2D()
viewer.scene.morphToColumbusView()
不同模式下,相机的交互方式和视锥体都有差异:
SCENE2D下相机只能平移和缩放,视锥体退化为正交投影。COLUMBUS_VIEW类似 3D 但地球被展开为平面,可以倾斜视角。- 大多数三维项目主要使用
SCENE3D。
常见问题 FAQ
1. flyTo 飞完看不到目标
常见原因:高度太低、pitch 方向不对、目标点坐标错误,或 3D Tiles 还没加载完成。建议:
- 先提高
destination高度 - 使用
viewer.flyTo(tileset)自动适配 - 在
complete回调里再确认状态
2. 相机飞到地下
可能是高度设置过低,或开启地形后地表高度高于相机高度。设置 minimumZoomDistance,并结合 sampleTerrainMostDetailed 查询实际地面高度后再设置相机位置。
3. 点击地球拿不到坐标
scene.pickPosition 依赖深度信息,无模型时可能失败。改用 camera.pickEllipsoid 拾取椭球面,或 globe.pick(ray) 拾取地形表面。
4. heading / pitch / roll 方向不符合预期
先确认单位是否是弧度。Cesium 的角度参数统一使用弧度,不是角度。
5. HTML 弹窗位置不跟随
需要在每帧或相机移动时重新调用 SceneTransforms.worldToWindowCoordinates,将三维坐标换算成屏幕坐标,并在监听 scene.postRender 时更新 DOM 位置。
js
viewer.scene.postRender.addEventListener(() => {
const pos = Cesium.SceneTransforms.worldToWindowCoordinates(
viewer.scene,
targetPosition
)
if (Cesium.defined(pos)) {
el.style.left = pos.x + 'px'
el.style.top = pos.y + 'px'
}
})
6. 相机变化导致频繁请求接口
不要在相机每一帧都请求接口。改用 moveEnd 事件,或在 camera.changed 上加防抖。
7. lookAt 之后鼠标旋转行为异常
lookAt 会锁定相机围绕目标点旋转,不是自由旋转。如需恢复:
js
viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY)
8. flyTo 飞行中途如何切换目标
js
viewer.camera.cancelFlight()
viewer.camera.flyTo({ destination: newDestination })
小结
Camera 是 Cesium 里控制视角的核心对象,实际开发中最常用的 API:
| 场景 | 推荐 API |
|---|---|
| 初始化 / 重置视角 | camera.setView |
| 飞到某个经纬度 | camera.flyTo |
| 飞到模型或 Tileset | viewer.flyTo |
| 通过包围球定位 | camera.flyToBoundingSphere |
| 围绕目标观察 | camera.lookAt |
| 取消正在进行的飞行 | camera.cancelFlight |
| 屏幕点转三维坐标 | scene.pickPosition / camera.pickEllipsoid / globe.pick |
| 三维坐标转屏幕坐标 | SceneTransforms.worldToWindowCoordinates |
| 获取当前相机位置 | camera.positionCartographic |
| 获取当前视野范围 | camera.computeViewRectangle |
| 限制用户交互 | screenSpaceCameraController |
| 相机停止后触发逻辑 | camera.moveEnd |
| 实时监听相机变化 | camera.changed + percentageChanged |