如何创建EasyAR头显扩展

这篇文章将说明如何在一个尚未受到EasyAR支持的头显设备上支持EasyAR的功能。EasyAR已经支持的头显及常规使用说明请参考 EasyAR的头显支持 。

在该文档成文之时(2023年),AR/VR/MR/XR行业内还没有形成非常统一的接口方案,虽然OpenXR是个很好的候选,但规范演化和行业实现还需要时间。所以通常来说市贩设备直接运行EasyAR并不那么容易,很有可能存在数据接口缺失的情况(大概率)。如果你是应用或内容开发者,请联系硬件制造方或EasyAR商务。2024年苹果开放了Vision Pro的部分接口,接口比较完善,建议硬件厂商参考。

因此这份文档主要的阅读者是硬件供应商,而非普通消费者。通常来说,本文档在提供规范的同时并不限定所有实现细节,任何实现方式或接口定义都可以讨论,欢迎通过商务渠道联系沟通。

本文档覆盖的硬件自身需要有运动跟踪(VIO)能力。通常我们假定EasyAR的功能运行在良好的设备跟踪能力之上,通常不建议靠EasyAR优化设备的跟踪从而产生循环依赖(站在最上层架构上考虑,不排除误差被正反馈放大从而导致系统趋于不稳)。如果设备本身没有运动跟踪能力,那么支持方案并不在本文档覆盖范围之内,如有需要可通过商务渠道进行沟通。

目标与合理预期

不要误解即将完成的目标是极其重要的。对即将投入的资源以及最终会获得的结果要有合理预期。

什么是EasyAR头显扩展

如果你不希望将对接细节暴露在外部系统中,请联系EasyAR进行沟通。在EasyAR Sense内部(c++)直接对接是可行且有先例的。

你在写的扩展是,

  • 使用 EasyAR Sense 的自定义相机接口,从你的设备API抓取数据并发送进 EasyAR Sense 的一堆代码。

  • 在Unity中,头显扩展会使用可继承的frame source API和 EasyAR Sense Unity Plugin 定义的一套 EasyAR Sense 数据流来简化 EasyAR Sense 自定义相机开发。

  • 在Unity中,头显扩展是一个 Unity package,包含运行时脚本,编辑器脚本和扩展的sample,你或EasyAR可以将它分发给下游用户。

你在写扩展的时候,可能会,

  • 修改你的SDK接口设计和内部实现。

  • 与你的团队一起讨论确认数据获取和使用方案。

  • 花大量时间进行数据正确性验证而不是写代码。

写完扩展后,你将会有,

  • 大多数 EasyAR Sense 功能(比如图像跟踪,稠密空间地图)在你的设备上可以使用,这些功能会利用你设备的运动跟踪(VIO)能力。

  • EasyAR Sense 内支持的EasyAR云服务在你的设备上可以使用。

  • 使用自定义相机时的所有 EasyAR license 限制以相同方式适用于你的设备。

  • 只能使用xr license,个人版、专业版以及经典版的license无法使用。

什么不是EasyAR头显扩展

这个扩展不能脱离 EasyAR Sense 使用,

  • 这个头显扩展不会独立运行,作为依赖, EasyAR Sense 也是需要的。在Unity中则必须使用 EasyAR Sense Unity Plugin 。

  • 它不会直接调用EasyAR云服务API,比如EasyAR Mega定位服务,它们在 EasyAR Sense 中完成。

  • 在Unity中,不会直接调用比如图像跟踪API的方法,它们在 EasyAR Sense Unity Plugin 中完成。

  • 在Unity中,头显扩展不会修改场景中物体或跟踪目标的 transform,它们在 EasyAR Sense Unity Plugin 中完成。

这个扩展不能脱离你的设备SDK使用,

  • 在Unity中,头显扩展或 EasyAR Sense Unity Plugin 不会修改场景中相机的 transform,这必须在你的设备SDK中完成。

通过头显扩展有一些EasyAR功能仍是无法使用的,

  • 表面跟踪功能将无法使用。

  • EasyAR运动跟踪将无法使用。

  • 平面检测(EasyAR运动跟踪的一部分)将无法使用。

通常来说,写扩展不能将资源限制在使用Unity做开发上面,

  • 由于缺少标准,通常它无法只在3D引擎上面实现,所以建议从第一天起就让系统工程师和SDK工程师等底层开发工程师参与进来。

所以我该如何在我的设备上使用Mega呢?

在你的设备上运行验证 EasyAR Sense ,然后EasyAR Mega将会自然被支持,不需要其它多余工作。请确保不要一开始就直接使用Mega sample来在你设备上运行验证EasyAR,你很可能会失败。

背景知识

打造 AR/VR 设备需要一些领域知识,相似地,在设备上运行验证 EasyAR Sense 将需要你或你的团队是如下领域的专家,

如果你工作在Unity上,你还需要知道这些,

另外,有一点在这些领域的知识将帮助你更好地理解系统,尤其是如何发送正确的数据到EasyAR,

数据需求

为使EasyAR在你的设备上工作,最重要的工作同时也是最棘手的部分是确保数据正确性。

EasyAR Sense 通常需要两组数据,我们根据接口调用时间和数据特征,将这两组数据称为,

  1. 相机帧数据(Camera frame data)

  2. 渲染帧数据(Rendering frame data)

相机帧数据

数据需求:

  1. 时间戳(Timestamp)

  2. 跟踪状态(Tracking status)

  3. 设备位姿(Device pose)

  4. 内参(Intrinsics,包括图像大小、焦距、主点。如果有畸变还需要畸变模型和畸变参数)

  5. 外参(Extrinsics,Tcw或Twc,标定的矩阵,表达相机相对设备/头的pose原点的物理偏移)

  6. 原始相机图像数据

数据时间:

  • 曝光中点

数据使用:

  • API调用时间:可根据你的设计改变。一个大多数设备使用的常规方法是在3D引擎的渲染更新中查询,然后根据frame中的时间戳来判断是否进一步进行数据处理

  • API调用线程:3D引擎的game thread或任何其它线程(如果你的所有API都是线程安全的)

Unity中API调用示例如下,

void TryInputCameraFrameData()
{
    double timestamp;

    if (timestamp == curTimestamp) { return; }
    curTimestamp = timestamp;

    PixelFormat format;
    Vector2Int size;
    Vector2Int pixelSize;
    int bufferSize;

    var bufferO = TryAcquireBuffer(bufferSize);
    if (bufferO.OnNone) { return; }
    var buffer = bufferO.Value;

    IntPtr imageData;
    buffer.tryCopyFrom(imageData, 0, 0, bufferSize);

    var historicalHeadPose = new Pose();
    MotionTrackingStatus trackingStatus = (MotionTrackingStatus)(-1);

    using (buffer)
    using (var image = Image.create(buffer, format, size.x, size.y, pixelSize.x, pixelSize.y))
    {
        HandleCameraFrameData(deviceCamera, timestamp, image, cameraParameters, historicalHeadPose, trackingStatus);
    }
}

上面的代码不会通过编译,它只是一个在Unity中的简化的API调用示例。请通过 com.easyar.sense.ext.hmdtemplate 模板获取可以使用的代码样例。

渲染帧数据

数据需求:

  1. 时间戳(Timestamp)

  2. 跟踪状态(Tracking status)

  3. 设备位姿(Device pose)

数据时间:

  • 上屏时刻。Timewrap不计算在内。相同时刻的device pose数据会被用来设置3D相机的transform以渲染当前帧。

数据使用:

  • API调用时间:3D引擎的每个渲染帧

  • API调用线程:3D引擎的game thread

Unity中API调用示例如下,

private void InputRenderFrameMotionData()
{
    double timestamp = 0e-9;
    var headPose = new Pose();
    MotionTrackingStatus trackingStatus = (MotionTrackingStatus)(-1);
    HandleRenderFrameData(timestamp, headPose, trackingStatus);
}

上面的代码不会通过编译,它只是一个在Unity中的简化的API调用示例。请通过 com.easyar.sense.ext.hmdtemplate 模板获取可以使用的代码样例。

额外细节

相机图像数据,

  • 图像坐标系:在传感器水平时获取的数据也应是水平的。数据应该以左上角为原点,行优先存储。图像不应翻转或颠倒。

  • 图像FPS:正常 30或60 fps 的数据都可以。如果高fps有特殊影响,为达到合理的算法效果,最小可接受帧率为2。建议使用高于2的fps,通常情况下使用原始数据帧率即可。

  • 图像大小:为获取更好的计算结果,最大边应为960或更大。正常不鼓励在数据链路中进行耗时的图像缩放,建议直接使用原始数据,除非完整大小的数据拷贝时间已经长得无法接受。图像分辨率不能小于640*480。

  • 像素格式:优先跟踪效果并综合考虑性能的话,通常格式优先顺序为 YUV > RGB > RGBA > Gray (YUV中的Y分量)。在使用YUV数据时,需要完整的数据定义,包括数据封装和填充细节。相较单通道图像而言,使用彩色图像EasyAR Mega的效果会更好,但其它功能影响不大。

  • 数据访问:数据指针或等价实现。最好在数据链路中消除所有可能的非必须拷贝。EasyAR会在数据复制后异步使用。注意数据所有权。

时间戳,

  • 所有时间戳都应时钟同步,最好是硬件同步。

跟踪状态,

  • 跟踪状态由设备定义,需要包含跟踪丢失(VIO不可用)的状态。如有更多等级则更好。

设备位姿,

  • 所有pose(包括3D引擎中的相机transform)都应使用同一个原点。

  • 所有pose以及外参应该使用相同的坐标轴系统。

  • 在Unity中,pose数据的坐标轴系统类型应为Unity坐标轴系统或EasyAR坐标轴系统。如果头显扩展由EasyAR实现且使用了其它坐标轴系统定义方式,你应提供清晰的坐标轴系统定义或给出转换到Unity坐标轴系统或EasyAR坐标轴系统的方法。

  • 在Unity中,如果使用Unity XR 框架,只需要兼容 device模式即可。

内参,

  • 所有数值都应与图像数据匹配。如有需要请在发送给EasyAR之前对内参进行缩放。

  • 如果头显扩展由EasyAR实现,你应说明内参是否会在每一帧变化(区别是对应API应该调用一次还是每帧调用)。

外参,

  • 必须提供,它应该是一个标定矩阵,表达相机相对设备/头的pose原点的物理偏移。如果你的设备pose和相机pose相等,它应该是单位阵。

  • Apple Vision Pro 对应接口为: CameraFrame.Sample.Parameters.extrinsics

  • 在Unity中,外参的坐标轴系统类型应为Unity坐标轴系统或EasyAR坐标轴系统。如果头显扩展由EasyAR实现且使用了其它坐标轴系统定义方式,你应提供清晰的坐标轴系统定义或给出转换到Unity坐标轴系统或EasyAR坐标轴系统的方法。

性能,

  • 数据应以最优效率提供。在大多数实现中,API调用会发生在渲染过程,所以建议即使在底层需要进行耗时操作的情况下,也不要阻塞API调用,或者以合理的方式来使用这些API。

  • 如果头显扩展由EasyAR实现,你需要对所有耗时API调用进行说明。

多相机,

  • 至少一个相机的数据是需要的。这个相机可以是RGB相机、VST相机、定位相机等中的任意一个。如果只有一个相机,我们通常推荐使用在中央或在眼睛附近的RGB相机或VST相机。

  • 使用多相机可提升EasyAR算法效果。所有可用相机的 Camera frame data 的数据应在同一个时间点同时提供。

多相机目前尚未完全支持,请联系我们获取更多细节。

准备工作

有一些工作需要在开始写头显扩展之前完成。

为AR/MR准备你的设备

  • 准备你的 SLAM/VIO 系统

    确保设备跟踪误差受控。一些 EasyAR 功能比如Mega可以在某种程度上降低设备累积误差,但大的局部误差也会让EasyAR的算法变得不稳定。通常来说,我们期望VIO漂移小于1‰。有意图地使用EasyAR来降低VIO误差是错误的。

  • 准备你的显示系统

    确保当一个与现实中某个物体大小和轮廓相同的虚拟物体被放置在虚拟世界中,且它与相机的相对变换关系与真实世界中对应物体与设备的变换关系相同,在这样的情况下,虚拟物体可以覆盖显示在真实物体上,且移动设备不会打破显示效果。

    建议参考Vision Pro的效果。

  • 准备你的设备SDK

    确保你已经有API可以提供 数据需求 段落中所描述的数据需求。这些数据应该由你系统中的两个且只有两个时间点产生,请确保设计上不会出现因考虑不充分从而导致数据无法对齐的情况。

从应用开发角度学习 EasyAR Sense

先学习一下 EasyAR Sense 是很重要的。如果你在使用Unity,则还需要学习 EasyAR Sense Unity Plugin 。你必须能够在Android系统下运行样例,EasyAR需要一些基础配置,同时你也会学到如何使用 EasyAR Sense 的许可证。

建议先在手机上运行Unity中的这些样例,学习这些功能的行为是什么样的。在你的设备上运行这些功能的时候并不会有根本性的不同。

注意:即使你工作在其它3D引擎上,也请确保运行一下Unity样例。

为开发Unity的package做好准备

你可以通过Unity的 Package Manager window 来 使用本地tarball文件安装插件 导入 EasyAR Sense Unity Plugin (package com.easyar.sense),或者也可以使用任何Unity允许的方式来导入它。

你应该解压头显扩展模板 (package com.easyar.sense.ext.hmdtemplate)到你可以开发package的位置,或者根据 Unity创建自定义package的指南 来创建一个新的package。

注意:如果你的设备SDK未使用Unity的package来组织,你需要解压头显扩展模板到Unity的Assets文件夹,然后从解压的文件中删除package.json以及任何以 .asmdef 为后缀名的文件。请注意在这种使用方式下,同时使用两个SDK的用户将无法获得合理的版本依赖。

熟悉头显模板

com.easyar.sense.ext.hmdtemplate package 是为你准备的样例以及模板。它是一个SDK的实现,并且包含了给你用户的样例。你应该先熟悉一下所有文件。在你知道那些文件尤其是所有脚本文件的用途之前不要急于做修改,你应该完全掌控这个package。

这个头显扩展是一个SDK,它的上游是 EasyAR Sense (在Unity中是 EasyAR Sense Unity Plugin )以及你的设备SDK,下游是用户app。为了开发一个SDK,你不仅需要从app开发的视角看这个package,还需要以SDK供应商的视角来思考。因此,你需要比正常app开发者学习更多的EasyAR细节。

这个package的包结构遵循了 Unity推荐的文件布局 ,

.
├── CHANGELOG.md
├── Documentation~
├── Editor
├── LICENSE.md
├── package.json
├── Runtime
└── Samples~
    └── Combination_BasedOn_HMD

请确保通过 Unity 文档 来学习package开发。我们在此列出一些要点如下,

  • Runtime :存放运行时平台资产的文件夹。这是模板中最重要的文件夹 ,你将主要修改它里面的脚本。

  • Samples~ :存放package中所有样例的文件夹。它包含给下游使用的样例,你可以将它用作测试扩展的demo。为了原地开发这个样例,请确保修改文件夹名为 Samples 。Unity的 Client.Pack 方法会在你打包一个新的发布时将其自动重命名为 Samples~ 。

  • Editor :存放编辑时平台资产的文件夹。这个文件夹的脚本主要用于创建菜单项,通常你需要修改其中的一部分文字用以代表你的设备。

  • package.json :package的清单文件,请确保在发布前修改。

深入了解 EasyAR Sense Unity Plugin : ARSession 工作流

请参考 Session验证工具 及 Workflow_ARSession sample。

在使用头显时,相机的transform不会被修改。

请阅读 ARSession 的API文档或源码来学习更多细节。

深入了解 EasyAR Sense Unity Plugin : Frame Source

写头显扩展时最重要的部分就是写一个自定义相机设备(或者在Unity插件中,写一个external frame source)。

Frame source 是在 EasyAR Sense Unity Plugin 中设计的一个组件,用于抽象所有相机设备以及其它提供图像(和pose)的组件。在 EasyAR Sense Unity Plugin 中, CameraDeviceFrameSource 、 MotionTrackerFrameSource 、 ARKitFrameSource 、 ARCoreFrameSource 都表达了frame source。而在 EasyAR Sense 中,同功能的组件为 CameraDevice 、 MotionTrackerCameraDevice 、 ARKitCameraDevice 和 ARCoreCameraDevice 。请确保阅读 EasyAR Sense 的 API 概览 来学习 EasyAR Sense 数据流和自定义相机。

在Unity中,你可以在创建你的头显扩展时使用一些预定义的抽象 frame source作为基类。

../_images/image_h2_2.png

上图显示了这些frame source的相互关系,以及EasyAR提供的一些头显设备支持的frame source。

在写一个新的头显扩展的时候,你需要知道一些重要的细节,请 注意阅读下面链接指向的接口文档。

FrameSource:

ExternalFrameSource:

ExternalDeviceFrameSource:

ExternalDeviceMotionFrameSource:

“External”设备运动的意思是,SLAM由非EasyAR组件提供,并且3D相机的transform已经由你的设备SDK控制。

ExternalDeviceRotationFrameSource:

“External”设备旋转的意思是,设备不提供6DOF跟踪能力,提供3DOF旋转跟踪,并且3D相机的transform已经由你的设备SDK控制。

请阅读每个frame source的API文档或源代码来学习更多细节。

如果你需要定义Unity消息,比如Awake或OnEnable,请确保检查你的基类是否已经使用这些方法,并在你的实现中调用基类中的方法。

为 EasyAR Sense Unity Plugin 写头显扩展

在这个段落中,我们会使用package com.easyar.sense.ext.hmdtemplate 来演示,但可能不会覆盖模板中的所有细节。在写你的头显扩展时,请确保阅读package中的所有细节。源码中已经有所有接口和数据需求的解释。

注意:不同版本的实现细节可能会有所区别。

写头显扩展:定义头显

Override IsHMD 并设为true。

public override bool IsHMD { get => true; }

Override Display 并设置合理数据。

protected override IDisplay Display => easyar.Display.DefaultHMDDisplay;

写头显扩展:可用性

所有EasyAR功能(包括设备支持)都会定义可用性接口,以便让用户知道对应功能在运行时在session的某个状态下以及在特定设备上是否可以使用。可用性接口会在 ARSession.Assemble 时使用,不可用的组件将不会被选择且它的方法在 ARSession 运行(running)时不会被调用。

你需要override属性 IsAvailable 。如果 IsAvailable session启动之前有值,则 CheckAvailability 不会被调用。 CheckAvailability 是一个协程。有时在可用性可被决定之前你可能需要等待设备准备好或等待数据更新。如果不需要等待可以直接返回null。

protected override Optional<bool> IsAvailable => throw new NotImplementedException("Please finish this method using your device SDK API");

举例, RokidUXRFrameSource 中的实现方式如下,

protected override Optional<bool> IsAvailable => Application.platform == RuntimePlatform.Android;

写头显扩展:渲染相机

frame source中定义的渲染相机会用来在你的眼前显示 ARSession 运行时的一些信息。

你需要override属性 Camera ,它会在 ARSession.Assemble 时使用。如果 ExternalDeviceFrameSource.OriginType 是 DeviceOriginType.XROrigin ,则不需要override,这样会使用Unity XR框架中定义的相机。

protected override Camera Camera
{
    get
    {
        if (OriginType == DeviceOriginType.XROrigin) { return base.Camera; }

        // NOTE: Return the rendering camera. It is used to display messages in front of your eyes.
        //       If OriginType is XROrigin, just remove the following line.
        throw new NotImplementedException("Please finish this method using your device SDK API");
    }
}

举例, RokidUXRFrameSource 中的实现方式如下,

protected override Camera Camera => cameraCandidate ? cameraCandidate : Camera.main;

写头显扩展:Session原点

定义原点可以获得更多灵活性,而不定义原点你会损失一些灵活性,尤其是只能支持有限的中心模式,物体的移动方式也会随之受限。应用开发者必须对于他们如何摆放虚拟物体十分小心,因为在使用这个类的时候EasyAR节点和物体永远都会动。所有放在Unity世界坐标系下的物体在任何配置下都永远不可能显示在正确的位置。

你需要override属性 Origin 和 OriginType 来返回你的设备SDK定义的原点,它会在 ARSession.Assemble 时使用。如果 ExternalDeviceFrameSource.OriginType 不是 DeviceOriginType.Custom ,则不需要override,这样 Origin 会自动使用 XR.CoreUtils.XROrigin 。

protected override DeviceOriginType OriginType => throw new NotImplementedException("Please set your origin type");

protected override GameObject Origin
{
    get
    {
        if (OriginType != DeviceOriginType.Custom) { return base.Origin; }

        // NOTE: The Origin is used to setup transform base in SessionOrigin center mode and to transform camera-origin pair together in other center modes.
        //       If OriginType is not Custom, just remove the following line.
        throw new NotImplementedException("Please finish this method using your device SDK API");
    }
}

写头显扩展:设备相机

提供相机帧数据的设备相机。如果相机帧数据由多个相机提供,列表中需要包含所有相机。相机需要在数据输入到EasyAR前创建。 FrameSource.CameraFrameStarted 为true时必须完成创建。

你需要override属性 DeviceCameras , 你会在 HandleCameraFrameData 中使用你所创建的设备相机。需要创建 DeviceFrameSourceCamera 类型的相机,详情请参考接口文档。

private DeviceFrameSourceCamera deviceCamera;
protected override List<FrameSourceCamera> DeviceCameras => new List<FrameSourceCamera> { deviceCamera };

{
    var cameraDeviceType = CameraDeviceType.Back; // NOTE: Must set to Back.
    var cameraOrientation = 0; // NOTE: Replace with device calibration data. Acceptable value: 0, 90, 180, 270.
    var imageSize = new Vector2Int(); // NOTE: Replace with device calibration data.

    var frameRateRange = new Vector2(0, 0); // NOTE: Replace with device calibration data. Set lower bound and upper bound to the same value if fps is constant.
    var axisSystem = AxisSystemType.Unity; // NOTE: Replace with the one you need. All poses and extrinsics should use the same axis system.
    var extrinsics = new DeviceFrameSourceCamera.CameraExtrinsics(new Pose(), true); // NOTE: Replace with device calibration data, using above axis system.
    deviceCamera = new DeviceFrameSourceCamera(cameraDeviceType, cameraOrientation, imageSize, frameRateRange, extrinsics, axisSystem);
}

举例, RokidUXRFrameSource 中的实现方式如下,

private DeviceFrameSourceCamera deviceCamera;
protected override List<FrameSourceCamera> DeviceCameras => new List<FrameSourceCamera> { deviceCamera };

{
    var imageDimensions = new int[2];
    NativeInterface.NativeAPI.GetImageDimensions(imageDimensions);
    var size = new Vector2Int(imageDimensions[0], imageDimensions[1]);
    deviceCamera = new DeviceFrameSourceCamera(CameraDeviceType.Back, 0, size, new Vector2(50, 50), new DeviceFrameSourceCamera.CameraExtrinsics(Pose.identity, true), AxisSystemType.Unity);
    started = true;
}

写头显扩展:相机启动和停止

CameraFrameStarted 被用于插件内部做状态及流程控制。

你需要override属性 CameraFrameStarted ,在相机准备好并可以输入数据到EasyAR之后返回 true ,相机停止运行后返回 false 。在 CameraFrameStarted 为false时,EasyAR不会工作。 CameraFrameStarted 时必须保证 DeviceCameras 数据可以访问且不间断地向EasyAR输入 Camera frame data 。在插件检查到相机帧连续长时间无输入后会弹出警告,以便对功能无响应问题进行解耦。

protected override bool CameraFrameStarted => started;

写头显扩展:Session启动和停止

OnSessionStart 会在 ARSession 启动的时候在每个EasyAR组件上调用。 该frame source的 OnSessionStart 会在 ARSession.Assemble 结束选择组件且你的frame source已经被选择之后才会发生。这个方法设计上是用来做延迟初始化的。

你需要override方法 OnSessionStart 然后做AR独有的初始化工作。需要确保先调用base.OnSessionStart。

protected override void OnSessionStart(ARSession session)
{
    base.OnSessionStart(session);
    StartCoroutine(InitializeCamera()); // NOTE: Start to do initialization for acquiring camera data, and / or wait for device ready.
}

这里是打开设备相机(比如RGB相机或VST相机等)的好地方,尤其是如果这些相机没有被设计成要一直打开时。同时这里也是获取整个生命周期内不会变化的标定数据的好地方。有时在这些数据可以被获取前你可能需要等待设备准备好或等待数据更新。

private IEnumerator InitializeCamera()
{
    yield return new WaitUntil(() => false); // NOTE: Wait until device initialized, so don't forget to change this endless waiting sample.

    var cameraModel = (CameraModelType)(-1); // NOTE: Replace with device calibration data. Use CameraModelType.Pinhole if camera model is pinhole.
    var imageSize = new Vector2Int(); // NOTE: Replace with device calibration data.
    var cameraParamList = new List<float>(); // NOTE: Replace with device calibration data. When using Mega, CLS v3 or later is required to support non-pinhole.
    var cameraDeviceType = CameraDeviceType.Back; // NOTE: Must set to Back.
    var cameraOrientation = 0; // NOTE: Replace with device calibration data. Acceptable value: 0, 90, 180, 270.
    var parameters = CameraParameters.tryCreateWithCustomIntrinsics(imageSize.ToEasyARVector(), cameraParamList, cameraModel, cameraDeviceType, cameraOrientation); // NOTE: If online optimize exists, generate in TryInputCameraFrameData instead.
    if (parameters.OnNone)
    {
        throw new InvalidOperationException("Invalid intrinsics in CameraParameters.tryCreateWithCustomIntrinsics");
    }
    cameraParameters = parameters.Value;

    var frameRateRange = new Vector2(0, 0); // NOTE: Replace with device calibration data. Set lower bound and upper bound to the same value if fps is constant.
    var axisSystem = AxisSystemType.Unity; // NOTE: Replace with the one you need. All poses and extrinsics should use the same axis system.
    var extrinsics = new DeviceFrameSourceCamera.CameraExtrinsics(new Pose(), true); // NOTE: Replace with device calibration data, using above axis system.
    deviceCamera = new DeviceFrameSourceCamera(cameraDeviceType, cameraOrientation, imageSize, frameRateRange, extrinsics, axisSystem);

    started = true; // NOTE: If you need to control start/stop in other timing, make sure to set started there.
    StartCoroutine(InputDeviceData()); // NOTE: Start to input data into EasyAR.

    throw new NotImplementedException("Please finish this method using your device SDK API");
}

同时,这里也是一个启动数据输入循环的好地方。你也可以在 Unity 脚本 Update 或其它方法中写这个循环,尤其是当你的数据需要在Unity执行顺序的某个特殊时间点获取的时候。在session准备好(ready)之前不要输入数据。

private IEnumerator InputDeviceData()
{
    while (true)
    {
        InputRenderFrameMotionData();
        TryInputCameraFrameData();
        yield return null;
    }
}

如果你希望,你也可以忽略启动过程并在每次更新时做数据检查,这完全取决于你。

举例, RokidUXRFrameSource 中的实现方式如下,

private IEnumerator InitializeCamera()
{
    yield return new WaitUntil(() => NativeInterface.NativeAPI.GetHeadTrackingStatus() >= HeadTrackingStatus.Detecting && NativeInterface.NativeAPI.GetHeadTrackingStatus() < HeadTrackingStatus.Tracking_Paused);

    var focalLength = new float[2];
    NativeInterface.NativeAPI.GetFocalLength(focalLength);
    var principalPoint = new float[2];
    NativeInterface.NativeAPI.GetPrincipalPoint(principalPoint);
    var distortion = new float[5];
    NativeInterface.NativeAPI.GetDistortion(distortion);
    var imageDimensions = new int[2];
    NativeInterface.NativeAPI.GetImageDimensions(imageDimensions);

    size = new Vector2Int(imageDimensions[0], imageDimensions[1]);
    var cameraParamList = new List<float> { focalLength[0], focalLength[1], principalPoint[0], principalPoint[1] }.Concat(distortion.ToList().GetRange(1, 4)).ToList();
    cameraParameters = CameraParameters.tryCreateWithCustomIntrinsics(size.ToEasyARVector(), cameraParamList, CameraModelType.OpenCV_Fisheye, CameraDeviceType.Back, 0).Value;

    deviceCamera = new DeviceFrameSourceCamera(CameraDeviceType.Back, 0, size, new Vector2(50, 50), new DeviceFrameSourceCamera.CameraExtrinsics(Pose.identity, true), AxisSystemType.Unity);
    started = true;
}

写头显扩展:相机帧

这里是你发送 Camera frame data 到 EasyAR Sense 内部的地方。请参考 数据需求 来了解细节。

没有必要每帧调用。最小可接受帧率 = 2。它可以在任何线程调用,只要你的API都是线程安全的即可。这些数据需要与相机传感器曝光时的数据一致。只要可以获取,建议输入色彩数据到EasyAR Sense,这对EasyAR Mega的效果是有帮助的。为实现最佳效率,你可以设计整个数据链条让原始YUV数据直接通过共享内存透传,并直接使用数据指针传入EasyAR Sense。请注意数据所有权。

private void TryInputCameraFrameData()
{
    ...
    if (timestamp == curTimestamp) { return; } // NOTE: Do not waste time sending the same data again. And if possible, do not copy memory or do any time-consuming tasks in your own API getting camera data.
    curTimestamp = timestamp;

    ...
    // NOTE: Make sure dispose is called. There will be serious memory leak otherwise.
    using (buffer)
    using (var image = Image.create(buffer, format, size.x, size.y, pixelSize.x, pixelSize.y))
    {
        HandleCameraFrameData(deviceCamera, timestamp, image, cameraParameters, historicalHeadPose, trackingStatus);
    }

    throw new NotImplementedException("Please finish this method using your device SDK API");
}

重要 :不要忘记在使用后dispose EasyAR Sense的数据。否则会出现严重内存泄漏,buffer pool获取buffer也可能会失败。

举例, RokidUXRFrameSource 中的实现方式如下,

protected void LateUpdate()
{
    if (NativeInterface.NativeAPI.GetHeadTrackingStatus() < HeadTrackingStatus.Detecting) { return; }
    if (NativeInterface.NativeAPI.GetHeadTrackingStatus() >= HeadTrackingStatus.Tracking_Paused) { return; }
    if (!started) { return; }

    TryInputCameraFrameData();
    ...
}

private void TryInputCameraFrameData()
{
    var yuvImage = RKSensorAPI.Instance.GetYUVImage();
    if (!yuvImage.success) { return; }
    if (yuvImage.yuvBytes == null || yuvImage.yuvBytes.Length == 0 || yuvImage.timeStamp == 0) { return; }
    if (yuvImage.timeStamp == curTimestamp) { return; }

    curTimestamp = yuvImage.timeStamp;

    var pose = NativeInterface.NativeAPI.GetHistoryCameraPhysicsPose(yuvImage.timeStamp);
    // NOTE: Use real tracking status when camera exposure if possible when writing your own device frame source.
    var trackingStatus = NativeInterface.NativeAPI.GetHeadTrackingStatus().ToEasyARStatus();

    var pixelSize = size;
    var pixelFormat = PixelFormat.Gray;
    var yLen = pixelSize.x * pixelSize.y;
    var bufferBlockSize = yLen;

    var bufferO = TryAcquireBuffer(bufferBlockSize);
    if (bufferO.OnNone) { return; }

    var buffer = bufferO.Value;
    buffer.copyFromByteArray(yuvImage.yuvBytes, 0, 0, bufferBlockSize);

    using (buffer)
    using (var image = Image.create(buffer, pixelFormat, size.x, size.y, pixelSize.x, pixelSize.y))
    {
        HandleCameraFrameData(deviceCamera, yuvImage.timeStamp * 1e-9, image, cameraParameters, pose, trackingStatus);
    }
}

写头显扩展:渲染帧

这里是你发送 Rendering frame data 到 EasyAR Sense 内部的地方。请参考 数据需求 来了解细节。

请确保在设备数据准备好之后每帧调用,不能跳帧。这些数据需要与驱动同一帧内当前Unity渲染相机的数据一致。

private void InputRenderFrameMotionData()
{
    ...
    HandleRenderFrameData(timestamp, headPose, trackingStatus);

    throw new NotImplementedException("Please finish this method using your device SDK API");
}

举例, RokidUXRFrameSource 中的实现方式如下,

protected void LateUpdate()
{
    if (NativeInterface.NativeAPI.GetHeadTrackingStatus() < HeadTrackingStatus.Detecting) { return; }
    if (NativeInterface.NativeAPI.GetHeadTrackingStatus() >= HeadTrackingStatus.Tracking_Paused) { return; }
    if (!started) { return; }

    ...
    InputRenderFrameMotionData();
}

private void InputRenderFrameMotionData()
{
    var pose = NativeInterface.NativeAPI.GetCameraPhysicsPose(out long timestamp);
    if (timestamp == 0) { return; }
    HandleRenderFrameData(timestamp * 1e-9, pose, NativeInterface.NativeAPI.GetHeadTrackingStatus().ToEasyARStatus());
}

运行验证(bring-up)头显扩展

准备用于运行验证的样例

样例位于 Samples~/Combination_BasedOn_HMD。为了在原地开发样例,请确保将文件夹名从 Samples~ 重命名为 Samples 。简单起见,sample中没有代码,全部由场景中配置实现。

  1. 添加你的设备支持物体到场景中。

你也可以反过来做,使用一个可以在你设备上运行的场景,然后添加EasyAR组件和sample场景中的其它物体到你的场景中。

  1. 如果场景中定义了任何原点,移动 "Cube" 和 "UI" 到原点节点下。

../_images/image_h2_3.png

"Cube"方块会提供一个设备SLAM行为的参照,这会帮助判断跟踪不稳定时候的原因。

  1. 设置"UI"的constraint source为你的渲染相机,以确保 "HUD" 按钮可以按预期工作。

../_images/image_h2_4.png
  1. 注意"UI" 节点下的"Canvas",确保raycast可以工作,以确保所有按钮和开关可以按预期工作。

../_images/image_h2_5.png
  1. 如果你当前没有使用 EasyAR Mega ,确保关闭 ARSession 下面的 Mega Tracker 物体,否则会出现错误。

../_images/image_h2_17.png

如果要了解sample场景中的细节以及正常sample是如何创建的,请参考 完成包:样例 。

构建和运行样例

请确保通过 样例使用说明 学习如何使用样例。对于Android的一些特殊配置,请阅读 Android 工程配置 。

你可以参考 头显样例说明 来了解如何使用样例。

为了运行图像跟踪,你需要打印namecard.jpg到A4纸,并确保图像宽度与纸的长边一致。

第一次在你的设备上运行验证EasyAR时,请确保先顺序运行这些功能,尤其是不要急于运行Mega,因为EasyAR Mega有一些容错性,在短时间运行或单一现实场景中运行的时候难以发觉。

  • 阅读眼前显示的 session dump信息,确保没有意外情况发送,并确保frame count在持续增长。

  • 运行 Image ,即 EasyAR 平面图像跟踪 功能,与手机运行效果对比。

  • 运行 Dense ,即 EasyAR 稠密空间地图 功能,与手机运行效果对比。

注意

在自定义相机或头显上使用试用产品(个人版license、试用版XR license或试用版Mega服务等)时,EasyAR Sense每次启动后会在固定的有限时间内停止响应。使用付费版本的EasyAR Sense和付费的EasyAR Mega服务没有这个限制。默认情况下,Unity插件会在到时间后主动崩溃,你可以通过 消息显示及错误围栏 的 SenseError 和 SessionError 选项来改变这个 "主动崩溃" 的行为。

运行验证过程中的问题分解

为使EasyAR在你的设备上工作,最重要的工作同时也是最棘手的部分是确保数据正确性。在一个新设备上首次运行验证EasyAR时,超过90%的问题都是由错误的数据造成的。强烈建议在没有EasyAR存在的时候,仅通过你的设备使用一些方法来直接验证数据的正确性。我们下面会提供一些使用EasyAR功能来验证你的数据的经验方法,它能帮助你理解 数据需求 ,但使用如此耦合的系统绝不是确保数据正确性的最佳方法。

如果 Image (跟踪和目标覆盖显示)以及 Dense (mesh位置、生成速度和质量)都和手机上的效果表现一致或更好(最好使用iPhone做对比)那么大部分EasyAR的功能都可以在你的设备上正常工作,你可以开始测试Mega(如果这是你的目标的话)。需要注意 EasyAR 稠密空间地图 可能无法在部分Android设备上运行,mesh质量也会根据设备而变化。

如果你无法重现与手机上相同的结果,那接下来是一个详细的问题分解过程,你可以参考它来寻找根因。

请确保始终关注adb的日志输出。 Dense spatial map存在已知问题,会在运行一段时间之后持续输出mesh组件相关的错误日志。这不会影响已经显示出来的mesh的质量,会在今后的版本中修复。

  1. 你的系统误差

    还记得准备阶段所描述的的SLAM和显示需求吗?

    SLAM/VIO误差会始终以不同方式影响EasyAR算法的稳定性。请始终记住这一点。

    显示系统误差可能会导致虚拟物体和现实物体无法完美对齐。在一些误差比较大的情形下,虚拟物体会看起来悬浮于真实物体上面或下面,然后(看起来)一直在漂移。这个现象可以在Pico 4E上观察到,即使不使用EasyAR只打开它自己的VST也有同样的现象。

  2. 会话状态显示及转储 显示

    必需的健康功能或数据:

    • Frame source Availability

    • Frame source Rendering Camera

    如果你看不到dump信息的显示,请尝试修改选项为 Log 然后阅读session的状态和正在使用的frame source的名字。

    可以尝试在 ARSession 中输出日志以找到系统中发生了什么。另外你还可以尝试删除在场景中的 ARSession 节点下所有其它frame source,然后看是否有什么变化。

  3. 接收到的Camera frame Count

    必需的健康功能或数据:

    • EasyAR Sense Unity Plugin 中 Frame source Camera frame data 数据通路(不包含数据正确性以及到 EasyAR Sense 的数据通路)

    这个数据应该随时间增长,否则会在几秒之后弹出显示警告信息。

    如果你发现这个数值不增长,你应该debug以寻找原因。

  4. 在设备上录制EIF,然后在Unity编辑器中回放

    必需的健康功能或数据:

    • Frame source Camera frame data 到 EasyAR Sense 的数据通路(不包含数据正确性)

    点击 EIF 来启动录制,再次点击停止。你必须停止录制才能获取到可以使用的EIF文件,否则录制的文件将不可使用。在Unity编辑器中运行EIF数据时最好使用纯净的EasyAR场景或使用EasyAR的样例以避免场景中不正确的配置。

    使用EIF你可以做很多事情,你可以在Unity编辑器中使用EIF运行 EasyAR 平面图像跟踪 和 EasyAR 稠密空间地图 。但是需要记住的是,在设备上运行的显示效果有可能是不一样的。

    EasyAR会在计算中使用畸变参数但不会对图像做反畸变。所以如果你输入了这些数据,当你在Unity中回放EIF文件时,你会观察到没有反畸变的数据,这是符合预期的。

    必需的健康功能或数据:

    • Camera frame data 中的 Raw camera image data

    • Camera frame data 中的 Timestamp (不包括时间点和数据同步)

    你可以在Unity编辑器中看到相机帧的回放。图像数据并不是字节相等的,整个流程中有有损编解码。修改Unity game窗口的比例与输入相同,否则数据会被裁剪显示。

    如果数据播放偏快或偏慢,你需要检查你的 Timestamp 输入。

  5. 使用EIF运行 EasyAR 平面图像跟踪

    必需的健康功能或数据:

    • Camera frame data 中的 Raw camera image data

    • Camera frame data 中的 Intrinsics (数据正确性不能完全保证,因为算法对误差存在容忍度)

    在Unity编辑器中使用EIF运行 ImageTracking_Targets 样例,你需要录制一个图像可以被跟踪到的EIF。

    请注意, EasyAR 平面图像跟踪 需要跟踪目标占据整个图像的一定比例,如果你无法跟踪到图像,尝试移动头到更加接近图像的位置。

    如果跟踪持续失败或虚拟物体显示在图像中远离目标的位置,则很有可能 Intrinsics 是错的。

    如果你的图像数据有畸变,你可能会看到虚拟物体不会完美的覆盖图像上的跟踪目标,这是符合预期的。

  6. 在设备上运行 EasyAR 平面图像跟踪

    必需的健康功能或数据:

    • 你的显示系统

    • Camera frame data 中的 Raw camera image data

    • Camera frame data 中的 Intrinsics (数据正确性不能完全保证,因为算法对误差存在容忍度)

    • Camera frame data 中的 Extrinsics

    • Camera frame data 和 Rendering frame data 中 Device pose 的坐标一致性

    • Camera frame data 和 Rendering frame data 中 Device pose 的时间差

    请注意, EasyAR 平面图像跟踪 需要跟踪目标占据整个图像的一定比例,如果你无法跟踪到图像,尝试移动头到更加接近图像的位置。

    请注意, EasyAR 平面图像跟踪 需要目标的scale与真实世界中物体的大小一致,在样例中需要你跟踪一个宽度上撑满A4纸的图像,因此不要跟踪显示在电脑屏幕上的图像,除非你使用一把尺子并参照尺子将图像调整到A4大小。

    如果在使用EIF时图像跟踪很完美但在设备上却不同,请在继续其它测试前解决它。在后续步骤中解决问题要困难得多。

    如果虚拟物体悬浮显示在某个远离真实物体的地方,而且即使人不动也是如此,那很有可能 Intrinsics 或 Extrinsics 不正确或 Camera frame data 和 Rendering frame data 中 Device pose 不在同一个坐标系,或者你的显示系统产生了这个误差。

    如果虚拟物体在你移动头部的时候持续移动并且看起来就像是有延迟一样,那有很大可能性 Device pose 不够像要求中描述的那样健康。这经常发生于几种情况,比如 Device pose 与 Raw camera image data 的数据时间不同步,或者 Camera frame data 和 Rendering frame data 中使用了相同的pose等等。

  7. 使用EIF或在设备上运行 EasyAR 稠密空间地图

    必需的健康功能或数据:

    • 你的显示系统

    • Camera frame data 中的 Raw camera image data

    • Camera frame data 中的 Intrinsics (数据正确性不能完全保证,因为算法对误差存在容忍度)

    • Camera frame data 中的 Extrinsics

    • Camera frame data 中的 Device pose

    如果mesh生成速度非常慢和/或地面重建坑坑洼洼,那非常有可能 Device pose 有问题。也有可能pose的坐标系不正确或pose的时间点不对。

    通常分辨精确的mesh位置不是非常容易,所以在使用 EasyAR 稠密空间地图 时你的显示系统误差可能不一定能观察出来。

EasyAR Mega

准备工作

在使用EasyAR Mega之前你需要首先阅读 EasyAR Mega 入门指南 。然后跟随 EasyAR Mega Unity 开发手册 学习使用EasyAR Mega时应该如何做Unity侧的开发。

请确保先在手机上运行Mega并了解流程。

如果你之前关闭了ARSession下的 Mega Tracker 物体,不要忘记启用它。

../_images/image_h2_18.png

运行验证过程中的问题分解

必需的健康功能或数据:

  • 你的显示系统

  • Camera frame data 和 rendering frame data 中的所有数据

如果你跟随这篇文章成功运行验证了 EasyAR 平面图像跟踪 以及 EasyAR 稠密空间地图 两个功能,那么EasyAR Mega应该已经被支持了。如果在头显上运行的表现明显比手机上差,请重点关注 Camera frame data 和 rendering frame data 中的pose数据和timestamp。另外还需要关注你的SLAM/VIO系统输出。 Origin 下面的方块会是一个好的参考。

如果你还没有尝试过 EasyAR 平面图像跟踪 和 EasyAR 稠密空间地图 就在测试EasyAR Mega,那请返回上一段落,耐心地跟着前面的问题分解指引一条一条查看。

打包和发布

完成包:编辑器

修改MenuItems类中的 "HMD Template" 字符串以代表你的设备。如果你需要其它自定义编辑器功能,也可以添加其它脚本。

完成包:样例

样例位于 Samples~/Combination_BasedOn_HMD。为了在原地开发样例,请确保将文件夹名从 Samples~ 重命名为 Samples 。Unity的 Client.Pack 方法会在你打包一个新的发布时将其自动重命名为 Samples~ 。

模板中的样例主要为了两个目的而提供,在运行验证过程中验证设备以及为下游用户提供使用参考。因此在发布给应用开发者之前需要先完成和完善这个样例。

首先,让我们看一下在我们发布整个模板之前是如何创建这个样例的。

  1. 创建 EasyAR ARSession

你可以参考 从零创建可运行的工程 来在场景中创建EasyAR组件。

创建 ARSession,

../_images/image_h2_6.png

往session中添加一些filter。下图显示了如何往session中添加image tracker。

../_images/image_h2_7.png
  1. 创建sample中需要使用的image target和sparse spatial map

举例来讲,使用这个菜单来创建image target,

../_images/image_h2_8.png

配置image target,

../_images/image_h2_9.png

请注意,在完成上述配置之后,Unity Scene窗口中显示的图像是gizmo。这个sample中通过一个quad来显示同一图像的虚拟物体。

添加显示在target上面的虚拟物体,

../_images/image_h2_10.png
  1. 添加一个立方体作为SLAM的参考

这个立方体对你们、我们以及下游用户都很重要,它用于解耦设备SLAM和EasyAR算法。

../_images/image_h2_11.png
  1. 添加功能选择的UI

../_images/image_h2_12.png
  1. 关闭启动中启用的EasyAR功能,并通过UI开关来打开它们

例如,图像跟踪的功能在启动时可以关闭,只需要设置对应组件的enable为false即可,

../_images/image_h2_13.png

然后添加UI开关处理,

../_images/image_h2_14.png

你需要使用你的设备SDK完成这个sample。你已经在 运行验证(bring-up)头显扩展 这个段落中做过,因此我们在这里略过详细步骤。请确保在场景中添加你的设备支持,然后关注 "Cube"、"UI" 以及 "UI" 下的 "Canvas" 节点的相关配置。

如果之前关闭过,不要忘记打开ARSession下的 Mega Tracker 物体,

../_images/image_h2_18.png

别忘了清理场景中的一些字符串,比如“(Move into Origin if there is any, set constraint source to your rendering camera)”或“Cube (Move into Origin if there is any)”,这些字符串是写给你而非app开发者的。

完成包:包定义

包本身的定义在package.json中,你可以根据 Unity创建自定义package的指南 来修改这个文件或创建一个新的包。请确保修改 package的 "name" 和 "displayName",否则它会与模板本身或其它供应商的扩展发生冲突。

完成包:重新生成meta

请重新生成package中所有文件的 .meta 文件。否则它们会与模板本身或其它供应商的扩展发生冲突。

发布

你可能还希望修改package中的其它一些文件,请确保在发布前仔细审查整个package。

检查扩展与你的设备SDK以及EasyAR Sense Unity Plugin的版本兼容性。请注意EasyAR 不使用Unity所要求的 semantic versioning。主要区别是,minor版本号的变化也可能会引入不兼容的变化,虽然并不总是如此。如果你在使用Mega预发布版本,每次更新都可能会有API修改。

建议使用Unity package来打包你的文件。但是如果你的设备SDK并没有准备好以package形式发布,你也可以选择通过asset package来发布。

你需要提醒你的用户,EasyAR license key的所有限制(尤其是针对自定义相机的限制)都适用于你发布的包。

为 EasyAR Sense 写头显扩展

你需要有一个用于替代Unity的3D引擎。这并没有很多人想的那么容易,所以正常来说你需要使用Unity并按上面几段文档的描述来操作。如果你在尝试在这样的平台上运行验证 EasyAR Sense ,那下面列出了一些你需要重点关注的事情。

我们在与一些厂商紧密合作,比如微信上的XR-Frame,支付宝上的Paladin,以及粒界。如果你在尝试让你的设备工作在这些平台上,请先与我们联系了解最新进展。

额外的背景知识和准备工作

  1. 精通你在使用的3D引擎。

  2. 知道如何处理c++/java/jni开发,以及(根据你使用的3D引擎提供的脚本语言)其它跨语言开发。

  3. 把 EasyAR Sense Unity Plugin 看作 EasyAR Sense 的一个样例,你需要通过 EasyAR Sense Unity Plugin 的源码学习很多细节。

推荐步骤

  1. 运行验证(bring up) EasyAR Sense Unity Plugin + HMD:强烈建议先完成上面的 Unity端测试。 EasyAR Sense Unity Plugin 做了很多相关工作,这会利于整体的验证。

  2. 运行验证(bring up) EasyAR Sense + 3D engine:参考 EasyAR Sense Unity Plugin 来在你的3D引擎中和手机上运行验证 EasyAR Sense 。确保验证 EasyAR 平面图像跟踪 功能(如果可能,也要验证 EasyAR 稠密空间地图 功能)。

  3. 运行验证(bring up) EasyAR Sense + 3D engine + HMD:参考 EasyAR Sense Unity Plugin 和头显模板来你的3D引擎中和头显设备上运行验证 EasyAR Sense 。

关键点和与手机的差异

  • EasyAR Sense 初始化

  • 获取同步数据

    • 重点关注 MegaTracker.setResultAsyncMode 和 MegaTracker.getSyncResult 。确保在创建后调用 setResultAsyncMode(false) ,然后每个渲染帧使用 getSyncResult 替换 OutputFrame 的对应输出。你仍需要使用整个 OutputFrame 路径来完成其它任务,因此不要删除它。其它功能组件类似。

  • EasyAR Mega的特殊代码路径

    • 不要连接Accelerometer的输出到MegaTracker.accelerometerResultSink ( EasyAR Sense Unity Plugin 中的 IsHMD 控制选项)。

    • 如果CLS内有多个block,使用一个父节点组织它们,并通过父节点进行整体的节点移动。

  • 3D相机 ( EasyAR Sense Unity Plugin 中的 IsCameraUnderControl 控制选项)

    • 不要使用EasyAR Sense的数据渲染相机图像。

    • 不要使用EasyAR Sense的数据设置3D相机的投影矩阵。

    • 不要使用EasyAR Sense的数据设置相机的transform。

  • 中心模式设计,原点设计和节点/相机控制

    • 如果可能,设计类似 ARSession.ARCenterMode 的中心模式。

    • 强烈建议设计类似 XR.CoreUtils.XROrigin 的原点。

    • 最佳方式是做类似Unity XR框架的 XR.CoreUtils.XROrigin 的设计,使用设备数据设置3D相机的local transform,然后通过 EasyAR Sense 中返回的数据移动 origin。如果在整体设计中没有 Origin ,则需要使用设备数据设置3D相机的world transform,然后通过 EasyAR Sense 中返回的数据移动 EasyAR Sense 的 target 或 block (这就是 EasyAR Sense Unity Plugin 中的 SessionOrigin 中心模式)。

    • 永远不要通过 EasyAR Sense 中计算的target pose来设置3D相机的 transform,如果这么做显示将是错的。

    • 你可以参考 EasyAR Sense Unity Plugin 的源代码来做 pose 计算,可以从 ARSession.OnFrameUpdate 开始理解代码。

额外的问题分解方法

  • 与 EasyAR Sense Unity Plugin 对比并寻找差异

  • 对Mega来说,可以对比 Mega Toolbox

  • 在 Origin 下放置方块或其它物体来解耦设备SLAM和EasyAR算法

  • 录制EIF然后在Unity编辑器(或你的3D编辑器)中播放

  • 录制rendering frame data用来分析(rendering frame data没有记录在EIF文件中)

  • 使用类似 运行验证(bring-up)头显扩展 中描述的问题分解步骤