刚接触 CesiumJS 的时候,很多人会被三个看起来很像的概念搞晕:classificationType、heightReference 和 clampToGround。它们都和”东西该放在哪”有关,但解决的问题完全不同。
这篇文章从实际开发场景出发,把这三个属性的区别掰开讲清楚,附带可直接运行的代码示例。
从一道选择题说起
假设你要在 Cesium 地图上画一个围栏区域,标出某个禁飞区的范围。你有三种做法:
- 用
PolygonGraphics直接画在地面上 - 把 Polygon 贴在地形上,让它随着山势起伏
- 用 Polygon 去”切割”已有的 3D Tiles 模型,让禁飞区高亮出来
这三件事在 Cesium 里分别对应 clampToGround、heightReference 和 classificationType。它们虽然是三个独立属性,但经常要搭配使用。
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 椭球),不理会地形高低。这在画大范围行政边界、航线图、海面区域时非常实用。
但注意几个关键限制:
- 只支持
PolygonGraphics和PolylineGraphics,不支持EllipseGraphics或RectangleGraphics直接加这个属性 - 贴的是椭球体,不是地形。如果你要画出山谷河流的等高线,它无法跟随地形起伏
- 性能很好,因为底层用的是 GroundPrimitive,GPU 端做了大量优化
heightReference:更精细的高度控制
heightReference 是一个枚举,控制 entity 在垂直方向上的定位方式。它有四个可选值:
NONE(默认):使用绝对高度,entity 在 WGS84 椭球上方指定的 height 处CLAMP_TO_GROUND:贴在地形表面上RELATIVE_TO_GROUND:相对于地形表面再加上你指定的 heightRELATIVE_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 TilesCESIUM_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 上分别应用不同样式的叠加层
使用 ClassificationPrimitive 或 GroundPrimitive 时,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 同时用会怎样?
答案是冲突。当前版本里,clampToGround 和 heightReference 在同一个 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 和各种点线面标注,建议在设计阶段就明确每个图层的”定位层级”和”显示意图”,而不是在开发过程中反复试错。