Cesium中通过 Primitive 高效添加 点、线、多边形、圆、椭圆、球、模型 等地理要素,以下是各类地理要素的高效添加方式:
一、公告板
1. 创建 BillboardCollection 并添加到场景
const billboards = viewer.scene.primitives.add(new Cesium.BillboardCollection());
- new Cesium.BillboardCollection()
创建一个新的 公告板集合(BillboardCollection),用于管理多个公告板(Billboard)。
- viewer.scene.primitives.add(...)
将这个公告板集合添加到 Cesium 的 场景(Scene) 中,使其能够被渲染。
作用:
类似于创建一个“容器”,用于高效管理多个公告板(比如多个图标、标记点等),而不是单独添加每个公告板(单独添加会有更高的性能开销)。
2. 向 BillboardCollection 添加一个公告板
billboards.add({
position: Cesium.Cartesian3.fromDegrees(113.3244, 23.1049, 600),
image: '/Assets/nav.svg',
width: 32,
height: 32,
scaleByDistance: new Cesium.NearFarScalar(1e3, 1.0, 2e6, 0.2)
});
- position: Cesium.Cartesian3.fromDegrees(113.3244, 23.1049, 600)
定义公告板的 3D 坐标位置:
113.3244, 23.1049 是经纬度(WGS84 坐标系)。
600 是高度(单位:米),表示公告板在地球表面上方 600 米处。
- image: '/Assets/nav.svg'
公告板显示的 图片路径(这里是 /Assets/nav.svg,可以是一个 SVG 或 PNG 图标)。
- width: 32, height: 32
公告板的 尺寸(宽度和高度均为 32 像素)。
- scaleByDistance: new Cesium.NearFarScalar(1e3, 1.0, 2e6, 0.2)
视距缩放优化(根据相机距离动态调整公告板大小):
当相机距离 1000 米(1e3) 时,公告板显示原始大小(1.0 倍)。
当相机距离 2000000 米(2e6) 时,公告板缩小到 0.2 倍(避免远处图标过大)。
作用:
在指定位置(广州附近,高度 600 米)添加一个 导航图标(nav.svg),并优化其显示大小(近大远小)。
3、整体作用
这段代码的 核心功能 是:
- 创建一个公告板集合(BillboardCollection),用于高效管理多个公告板。
- 向集合中添加一个公告板,指定其:
- 位置(经纬度 + 高度)。
- 显示的图片(nav.svg)。
- 尺寸(32x32 像素)。
- 视距缩放优化(近大远小,避免远处图标过大)。
4、优化点
- 使用 BillboardCollection 而不是单独添加 Billboard
批量管理多个公告板时,性能更高(减少 GPU 调用次数)。
- scaleByDistance 优化
避免远处图标过大,提升视觉效果。
- 支持 3D 位置(含高度)
不仅能在地表放置图标,还能在 3D 空间(如空中)放置。
5、扩展用法
如果需要添加多个公告板,可以循环调用 billboards.add():
const positions = [
{ lon: 113.3244, lat: 23.1049, height: 600 },
{ lon: 113.3254, lat: 23.1059, height: 700 }
];
positions.forEach(pos => {
billboards.add({
position: Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, pos.height),
image: '/Assets/nav.svg',
width: 32,
height: 32
});
});
性能更好的方式:
const billboards = viewer.scene.primitives.add(
new Cesium.BillboardCollection({
scene: viewer.scene,
debugShowBoundingVolume: false // 关闭调试框,提升性能[4](@ref)
})
);
const positions = [
{ lon: 116.40, lat: 39.91, image: "icon1.png" },
{ lon: 121.47, lat: 31.23, image: "icon2.png" }
];
const billboardList = positions.map(pos => {
return {
image: pos.image,
position: Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat),
scale: 0.8,
color: Cesium.Color.WHITE.withAlpha(0.9),
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM
};
});
// 批量添加(减少渲染调用)
billboards.add(billboardList);
6、动态更新策略
直接修改属性
const billboard = billboards.get(0); // 获取第一个广告牌
billboard.scale = 1.2; // 修改缩放比例
billboard.position = Cesium.Cartesian3.fromDegrees(120.0, 30.0); // 更新位置
动态效果(旋转/闪烁)
// 通过 preRender 事件实现旋转动画[9](@ref)
viewer.scene.preRender.addEventListener(() => {
const time = Date.now() * 0.001;
billboard.rotation = time % (Math.PI * 2); // 持续旋转
billboard.color.alpha = 0.5 + 0.5 * Math.sin(time); // 透明度闪烁
});
按需更新
// 仅当广告牌可见时更新
if (billboard.show) {
billboard.scale = calculateScaleBasedOnDistance();
}
7、性能优化技巧
7.1 GPU 合并渲染
批量添加:单次 billboards.add() 提交多个广告牌,触发 GPU 实例化渲染。
纹理复用:相同图片自动合并纹理,减少 Draw Call。
7.2 距离动态控制
billboard.scaleByDistance = new Cesium.NearFarScalar(1e3, 1.0, 1e5, 0.2);
billboard.translucencyByDistance = new Cesium.NearFarScalar(1e4, 1.0, 2e5, 0.1);
近距离正常显示,远距离缩小并渐隐,降低渲染负载。
7.3 视锥体裁剪
viewer.scene.frustumCulling = true; // 默认开启,自动剔除视野外广告牌
8、内存管理机制
8.1 移除单个广告牌
billboards.remove(billboard); // 移除指定对象
8.2 批量清理
// 移除所有广告牌
billboards.removeAll();
// 或从场景中移除整个集合
viewer.scene.primitives.remove(billboards);
8.3 避免内存泄漏
// 销毁时释放资源
viewer.scene.primitives.destroyPrimitives = true;
9、总结
代码部分 | 作用 |
---|---|
new Cesium.BillboardCollection() |
创建公告板集合(高效管理多个 Billboard) |
viewer.scene.primitives.add(...) |
将集合添加到场景(使其可渲染) |
billboards.add({...}) |
添加一个公告板,指定位置、图片、尺寸和缩放优化 |
这段代码是 Cesium 中高效添加和管理 3D 图标/标记的标准方式,适用于地图、仿真、游戏等场景。
二、文本
使用 Primitive API
// 初始化Viewer
const viewer = new Cesium.Viewer('cesiumContainer', {
terrainProvider: Cesium.createWorldTerrain(),
sceneMode: Cesium.SceneMode.SCENE3D
});
// 创建LabelCollection图元
const labelCollection = viewer.scene.primitives.add(
new Cesium.LabelCollection({
show: true,
// 启用深度测试避免被地形遮挡(需权衡性能)
depthTest: false
})
);
// 批量添加文本标签
const positions = [
{ lon: 116.404, lat: 39.915, text: "北京" },
{ lon: 121.47, lat: 31.23, text: "上海" },
// 更多位置数据...
];
positions.forEach(pos => {
labelCollection.add({
position: Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat),
text: pos.text,
font: '14px sans-serif', // 字体优化:避免过大字号
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
// 垂直对齐:文本位于坐标点下方
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
// 像素偏移:微调位置
pixelOffset: new Cesium.Cartesian2(0, -15)
});
});
三. 点(Point)
使用 Primitive API(高性能,适合大量点)
const pointPrimitiveCollection = viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection());
pointPrimitiveCollection.add({
position: Cesium.Cartesian3.fromDegrees(113.3244, 23.1049, 0),
color: Cesium.Color.RED,
pixelSize: 10
});
四、线(Polyline)
使用 Primitive API(Primitive 方式需手动构建 Geometry)
const polylineCollection = viewer.scene.primitives.add(new Cesium.PolylineCollection());
polylineCollection.add({
positions: Cesium.Cartesian3.fromDegreesArray([
113.3244, 23.1049,
113.3254, 23.1059
]),
width: 2,
material: new Cesium.ColorMaterialProperty(Cesium.Color.BLUE)
});
五、多边形(Polygon)
使用 Primitive API(Primitive 方式需手动构建 Geometry)
const polygonCollection = viewer.scene.primitives.add(new Cesium.PolygonCollection());
polygonCollection.add({
hierarchy: Cesium.Cartesian3.fromDegreesArray([
113.3244, 23.1049,
113.3254, 23.1059,
113.3264, 23.1039
]),
material: new Cesium.ColorMaterialProperty(Cesium.Color.GREEN.withAlpha(0.5))
});
六、圆(Circle)
使用 Primitive API(需手动计算圆周点)
// 初始化Cesium
const viewer = new Cesium.Viewer('cesiumContainer', {
terrainProvider: Cesium.createWorldTerrain(),
baseLayerPicker: false, // 禁用底图选择器
geocoder: false, // 禁用地理编码器
homeButton: false, // 禁用主页按钮
infoBox: false, // 禁用信息框
sceneModePicker: false, // 禁用场景模式选择器
selectionIndicator: false, // 禁用选择指示器
navigationHelpButton: false, // 禁用导航帮助按钮
animation: false, // 禁用动画控件
timeline: false, // 禁用时间轴
fullscreenButton: false // 禁用全屏按钮
});
// 定义圆的中心点和半径
const centerLon = 113.3244;
const centerLat = 23.1049;
const radiusInMeters = 1000;
// 将圆心转换为 Cartesian3 坐标
const centerCartesian = Cesium.Cartesian3.fromDegrees(centerLon, centerLat);
// 计算圆的 Cartesian3 点集(近似采样)
const granularity = Cesium.Math.RADIANS_PER_DEGREE; // 采样精度(弧度/度)
const positions = [];
for (let angle = 0; angle < 360; angle += granularity) {
const radians = Cesium.Math.toRadians(angle);
// 计算圆周上的点(基于球面坐标)
const x = radiusInMeters * Math.cos(radians);
const y = radiusInMeters * Math.sin(radians);
// 将局部坐标转换为全局 Cartesian3
const point = Cesium.Cartesian3.fromDegrees(
centerLon + x / 111320, // 经度偏移(1度≈111320米)
centerLat + y / (111320 * Math.cos(Cesium.Math.toRadians(centerLat))), // 纬度偏移(考虑纬度缩放)
0 // 高度(与圆心相同)
);
positions.push(point);
}
// 闭合圆(首尾相连)
positions.push(positions[0]);
// 使用 Primitive API 添加圆
const primitiveCollection = viewer.scene.primitives.add(new Cesium.PrimitiveCollection());
primitiveCollection.add(
new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(positions),
perPositionHeight: false // 固定高度
}),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(
Cesium.Color.BLUE.withAlpha(0.5) // 半透明蓝色
)
}
}),
appearance: new Cesium.PerInstanceColorAppearance({
outline: true,
outlineColor: Cesium.Color.RED,
outlineWidth: 2
})
})
);
// 定位相机到圆的位置
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(centerLon, centerLat, 5000),
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-30)
}
});
七、椭圆(Ellipse)
使用 Primitive API(需手动计算椭圆点集)
const viewer = new Cesium.Viewer('cesiumContainer', {
terrainProvider: Cesium.createWorldTerrain(),
baseLayerPicker: false, // 禁用底图选择器
geocoder: false, // 禁用地理编码器
homeButton: false, // 禁用主页按钮
infoBox: false, // 禁用信息框
sceneModePicker: false, // 禁用场景模式选择器
selectionIndicator: false, // 禁用选择指示器
navigationHelpButton: false, // 禁用导航帮助按钮
animation: false, // 禁用动画控件
timeline: false, // 禁用时间轴
fullscreenButton: false // 禁用全屏按钮
});
// 定义椭圆的中心点、半长轴、半短轴和旋转角度
const centerLon = 113.3244;
const centerLat = 23.1049;
const semiMajorAxis = 2000; // 半长轴(米)
const semiMinorAxis = 1000; // 半短轴(米)
const rotation = Cesium.Math.toRadians(45); // 旋转角度(弧度)
// 将椭圆中心转换为 Cartesian3 坐标
const centerCartesian = Cesium.Cartesian3.fromDegrees(centerLon, centerLat);
// 计算椭圆的 Cartesian3 点集(近似采样)
const granularity = Cesium.Math.RADIANS_PER_DEGREE; // 采样精度(弧度/度)
const positions = [];
for (let angle = 0; angle < 360; angle += granularity) {
const radians = Cesium.Math.toRadians(angle);
// 计算椭圆上的点(基于参数方程)
const x = semiMajorAxis * Math.cos(radians);
const y = semiMinorAxis * Math.sin(radians);
// 旋转椭圆
const rotatedX = x * Math.cos(rotation) - y * Math.sin(rotation);
const rotatedY = x * Math.sin(rotation) + y * Math.cos(rotation);
// 将局部坐标转换为全局 Cartesian3
const point = Cesium.Cartesian3.fromDegrees(
centerLon + rotatedX / 111320, // 经度偏移(1度≈111320米)
centerLat + rotatedY / (111320 * Math.cos(Cesium.Math.toRadians(centerLat))), // 纬度偏移(考虑纬度缩放)
0 // 高度(与椭圆中心相同)
);
positions.push(point);
}
// 闭合椭圆(首尾相连)
positions.push(positions[0]);
// 使用 Primitive API 添加椭圆
const primitiveCollection = viewer.scene.primitives.add(new Cesium.PrimitiveCollection());
primitiveCollection.add(
new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(positions),
perPositionHeight: false // 固定高度
}),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(
Cesium.Color.BLUE.withAlpha(0.5) // 半透明蓝色
)
}
}),
appearance: new Cesium.PerInstanceColorAppearance({
outline: true,
outlineColor: Cesium.Color.RED,
outlineWidth: 2
})
})
);
// 定位相机到椭圆位置
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(centerLon, centerLat, 5000),
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-30)
}
});
八、球(Sphere)
使用 Primitive API(Primitive 方式需手动构建 Geometry)
const sphereCollection = viewer.scene.primitives.add(new Cesium.PrimitiveCollection());
sphereCollection.add(
new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: new Cesium.EllipsoidGeometry({
vertexFormat: Cesium.VertexFormat.POSITION_AND_NORMAL,
radii: new Cesium.Cartesian3(100, 100, 100)
})
}),
appearance: new Cesium.PerInstanceColorAppearance()
})
);
九、3D 模型(Model)
使用 Primitive API(Primitive 方式需手动加载模型)
性能优化建议
场景 | 推荐方式 | 原因 |
---|---|---|
少量要素 | Entity API |
代码简洁,开发效率高 |
大量要素(>1000) | Primitive API |
性能更高,减少 CPU-GPU 通信开销 |
动态更新(如轨迹动画) | Entity API |
支持更简单的属性动画 |
自定义渲染(如特殊着色器) | Primitive API |
可深度定制渲染逻辑 |
总结
要素类型 | 推荐 API | 示例代码 |
---|---|---|
点 | Entity 或 PointPrimitiveCollection |
viewer.entities.add({ point: {...} }) |
线 | Entity 或 PolylineCollection |
viewer.entities.add({ polyline: {...} }) |
多边形 | Entity 或 PolygonCollection |
viewer.entities.add({ polygon: {...} }) |
圆/椭圆 | Entity API(更简单) |
viewer.entities.add({ circle: {...} }) |
球 | Entity API(更简单) |
viewer.entities.add({ ellipsoid: {...} }) |
模型 | Entity API(更简单) |
viewer.entities.add({ model: {...} }) |