0%

基于Cesium的三维视锥绘制与画面模拟

基于Cesium的三维视锥绘制与画面模拟

有朋友咨询我cesium下的视锥绘制与监控视角模拟是如何实现的,这里以JavaScript为例,给出一些示例代码。前置条件是,创建了Cesium的Viewer,而为了观察更加真实的模拟视角,可以向地图添加3dtiles。

空间定位参数:共同确定了视锥体的顶点位置及其在三维地球坐标系中的指向。

  • Position (Lon, Lat, Alt):视锥体的顶点坐标。它是相机的光心位置,即所有视线的汇聚点。在 Cesium 中通常使用经纬度和高度
  • Orientation (Heading, Pitch, Roll):视锥体的*
    • Heading:决定水平旋转(左右看)
    • Pitch:决定俯仰角度(上下看),通常监控视角为负值。
    • Roll:决定相机的侧倾(

镜头几何参数:定义了视锥体截面的形状,模拟了不同焦距镜头产生的视觉效果。

  • FOV (Field of View - 视场角):通常指垂直方向的张角。FOV 越大,画面容纳的内容越多(广角镜头);FOV 越小,画面越窄(长焦镜头)。
  • Aspect Ratio (宽高比):即视野截面的宽度与高度之比。它必须与你的显示画布(Canvas)或视频流(如 16:9)一致,否则画面会产生拉伸或压缩。

裁剪平面参数:定义了视锥体在视线方向上的有效渲染范围。

  • Near (近裁剪面):距离相机的最近可见距离。小于此距离的物体会被切掉(防止相机穿模)。
  • Far (远裁剪面):距离相机的最远可见距离。超过此距离的物体将不再渲染,这直接影响渲染性能和地平线的可见度。

绘制流程首先通过 Cesium.Cartesian3.fromDegrees 将经纬度高度转换为空间位置,随后利用 HeadingPitchRoll 结合 -90 度的坐标系偏移校准生成四元数,以确定视锥在地球表面的精确指向。
接着实例化 PerspectiveFrustum 来定义透视矩阵的核心属性(如张角、宽高比及远近平面),并基于此数学模型分别创建 FrustumGeometry(填充体)和 FrustumOutlineGeometry(轮廓线)两个几何实例。
最后,通过设置 ColorGeometryInstanceAttribute 定义颜色与透明度,将实例封装进 Primitive 中并同步添加到场景集合 viewer.scene.primitives,从而在三维空间中渲染出一个代表相机监控范围的半透明四棱台。

给出实现绘制视锥的代码。

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/**
* Cesium 视锥体(传感器/相机视野)可视化工具
* 适用场景:相机监控范围模拟、多目标跟踪(MTMC)视野展示
*/
class FrustumVisualizer {
/**
* 创建并添加一个视锥体到场景中
* @param {Cesium.Viewer} viewer - Cesium 视图实例
* @param {Object} params - 配置参数
* @param {number} params.lon - 经度 (度)
* @param {number} params.lat - 纬度 (度)
* @param {number} params.alt - 高度 (米)
* @param {number} [params.heading=0] - 偏航角 (0为正北,顺时针为正)
* @param {number} [params.pitch=-30] - 俯仰角 (负数向下看)
* @param {number} [params.vfov=60] - 垂直视场角 (度)
* @param {number} [params.aspectRatio=1.77] - 宽高比 (16/9 ≈ 1.77)
* @param {number} [params.near=0.1] - 近裁剪面距离 (米)
* @param {number} [params.far=50] - 远裁剪面距离 (米)
* @param {Cesium.Color} [params.color] - 视锥颜色
* @returns {Object} 包含 fill 和 outline 两个 Primitive 对象的实例,用于后续销毁
*/
static createFrustum(viewer, params) {
// 1. 提取参数并设置默认值
const {
lon, lat, alt,
heading = 0,
pitch = -30,
vfov = 60,
aspectRatio = 16 / 9,
near = 0.5,
far = 50,
color = Cesium.Color.RED
} = params;

// 2. 空间位置计算:将经纬度转换为 Cesium 笛卡尔空间坐标 (XYZ)
const position = Cesium.Cartesian3.fromDegrees(lon, lat, alt);

// 3. 姿态计算 (Orientation)
// 注意:Cesium 默认 FrustumGeometry 的朝向是沿局部 Z 轴负方向
// 为了符合地图常规(Heading 0指向北),需要进行 -90 度的坐标系偏移校准
const hpr = new Cesium.HeadingPitchRoll(
Cesium.Math.toRadians(heading - 90), // 调整 Heading 使 0 度指向北方
Cesium.Math.toRadians(pitch - 90), // 调整 Pitch 使 0 度水平,负值向下
Cesium.Math.toRadians(0) // Roll 滚动角
);

// 将位置和旋转角度转换为四元数(Quaternion),这是 Cesium 定位几何体的标准方式
const orientationQuat = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);

// 4. 定义视锥几何属性 (Frustum Definition)
const frustum = new Cesium.PerspectiveFrustum({
fov: Cesium.Math.toRadians(vfov), // 弧度单位的视场角
aspectRatio: 1.0 / aspectRatio, // 注意:Cesium 的几何宽高比通常取倒数
near: near,
far: far
});

// 5. 创建“填充体”几何实例
const fillInstance = new Cesium.GeometryInstance({
geometry: new Cesium.FrustumGeometry({
frustum: frustum,
origin: position,
orientation: orientationQuat,
vertexFormat: Cesium.VertexFormat.POSITION_ONLY // 仅需位置信息,提高渲染效率
}),
attributes: {
// 设置实例颜色属性
color: Cesium.ColorGeometryInstanceAttribute.fromColor(color.withAlpha(0.25))
}
});

// 6. 创建“轮廓线”几何实例
const outlineInstance = new Cesium.GeometryInstance({
geometry: new Cesium.FrustumOutlineGeometry({
frustum: frustum,
origin: position,
orientation: orientationQuat
}),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(color.withAlpha(0.8))
}
});

// 7. 将几何体作为 Primitive 添加到场景
// Primitive 比 Entity 更底层,适合高频创建或大量展示
const fillPrimitive = viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: fillInstance,
appearance: new Cesium.PerInstanceColorAppearance({
closed: true, // 封闭几何体
translucent: true // 开启透明支持
}),
asynchronous: false // 同步创建,防止第一帧不显示
}));

const outlinePrimitive = viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: outlineInstance,
appearance: new Cesium.PerInstanceColorAppearance({
flat: true, // 线条使用扁平着色,不计算光照
translucent: true
}),
asynchronous: false
}));

// 返回对象,方便外部管理(如:removal)
return { fill: fillPrimitive, outline: outlinePrimitive };
}
}

在你的主逻辑文件中,直接传入坐标和旋转参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 假设你已经初始化了 viewer
const cameraOptions = {
lon: 121.45, // 经度
lat: 31.22, // 纬度
alt: 10, // 离地高度 10 米
heading: 45, // 朝向东北
pitch: -45, // 向下俯冲 45 度看地面
vfov: 50, // 视野范围
color: Cesium.Color.AQUA // 也可以自定义颜色
};

// 调用并存储返回的对象
const myFrustum = FrustumVisualizer.createFrustum(viewer, cameraOptions);

模拟流程首先根据传感器坐标设定相机的 destination 目标位置,随后调用 viewer.camera.flyTo 并传入原始的航向角(Heading)与俯仰角(Pitch),启动平滑飞行过渡以对准观察方向。
在相机飞抵指定点后,最关键的一步是手动覆盖 viewer.camera.frustum 属性,通过 new Cesium.PerspectiveFrustum 强制修改相机的投影矩阵。通过同步垂直视场角(fov)、画布宽高比(aspectRatio)以及远近裁剪面,使当前屏幕显示的渲染画面与之前在场景中绘制的红色视锥几何体在数学投影上完全重合。
给出模拟视锥视角的代码:

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
/**
* 将场景相机定位至指定传感器视角,并同步镜头参数
* @param {Cesium.Viewer} viewer - Cesium 视图实例
* @param {Object} props - 传感器参数对象
* @param {number} props.lon - 经度
* @param {number} props.lat - 纬度
* @param {number} props.alt - 高度 (米)
* @param {number} props.heading - 偏航角 (度)
* @param {number} props.pitch - 俯仰角 (度)
* @param {number} props.vfov - 垂直视场角 (度)
* @param {number} props.aspectRatio - 宽高比
* @param {number} props.near - 近裁剪面距离
* @param {number} props.far - 远裁剪面距离
*/
function goToCameraView(viewer, props) {
if (!viewer || !props) return;

// 1. 计算相机目标位置 (Cartesian3 笛卡尔坐标)
const destination = Cesium.Cartesian3.fromDegrees(
props.lon,
props.lat,
props.alt
);

// 2. 执行相机飞行定位
viewer.camera.flyTo({
destination: destination,
orientation: {
// Cesium 相机默认 0 度指向正北,无需像几何体那样做 -90 度校准
heading: Cesium.Math.toRadians(props.heading),
pitch: Cesium.Math.toRadians(props.pitch),
roll: 0.0 // 保持水平,不翻转
},
duration: 1.5 // 飞行持续时间(秒)
});

// 3. 同步相机的视锥体(Lens Parameters)
// 核心逻辑:将虚拟相机的“镜头”修改为与绘制的视锥几何完全一致
viewer.camera.frustum = new Cesium.PerspectiveFrustum({
// 将垂直视场角从度转换为弧度
fov: Cesium.Math.toRadians(props.vfov),
// 画面宽高比,决定了横向视野的开阔度
aspectRatio: props.aspectRatio,
// 决定相机能看到的最近和最远距离
near: props.near,
far: props.far
});
}