ARCore 中文文档 下载 App

Trackable 类型与几何平面详解

Trackable 是什么

Trackable 是 ARCore 中所有会被持续跟踪的现实世界对象的基础接口。锚点可以绑定到 Trackable 上,这样即使 SLAM 修正了设备的整体姿态,锚点相对于 Trackable 的位置也保持不变。

ARCore Session 在每一帧都会通过 session.getAllTrackables<T>() 返回当前所有被跟踪的对象列表,包括:

Trackable 类型含义创建方式
Plane检测到的水平/垂直/倾斜平面ARCore 自动检测
Point识别到的有向特征点ARCore 自动检测
AugmentedImage注册过的 2D 图像开发者注册后,ARCore 自动匹配
AugmentedFace检测到的人脸ARCore 自动检测
StreetscapeGeometryGeospatial 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 会自动调整以保持相对位置不变。