一、可视化平面的另一种方式
在上一节中, 我们已经实现了可视化检测到的平面,运行后效果良好,通过代码,我们更清楚的了解到了ARCore是如何让检测到的平面可视化的,这对于我们理解ARCore的工作方式会有很大的帮助。其实,ARCore已经简化了这个过程,我们下面来看看可视化平面的另一种实现方式。首先将上节中添加的代码从AppController中删除。然后在Hierarchy窗口右键,选择”Create Empty”,并将生成的空对象命名为”DetectedPlaceGenerater”,如下图所示:

保持选中”DetectedPlaceGenerater”,Inspector窗口,点击 “Add Component”按钮,在搜索框中输入”Detected”,可以找到DetectedPlaneGenerator,将这个脚本附件到我们的平面上。如下图所示:

将我们制作的平面Prefab拖到DetectedPlacePrefab框中,这样我们完成平面可视的工作。

这比前面我们自己写代码来实现要简单很多。
二、放置位置
现在,我们有了可供我们放置物体的平面了,我们将要在平面上放置我们物体,但在场景中什么位置放置我们物体呢?我们要知道检测到的平面是三维的,而我们的手机屏幕却是二维的,如何在二维的平面上操作旋转三维的虚拟物体?通常的做法就是作射线检测(Raycast),这与我们在VR中用鼠标拾取物体一样。
射线检测(Raycast)的基本思路是在三维世界中从一个点沿一个方向发射出一条无限长的射线线,在射线的方向上,一旦与添加了碰撞器的模型发生碰撞,则产生一个碰撞检测到的对象,我们可以利用射线实现子弹击中目标的检测,也可以用射线来检测发生碰撞的位置,例如,我们可以从屏幕中用户点击的点,利用摄像机(AR中就是我们的眼睛)的位置来构建一条射线,与场景中的平面进行碰撞检测,如果发生碰撞则返回碰撞的位置,这样,我们就可以在检测到的平面上放置我们的虚拟对象了。
ARCore在Frame中为我们准备了四种发射射线检测物体的方法。
Frame中公有静态方法 | 说明 |
---|---|
Raycast(float x, float y, TrackableHitFlags filter, out TrackableHit hitResult) | 对Arcore跟踪的物理对象执行光线投射,参考1,2为屏幕坐标点,一旦发生碰撞则返回,返回值为Bool型,true表示发生碰撞,false表示未发生碰撞。 |
Raycast(Vector3 origin, Vector3 direction, out TrackableHit hitResult, float maxDistance, TrackableHitFlags filter) | 对Arcore跟踪的物理对象执行光线投射,参数1为射线起点,参数2为射线方向,一旦发生碰撞则返回,返回值为Bool型,true表示发生碰撞,false表示未发生碰撞。 |
RaycastAll(float x, float y, TrackableHitFlags filter, List< TrackableHit > hitResults) | 对Arcore跟踪的物理对象执行光线投射,与所有对象进行检测,返回值为Bool型,true表示发生碰撞,false表示未发生碰撞。 |
RaycastAll(Vector3 origin, Vector3 direction, List< TrackableHit > hitResults, float maxDistance, TrackableHitFlags filter) | 对Arcore跟踪的物理对象执行光线投射,与所有对象进行检测,返回值为Bool型,true表示发生碰撞,false表示未发生碰撞。 |
TrackableHitFlags用来过滤需要进行碰撞检测的对象类型,其值可以是以下属性值的一个,也可以是几个。
TrackableHitFlags 属性 | 说明 |
---|---|
Default | 这个值用于与放置的所有物体发生碰撞检测。如果我们填写这个值,那么在ARCore中,我们发射的射线将与场景中的所有平面、包围多边形、带法线的特征点进行碰撞检测。 |
FeaturePoint | 与当前帧点云中所有的特征点进行碰撞检测。 |
FeaturePointWithSurfaceNormal | 与当前帧点云中的带有表面法线估计(方向)的特征点进行碰撞检测。 |
None | 此值用来表示trackableHit返回中没有碰撞发生,如果将此值传递给raycast,则不会得到任何碰撞结果。 |
PlaneWithinBounds | 与当前帧中已检测平面内的包围盒进行碰撞检测。 |
PlaneWithinInfinity | 与已检测到的平面进行碰撞检测,但这个检测不仅仅局限于包围盒或者多边形,而是可以与已检测到的平面的延展平面进行碰撞检测。 |
PlaneWithinPolygon | 与已检测平面内的凸边界多边形进行碰撞检测。 |
TrackableHit类保存的是发生碰撞检测时的检测到相关信息。
TrackableHit 属性 | 说明 |
---|---|
Distance | float类型,获取从射线源到命中点的距离。 |
Flags | TrackableHitFlags类型,获取一个位掩码,设置trackablehitmark标志对应于命中所属对象的类别。 |
Pose | Pose类型,获取光线投射击中的物体在Unity世界坐标中的姿态。 |
Trackable | Trackable类型,获取命中的可跟踪对象 |
三、放置物体
有了上面的基础, 我们很容易理解下面的代码。
TrackableHit hit;
TrackableHitFlags raycastFilter = TrackableHitFlags.PlaneWithinPolygon | TrackableHitFlags.PlaneWithinBounds;
if (Frame.Raycast(touch.position.x, touch.position.y, raycastFilter, out hit))
{
if ((hit.Trackable is DetectedPlane) && Vector3.Dot(FirstPersonCamera.transform.position - hit.Pose.position, hit.Pose.rotation * Vector3.up) < 0)
{
Debug.Log("射线击中了DetectedPlane的背面!");
}
else
{
var FoxObject = Instantiate(prefab, hit.Pose.position, hit.Pose.rotation);
FoxObject.transform.Rotate(0, mModelRotation, 0, Space.Self);
var anchor = hit.Trackable.CreateAnchor(hit.Pose);
FoxObject.transform.parent = anchor.transform;
}
}
首先,我们定义我们过虑器,我们只想检测在多边形内与边界内的平面。然后我们以用户点击点构建射线做碰撞检测。如果发生了碰撞,我们则对碰撞情况进行分析,如果是击中了检测到的平面并且又不是平面的背面,我们则实例化我们的Prefab,同时我们生成一个anchor,并将我们的Prefab挂载到这个anchor上,以便ARCore跟踪这个物体的位置。在代码中Vector3.Dot(FirstPersonCamera.transform.position - hit.Pose.position, hit.Pose.rotation * Vector3.up) < 0)
,这句代码的意思是对从摄像机发射到碰撞点的向量与碰撞点的法向量做点积,小于0,说明角度大于90度,因此击中的是检测平面的背面。
为了放置我们的虚拟物体,我们下面我们需要对AppController进行一下设置

完整的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using GoogleARCore;
using GoogleARCore.Examples.Common;
public class AppController : MonoBehaviour {
public Camera FirstPersonCamera;
public GameObject prefab;
private bool mIsQuitting = false;
private const float mModelRotation = 180.0f;
// Use this for initialization
void Start () {
OnCheckDevice();
}
// Update is called once per frame
void Update () {
UpdateApplicationLifecycle();
Touch touch;
if (Input.touchCount < 1 || (touch = Input.GetTouch(0)).phase != TouchPhase.Began)
{
return;
}
TrackableHit hit;
TrackableHitFlags raycastFilter = TrackableHitFlags.PlaneWithinPolygon | TrackableHitFlags.PlaneWithinBounds;
if (Frame.Raycast(touch.position.x, touch.position.y, raycastFilter, out hit))
{
if ((hit.Trackable is DetectedPlane) && Vector3.Dot(FirstPersonCamera.transform.position - hit.Pose.position, hit.Pose.rotation * Vector3.up) < 0)
{
Debug.Log("射线击中了DetectedPlane的背面!");
}
else
{
var FoxObject = Instantiate(prefab, hit.Pose.position, hit.Pose.rotation);
FoxObject.transform.Rotate(0, mModelRotation, 0, Space.Self);
var anchor = hit.Trackable.CreateAnchor(hit.Pose);
FoxObject.transform.parent = anchor.transform;
}
}
}
运行后的效果如下图:
