Cesium 中 classificationType、heightReference、clampToGround 的区别与使用实践

CesiumJS 中 classificationType、heightReference 和 clampToGround 三个属性都控制元素的显示位置,但解决的问题完全不同。本文从实际场景出发,配合代码示例彻底讲清三者的区别与搭配方式。

刚接触 CesiumJS 的时候,很多人会被三个看起来很像的概念搞晕:classificationTypeheightReferenceclampToGround。它们都和”东西该放在哪”有关,但解决的问题完全不同。

这篇文章从实际开发场景出发,把这三个属性的区别掰开讲清楚,附带可直接运行的代码示例。

从一道选择题说起

假设你要在 Cesium 地图上画一个围栏区域,标出某个禁飞区的范围。你有三种做法:

  1. PolygonGraphics 直接画在地面上
  2. 把 Polygon 贴在地形上,让它随着山势起伏
  3. 用 Polygon 去”切割”已有的 3D Tiles 模型,让禁飞区高亮出来

这三件事在 Cesium 里分别对应 clampToGroundheightReferenceclassificationType。它们虽然是三个独立属性,但经常要搭配使用。

clampToGround:最简单的”贴地”

clampToGround 是一个布尔值,直接决定一个 entity 是否要”贴在地球表面”。

// 在地球上画一个贴地的多边形围栏
viewer.entities.add({
  polygon: {
    hierarchy: Cesium.Cartesian3.fromDegreesArray([
      113.5, 23.0,
      113.6, 23.0,
      113.6, 23.1,
      113.5, 23.1
    ]),
    material: Cesium.Color.RED.withAlpha(0.5),
    clampToGround: true
  }
});

设为 true 时,图形会紧贴椭球体表面(WGS84 椭球),不理会地形高低。这在画大范围行政边界、航线图、海面区域时非常实用。

但注意几个关键限制:

  • 只支持 PolygonGraphicsPolylineGraphics,不支持 EllipseGraphicsRectangleGraphics 直接加这个属性
  • 贴的是椭球体,不是地形。如果你要画出山谷河流的等高线,它无法跟随地形起伏
  • 性能很好,因为底层用的是 GroundPrimitive,GPU 端做了大量优化

heightReference:更精细的高度控制

heightReference 是一个枚举,控制 entity 在垂直方向上的定位方式。它有四个可选值:

  • NONE(默认):使用绝对高度,entity 在 WGS84 椭球上方指定的 height 处
  • CLAMP_TO_GROUND:贴在地形表面上
  • RELATIVE_TO_GROUND:相对于地形表面再加上你指定的 height
  • RELATIVE_TO_ELLIPSOID:相对于椭球体表面再加上 height
// 三种效果对比
// 1. 贴地标牌
viewer.entities.add({
  position: Cesium.Cartesian3.fromDegrees(113.55, 23.05, 0),
  billboard: {
    image: '/marker.png',
    heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
  }
});

// 2. 距离地面 50 米的浮动注记
viewer.entities.add({
  position: Cesium.Cartesian3.fromDegrees(113.56, 23.05, 50),
  label: {
    text: '高度50m',
    heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND
  }
});

heightReference 主要用在点状 entity 上——比如标牌(Billboard)、标注(Label)、点(Point)、模型(Model)等。它依赖地形数据做碰撞检测,如果地形加载完成前 entity 已经创建,可能会出现”跳一下”的视觉效果。这时可以用 terrainProviderChanged 事件做延迟创建来解决。

classificationType:谁可以”切割”谁

classificationType 是最容易被忽略但威力最大的一个。它决定了这个图形是”画”在表面上的,还是去”切割”别的图层。

它是一个枚举,可选值:

  • TERRAIN:只作用于地形层,不影响 3D Tiles
  • CESIUM_3D_TILE:只作用于 3D Tiles 模型
  • BOTH:两者都作用
// 在一个倾斜摄影模型上高亮出某个区域
const tileset = viewer.scene.primitives.add(
  new Cesium.Cesium3DTileset({
    url: 'https://your-tileset-url'
  })
);

// 创建 classification 面片,只切割 3D Tiles
viewer.scene.primitives.add(
  new Cesium.ClassificationPrimitive({
    geometryInstances: new Cesium.GeometryInstance({
      geometry: new Cesium.PolygonGeometry({
        polygonHierarchy: new Cesium.PolygonHierarchy(
          Cesium.Cartesian3.fromDegreesArray([
            113.5, 23.0,
            113.6, 23.0,
            113.6, 23.1,
            113.5, 23.1
          ])
        )
      })
    }),
    classificationType: Cesium.ClassificationType.CESIUM_3D_TILE
  })
);

这个特性的典型应用场景:

  • 在倾斜摄影模型上画禁飞区或施工区域
  • 在 3D Tiles 白模上做淹没分析、热力图
  • 地形和 3D Tiles 上分别应用不同样式的叠加层

使用 ClassificationPrimitiveGroundPrimitive 时,classificationType 是必选项。如果不设,默认是 TERRAIN,你画的区域会消失在 3D Tiles 底下——很多初学者踩的就是这个坑。

三者的关系与搭配使用

它们不是互斥的,而是从不同维度控制显示行为:

维度 属性 控制内容
水平贴地 clampToGround 是否紧贴椭球体表面
垂直定位 heightReference 高度参考系:椭球、地形还是绝对位置
层级切割 classificationType 作用于哪个图层:地形、3D Tiles、还是两者

在实际项目中,经常需要组合使用:

// 场景:在地形上画一个半透明的面,同时在倾斜摄影上画一个红色的边界
// 地形面上的面
const terrainPolygon = viewer.scene.primitives.add(
  new Cesium.GroundPrimitive({
    geometryInstances: new Cesium.GeometryInstance({
      geometry: new Cesium.PolygonGeometry({
        polygonHierarchy: polygonHierarchy,
        perPositionHeight: false
      })
    }),
    classificationType: Cesium.ClassificationType.TERRAIN,
    appearance: new Cesium.ClassificationMaterialAppearance()
  })
);

// 3D Tiles 上的红色边界
const tilesetPolygon = viewer.scene.primitives.add(
  new Cesium.ClassificationPrimitive({
    geometryInstances: new Cesium.GeometryInstance({
      geometry: new Cesium.PolygonGeometry({
        polygonHierarchy: polygonHierarchy
      })
    }),
    classificationType: Cesium.ClassificationType.CESIUM_3D_TILE,
    appearance: new Cesium.PolygonMaterialAppearance({
      material: Cesium.Material.fromType('Color')
    })
  })
);

容易踩坑的地方

坑 1:clampToGround 和 heightReference 同时用会怎样?

答案是冲突。当前版本里,clampToGroundheightReference 在同一个 entity 上同时设置时,clampToGround 的优先级更高。最佳实践是:Point/Billboard/Label 用 heightReference,Polygon/Polyline 用 clampToGround,各司其职。

坑 2:地形加载时序

如果页面加载时地形数据还没下载完,heightReference: CLAMP_TO_GROUND 的 entity 会先出现在椭球面上,等地形回流之后再跳回正确位置。解决方案是在 terrainProviderChanged 之后再创建这类 entity,或者先设成 NONE,等地形就绪再动态切换。

坑 3:ClassificationPrimitive 不支持透明度?

这是很多人的误区。早期版本确实不支持,但从 Cesium 1.88 开始,ClassificationPrimitive 配合 ClassificationMaterialAppearance 已经可以设置半透明材质了。如果还在用 1.87 以下版本,建议升级。

坑 4:分类面片的大小限制

ClassificationPrimitive 并不是万能的。当分类区域覆盖范围过大(比如整个城市级别),或者边界过于复杂(上千个顶点),GPU 端的光栅化效率会显著下降。这时候需要考虑分层切割或者改用 ClippingPlaneCollection

发布前的检查清单

  • 确认你的 Cesium 版本号(1.88+ 有完整的透明分类支持)
  • 测试时打开 scene.globe.showGroundPrimitives 确保 ground primitives 已开启(默认 true)
  • 如果分类区域不显示,先检查 classificationType 是否设为了正确的层级
  • 点状 entity 不贴地时,确认 heightReference 是否设为了 CLAMP_TO_GROUND,而不是只设了 height = 0
  • 用 GroundPrimitive 时,确认地形数据源支持(Cesium World Terrain 或自定义 terrain provider)

总结

clampToGround 解决的是”平不平”——让多边形和折线贴到椭球表面;heightReference 解决的是”高不高”——决定点状 entity 的高度参考系是椭球、地形还是绝对高度;classificationType 解决的是”切不切”——是否切割地形或 3D Tiles 来显示叠加内容。

理解这三者的定位之后,写 Cesium 相关的叠加渲染代码时会少走很多弯路。如果项目中同时涉及地形、3D Tiles 和各种点线面标注,建议在设计阶段就明确每个图层的”定位层级”和”显示意图”,而不是在开发过程中反复试错。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注