版权声明:Davidwang原创文章,严禁用于任何商业途径,授权后方可转载。
(一)射线检测概念
Raycasting,直译为射线投射,通常我们根据它的作用称为射线检测,射线检测是在3D数字世界里选择某个特定物体常用的一种技术,如在3D、VR游戏中检测子弹命中敌人情况或者从地上捡起一支枪,都要用到射线检测。
在AR中,当我们检测并可视化一个平面后,要在平面上放置我们物体,但在平面上什么位置放置我们物体呢?我们要知道检测到的平面是三维的,而我们的手机屏幕却是二维的,如何在二维的平面上选择三维放置点?通常的做法也是作射线检测。
射线检测的基本思路是在三维世界中从一个点沿一个方向发射出一条无限长的射线,在射线的方向上,一旦与添加了碰撞器的模型发生碰撞,则产生一个碰撞检测到的对象,我们可以利用射线实现子弹击中目标的检测,也可以用射线来检测发生碰撞的位置,例如,我们可以从屏幕中用户点击的点,利用摄像机(AR中就是我们的眼睛)的位置来构建一条射线,与场景中的平面进行碰撞检测,如果发生碰撞则返回碰撞的位置,这样,我们就可以在检测到的平面上放置虚拟物体了。
在前文中,我们使用了如下代码来放置虚拟物体。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
[RequireComponent(typeof(ARRaycastManager))]
public class AppControler : MonoBehaviour
{
public GameObject spawnPrefab;
static List<ARRaycastHit> Hits;
private ARRaycastManager mRaycastManager;
private GameObject spawnedObject = null;
private void Start()
{
Hits = new List<ARRaycastHit>();
mRaycastManager = GetComponent<ARRaycastManager>();
}
void Update()
{
if (Input.touchCount == 0)
return;
var touch = Input.GetTouch(0);
if (mRaycastManager.Raycast(touch.position, Hits, TrackableType.PlaneWithinPolygon | TrackableType.PlaneWithinBounds))
{
var hitPose = Hits[0].pose;
if (spawnedObject == null)
{
spawnedObject = Instantiate(spawnPrefab, hitPose.position, hitPose.rotation);
}
else
{
spawnedObject.transform.position = hitPose.position;
}
}
}
}
在上述代码中,我们使用mRaycastManager.Raycast()方法做射线检测来确实放置虚拟物体的位置。事实上,在AR中,除非是对位置无要求的自动放置(如在检测到的平面上长草),在精确放置虚拟物体时,通常都要做射线检测确定位置。
(二)射线检测详解
在ARFoundation中,目前射线检测只能用来检测平面和点云。ARFoundation中的射线检测与Unity中Physics模块使用的射线检测相似,但提供了独立的接口,在ARFoundation中使用射线检测需要ARRaycastManager组件,因为射线检测与被检测物体需要在同一个坐标空间中,所以该组件需要挂载在ARSessionOrigin对象上。
ARFoundation射线检测可以使用以下两个方法之一,其函数原型如下表所示。
射线检测方法 | 说明 |
---|---|
public bool Raycast(Vector2 screenPoint, List hitResults, TrackableType trackableTypeMask = TrackableType.All) | 参考1为屏幕坐标点,参数2为所有与射线发生碰撞的对象列表,参数3为Trackable类型掩码(即只对该类或几类可跟踪对象进行射线检测),该方法的返回值为Bool型,true表示发生碰撞,false表示未发生碰撞。 |
public bool Raycast(Ray ray, List hitResults, TrackableType trackableTypeMask = TrackableType.All) | 参考1为Ray类型的射线(包括位置与方向),参数2为所有与射线发生碰撞的对象列表,参数3为Trackable类型掩码(即只对该类或几类可跟踪对象进行射线检测),该方法的返回值为Bool型,true表示发生碰撞,false表示未发生碰撞。 |
trackableTypeMask 用来过滤需要进行碰撞检测的对象类型,Trackable类型其值可以是以下属性值的一个,也可以是几个,如果是几个,可以采用按位或,如TrackableType.PlaneWithinPolygon | TrackableType.FeaturePoint。
trackableType 属性 | 说明 |
---|---|
All | 这个值用于与放置的所有物体发生碰撞检测。如果我们填写这个值,那么在ARFoundation中,我们发射的射线将与场景中的所有平面、包围多边形、带法线的特征点进行碰撞检测。 |
FeaturePoint | 与当前帧点云中所有的特征点进行碰撞检测。 |
None | 此值用来表示trackableHit返回中没有碰撞发生,如果将此值传递给raycast,则不会得到任何碰撞结果。 |
PlaneWithinPolygon | 与已检测平面内的凸边界多边形进行碰撞检测。 |
PlaneWithinBounds | 与当前帧中已检测平面内的包围盒进行碰撞检测。 |
PlaneWithinInfinity | 与已检测到的平面进行碰撞检测,但这个检测不仅仅局限于包围盒或者多边形,而是可以与已检测到的平面的延展平面进行碰撞检测。 |
FeaturePointWithSurfaceNormal | 射线与评估平面进行碰撞检测,评估平面不一定能形成一个平面。 |
Planes | 射线与以上所有平面类型进行碰撞检测。 |
Image | 与2D图像进行射线检测。 |
Face | 与人脸进行射线检测。 |
ARRaycastHit类保存的是发生碰撞时检测到的碰撞体相关信息,其主要属性如下表所示。
ARRaycastHit 属性 | 说明 |
---|---|
Distance | float类型,获取从射线源到命中点的距离。 |
trackableId | 发生碰撞的可跟踪对象ID。 |
Pose | Pose类型,获取光线投射击中的物体在Unity世界坐标中的姿态。 |
hitType | Trackable类型,获取命中的可跟踪对象,即前文所述的七种可跟踪类型之一(实际上,目前ARFoundation只能对平面与特征点做射线检测)。 |
sessionRelativeDistance | 在Session空间中从射线起点到碰撞点的距离。 |
sessionRelativePose | 在Session空间碰撞点的姿态。 |
在有了以上基础之后,我们就很容易理解上节中的代码,首先我们初始化一个ARRaycastHit类型的List数组用来保存所有与射线检测发生碰撞的可跟踪对象,这个List一定要完成初始化,不可为null值,这也是为了避免垃圾回收机制(Garbage Collection,GC)来回分配内存。然后调用射线检测方法对指定可跟踪对象进行碰撞检测,如果发生碰撞,就在Hits[0](这是第一个与射线发生碰撞的符合条件的可跟踪对象,也是离射线源最近的一个,通常这就是我们需要的结果,我们一般会选择与射线发生碰撞的第一个对象)所在的位置放置虚拟物体。
参考文献
About AR Foundation About AR Foundation