Trackable 类型与几何平面详解
Trackable 是什么
Trackable 是 ARCore 中所有会被持续跟踪的现实世界对象的基础接口。锚点可以绑定到 Trackable 上,这样即使 SLAM 修正了设备的整体姿态,锚点相对于 Trackable 的位置也保持不变。
ARCore Session 在每一帧都会通过 session.getAllTrackables<T>() 返回当前所有被跟踪的对象列表,包括:
| Trackable 类型 | 含义 | 创建方式 |
|---|---|---|
| Plane | 检测到的水平/垂直/倾斜平面 | ARCore 自动检测 |
| Point | 识别到的有向特征点 | ARCore 自动检测 |
| AugmentedImage | 注册过的 2D 图像 | 开发者注册后,ARCore 自动匹配 |
| AugmentedFace | 检测到的人脸 | ARCore 自动检测 |
| StreetscapeGeometry | Geospatial API 返回的街景建筑/地形 | Geospatial API 启用时自动返回 |
Plane:最常用的 Trackable
Plane 是环境理解能力的核心产出——ARCore 自动在检测到的平面上创建 Plane 对象。
Plane 的三种类型
| 类型 | 检测对象 | 典型场景 |
|---|---|---|
| HORIZONTAL_UPWARD_FACING | 朝上的水平面 | 桌面、地板、茶几 |
| HORIZONTAL_DOWNWARD_FACING | 朝下的水平面 | 天花板 |
| VERTICAL | 垂直面 | 墙壁 |
ARCore 还可能返回 UNKNOWN 类型的 Plane,通常是在检测初期还无法确定方向时。
Plane 的关键属性
val plane: Plane = ...
// 中心点的 Pose(平面中心在世界坐标系中的位置和朝向)
val centerPose: Pose = plane.centerPose
// 平面的实际边界(多边形,可能不完整)
// 类型是 List<List<Pose>>,每个 List 是一个边界环(外边界 + 可能的内孔)
val polygon: List<List<Pose>> = plane.polygon
// 平面的近似范围(不精确但快速获取)
val extents: FloatArray = plane.extents // [0] = 宽度, [1] = 长度
// Plane 的 tracking state
val state: TrackingState = plane.trackingState
监听新平面
通常在每帧的 session.update() 之后检查新增的 Plane:
// 在每一帧的更新回调中
session.update(frame)
val planes = session.getAllTrackables<Plane>()
planes.forEach { plane ->
// 只处理新检测到的平面(可以用一个 Set 记录已处理的 plane 标识符)
if (plane.trackingState == TrackingState.TRACKING) {
// 在这个平面上放虚拟物体等
}
}
边界的意义
Plane 的边界是一个多边形列表——ARCore 会尽量检测到桌子的实际边缘,而不是假设桌子无限大。如果边界检测不完整(用户还没把手机对准整张桌子),polygon 会小于实际平面。
渲染时可以用 polygon 数据做:
- 在检测到的桌子上画出半透明轮廓
- 限制用户只能在桌子范围内放置虚拟物体
- 计算可用区域面积
Point:有向特征点
Point 代表 ARCore 检测到的单个特征点,它携带一个估计的表面法线方向。
val point: Point = ...
// 特征点的 Pose(位置 + 法线方向)
val pose: Pose = point.pose
// Point 没有 polygon 或 extents,因为它是一个点
Point 的使用场景比 Plane 少,但在 Hit Test 中很有用——当 Hit Test 命中的不是 Plane 而是单个 Point 时,返回的 Pose 仍然包含表面方向。
平面合并:多个 Plane 变成一个
一个常见的坑:用户慢慢扫过一张大桌子,ARCore 可能会先后检测到多个小 Plane(桌子的不同部分),然后在后续帧中合并成一个大 Plane。
合并后,原来的 Plane 对象会变成 TrackingState.STOPPED,新 Plane 对象有更大的 polygon 和 extents。开发者需要在渲染逻辑中处理这个变化:
// 如果只跟踪 TRACKING 状态的 Plane,自动就不会渲染已合并的旧 Plane
val activePlanes = session.getAllTrackables<Plane>()
.filter { it.trackingState == TrackingState.TRACKING }
Trackable 与 Anchor 的关系
Anchor 可以绑定到 Trackable 上,但 Trackable 本身不创建 Anchor。绑定关系:
// 1. 拿到一个 Trackable(Plane)
val plane = session.getAllTrackables<Plane>().first()
// 2. 在 Plane 的某个 Pose 上创建 Anchor(隐式绑定到这个 Plane)
val anchor = session.createAnchor(plane.centerPose)
Anchor 并没有显式存储"我绑定到哪个 Trackable"——这个关系由 SLAM 内部维护。当 SLAM 修正 Plane 的 pose 时,Anchor 的 pose 会自动调整以保持相对位置不变。