首页 / 专栏 / Cesium / Cesium PointPrimitive 深度教程:3D 场景高性能点元渲染

Cesium PointPrimitive 深度教程:3D 场景高性能点元渲染

  • 发布时间: 2026-02-24 23:30:31
  • 相关标签: cesium label 标签
  • 简介: 你需要一份关于 Cesium PointPrimitive(点元)的深入讲解教程,我会从核心概念、基础使用、属性详解到性能优化,全方位带你掌握这个用于在 3D 场景中渲染**高性能点状图形**的核心类,重点聚焦它的使用场景、参数配置和性能调优技巧。

一、PointPrimitive 核心认知

1.1 什么是 PointPrimitive

PointPrimitive 是 Cesium 中用于渲染单个点状图形的基础类,表现为 3D 场景中一个可配置颜色、大小、轮廓的像素点,通常用于标记坐标点(如 GPS 定位、设备点位、兴趣点 POI 等)。

1.2 核心注意事项

  • 禁止直接调用构造函数new Cesium.PointPrimitive() 是错误用法,必须通过 PointPrimitiveCollection#add() 创建(类似 Label 依赖 LabelCollection);
  • 性能特性:单个属性读取为常量时间,赋值虽为常量时间但会触发 CPU 到 GPU 的数据传输(批量更新建议清空重建);
  • 渲染逻辑:点的大小以像素为单位,不受场景缩放影响(除非配置 scaleByDistance),适合标记固定像素尺寸的点位。

1.3 适用场景

  • 海量点位渲染(如数万级 GPS 轨迹点、城市 POI 点);
  • 实时更新的设备点位(如物联网设备、移动终端定位);
  • 简单的坐标标记(无需复杂模型/图标,仅需点状提示)。

二、基础使用:创建第一个 PointPrimitive

2.1 完整基础示例

以下是创建 PointPrimitiveCollection 并添加点元的最小可运行代码,覆盖核心步骤:

// 1. 初始化 Cesium Viewer
const viewer = new Cesium.Viewer("cesiumContainer", {
  terrain: Cesium.Terrain.fromWorldTerrain(), // 可选:加载地形
  animation: false,
  timeline: false
});

// 2. 创建 PointPrimitiveCollection 集合(管理点元)
const pointCollection = new Cesium.PointPrimitiveCollection();
// 将集合添加到场景的原语中(关键:否则点元不会显示)
viewer.scene.primitives.add(pointCollection);

// 3. 添加单个 PointPrimitive 点元
const point = pointCollection.add({
  // 点位坐标(示例:北京经纬度转笛卡尔坐标)
  position: Cesium.Cartesian3.fromDegrees(116.403874, 39.914885, 100),
  // 核心配置
  pixelSize: 10, // 点的内部像素大小
  color: Cesium.Color.RED, // 点的填充色
  outlineColor: Cesium.Color.WHITE, // 轮廓颜色
  outlineWidth: 2, // 轮廓像素宽度
  show: true, // 是否显示
  id: "beijing-point" // 自定义 ID(拾取时返回)
});

// 4. 视角聚焦到点元位置
viewer.zoomTo(pointCollection);

2.2 核心步骤解析

  1. 创建集合:PointPrimitive 必须依赖 PointPrimitiveCollection 管理,先创建集合并添加到场景;
  2. 添加点元:通过 add() 方法传入配置参数,返回 PointPrimitive 实例可后续修改属性;
  3. 场景挂载:集合必须通过 scene.primitives.add() 挂载到场景,否则点元无法渲染。

三、核心属性详解

3.1 基础属性(必配)

属性名 类型 默认值 作用 示例
position Cartesian3 - 点元的 3D 笛卡尔坐标 Cesium.Cartesian3.fromDegrees(116.4, 39.9, 100)
pixelSize number - 点的内部像素大小 10(10 像素直径)
color Color - 点的填充色(支持透明度) Cesium.Color.RED / new Cesium.Color(1,0,0,0.5)(半透明红)
show boolean true 是否显示点元 false(隐藏点元)
id * undefined 自定义标识(拾取时返回) "point-1" / {id: 1, name: "北京"}

示例:配置带透明度的点元

pointCollection.add({
  position: Cesium.Cartesian3.fromDegrees(116.403874, 39.914885, 100),
  pixelSize: 15,
  // 半透明蓝色(RGBA:0,0,1,0.6)
  color: new Cesium.Color(0, 0, 1, 0.6),
  show: true
});

3.2 轮廓配置属性

属性名 类型 默认值 作用 注意事项
outlineColor Color - 点的轮廓颜色 需配合 outlineWidth 使用
outlineWidth number - 轮廓像素宽度 宽度会叠加到 pixelSize 上,总大小 = 内部尺寸 + 2*轮廓宽度

示例:带白色轮廓的红色点元

pointCollection.add({
  position: Cesium.Cartesian3.fromDegrees(116.403874, 39.914885, 100),
  pixelSize: 8, // 内部8像素
  color: Cesium.Color.RED,
  outlineColor: Cesium.Color.WHITE, // 白色轮廓
  outlineWidth: 2, // 轮廓2像素 → 总大小 12 像素
  show: true
});

3.3 距离相关动态属性

这类属性基于相机与点元的距离动态调整点的大小/透明度,核心依赖 NearFarScalar 类(需保证 far > near,否则抛出 DeveloperError)。

1. scaleByDistance:距离缩放

  • 作用:根据相机距离动态调整点的缩放比例(缩放 pixelSizeoutlineWidth);
  • 参数NearFarScalar(near, nearValue, far, farValue)(near/far 为距离,nearValue/farValue 为对应缩放比例)。

示例:距离越远,点越小(直至消失)

point.scaleByDistance = new Cesium.NearFarScalar(
  1000, // 相机距离点 1000 米内
  2.0,  // 缩放比例 2 倍(点变大)
  8000, // 相机距离点 8000 米外
  0.0   // 缩放比例 0(点消失)
);

2. translucencyByDistance:距离透明度

  • 作用:根据相机距离动态调整点的透明度;
  • 参数NearFarScalar(near, nearValue, far, farValue)(透明度值 0-1,1 为不透明,0 为完全透明)。

示例:距离越远,点越透明

point.translucencyByDistance = new Cesium.NearFarScalar(
  1000, // 1000 米内
  1.0,  // 完全不透明
  8000, // 8000 米外
  0.0   // 完全透明
);

3. distanceDisplayCondition:距离显示条件

  • 作用:限定点元仅在指定距离范围内显示;
  • 参数DistanceDisplayCondition(near, far)(near 为最小显示距离,far 为最大显示距离)。

示例:仅在 500-5000 米范围内显示

point.distanceDisplayCondition = new Cesium.DistanceDisplayCondition(500, 5000);

3.4 高级属性

1. disableDepthTestDistance:禁用深度测试距离

  • 作用:设置距离相机多少米内禁用深度测试,避免点元被地形/模型遮挡;
  • 取值
    • 0:始终启用深度测试(默认);
    • Number.POSITIVE_INFINITY:始终禁用深度测试(点元永远在最上层);
    • 具体数值(如 10000):10000 米内禁用深度测试。

示例:避免点元被地形遮挡

point.disableDepthTestDistance = Number.POSITIVE_INFINITY; // 始终在最上层

2. splitDirection:分割方向

  • 作用:用于多视图分割场景(如左右屏、上下屏),指定点元在哪个分割视图中显示;
  • 默认值Cesium.SplitDirection.NONE(所有视图显示);
  • 常用值SplitDirection.LEFT(仅左视图)、SplitDirection.RIGHT(仅右视图)。

示例:仅左视图显示点元

point.splitDirection = Cesium.SplitDirection.LEFT;

四、核心方法详解

4.1 computeScreenSpacePosition(scene, result)

  • 作用:计算点元在屏幕空间的坐标(画布左上角为原点,x 向右、y 向下);
  • 参数
    • scene:场景实例(viewer.scene);
    • result(可选):存储结果的 Cartesian2 对象(避免重复创建);
  • 返回值Cartesian2 屏幕坐标。

示例:获取点元的屏幕坐标

const screenPos = point.computeScreenSpacePosition(viewer.scene);
console.log("点元屏幕坐标:", screenPos.x, screenPos.y);

4.2 equals(other)

  • 作用:判断两个点元是否相等(所有属性完全一致则返回 true);
  • 参数:待比较的 PointPrimitive 实例;
  • 返回值boolean

示例:比较两个点元

const point1 = pointCollection.add({...});
const point2 = pointCollection.add({...});
console.log("点元是否相等:", point1.equals(point2)); // false

五、批量操作与性能优化

5.1 批量添加点元(高性能)

当需要添加大量点元(如数万级),建议批量循环添加后统一渲染:

// 批量添加 1000 个随机点元
function addBatchPoints(count) {
  // 暂停渲染(减少中间渲染开销)
  viewer.scene.requestRenderMode = true;
  
  for (let i = 0; i < count; i++) {
    // 随机经纬度(北京周边)
    const lon = 116.0 + Math.random() * 1.0;
    const lat = 39.8 + Math.random() * 0.4;
    // 随机颜色
    const color = Cesium.Color.fromRandom({ alpha: 0.8 });
    
    pointCollection.add({
      position: Cesium.Cartesian3.fromDegrees(lon, lat, 50),
      pixelSize: 8,
      color: color,
      outlineColor: Cesium.Color.WHITE,
      outlineWidth: 1
    });
  }
  
  // 手动触发渲染
  viewer.scene.requestRender();
  viewer.scene.requestRenderMode = false;
}

// 调用:添加 1000 个点元
addBatchPoints(1000);

5.2 批量更新:清空重建优于逐个修改

根据 Cesium 官方建议,若大部分点元需要更新属性,清空集合后重新添加比逐个修改更高效:

// 低效方式:逐个修改(不推荐)
for (let i = 0; i < pointCollection.length; i++) {
  const p = pointCollection.get(i);
  p.color = Cesium.Color.BLUE; // 每个修改都触发 GPU 传输
}

// 高效方式:清空重建(推荐)
pointCollection.removeAll(); // 清空旧点元
// 重新添加更新后的点元
addBatchPoints(1000); // 批量添加新配置的点元

5.3 临时隐藏:用 show 替代增删

临时隐藏点元时,优先设置 show: false,而非移除后重新添加:

// 低效方式(不推荐)
pointCollection.remove(point); // 移除
// ... 后续重新添加
pointCollection.add({...});

// 高效方式(推荐)
point.show = false; // 隐藏
// ... 后续显示
point.show = true;

5.4 避免重复创建集合

多次更新点元时,复用同一个 PointPrimitiveCollection 比反复创建新集合更高效:

// 错误方式(性能差)
function updatePointsBad() {
  // 每次更新都创建新集合
  const newCollection = new Cesium.PointPrimitiveCollection();
  viewer.scene.primitives.remove(pointCollection); // 移除旧集合
  viewer.scene.primitives.add(newCollection); // 添加新集合
  // ... 添加点元
}

// 正确方式(性能优)
function updatePointsGood() {
  // 复用现有集合,清空后重建
  pointCollection.removeAll();
  addBatchPoints(1000); // 重新添加点元
}

六、交互实现:点元拾取与点击事件

6.1 点元拾取(获取点击的点元)

通过 scene.pick() 方法可拾取鼠标点击位置的点元,结合 id 属性实现交互:

// 监听鼠标左键点击事件
viewer.screenSpaceEventHandler.setInputAction((event) => {
  // 获取点击位置的原语
  const pickedObject = viewer.scene.pick(event.position);
  // 判断是否拾取到 PointPrimitive
  if (Cesium.defined(pickedObject) && pickedObject.id) {
    alert(`你点击了点元:${pickedObject.id}`);
    // 示例:修改点击点元的颜色
    pickedObject.color = Cesium.Color.GREEN;
  }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

6.2 点元高亮(鼠标悬浮效果)

结合鼠标移动事件实现点元悬浮高亮:

let highlightedPoint = null; // 记录当前高亮的点元

// 监听鼠标移动事件
viewer.screenSpaceEventHandler.setInputAction((event) => {
  // 清除上一个高亮点元的样式
  if (Cesium.defined(highlightedPoint)) {
    highlightedPoint.pixelSize = 8; // 恢复原大小
    highlightedPoint.outlineWidth = 1; // 恢复原轮廓
    highlightedPoint = null;
  }

  // 拾取当前鼠标位置的点元
  const pickedObject = viewer.scene.pick(event.endPosition);
  if (Cesium.defined(pickedObject) && pickedObject instanceof Cesium.PointPrimitive) {
    // 高亮样式:放大尺寸 + 加粗轮廓
    highlightedPoint = pickedObject;
    highlightedPoint.pixelSize = 12;
    highlightedPoint.outlineWidth = 3;
  }
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

七、常见问题与解决方案

问题 原因 解决方案
点元不显示 1. 集合未添加到 scene.primitives;2. 坐标错误;3. show: false 1. 调用 viewer.scene.primitives.add(pointCollection);2. 检查坐标格式;3. 确认 show: true
点元被地形/模型遮挡 深度测试导致 设置 disableDepthTestDistance: Number.POSITIVE_INFINITY
抛出 scaleByDistance.far must be greater than scaleByDistance.near NearFarScalar 的 far ≤ near 确保 far 值大于 near 值(如 new NearFarScalar(1000, 2, 8000, 0)
批量更新性能差 逐个修改点元属性 清空集合(removeAll())后重新添加点元
点元大小随场景缩放变化 误配置 scaleByDistance 或像素尺寸过大 1. 移除 scaleByDistance;2. 调整 pixelSize 为合理值(1-20)

八、PointPrimitive vs Billboard 对比

很多开发者会混淆 PointPrimitive 和 Billboard(广告牌),两者核心区别如下:

特性 PointPrimitive Billboard
表现形式 像素点(无纹理) 图片/画布纹理(可加载图标)
大小单位 像素(固定尺寸) 像素(固定尺寸)
性能 极高(海量点位首选) 较高(需加载纹理,略逊于点元)
样式配置 仅颜色、大小、轮廓 支持图片、旋转、拉伸等
适用场景 海量简单点位标记 需自定义图标/图片的点位

总结

  1. PointPrimitive 是 Cesium 中渲染高性能点状图形的核心类,禁止直接调用构造函数,必须通过 PointPrimitiveCollection#add() 创建;
  2. 核心配置包括:位置(position)、大小(pixelSize)、颜色(color)、轮廓(outlineColor/outlineWidth),以及距离相关的动态属性(scaleByDistance/translucencyByDistance);
  3. 性能优化关键:批量更新优先清空重建、临时隐藏用 show 属性、复用集合避免重复创建、禁用深度测试解决遮挡问题。

同分类推荐