在前一节中,我们创建了一个App Controller,构建了我们的AR应用框架来做应用程序的整体流程处理,但是,如果运行我们前面的框架,什么也不会看到,本节中,我们将使用摄像机生成的点云数据来检测和创建平面,同时我们还要可视化检测出来的平面,帮助用户指出一个可用的平面在哪里。
前面我们介绍过Prefabs,当检测到真实世界中的平面时,我们需要一种在虚拟空间中表示这一特征的方法,这就是使用可视化的虚拟平面,为了使用代码来创建平面,我们也要制作一个平面的Prefabs,当检测到更多真实世界的平面时,我们还要实例化并附加更多的Prefabs,以使可用的虚拟平面更大。
在Hierarchy窗口,点击右键,选择 “Create”-> “3D Object” ->”Plane”,新建一个平面,命名为”VisualDetectedPlane”如下图所示:
在这里,我们特别需要关注的是在平面的Inspector窗口中,Position一定要归0,同时还要确保Scale为(1,1,1),如果这里有少许偏移,那么在用代码实例化平面后也同样会出现偏移。
有了平面,我们还要给平面赋一个材质,以便在实例化后用户能看到这个平面。保持当前”VisualDetectedPlane”属于被选中状态,点击Mesh Renderer组件左侧的箭头打开Mesh Renderer组件详细信息,在详细信息栏展开后点击Element0后面的那个小圆圈,这将打开材质选择面板,在材质选择面板中,选择”PlaneGrid”,这就为我们的“VisualDetectedPlane”平面赋上了一个漂亮的可视材质纹理了。如下图所示:
有了平面,也有了材质纹理了,但我们还需要一个渲染器来将检测到或者扩展出来的平面渲染出来,如果我们自己去写这个渲染器将不会是一件愉快的工作,好在ARCore已经为我们写好了,我们只需要将这个写好的类附加到我们的平面上即可,点击 “Add Component”按钮,在搜索框中输入”Detected”,可以找到DetectedPlaneVisualizer,将这个脚本附件到我们的平面上。如下图所示:
与前面所说一样,在Hierarchy窗口中,将VisualDetectedPlane平面拖动到Projects窗口中的Prefabs文件夹中,这样我们就做好了VisualDetectedPlane平面的Prefabs,然后删除Hierarchy窗口VisualDetectedPlane平面。
好了,有了可视化的平面Prefabs了,我们现在需要更新一下我们的App Controller,以便处理检测到的平面的可视化问题。因为平面中我们添加加的DetectedPlaneVisualizer是由ARCore提供的,我们需要在我们的代码中引用其命名空间GoogleARCore.Examples.Common。
然后我们再申明一个GameObject,注意这个变量是public型的,等挂载这个脚本后,这个变量会出现在Inspector窗口中,另外,我们还实例化了两个list来存放我们新检测到的平面和已检测到的所有平面,代码如下所示:
public GameObject DetectedPlanePrefab;
private List mNewPlanes = new List();
private List mAllPlanes = new List();
接下来我们在update()方法中添加如下代码:
Session.GetTrackables(mNewPlanes, TrackableQueryFilter.New);
for (int i = 0; i < mNewPlanes.Count; i++)
{
GameObject planeObject = Instantiate(DetectedPlanePrefab, Vector3.zero, Quaternion.identity,
transform);
planeObject.GetComponent().Initialize(mNewPlanes[i]);
}
Session.GetTrackables(mAllPlanes);
这段代码的逻辑如下,首先我们从Session中得到标记为new的DetectedPlane,并将这些检测到的平面赋给mNewPlanes list表,然后我们根据新检测到的mNewPlanes数量,对每一个新检测到的平面实例化一个我们之前制作的VisualDetectedPlane平面,并将新实例化的平面赋给planeObject以便显示和利用。最后我们还保留一份所有检测到的平面的副本。完整代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using GoogleARCore;
using GoogleARCore.Examples.Common;
public class AppController : MonoBehaviour {
private bool mIsQuitting = false;
public GameObject DetectedPlanePrefab;
private List mNewPlanes = new List();
private List mAllPlanes = new List();
// Use this for initialization
void Start () {
OnCheckDevice();
}
// Update is called once per frame
void Update () {
UpdateApplicationLifecycle();
Session.GetTrackables(mNewPlanes, TrackableQueryFilter.New);
for (int i = 0; i < mNewPlanes.Count; i++)
{
GameObject planeObject = Instantiate(DetectedPlanePrefab, Vector3.zero, Quaternion.identity,
transform);
planeObject.GetComponent().Initialize(mNewPlanes[i]);
}
Session.GetTrackables(mAllPlanes);
}
///
/// 检查设备
///
private void OnCheckDevice()
{
if(Session.Status == SessionStatus.ErrorSessionConfigurationNotSupported)
{
ShowAndroidToastMessage("ARCore在本机上不受支持或配置错误!");
mIsQuitting = true;
Invoke("DoQuit", 0.5f);
}
else if (Session.Status == SessionStatus.ErrorPermissionNotGranted)
{
ShowAndroidToastMessage("AR应用的运行需要使用摄像头,现无法获取到摄像头授权信息,请允许使用摄像头!");
mIsQuitting = true;
Invoke("DoQuit", 0.5f);
}
else if (Session.Status.IsError())
{
ShowAndroidToastMessage("ARCore运行时出现错误,请重新启动本程序!");
mIsQuitting = true;
Invoke("DoQuit", 0.5f);
}
}
///
/// 管理应用的生命周期
///
private void UpdateApplicationLifecycle()
{
if (Session.Status != SessionStatus.Tracking)
{
const int lostTrackingSleepTimeout = 15;
Screen.sleepTimeout = lostTrackingSleepTimeout;
}
else
{
Screen.sleepTimeout = SleepTimeout.NeverSleep;
}
if (mIsQuitting)
{
return;
}
}
///
/// 退出程序
///
private void DoQuit()
{
Application.Quit();
}
///
/// 弹出信息提示
///
/// 要弹出的信息
private void ShowAndroidToastMessage(string message)
{
AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject unityActivity = unityPlayer.GetStatic("currentActivity");
if (unityActivity != null)
{
AndroidJavaClass toastClass = new AndroidJavaClass("android.widget.Toast");
unityActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
{
AndroidJavaObject toastObject = toastClass.CallStatic("makeText", unityActivity,message, 0);
toastObject.Call("show");
}));
}
}
}
至此,我们已经更新了App Controller,也制作了我们需要可视化的平面,现在我们要把这个脚本挂载到场景中,在Hierarchy窗口中,右键选择”Create Empty”,新建一个空对象,并命名为”AppController”。
点击Detected Plane Prefab后的小圆圈打开物体选择对话框,选择”VisualDetectedPlane”,如下图所示:
功能开发完毕,进行联机测试,按Ctrl+Shift+B打开Build Settings,确保选中我们的场景文件。
点击按钮 “Build And Run”,保存生成的apk应用到我们指定的路径,开始编译我们的工程。
等待片刻,编译完后会自动下载安装到我们的手机上,拿着手机前后左右移动一下,我们将会看到我们检测到的平面以可视化的形式展现出来了。