ARCore 中文文档 下载 App

Hit Test 射线检测与用户交互实现

Hit Test 是什么

Hit Test(射线检测)是 ARCore 中连接用户屏幕输入和 3D 世界坐标的桥梁。它解决的问题非常具体:

用户在手机屏幕上点了一下 (x, y)——这个屏幕位置对应现实世界中的哪个点?

ARCore 的做法是:从手机相机的光心向屏幕上的触点投射一条射线(Ray),然后计算这条射线和所有已知 Trackable(平面、特征点)的交点,返回交点的世界坐标 Pose。

Hit Test 完整流程

步骤一:拿到屏幕触点坐标

// 在你的手势检测回调中(比如 GestureDetector onSingleTapConfirmed)
fun onTap(x: Float, y: Float) {
    // x, y 是屏幕像素坐标
    performHitTest(x, y)
}

步骤二:从 Frame 获取 Hit Test 接口

Hit Test 必须在 session.update() 之后、从最新 Frame 获取:

session.update(frame)

// Frame 对象提供了 hitTest() 方法
// 不同版本的 API 签名略有不同,以下是典型用法
val hitResults = frame.hitTest(x, y)  // 旧版本 API
// 或
val hitResults = frame.hitTestInstantPlacement(x, y, 1.0f)  // 新版本支持 Instant Placement

步骤三:解析 Hit Test 结果

Hit Test 返回命中列表(按距离从近到远排序):

hitResults.forEach { result ->
    // 命中的类型:PLANE / POINT / INSTANT_PLACEMENT / DEPTH_POINT / SESSION / CAMERA
    val type = result.trackableType

    // 命中点的世界坐标 Pose(位置 + 朝向)
    val pose = result.pose

    // 命中的 Trackable(如果命中的是平面或特征点)
    val trackable = result.trackable

    when (type) {
        HitTest.TrackableType.PLANE -> {
            // 命中了平面,最理想的情况
            // result.trackable 是 Plane 对象
            val plane = result.trackable as Plane
        }
        HitTest.TrackableType.POINT -> {
            // 命中了特征点(没有 plane,只有点)
            // result.trackable 是 Point 对象
        }
        HitTest.TrackableType.INSTANT_PLACEMENT -> {
            // 即时放置,可能还不是精确位置
            // 详见 Instant Placement 页面
        }
        HitTest.TrackableType.SESSION -> {
            // 没有命中任何 Trackable,但 ARCore 返回了基于深度图的估计位置
        }
        HitTest.TrackableType.DEPTH_POINT -> {
            // Depth API 启用时,基于深度图的精确命中
        }
    }
}

步骤四:在命中点放置虚拟物体

// 取第一个命中(通常是最近的)
val firstHit = hitResults.firstOrNull { it.trackable == HitTest.TrackableType.PLANE }
    ?: hitResults.firstOrNull { it.trackable == HitTest.TrackableType.POINT }
    ?: return

// 创建锚点
val anchor = session.createAnchor(firstHit.pose)

// 在锚点位置放置虚拟物体
placeVirtualObjectAtAnchor(anchor)

Unity 中的 Hit Test

Unity AR Foundation 中 Hit Test 的用法封装得更优雅:

using UnityEngine.XR.ARFoundation;

public class HitTestExample : MonoBehaviour
{
    public ARRaycastManager raycastManager;
    private List<ARRaycastHit> hits = new();

    void Update()
    {
        if (Input.touchCount < 1) return;

        var touch = Input.GetTouch(0);
        if (touch.phase != TouchPhase.Began) return;

        // 从屏幕位置向世界投射
        if (raycastManager.Raycast(touch.position, hits, TrackableType.PlaneWithinPolygon))
        {
            var hit = hits[0];
            // hit.pose 是命中点的世界坐标 + 朝向
            // hit.trackable 是命中的 Plane 或 Point
            Debug.Log($"命中位置: {hit.pose.position}, 命中类型: {hit.trackable.GetType().Name}");
        }
    }
}

RaycastManager 可以配置要检测的 TrackableType 过滤条件——PlaneWithinPolygon 是只检测在已检测平面 polygon 范围内的命中。

射线不命中任何东西怎么办

以下场景 Hit Test 可能返回空列表:

处理方式:

  1. 降级到 Instant Placement:任何时候都能返回一个近似位置(但精度较低)
  2. 提示用户:Toast 显示"请对准一张桌子或墙壁"
  3. 用 Depth API 补充:如果设备支持 Depth,即使没有 Plane 也能返回基于深度图的精确命中

Depth API 增强 Hit Test

启用 Depth API 后,Hit Test 多了 DEPTH_POINT 类型的命中——这是基于深度图逐像素计算的,比只依赖特征点更精确:

// 启用 Depth API 后,Hit Test 返回的 DEPTH_POINT 类型
// 包含深度估计的精确命中位置
val depthHits = hitResults.filter { it.trackableType == HitTest.TrackableType.DEPTH_POINT }

Depth API Hit Test 的优势:

但要注意 Depth API 对设备有要求(见故障排查页面)。