在 Cesium 三维可视化开发中,经常需要精确控制相机的位置和视角。本文将介绍如何根据目标点位置、相机与目标的距离、视角方向等参数,计算出相机的精确位置,并实现相机飞行定位的功能。
相机定位的核心问题
在 Cesium 中,直接设置相机位置通常需要提供世界坐标系中的绝对位置。但在实际应用中,我们往往更关心:
- 相机需要看向哪个点(目标点)
- 相机距离目标点多远(距离)
- 相机应该从什么角度观察目标(方位角和俯仰角)
这就需要我们编写一个函数,将这些更直观的参数转换为 Cesium 相机所需的绝对位置。
解决方案原理
我们的解决方案基于以下步骤:
- 以目标点为中心建立局部坐标系(东北上 ENU 坐标系)
- 在该坐标系中计算相机的相对位置
- 将相对位置转换回世界坐标系
这样就能准确定位相机,无论目标点在地球上的哪个位置。
核心函数解析
下面是计算相机位置的核心函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function calculateCameraPosition(targetPosition, distanceFromTarget, heading, pitch) { var transform = Cesium.Transforms.eastNorthUpToFixedFrame(targetPosition);
var offset = new Cesium.Cartesian3(0, 0, distanceFromTarget);
var rotation = Cesium.Matrix3.fromHeadingPitchRoll( new Cesium.HeadingPitchRoll(-heading, pitch - Cesium.Math.PI_OVER_TWO, 0) ); Cesium.Matrix3.multiplyByVector(rotation, offset, offset);
var cameraPosition = new Cesium.Cartesian3(); Cesium.Matrix4.multiplyByPoint(transform, offset, cameraPosition);
return cameraPosition; }
|
关键步骤说明
创建局部坐标系:
1
| var transform = Cesium.Transforms.eastNorthUpToFixedFrame(targetPosition);
|
这一步创建了一个转换矩阵,将局部坐标系(东、北、上)与全球坐标系关联起来。
设置初始偏移:
1
| var offset = new Cesium.Cartesian3(0, 0, distanceFromTarget);
|
初始偏移设置为(0,0,distance),表示相机位于目标点正上方 distance 距离处。
计算旋转:
1 2 3
| var rotation = Cesium.Matrix3.fromHeadingPitchRoll( new Cesium.HeadingPitchRoll(-heading, pitch - Cesium.Math.PI_OVER_TWO, 0) );
|
这里创建一个旋转矩阵,注意参数的调整:
-heading:负号是因为 Cesium 中 heading 的方向定义
pitch - Cesium.Math.PI_OVER_TWO:减去 90 度是因为我们的初始位置在正上方
应用旋转:
1
| Cesium.Matrix3.multiplyByVector(rotation, offset, offset);
|
将旋转应用到偏移向量上。
转换到全球坐标:
1
| Cesium.Matrix4.multiplyByPoint(transform, offset, cameraPosition);
|
将局部坐标系中的偏移量转换为全球坐标系中的位置。
使用示例
了解了原理后,下面看看如何使用这个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| var targetPosition = Cesium.Cartesian3.fromDegrees(116.391, 39.904, 50);
var distanceFromTarget = 1000; var heading = Cesium.Math.toRadians(45); var pitch = Cesium.Math.toRadians(30);
var cameraPosition = calculateCameraPosition(targetPosition, distanceFromTarget, heading, pitch);
viewer.camera.setView({ destination: cameraPosition, orientation: { heading: heading, pitch: pitch, roll: 0, }, });
|
这样设置后,相机会精确地位于目标点东北方向 1000 米处,并以 45 度方位角和 30 度俯仰角观察目标点。
应用场景
这种相机定位方法在许多场景中非常有用:
- 关键位置展示:快速定位到建筑物、地标等关键点的最佳观察位置
- 动画路径设计:创建围绕目标点的相机飞行路径
- 自定义导航控制:实现更直观的导航控制,如”环绕目标点旋转”、”拉近/拉远”等
- 多视角切换:在不同角度之间平滑过渡,展示目标的不同方面
完整代码
这里是完整的实现代码,包含了计算函数和使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
function calculateCameraPosition(targetPosition, distanceFromTarget, heading, pitch) { var transform = Cesium.Transforms.eastNorthUpToFixedFrame(targetPosition);
var offset = new Cesium.Cartesian3(0, 0, distanceFromTarget);
var rotation = Cesium.Matrix3.fromHeadingPitchRoll( new Cesium.HeadingPitchRoll(-heading, pitch - Cesium.Math.PI_OVER_TWO, 0) ); Cesium.Matrix3.multiplyByVector(rotation, offset, offset);
var cameraPosition = new Cesium.Cartesian3(); Cesium.Matrix4.multiplyByPoint(transform, offset, cameraPosition);
return cameraPosition; }
function flyToPositionWithCamera() { var targetPosition = Cesium.Cartesian3.fromDegrees(116.391, 39.904, 50);
var distanceFromTarget = 1000; var heading = Cesium.Math.toRadians(45); var pitch = Cesium.Math.toRadians(30);
var cameraPosition = calculateCameraPosition(targetPosition, distanceFromTarget, heading, pitch);
viewer.camera.flyTo({ destination: cameraPosition, orientation: { heading: heading, pitch: pitch, roll: 0, }, duration: 2, }); }
flyToPositionWithCamera();
|
总结
通过这种方法,我们可以更加直观地控制 Cesium 中的相机位置,无需直接处理复杂的世界坐标系。这对于创建精确的观察视角、设计相机动画路径等场景非常有用。
希望这篇文章对您在 Cesium 开发中控制相机有所帮助!