地形高程与海拔精度控制
为什么"高度"总是不准
在 AR 应用中,高度相关的问题频繁出现:
- 把 AR 内容放在"地面"上,但看起来悬浮在空中 3 米
- 虚拟物体埋入地下或一半被截断
- 不同用户手机看到的同一个 AR 内容高度差了好几米
这些问题的核心是:地球没有一个"标准高度"。不同参考系下的"海拔"是不一样的。
四种高度参考系
| 参考系 | 定义 | 与 WGS84 的差异 |
|---|---|---|
| WGS84 椭球 | 一个数学上定义的理想椭球面 | 官方 GPS 用这个,和真实地面差 0~50 米 |
| 大地水准面(Geoid) | 假想的"平均海平面"——全球重力等位面 | 比 WGS84 椭球更接近真实地面,但仍有局部差异 |
| 地形表面(Terrain) | 该经纬度位置实际的地面高度 | 谷歌 Elevation API 提供的就是这个 |
| 建筑屋顶(Rooftop) | 建筑物最高点的高度 | Google 街景 LiDAR + 建筑模型 |
Terrain Anchor 用的是第三种——地形表面高度。这是最接近开发者直觉的:"我想把东西放在地面以上 1.5 米"。
Terrain Anchor 的高程数据来源
Google 从多个来源拼接出全球地形高度数据:
| 来源 | 覆盖 | 精度 | 分辨率 |
|---|---|---|---|
| SRTM(Shuttle Radar Topography Mission) | 全球陆地 | 10~15 米 | 30~90 米网格 |
| ALOS World 3D | 全球 | ~5 米 | 12 米网格 |
| 街景 LiDAR | 街道路面和建筑 | 亚米级 | 点云级别 |
| Google Earth Engine | 持续更新的卫星数据 | 因地区而异 | 10~30 米 |
把这些数据融合在一起,ARCore 的 Terrain Anchor 在大多数城市区域的地面高程精度在 1~5 米之间。
高程不准的常见原因
情况一:建筑密集区
街道两侧高楼林立时,Terrain 数据可能把"街道地面"和"屋顶"的高程搞混——Google 需要正确识别地形特征。高密度城市区域(比如香港、东京)精度可能下降。
情况二:山区/丘陵
SRTM 数据在陡峭山区的精度显著下降——分辨率只有 30 米网格,陡坡上的一个点和附近网格点的高程可能差十几米。
情况三:人工结构
桥梁、水坝、人工台地等结构的高度不在任何标准地形数据集里。Google 会用街景 LiDAR 补,但不是所有人工结构都覆盖。
情况四:海平面高度差
某些地区(尤其是岛屿或新填海区域)的地形高程本身就不准确——SRTM 是航天飞机测的,当时海平面和现在不同。
校正策略
策略一:VPS 定位时让手机靠近目标高度
VPS 定位不仅返回水平位置,也返回设备的当前高度。如果你的 AR 内容是给"站在地面上的人"看的,可以:
// 用户打开应用,让手机与地面平齐
val initialPose = arSession.earthCameraPose
val userGroundAltitude = initialPose.altitude // 手机的 VPS 精确高度
// 用这个高度作为参考,计算 Terrain Anchor 的 altitudeAboveTerrain
val targetLat = 37.7749
val targetLng = -122.4194
val terrainAnchorAltitude = 0.0 // 先假设刚好在地面
// 创建 Anchor 后,观察实际渲染效果,可能需要 +2m 或 -3m 校正
// 把校正值做成可调参数让用户微调
策略二:给用户一个高度微调 UI
AR 内容放好后,让用户可以上下滑动调整高度(±5 米范围)。把校正值存到本地,下次自动使用。
策略三:结合 SLAM 地面检测
VPS 定位到大致位置后,ARCore 的本地 SLAM 会检测附近的几何平面。可以用这些平面的 Y 坐标来校正 Terrain Anchor 的高度:
val terrainAnchorPose = terrainAnchor.pose // VPS 给出的位置
// SLAM 检测到地面平面
val groundPlane = session.getAllTrackables<Plane>()
.firstOrNull { it.type == Plane.Type.HORIZONTAL_UPWARD_FACING }
if (groundPlane != null) {
val slamGroundY = groundPlane.centerPose.translation.y
val terrainAnchorY = terrainAnchorPose.translation.y
// 用 SLAM 地面高度校正 Terrain Anchor 的 Y
val correction = slamGroundY - terrainAnchorY
terrainAnchor.pose = terrainAnchorPose.compose(
Pose.makeTranslation(0f, correction, 0f)
)
}
这种方法在 VPS 精度好的区域(亚米级)基本不需要,在精度差的区域效果明显。
策略四:用 Rooftop Anchor 处理特殊结构
如果你的 AR 内容是放在屋顶上的,直接用 Rooftop Anchor 而不是 Terrain Anchor——Google 会自动减去屋顶高程。
调试高程的实用技巧
在开发中怎么验证高度对不对
- 找地标参考:在一个有已知高度地标(如雕像、纪念碑)的位置测试,对比 AR 内容和地标的实际高度
- 多人比对:让多台设备在同一位置打开应用,观察 AR 内容高度是否一致
- 可视化高度差:调试时给 AR 内容加一个垂直方向的标尺,直观显示"距离地面还有多远"
Unity 中的高度调试
void Update()
{
var geospatialPose = session.GetComponent<ARCameraManager>()
.GetGeospatialPose();
// 实时在屏幕上显示当前高度
Debug.Log($"当前高度: {geospatialPose.altitude:F2}m, " +
$"精度: {geospatialPose.altitudeAccuracy:F2}m");
}
与 Depth API 的配合
在 Terrain Anchor 位置启用本地 Depth API(如果设备支持),Depth API 的逐像素深度数据可以用来做近距离的高度校正——VPS 给的是大尺度定位,Depth API 给的是厘米级的局部表面细节。两者结合能实现:
- VPS 确定"在哪栋楼前"
- Depth API 确定"距离墙面还有 2.3 米"
- Streetscape Geometry 确定"这是 4 层楼房,墙高 12 米"
- 三者融合后 AR 内容的位置和高度都很精确