为什么会搞混这两个参数
在 Cesium 里做三维 GIS 开发的人,几乎都遇到过同一个困惑:disableDepthTestDistance 和 distanceDisplayCondition,名字里都带着距离相关的字眼,看起来都影响物体怎么显示,到底有什么区别?
如果你也在做铁路设计系统,正在加载分段图层,会发现沿着铁路线密密麻麻的 label 糊在一起,根本看不清。你可能会想:让远处的 label 藏起来,或者让 label 不被地形挡住。这时候两个参数都会出现在你的搜索框里。
但把它们搞清楚之前,一个参数调不好,另一个又调过头,画面反而更乱。
disableDepthTestDistance:决定 label 能不能穿墙
先说本质:这个参数控制的是 深度测试(depth test) 的行为。Cesium 默认会对所有物体做深度测试——一个 label 如果被地形、建筑或其他模型挡住,就不会绘制。这很符合物理世界,但有时你希望 label 始终显示在最前面。
disableDepthTestDistance 的完整行为是:在相机距离物体 小于等于 这个值的范围内,关闭深度测试。也就是说,这个 label 不会被任何东西遮挡,始终浮在最上面。一旦相机距离 超过 这个值,深度测试恢复,label 开始受遮挡影响。
// 示例:相机在物体距离 1000 米内时,label 始终可见,不被地形遮挡
label.disableDepthTestDistance = 1000.0;
设为 0.0 表示不关闭深度测试(默认行为)。设为 Infinity 表示永远关闭深度测试,label 永远不被遮挡。
你可能会下意识把它理解成”可见距离”,但这个理解是错的。它只管是否遮挡,不管显示多远。
distanceDisplayCondition:决定 label 在多远范围内出现
这个就直观多了。distanceDisplayCondition 是一个 NearFar 结构(本质是一个二维向量),定义了物体可见的相机距离范围。
// 示例:相机距离在 200 米到 5000 米之间时显示 label
label.distanceDisplayCondition = new Cesium.DistanceDisplayCondition(200.0, 5000.0);
- near:相机小于这个距离时,物体隐藏(太近了不显示)
- far:相机大于这个距离时,物体隐藏(太远了不显示)
这个才是真正的”可见距离”。它决定了物体在什么距离范围内出现,跟遮挡没有任何关系。
两者的本质区别
disableDepthTestDistance:控制是否做深度测试(遮挡)。核心逻辑是近处不遮挡,远处恢复遮挡。典型用途是让 label 不被地形/建筑挡住。默认值 0.0(不做特殊处理)。
distanceDisplayCondition:控制可见的距离范围。核心逻辑是近处隐藏,远处隐藏,中间可见。典型用途是让远处密集 label 自动消失。默认值 undefined(始终可见)。
可以这样记:前者是穿透能力,后者是有效射程。
铁路设计系统中的实战场景
回到你的铁路分段图层加载场景。假设你有几百公里的铁路线,划分成若干分段,每个分段有一个 label 显示桩号、分段名称和状态。
问题 1:远处 label 拥挤
相机拉远后,几百个 label 全部显示,密密麻麻堆在一起,UI 完全没法看。
解决方案是用 distanceDisplayCondition 做层级疏密控制:
// 主要分段:500 米外开始显示,8000 米之外隐藏
mainLabel.distanceDisplayCondition = new Cesium.DistanceDisplayCondition(500.0, 8000.0);
// 次要分段:只在 2000 米内显示
minorLabel.distanceDisplayCondition = new Cesium.DistanceDisplayCondition(500.0, 2000.0);
这样近景时细节丰富,拉远后只保留主要信息,自动降噪。
问题 2:label 被地形吃掉
铁路线经常穿过山谷、隧道、桥梁区域。label 在起伏地形中很容易被遮挡。用 disableDepthTestDistance 解决:
// 相机 3000 米内,label 始终在最上层
label.disableDepthTestDistance = 3000.0;
注意别设成 Infinity,否则超远距离的 label 也会浮在画面最前面,遮挡视线,破坏沉浸感。
问题 3:两者一起用
这是最常见也最合理的方式:
const label = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(lng, lat, height),
label: {
text: `DK${mileage}+${offset}`,
font: '14px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
// 500 米内不显示(太近了不需要看标签),5000 米外隐藏
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(500.0, 5000.0),
// 3000 米内不被遮挡
disableDepthTestDistance: 3000.0,
}
});
这样就实现了:label 只在 500~5000 米的距离范围内显示,并且 3000 米内始终不被地形遮挡。远场景下 label 自动消失,近场景下清晰可读。
容易踩坑的地方
坑 1:disableDepthTestDistance 不是可见距离
这是最常见的误解。把 disableDepthTestDistance 设成 1000,不会让 1000 米外的 label 消失。它只控制 1000 米内是否做深度测试。想让 label 消失,用 distanceDisplayCondition。两个概念完全不重叠。
坑 2:disableDepthTestDistance 设为 Infinity 的副作用
如果你的 label 很多,又全部设为 Infinity,意味着所有 label 永远不做深度测试。它们会叠加在其他 3D 物体之上,包括其他 label 之间也会相互覆盖,造成视觉混乱。GPU 的深度缓冲区利用率也会下降,极端情况下影响性能。尽量给一个合理值,比如最大分段长度的 1.5 倍。
坑 3:distanceDisplayCondition 配合 label 的 heightReference
如果 label 使用了 CLAMP_TO_GROUND,label 会贴在地面上,可能导致近处距离条件难以触发。建议分段 label 使用绝对高度或 RELATIVE_TO_GROUND 来稳定距离行为。
坑 4:更新动态分段时忘记重置参数
铁路分段可能根据设计变更动态合并或拆分。如果复用一个 Entity 对象,记得在更新 label 属性时同时更新两个参数,否则旧参数会残留:
function updateSegmentLabel(entity, config) {
entity.label.distanceDisplayCondition =
new Cesium.DistanceDisplayCondition(config.near, config.far);
entity.label.disableDepthTestDistance = config.depthTestDistance;
}
发布后的检查清单
- 拉远视角确认 label 数量是否按预期减少
- 切换到山谷视角,确认 label 没有被地形吃掉
- 旋转视角时 label 是否漂移或闪烁
- 切换不同分段加载策略,参数是否自动更新
- 在不同缩放级别截图对比,验证疏密效果
- 记录每个分段使用的距离参数,方便上线后调优
总结
disableDepthTestDistance 和 distanceDisplayCondition 是 Cesium 中容易混淆但用途完全不同的两个参数。前者控制遮挡与否,后者控制显示范围。在铁路分段图层加载的场景中,合理搭配两者可以优雅地解决 label 拥挤和被遮挡两大痛点。
我的建议是:永远从 distanceDisplayCondition 想起,先把可见距离范围定好,再看是否需要 disableDepthTestDistance 解决遮挡问题。顺序反过来,大概率会调出奇怪的效果。
分段图层本身就是一种信息降维的设计——把大量数据按空间或语义拆分成有序的块。Label 的距离控制可以看作这种设计在视觉层面的延伸:近处给细节,远处给结构,用户才能在三维场景里快速理解路网整体与局部的关系。