实体与图元
Cesium 有两套绘制接口:Entity 面向业务对象,一个实体就是一条业务记录; Primitive 面向 GPU,一批几何合成一次绘制。选错接口,几百个对象就会掉帧。
两者不是替代关系
| 对比项 | Entity | Primitive |
|---|---|---|
| 抽象层级 | 业务对象,含 id、name、properties 元数据 | 几何体,只有顶点和外观 |
| 时间变化 | 图形属性可以是 Property,随 clock 变化 | 需要自己在每帧更新 |
| 绘制方式 | 同一类型的实体自动合批 | 手动 GeometryInstance 合批 |
| 拾取 | scene.pick 返回实体本身 | 返回 Primitive,要自己维护映射 |
| 数量级 | 数千个以内舒适 | 十万级顶点也只是一次绘制 |
业务数据(设备、车辆、行政区)用 Entity,能直接挂属性、做 hover 高亮、写 infoBox。 海量点云或规则网格用 Primitive,因为 Entity 每个对象都要走一遍属性系统, 上万个之后开销明显。
一个实体的结构
viewer.entities.add({...}) 接收的是一个配置对象,由三部分组成:
位置、朝向,以及一个或多个图形属性。
viewer.entities.add({
id: 'station-01',
name: '气象站 01',
position: Cesium.Cartesian3.fromDegrees(116.391263, 39.907359, 60),
point: {
pixelSize: 10,
color: Cesium.Color.fromCssColorString('#4d9bff'),
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2
},
label: {
text: '气象站 01',
font: '500 12px sans-serif',
fillColor: Cesium.Color.WHITE,
pixelOffset: new Cesium.Cartesian2(0, -20),
disableDepthTestDistance: Number.POSITIVE_INFINITY
},
properties: { // 业务字段,拾取后可以读
temperature: 21.4,
updatedAt: '2026-10-27T08:00:00Z'
}
});
id 省略时会自动生成 GUID。需要按业务主键查找时一定要显式给 id,
之后可以用 viewer.entities.getById('station-01') 直接拿到对象。
图形属性一览
| 属性 | 几何形态 | 常用参数 |
|---|---|---|
point | 屏幕像素圆点 | pixelSize、color、outlineWidth |
billboard | 图标,始终面向相机 | image、scale、verticalOrigin |
label | 文字 | text、font、pixelOffset、style |
polyline | 线 | positions、width、material、clampToGround |
polygon | 面 | hierarchy、height、extrudedHeight |
wall | 竖直墙面 | positions、maximumHeights |
model | glTF 模型 | uri、scale、minimumPixelSize |
ellipse / rectangle | 圆、矩形 | semiMajorAxis、coordinates |
让属性随时间变化
上面每个图形参数都可以写成 Property。ConstantProperty 是静态值,
插值、回调、采样三类属性可以随时间或逐帧变化。
// 采样属性:按给定时间点插值出位置
var position = new Cesium.SampledPositionProperty();
position.addSample(Cesium.JulianDate.fromIso8601('2026-10-27T00:00:00Z'),
Cesium.Cartesian3.fromDegrees(116.39, 39.90, 100));
position.addSample(Cesium.JulianDate.fromIso8601('2026-10-27T00:05:00Z'),
Cesium.Cartesian3.fromDegrees(116.45, 39.95, 100));
// 回调属性:每帧重新计算,用来做闪烁或呼吸效果
var pulse = new Cesium.CallbackProperty(function (time, result) {
var phase = Date.now() % 2000 / 2000;
return Cesium.Color.fromCssColorString('#4d9bff')
.withAlpha(0.35 + 0.65 * Math.abs(Math.sin(phase * Math.PI)));
}, false);
CallbackProperty 的第二个参数 isConstant 必须写
false,写成 true 会被当成静态值缓存,动画就不动了。
批量添加要暂停事件
每调用一次 entities.add,集合都会触发一次变更事件,触发重排序和重绘。
一次加几千个对象时,先把事件挂起:
viewer.entities.suspendEvents();
for (var i = 0; i < 5000; i++) {
viewer.entities.add({ /* ... */ });
}
viewer.entities.resumeEvents();
另一条路是把数据一次性构造成 EntityCollection,再用
viewer.entities = collection 整体替换。两种做法效果相同,
后者在数据完全重载时更干净。
拾取与高亮
scene.pick 的结果里,picked.id 就是 Entity 实例,
可以直接改它的图形属性做 hover 效果:
var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
var picked = viewer.scene.pick(movement.endPosition);
viewer.entities.values.forEach(function (e) {
e.point.pixelSize = 8;
});
if (Cesium.defined(picked) && picked.id instanceof Cesium.Entity) {
picked.id.point.pixelSize = 14;
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
大量实体时不要每帧遍历全表,先用 picked.id 记录上一个高亮对象,
再单独还原它。
四个容易忽略的细节
贴地图元需要地形或椭球面
heightReference: CLAMP_TO_GROUND 只对 point、billboard、label、model 有效,
而且要让 scene.globe.depthTestAgainstTerrain = true 才能被地形正确遮挡。
没开这个开关,贴地的图标会浮在山体前面。
线的宽度上限
大多数平台 WebGL 只保证 1 像素的线宽,width 写 5 可能仍然是 1 像素。
需要粗线时用 polyline.material 配合 Cesium.PolylineVolumeGraphics,
或者直接改用墙面几何模拟。
z-fighting
同一高度的两个面(比如省界和市界都贴地)会闪烁。给其中一个加上
height: 1,或者用 polygon.zIndex 调整绘制顺序。
删除要彻底
entity.show = false 只是不画,对象和它的几何仍在内存里。
数据量滚动更新时应该 viewer.entities.remove(entity),
否则跑一天之后内存会一直涨。
经纬志(2026)。《实体与图元》。https://www.jingweizhi.com/cesium/tutorial/entity.html(访问日期 2026-11-03)。