Cesium 高度采样 API 全解析

在 Cesium 三维应用开发中,经常会遇到高度采样,以及批量加载贴地、贴模型的 Label、Billboard、模型等需求。

Cesium 中常用的高度采样 API 有 5 个:

  • globe.getHeight
  • Cesium.sampleTerrain
  • Cesium.sampleTerrainMostDetailed
  • scene.sampleHeight
  • scene.sampleHeightMostDetailed

它们看起来都在“获取高度”,实际分属两套体系:

  • TerrainProvider 地形线:只采样 TerrainProvider 提供的地形高度。
  • Scene 场景线:采样 Globe、3D Tiles 等场景几何表面。

理解这两条线,再搞清楚各自的 LOD 策略,基本就知道该怎么选了。

五种 API 的关系图

五种 API 简介

globe.getHeight

1
2
const carto = Cesium.Cartographic.fromDegrees(116.39, 39.9)
const height = viewer.scene.globe.getHeight(carto)

globe.getHeight 直接查询 Globe 当前已经加载的地形瓦片,不会主动向 TerrainProvider 请求新的瓦片。

源码中,它会根据目标经纬度沿地形瓦片树向更细层级查找,尽可能使用当前已有的最细 terrain mesh,再通过 tile.data.pick(...) 与地形三角网求交得到高度。

因此,它的精度取决于当前已经加载的地形 LOD:

  • 细瓦片已加载:得到更精细的高度;
  • 只有粗瓦片:得到对应粗 LOD 的高度;
  • 没有可用 terrain mesh:返回 undefined

同步、快、不发起额外瓦片请求,但不保证最高精度。


Cesium.sampleTerrain

1
2
3
4
5
6
7
8
9
10
11
12
const positions = [
Cesium.Cartographic.fromDegrees(116.39, 39.9),
Cesium.Cartographic.fromDegrees(116.40, 39.91),
]

await Cesium.sampleTerrain(
viewer.terrainProvider,
12,
positions
)

// positions[i].height 已原地更新

sampleTerrain 会主动向 TerrainProvider 请求指定 Level 的地形瓦片,并基于该层级的地形三角网采样高度。上面的 12 就是指定的 Terrain Level。

它适合需要主动加载地形、但不需要最高可用 Level 的场景,开发者可以自行控制采样精度和请求成本。


Cesium.sampleTerrainMostDetailed

1
2
3
4
5
6
7
8
const positions = [
Cesium.Cartographic.fromDegrees(116.39, 39.9),
]

await Cesium.sampleTerrainMostDetailed(
viewer.terrainProvider,
positions
)

sampleTerrainMostDetailed 可以看作 sampleTerrain 的自动选级版本:Level 不再由开发者指定,而是根据 TerrainProvider 的 availability 确定每个位置的最高可用 Level,再进行地形采样。

它只处理 TerrainProvider,不会采到建筑、倾斜摄影等 3D Tiles 表面。


scene.sampleHeight

1
2
3
4
5
6
const carto = Cesium.Cartographic.fromDegrees(116.39, 39.9)

const height = viewer.scene.sampleHeight(
carto,
objectsToExclude
)

和前面的 Terrain API 不同,sampleHeight 查询的是 Scene 中的场景表面

对于 Globe tiles 和 3D Tiles,它使用当前视图中已经渲染的瓦片,不会为了这次查询主动把 3D Tiles 加载到最大 LOD。其他可拾取 primitives 的可见性规则与 Globe / 3D Tiles 不完全相同。

因此它很适合鼠标点击、即时贴地等交互场景。


scene.sampleHeightMostDetailed

1
2
3
4
5
6
7
8
9
10
const positions = [
Cesium.Cartographic.fromDegrees(116.39, 39.9),
Cesium.Cartographic.fromDegrees(116.40, 39.91),
]

const updatedPositions =
await viewer.scene.sampleHeightMostDetailed(
positions,
objectsToExclude
)

sampleHeightMostDetailed 是 Scene 系列的异步高精度版本。

它会为 Most Detailed Picking 准备所需的 3D Tiles 内容,在高精度瓦片就绪后再执行场景表面采样。

MostDetailed 官方明确强调的是对 3D Tilesets 使用 maximum level of detail,不要理解成 Terrain 和 3D Tiles 都会主动加载到各自最高 LOD。

五种 API 的区别

五种 API 对比

API 怎么选

场景一:鼠标点击、即时高度查询

需要当前场景表面:

1
const height = viewer.scene.sampleHeight(carto)

只有地形,并且只关心当前已加载的地形高度:

1
const height = viewer.scene.globe.getHeight(carto)

这类交互通常追求响应速度,没有必要主动加载最高 LOD。

场景二:3D Tiles 批量贴模型、贴地

需要采样建筑、倾斜摄影等 3D Tiles 的高精度表面:

1
2
const updatedPositions =
await viewer.scene.sampleHeightMostDetailed(positions)

传入的 positions 会被原地更新;如果某个位置无法采样,对应元素可能为 undefined

场景三:TerrainProvider 最高可用 Level 采样

只关心地形,并希望采样每个位置的最高可用 Terrain Level:

1
2
3
4
await Cesium.sampleTerrainMostDetailed(
viewer.terrainProvider,
positions
)

场景四:TerrainProvider 大批量固定精度采样

如果点位很多,又不需要最高可用 Level:

1
2
3
4
5
await Cesium.sampleTerrain(
viewer.terrainProvider,
12,
positions
)

指定 Level 可以在精度、瓦片请求量和采样成本之间做取舍。

场景五:地形 + 倾斜摄影 + 建筑混合场景

关键是先确定想采什么表面

  • 想贴建筑、倾斜摄影等场景表面:选择 sampleHeight 系列;
  • 只想获取 TerrainProvider 地形:选择 sampleTerrain 系列;
  • 是否使用 MostDetailed,再根据 LOD 需求决定。

如果只是想让 Scene 系列忽略少量已知对象,可以使用 objectsToExclude

💣 几个容易踩的坑

1. sampleHeightMostDetailed 不保证地形最高 LOD

sampleHeightMostDetailedMostDetailed 针对 3D Tiles:源码会通过 MOST_DETAILED_PRELOAD 准备所需的最大 LOD 3D Tiles,但不会像 sampleTerrainMostDetailed 那样,根据 TerrainProvider.availability 查找并请求最高可用 Terrain Level。

因此:

  • 3D Tiles 最大 LOD 表面sampleHeightMostDetailed
  • Terrain 最高可用 LevelsampleTerrainMostDetailed

即使场景中只有 Terrain,sampleHeightMostDetailed 也不等于最高可用 Level 的地形采样。


2. Cartographic.height 不一定直接等同于“海拔”

Cesium 的 Cartographic.height 在坐标模型中表示相对于参考椭球面的高度。但对于 sampleTerrain 等 API,最终采样到的数值还与 TerrainProvider 所使用的地形数据及其垂直基准有关。

如果业务对“海拔”“正高”“椭球高”有严格要求,需要确认地形数据的垂直基准,必要时进行 Geoid / 垂直基准转换。

🔬 本地源码验证

以下基于本机 gis-template 项目的 node_modules/cesium@1.136.0 核对。不同 Cesium 版本的打包行号可能变化,建议直接搜索函数名。

结论 源码入口 关键点
globe.getHeight 基于当前地形 mesh 求交 Globe.prototype.getHeight 不主动请求新瓦片
sampleTerrain 采样指定 Level sampleTerrain 参数显式传入 level
sampleTerrainMostDetailed 查找最高可用 Level sampleTerrainMostDetailed 依赖 TerrainProvider availability
sampleHeight 获取 Scene 表面高度 Picking.prototype.sampleHeight Globe / 3D Tiles 使用当前视图中已渲染内容
sampleHeightMostDetailed 执行 Most Detailed Picking Most Detailed picking 相关逻辑 对 3D Tilesets 使用 maximum LOD
sampleHeight 依赖 depth texture Scene#sampleHeight JSDoc 通过 sampleHeightSupported 判断支持情况

总结

如果只想记住一张表:

需求 API
当前已加载的地形高度 globe.getHeight
指定 Terrain Level sampleTerrain
最高可用 Terrain Level sampleTerrainMostDetailed
当前 Scene 表面高度 sampleHeight
Scene + 3D Tiles 最大 LOD sampleHeightMostDetailed

拓展:clampToHeight

如果需求不是“获取一个 height”,而是直接把 Cartesian3 点贴到场景表面,还可以使用:

  • scene.clampToHeight
  • scene.clampToHeightMostDetailed

它们和 sampleHeight / sampleHeightMostDetailed 属于相近的一组 API,输入输出使用 Cartesian3,适合直接处理 Entity、Primitive 或模型的位置。

官方文档

先确定采地形还是采场景,再确定需要什么 LOD,这 5 个 API 就很好选了。