共计 2320 个字符,预计需要花费 6 分钟才能阅读完成。
问题现象
当我们在 Cesium 中加载地形数据后,经常会遇到一个令人头疼的问题:原本应该固定在地表上的 Label 标注,随着相机的移动会漂浮在空中或陷入地下。这种现象在地形起伏较大的区域尤为明显,比如山区或峡谷地带。这种视觉分离不仅影响美观,更会误导用户对空间位置的判断。

技术分析
要理解这个问题的根源,我们需要先搞清楚 Cesium 中两种位置属性的区别:
- Entity.position:定义实体在地球坐标系中的绝对位置,会随地形起伏自动调整高度
- LabelGraphics.pixelOffset:控制标签相对于屏幕空间的偏移量,与地形无关
当使用固定 pixelOffset 时,标签会始终保持在屏幕固定位置,而当地形起伏导致实际地表高度变化时,就会出现视觉分离。
核心解决方案
通过 Viewer.scene.postRender 事件循环动态更新标签位置是最可靠的方案。以下是完整的 TypeScript 实现:
// 初始化 Viewer 时开启地形
const viewer = new Cesium.Viewer('cesiumContainer', {
terrainProvider: new Cesium.CesiumTerrainProvider({url: Cesium.IonResource.fromAssetId(1),
requestVertexNormals: true
})
});
// 创建标签实体
const labelEntity = viewer.entities.add({
label: {
text: '动态标签',
font: '14pt sans-serif',
style: Cesium.LabelStyle.FILL,
pixelOffset: new Cesium.Cartesian2(0, -20), // 初始偏移
eyeOffset: new Cesium.Cartesian3(0, 0, 0),
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
},
position: Cesium.Cartesian3.fromDegrees(116.39, 39.9) // 初始坐标
});
// 防抖变量
let lastUpdate = 0;
const updateInterval = 100; // 毫秒
// 动态更新逻辑
viewer.scene.postRender.addEventListener(() => {const now = Date.now();
if (now - lastUpdate < updateInterval) return;
try {
// 获取当前标签的经纬度
const cartographic = Cesium.Cartographic.fromCartesian(labelEntity.position.getValue(viewer.clock.currentTime)
);
// 转换为屏幕坐标
const scratchPosition = new Cesium.Cartesian3();
const position = Cesium.Cartesian3.fromDegrees(Cesium.Math.toDegrees(cartographic.longitude),
Cesium.Math.toDegrees(cartographic.latitude),
cartographic.height
);
const pixelPosition = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
viewer.scene,
position,
scratchPosition
);
if (pixelPosition) {
// 更新 pixelOffset 保持标签在地表上方
labelEntity.label.pixelOffset = new Cesium.ConstantProperty(new Cesium.Cartesian2(0, -20 - pixelPosition.y + viewer.canvas.height/2)
);
}
} catch (error) {console.warn('地形数据获取失败:', error);
// 降级方案:回退到固定高度
labelEntity.label.heightReference = Cesium.HeightReference.NONE;
}
lastUpdate = now;
});
性能优化
动态更新虽然解决了视觉问题,但需要考虑性能影响:
- requestAnimationFrame 平衡 :
- 更新频率建议控制在 30-60fps 之间
-
复杂场景可使用防抖机制 (throttling)
-
精度优化 :
- 对于远处物体可降低更新频率
- 使用 LOD(Level of Detail) 技术分级处理
生产环境注意事项
地形 LOD 切换处理
当地形细节层级变化时,可能会导致标签突然跳动。解决方案:
viewer.scene.globe.tileLoadProgressEvent.addEventListener(() => {
// 强制刷新标签位置
lastUpdate = 0;
});
对象池管理
当需要显示大量标签时,建议:
- 复用 Entity 对象而非频繁创建销毁
- 使用四叉树空间索引管理可见标签
- 实现按需加载机制
移动端优化
移动设备上需要特别注意:
- 减少同时显示的标签数量
- 禁用抗锯齿 (MSAA)
- 使用更简单的字体渲染
延伸思考
更复杂的场景中,我们还需要考虑:
- 如何实现标签自动避让(当多个标签重叠时)
- 如何处理被地形遮挡的标签(如山体背面的标注)
- 如何优化跨精度级别的标签显示
这些问题留给大家思考,也欢迎在评论区分享你的解决方案。通过不断优化,我们可以让 Cesium 应用的用户体验更上一层楼。
正文完
