VR的关键在于沉浸,而沉浸的一个关键性元素就是在空间中追踪用户位置的能力。但是迄今为止,这只在桌面和主机VR中可用,尽管现代智能手机已经集成使移动VR也具备此种能力的必要技术。本文是ARM的高级开发工程师Roberto Lopez Mendez介绍如何只使用Unity、AR SDK以及手机,实现移动VR的Inside-out追踪。Inside-out技术简单来说就是利用设备自身,而不依靠外部的传感器等配件,实现虚拟场景里的空间定位,以及更多的人机交互。

100530wqdzd6zuizk06iy3.png

引言

如果你曾尝试过房间规模的VR主机或桌面游戏,那你应该能明白为什么我这么急切的想实现移动Inside-out VR追踪。过去的问题是没有SDK或手机可以进行尝试。在2017年CES上,出现支持Tango和Daydream的新ASUS版本时,这让人看到了第一次机会。我在Unity中使用Tango SDK创建了第一个Inside-out移动VR追踪项目。项目运行起来后感觉很棒,让人能够在真实世界中四处走动,并能在VR中看到摄像机也围绕着虚拟对象移动。

第二次实现Inside-out追踪的机会,出现在Google发布ARCore SDK的时候。同日Unity发布了一个新版本支持该功能。于是,我在Unity中编写了第二个Inside-out移动VR追踪项目。这次在三星Gear VR上使用的是运行Google ARCore SDK的三星Galaxy S8。这个移动设备拥有ARM Mali-G71 MP20 GPU,可以在VR中使用8x MSAA以及60 FPS持续提供高品质画面。

本文旨在分享我开发Inside-out移动VR追踪应用的经验,并使其可供Unity开发者所用。集成ARCore SDK的Unity还不是太适合进行Inside-out移动VR追踪。

我希望当你实现具有Inside-out追踪功能的Unity移动VR项目时,能获得与我一样的满足感。本文将逐步介绍如何使用ARCore SDK进行实现。我首先用Tango SDK做了实现,这个实现的具体步骤我也将其作为额外的附件项目附在文末。

在Unity中使用Google ARCore SDK实现移动Inside-out VR追踪

假设你已安装了Unity 2017.2.0b9或更新版本,并已设置好生成Android应用的环境。此外你还需要一部三星Galaxy S8。要尝试基于Google ARCore的Inside-out VR追踪,目前的选择只有这个品牌的手机,或Google Pxiel和Pixel XL。

首先下载Google ARCore SDK for Unity,并导入到你的项目中。项目很简单,只需在一个平面上放置一个球体、一个柱体和一个立方体。还需要下载Google ARCore service.,并安装到你的设备上。现在项目中应该已有一个名为“GoogleARCore”的文件夹,里面包含一个会话配置资源,一个示例,预制件以及SDK。

095752znm1hpgb14j6bmph.jpg
图1. 导入Unity后的Google ARCore SDK文件夹

我们现在可以开始在集成ARCore了。拖拽Prefabs预制件文件夹中的ARCore Device预制件到场景层级。这个预制件包含一个第一人称摄像机。最初的想法是勾选Player Settings中的“Virtual Reality Supported”,使这个摄像机自动转化为VR摄像机。但是这个摄像机是用于AR的,所以这是一个错误的决定。我们的意图是使这个摄像机将手机摄像机的输入和我们添加到“真实世界场景”中的虚拟对象一起渲染。目前我已发现了三个很大的不便之处:
需要手动注释掉SessionComponent脚本中调用_SetupVideoOverlay()的代码行,因为如果取消会话设置资源(见图3)中的“Enable AR Background”选项,摄像机姿态追踪就不会工作。

不能应用任何可能需要的比例因子,将真实世界映射到虚拟世界中。不能总是使用1:1映射。

选择Single-pass Stereo Rendering选项后,我发现左眼渲染无误,但右眼的渲染质量却欠佳。Single-pass Stereo Rendering是我们必须使用的东西,它可以减少CPU负荷以及由ARCore追踪带来的额外负载。


基于以上问题,我们将使用自己的摄像机。由于我们正在使用的是一个VR项目,将摄像机设为一个游戏对象的子对象,以便我们可以根据来自ARCore子系统的追踪姿态数据,调整摄像机的坐标。值得注意的是,ARCore子系统提供了摄像机的位置和方向,但我决定仅使用摄像机位置,并让VR子系统按期望的行为工作。VR子系统提供的头部方向追踪与timewarp进程同步,我们不应打断这个同步。

下一步是配置ARCore会话,独占使用追踪所需的东西。点击ARCore Device游戏对象,你将在检视窗口中看到它附加的脚本,如下图所示:

095845anjujnkckw1j2q5n.jpg
图2. ARCore Device游戏对象以及附加的脚本

双击DefaultSessionConfig,打开配置选项,取消“Plane Finding”和“Point Cloud”选项,因为我们不需要这两个会增加潜在CPU负担的功能。我们需要保持“Enable AR Background”(直通模式)的勾选,否则AR Session组件无法正常工作,而我们也无法获取任何摄像机姿态追踪数据。

095857czjuis9aaw9j5wb6.jpg
图3. 所需的会话设置

下一步是添加我们的ARCore。创建一个新的游戏对象ARCoreController,并将脚本HelloARController.cs附加给它,这个脚本可在GoogleARCore/HelloARExample/Scripts中找到。我将其重命名为ARTrackingController,并移除了一些不需要的项。我的ARCoreController的设置如下图所示。我还在上面附加了一个用于计算FPS的脚本。

095910fdve5e1eon9l9n9v.jpg
图4. ARCoreController游戏对象

ARTrackerController脚本的Update函数如下所示:

[C#] 纯文本查看 复制代码public void Update (){ _QuitOnConnectionErrors(); if (Frame.TrackingState != FrameTrackingState.Tracking) { trackingStarted = false; //如果追踪失败或者没有初始化 m_camPoseText.text = Lost tracking, wait ...; const int LOST_TRACKING_SLEEP_TIMEOUT = 15; Screen.sleepTimeout = LOST_TRACKING_SLEEP_TIMEOUT; return; } else { m_camPoseText.text = ; } Screen.sleepTimeout = SleepTimeout.NeverSleep; Vector3 currentARPosition = Frame.Pose.position; if (!trackingStarted) { trackingStarted = true; m_prevARPosePosition = Frame.Pose.position; } //请记住先前的位置,这样我们可以处理增量 Vector3 deltaPosition = currentARPosition - m_prevARPosePosition; m_prevARPosePosition = currentARPosition; if (m_CameraParent != null) { Vector3 scaledTranslation = new Vector3 (m_XZScaleFactor * deltaPosition.x, m_YScaleFactor * deltaPosition.y, m_XZScaleFactor * deltaPosition.z); m_CameraParent.transform.Translate (scaledTranslation); if (m_showPoseData) { m_camPoseText.text = Pose = + currentARPosition + \n + GetComponent<FPSARCoreScript> ().FPSstring + \n + m_CameraParent.transform.position; } } }


我移除了所有东西,仅留下了检查连接错误和正确追踪状态的部分。我还使用下面这些替换了原始的类成员:

[C#] 纯文本查看 复制代码public Text m_camPoseText; public GameObject m_CameraParent; public float m_XZScaleFactor = 10; public float m_YScaleFactor = 2; public bool m_showPoseData = true; private bool trackingStarted = false; private Vector3 m_prevARPosePosition;

然后,你需要检视窗口中设置各个公共成员的值。camPoseText用于在屏幕上显示调试和错误数据,当追踪丢失时,还会一起显示从Frame中获取的摄像机位置以及应用了比例因子后的虚拟摄像机位置。

如我前面所说,你几乎不可能将你的真实环境1比1映射到虚拟场景,这也是我为XZ平面以及Y轴(上-下)的移动引入多个比例因子的原因。比例因子取决于我们希望走过的虚拟大小(vSize)以及真实世界中我们能使用的实际空间。如果平均步长是0.762米,并且我们知道走过真实世界中的房间仅需要nSteps。那么第一个近似于XZ尺度因子将是:

[C#] 纯文本查看 复制代码scaleFactorXZ = vSize / (nSteps x 0.762 m)

我保留了_QuitOnConnectionErrors()类方法,仅将其消息输出改为使用Text组件m_camPoseText。

[C#] 纯文本查看 复制代码private void _QuitOnConnectionErrors() { //如果ARCore不在追踪状态,请不要更新 if (Session.ConnectionState == SessionConnectionState.DeviceNotSupported) { m_camPoseText.text = This device does not support ARCore.; Application.Quit(); } else if (Session.ConnectionState == SessionConnectionState.UserRejectedNeededPermission) { m_camPoseText.text = Camera permission is needed to run this application.; Application.Quit(); } else if (Session.ConnectionState == SessionConnectionState.ConnectToServiceFailed) { m_camPoseText.text = ARCore encountered a problem connecting. Please start the app again.; Application.Quit(); } }

一切都可以正常工作后,你的层级窗口,应当如下图所示:

100118qvxgkocorejujkii.jpg
图5. 层级窗口中列出的所需的ARCore游戏对象

由于在我的项目中摄像机会与棋室中的一些棋子发生碰撞,我为其添加了一个CharacterController组件。

到这里我们已基本就绪。我们只需设置下Player Settings。除了为Android所做的通常标准设置外,Google建议:

[C#] 纯文本查看 复制代码Other Settings -> Multithreaded Rendering: Off Other Settings -> Minimum API Level: Android 7.0 or higher Other Settings -> Target API Level: Android 7.0 or 7.1 XR Settings -> ARCore Supported: On

下面你可以看到我的XR Settings截图。设置Single-pass选项,减少我们发往GPU的绘制调用数量很重要。

100207pocn45lk3mld1l44.jpg
图6. XR Settings截图

如果你遵循以上步骤生成项目,应该能使移动VR Inside-out追踪正常工作。下面的图中显示的是我的项目渲染结果。第一行文本显示的是由Frame.Pose提供的手机摄像机在真实世界中的位置。第二行显示的是FPS,第三行显示了虚拟世界中的VR摄像机位置。

尽管场景并不复杂,棋子都是基于局部立方体贴图(Cubemap)的反射进行渲染,有摄像机和棋子,以及棋子和棋室的碰撞。我使用8x MSAA来获得高质量的图像效果。此外, ARCore追踪子系统以及所有的这些都工作于三星S8 CPU以及ARM Mali-G71 MP20 GPU,场景渲染稳定在60FPS。

100218kbtnvh411hfwkbt9.jpg
图7. 一张来自三星Galaxy S8的屏幕截图,正在开发者模式中运行带Inside-out追踪功能的VR

项目总结

此时,我希望你已遵循本文步骤,生成了你自己的具有Inside-out追踪以及以上全部功能的移动VR Unity项目,体验了在围绕一个虚拟对象行走的同时也能在现实世界中进行对应的动作。相信你一定会同意我的观点,这种体验非常自然,并为VR体验增添了更多的沉浸感。

关于追踪的质量,我没有做过严格的测量,这些只是在一些测试之后的第一印象,以及来自尝试过我的应用程序的同事们的反馈。我在室内和室外都尝试过这两种实现方法,它们在两种情况下都很稳定。行走闭环很完美,回到出发点时没有明显的区别。当使用Google ARCore时,我走出房间后追踪依然工作地很好。不过,需要进行正式的测试来确定跟踪的错误和稳定性。

到目前为止,我们一直被限制在椅子上,通过一些接口来移动虚拟相机,只能用我们的头来控制摄像机的方向。但是,现在我们能完全控制摄像机,就像我们控制眼睛和身体一样。我们能通过复制现实世界中的运动,来移动虚拟摄像机。这种“6自由度力量”所带来的影响非常重要。很快,我们应该能够在我们的移动电话上玩到那些目前为止只能在主机和桌面空间中才能有的新游戏类型。在培训和教育方面,移动Inside-out VR追踪功能的其他潜在应用也将很快成为可能,只需一个手机和一个VR头盔。

附件项目:在Unity中使用Tango SDK实现移动Inside-out VR追踪

正如我在本文开始所说的那样,我最初使用Tango SDK和Daydream实现了VR Inside-out追踪。下面将详解实现步骤。你需要一部ASUS AR Zenfone,因为它是目前唯一同时支持Tango和Daydream平台的手机。

创建一个非常简单的Unity项目,在一个平面上方一定距离创建一个球体、一个立方体、一个胶囊和一个圆柱体,以便你后面可以从下方观察这些物体(见图8)。

100303vi687hz8rcv6gtce.jpg
图8. 用于测试使用Tango和Daydream实现移动VR Inside-out追踪的简单Unity场景

首先是下载Google Tango SDK for Unity。我使用的是2016年12月发布的版本Biyelgee (TangoSDK_Biyelgee_Unity5.unitypackage ),我曾经在Unity 5.6.0p4中正常使用过它。你需要下载并导入这个Unity包。

导入Biyelgee Unity包后,会在Asset文件夹里生成一个Google-Unity文件夹,其中包含一个脚本文件夹以及两个文件夹,如下图所示:

100317gqup83qu9uiemz93.jpg
图9. 导入Tango Unity包后TangoPrefabs和TangoSDK文件夹的内容

下一步是将TangoPrefabs文件夹中的Tango Delta Camera和Tango Manager预制件添加到层级中。Tango Delta Camera带有Trail,Frustum和Axis三个游戏对象,我们不需要它们,所以可以移除或禁用,如下图所示那样。我们也不需要Main Camera,因为我们将使用Tango Delta Camera预制件中的Multi Camera,所以我把它也禁用了。

100331d29ra2tali8jtioj.jpg
图10. 显示Tango组件的项目层级

最后一步是编辑附加在Tango Delta Camera上的TangoDeltaPoseController脚本,如下图11所示:

100342tp3pmcq3meov3gsg.jpg
图11. 附加在Tango Delta Camera上的脚本

这里,我们有个与使用ARCore时类似的问题。TangoDeltaPoseController设置附加在Tango Delta Camera上的CharacterController的位置和方向。然而,VR子系统会根据惯性传感器的数据调整头部方向,并与timewarp进程同步。我们不必更改其中的任何东西,所以我们只需要设置角色的位置,并将设置方向的代码行注释,如下所示:

[C#] 纯文本查看 复制代码//计算最终的位置与旋转的增量,并将它们应用到对象上 Vector3 deltaPosition = m_tangoPosition - m_prevTangoPosition; Quaternion deltaRotation = m_tangoRotation * Quaternion.Inverse(m_prevTangoRotation); if (m_characterMotion m_characterController != null) { m_characterController.Move(deltaPosition); //transform.rotation = deltaRotation * transform.rotation; } else { transform.position = transform.position + deltaPosition; //transform.rotation = deltaRotation * transform.rotation; } m_tangoPosition.x *= m_motionMappingScaleXZ; m_tangoPosition.z *= m_motionMappingScaleXZ; m_tangoPosition.y *= m_motionMappingScaleY;

如果你还想控制映射比例,默认是1:1,需要修改这个脚本。找到从uwOffsetTuc矩阵获取m_tangoPosition的代码行:

[C#] 纯文本查看 复制代码m_tangoPosition = uwOffsetTuc.GetColumn(3);

在其下方,插入以下代码片段:

[C#] 纯文本查看 复制代码m_tangoPosition.x *= m_motionMappingScaleXZ; m_tangoPosition.z *= m_motionMappingScaleXZ; m_tangoPosition.y *= m_motionMappingScaleY;

m_motionMappingScaleXZ和 m_motionMappingScaleY是分别应用于XZ平面和上下运动的缩放因子。你可以将两者都声明为公共类成员,以便能在检视器中对其进行设置用于测试,直至你找到合适的值。此时,我们已准备好进行项目生成,但首先要设置生成设置。在项目设置中,我们需要勾选Virtual Reality Supported,并将Daydream添加到Virtual Reality SDks列表中。

当使用Daydream时,我们必须将Minimum API level设置为24。但在生成项目时,会出现以下错误:
“Unable to merge Android Manifest. See the Console for more details.”

控制台消息显示:

[C++] 纯文本查看 复制代码Error: [Temp\StagingArea\AndroidManifest-main.xml:4, C:\…\…\MyProject\Temp\StagingArea\android-libraries\unitygvr\AndroidManifest.xml:3] Main manifest has <uses-sdk android:minSdkVersion=’17’> but library uses minSdkVersion=’19’

要解决此问题,我们需要编辑AndroidManifest.xml文件,该文件已添加到Assets/Plugins/Android文件夹,将原始行

[C#] 纯文本查看 复制代码<uses-sdk android:minSdkVersion=”17″ android:targetSdkVersion=”23″ />

改为

[C#] 纯文本查看 复制代码<uses-sdk android:minSdkVersion=”19″ android:targetSdkVersion=”23″ />

解决此问题后,应该能顺利生成项目,但是在此之前,别忘了将Stereo-Rendering设置为Single-Pass,以减少CPU负载,并平衡由Tango追踪系统带来的额外负载。

生成项目并在你的ASUS AR Zenfone上运行后,你将在同步(图12)后看到,Tango子系统在初始化期间显示出经典的“HOLD TIGHT”屏幕(图13)。数秒后,你将能看到你的3D虚拟场景(图14),并能像在真实世界中那样在其中四处走动。

100756s7sc2bhh1p1u582c.jpg
图12-14. 我们在启动应用时看到的画面:同步屏幕,Tango初始化屏幕,以及应用运行屏幕。图15. Daydream设备盖子下清晰地露出Tango传感器

小结

希望你喜欢这个指南,并成功实现Inside-out追踪!后面我们还会继续为大家分享Unity精彩内容在Unity官方中文社区(unitychina.cn),请保持关注!

游戏, Unity锐亚教育

锐亚教育 锐亚科技 unity unity教程