Snapdragon Spaces 开发指南(9)
5.6.3 命中测试示例
此示例演示了如何对现实世界中发现的点和平面进行命中测试。有关通过光线投射进行命中测试以及 AR Foundation 的 AR Raycast 子系统
组件功能的基本信息,请参阅 Unity 文档(打开新窗口)。为了使用此功能,必须在项目设置 > XR 插件管理 > OpenXR(> Android 选项卡)
下的 OpenXR 插件设置中启用它。射线投射和命中测试这两个词在整个文档中可以互换使用。
5.6.3.1 示例如何工作
警告
如果在“平面检测”功能设置中启用了“使用场景理解平面检测”,则ARRaycastHit.trackableId
字段将始终返回0-0
的 trackableId 作为命中的可跟踪对象的会话唯一标识符。如果禁用“使用场景理解平面检测”,则ARRaycastHit.trackableId
将返回光线投射已命中的平面的 trackableId。
将 AR Raycast Manager
添加到 AR Session Origin GameObject
将启用光线投射子系统。如果没有可用的平面管理器,命中测试
功能将为命中测试创建底层基础。如果稍后生成平面管理器,则底层平面检测子系统将针对该管理器进行调整,并且命中跟踪的工作方式可能会有所不同(例如,结果取决于平面管理器上设置的平面过滤器)。为了获得最佳结果,如果您还使用命中测试,请启用平面管理器上的所有过滤器。如果您不使用单独的平面管理器,命中测试功能将自动处理此问题以获得最佳结果。
AR Raycast Manager
组件提供了一个字段,用于定义在成功与平面相交时生成的预制件。对于此示例,预制件已留空,因为此功能是在命中测试示例控制器
组件中实现的。
光线投射管理器的光线投射
函数将返回一个布尔值,具体取决于光线是否击中平面。命中结果将添加到 ARRaycastHit 列表中,按升序距离排序(即最接近的第一个)。
public void Update() {
CastRay();
_activeIndicator.transform.position = _desiredPosition;
}
private void CastRay() {
Ray ray = new Ray(_cameraTransform.position, _cameraTransform.forward);
List<ARRaycastHit> hitResults = new List<ARRaycastHit>();
if (_raycastManager.Raycast(ray, hitResults)) {
_desiredPosition = hitResults[0].pose.position;
...
}
else {
...
}
}
示例场景有一个跟随注视的默认红色小控件指示器。每一帧都会从头部原点到凝视方向创建光线投射。如果光线投射击中平面,指示器小控件将改变颜色并移动到击中位置。如果它没有击中任何东西,指示器小工具将保持不变。
5.6.4 图像跟踪示例
该示例演示了如何检测和增强现实世界中发现的图像目标。有关图像跟踪以及 AR Foundation 的 AR Tracked Image Manager
组件功能的基本信息,请参阅 Unity 文档(打开新窗口)。为了使用此功能,必须在项目设置 > XR 插件管理 > OpenXR(> Android 选项卡)
下的 OpenXR 插件设置中启用它。
5.6.4.1 示例如何工作
首先也是最重要的,确保在 OpenXR 项目设置中启用图像跟踪
功能。
提示
您可以在用于测试的图像目标部分
找到使用的参考图像
图像目标通过 XR 参考图像库
馈送到底层 XR 插件。添加的图像具有一个名称,该名称对于稍后识别跟踪目标非常有用,并且还有一个重要的标志在运行时保留纹理,该标志应设置为 true。这使得子系统能够将纹理数据传递到 Snapdragon Spaces 服务以进行跟踪。
此外,样品盒中图像目标的测量高度为 26 厘米(以 DIN A4 或 US letter 打印时)。正确的措施对于正确的姿势估计和随后的增强放置至关重要。因此,必须在启用“指定大小”后指定。
在导入设置中,纹理应启用读/写标志,并将格式设置为 RGB 24 位(也可能显示为 RGB8)。如果格式设置为自动,项目的图形设置可能会错误地设置格式。
将 ARTrackedImageManager
添加到 ARSessionOrigin
GameObject 将启用 Snapdragon Spaces 包中包含的图像跟踪子系统。该组件提供三个字段:一个用于上面创建的 RuntimeReferenceimageLibrary
的字段,一个用于指定最大移动图像数的字段,以及一个用于定义在检测到跟踪图像时生成的预制件的字段。使用的预制件是一个小控件,指示跟踪图像的方向。
通过订阅 ARTrackedImageManager
的方法来侦听跟踪图像的更改,可以为跟踪图像的跟踪状态和位置设置适当的 UI 信息,如下面的简化代码示例所示。
警告
为了使该示例正常工作,XR 参考图像库
中设置的参考图像名称必须是唯一的。任何相同的名称都会导致_trackedImages
字典中的哈希码冲突。
...
private Dictionary<TrackableId, ...> _trackedImages = new Dictionary<TrackableId, ...>();
public override void OnEnable() {
...
FindObjectOfType<ARTrackedImageManager>().trackedImagesChanged += OnTrackedImagesChanged;
}
public override void OnDisable() {
...
FindObjectOfType<ARTrackedImageManager>().trackedImagesChanged -= OnTrackedImagesChanged;
}
private void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs args) {
foreach (var trackedImage in args.added) {
if (trackedImage.referenceImage.name == "Spaces Town") {
_trackedImages.Add(trackedImage.trackableId, ...);
...
}
}
foreach (var trackedImage in args.updated) {
var info = _trackedImages[trackedImage.trackableId];
...
}
foreach (var trackedImage in args.removed) {
_trackedImages.Remove(trackedImage.trackableId);
...
}
}
可选的 Spaces Reference Image Configurator
组件也可以添加到与 ARTrackedImageManager
相同的 GameObject 中。该组件为每个跟踪图像提供附加配置选项。
跟踪模式字段允许为每个图像定义三种不同的跟踪模式之一:
- 动态(默认)- 动态模式每帧更新跟踪图像的位置,适用于移动和静态目标。如果找不到跟踪的图像,则不会报告位置或姿势。相对于其他跟踪模式,该模式具有更高的功耗。如果未找到
Spaces Reference Image Configurator
组件,则所有跟踪的图像将默认使用此跟踪模式。 - 静态 - 静态模式对于跟踪已知静态的图像非常有用,这可以降低功耗并提高性能。例如,这些图像可以是粘贴在地板或墙上的图像。此模式对于继续显示即使在图像不再可见后仍应可见的增强非常有用。在静态模式下跟踪的图像的位置永远不会更新,无论图像是否移动或离开视线。
- 自适应 - 自适应模式适用于静态图像,但如果跟踪图像发生轻微移动,则定期更新它们的姿势(大约每 5 帧一次)。对看不见的图像的跟踪最终将丢失。此模式平衡了图像的功耗和跟踪精度,这些图像将保持固定在适当的位置。
启动应用程序后,每个跟踪图像最初将使用 Spaces Reference Image Configurator
组件中定义的跟踪模式,但如果需要,可以在运行时更新该值,如下面的简化示例代码所示。
public SpacesReferenceImageConfigurator referenceImageConfigurator;
private Dictionary<TrackableId, ...> _trackedImages = new Dictionary<TrackableId, ...>();
...
string referenceImageName = ...; // referenceImageName to change Tracking Mode for
TrackableId trackableId = ...; // a TrackableId for an existing tracked instance of this image
...
// first, stop tracking any instances of the tracked image for which the Tracking Mode should be updated.
// if tracking is not stopped, the instance can (but is not guaranteed to) continue to be tracked using the old Tracking Mode for some time
referenceImageConfigurator.StopTrackingImageInstance(referenceImageName, trackableId);
...
// then, set the desired tracking mode for all future instances of the tracked image with that referenceImageName
referenceImageConfigurator.SetTrackingModeForReferenceImage(referenceImageName, SpacesImageTrackingMode.DYNAMIC);
referenceImageConfigurator.SetTrackingModeForReferenceImage(referenceImageName, SpacesImageTrackingMode.STATIC);
referenceImageConfigurator.SetTrackingModeForReferenceImage(referenceImageName, SpacesImageTrackingMode.ADAPTIVE);
5.6.4.2 在运行时交换参考库
可以在运行时更改 AR 跟踪图像管理器使用的参考图像库,而无需切换场景。为此,必须考虑以下因素:
- 需要重新启动图像跟踪子系统才能使库更改生效。这可以通过重新启用 AR 跟踪图像管理器组件来实现。
- 必须使用组件的
SyncTrackingModes
函数在新库的 Spaces 参考图像配置器中重新同步跟踪模式。必须提供参考图像名称和所需的起始跟踪模式的Dictionary<string, SpacesImageTrackingMode>
作为参数。
警告
如果在之前的库中找不到参考图像名称,则交换库时未能在 Spaces 参考图像配置器上重新应用跟踪模式将导致其SetTrackingModeForReferenceImage
函数失败。
在这种情况下,动态模式将成为参考图像的默认跟踪模式。
下面的示例代码实现了一个函数 SwapTargetLibrary
,该函数在运行时交换到不同的参考图像库。首先,禁用跟踪图像管理器组件,并在空间参考图像配置器组件的 SyncTrackingModes
函数中重新同步图像跟踪模式。参考图像名称和所需的起始跟踪模式的 Dictionary<string, SpacesImageTrackingMode>
由辅助 CreateTrackingModesDictionary
函数提供。最后,新的参考库应用于 AR 跟踪图像管理器组件,并重新启用后者。
public ARTrackedImageManager TrackedImageManager;
public SpacesReferenceImageConfigurator ReferenceImageConfigurator;
void SwapTargetLibrary(XRReferenceImageLibrary library)
{
TrackedImageManager.enabled = false;
ReferenceImageConfigurator.SyncTrackingModes(CreateTrackingModesDictionary(library));
TrackedImageManager.referenceLibrary = library;
TrackedImageManager.enabled = true;
}
Dictionary<string, SpacesImageTrackingMode> CreateTrackingModesDictionary(XRReferenceImageLibrary library)
{
Dictionary<string, SpacesImageTrackingMode> trackingModes = new Dictionary<string, SpacesImageTrackingMode>();
foreach (var referenceImage in library)
{
trackingModes[referenceImage.name] = SpacesImageTrackingMode.DYNAMIC;
}
return trackingModes;
}
5.6.5 平面检测示例
此示例演示了如何可视化现实世界中发现的跟踪平面。有关平面检测/跟踪以及 AR Foundation 的 AR Plane Manager
组件功能的基本信息,请参阅 Unity 文档(打开新窗口)。为了使用此功能,必须在项目设置 > XR 插件管理 > OpenXR(> Android 选项卡)
下的 OpenXR 插件设置中启用它。
5.6.5.1 示例如何工作
首先,确保在 OpenXR 项目设置中启用平面检测
功能。
在运行体验时,场景中只需要两个组件即可看到飞机。将 ARPlaneManager
添加到 ARSession
游戏对象将启用 Snapdragon Spaces 包中包含的平面子系统。该组件提供了一个字段,用于定义在创建平面时生成的预制件。该预制件附加了 AR 平面
和 AR 平面网格可视化工具
,位于网格渲染器和线渲染器旁边。 AR 平面网格可视化工具会定期调整网格和线条,并生成一个带有填充和边框的平面,可以通过更改渲染器的材质和属性来调整这些填充和边框。此外,可以将网格碰撞器组件添加到预制件中以接收光线投射命中。
5.6.5.2 功能设置
单击 OpenXR 项目设置中平面检测
功能旁边的齿轮图标可以找到功能设置。
- 使用场景理解平面检测:
- 启用或禁用此设置将在检测到的平面的形状和数量方面产生不同的结果。
- 启用此设置将启用
场景理解
来检测平面。这利用了空间网格划分(实验)
功能所使用的相同技术。 - 当启用使用场景理解平面检测时,当示例运行时,用户可以将该选项切换为使用凸包。
- 禁用“使用场景理解平面检测”时,默认情况下启用“使用凸包”,并且在示例运行时用户无法取消选中。
- 启用“使用凸包”后,检测到的平面的凸包用于生成更复杂的形状。禁用时,它将根据检测到的平面的范围生成平面。
- 如果由于任何原因无法加载场景理解,则此功能将表现为“使用场景理解平面检测”被禁用。
默认平面检测与基于场景理解的平面检测之间的差异
指标 | 默认 | 场景理解 |
---|---|---|
检测速度和首次检测 | 正常 | 快速 |
误报 | 误报率极低 | 容易出现误报 |
平面精度 | 高 | 高 |
平面数量 | 很少 | 很多 |
平面更新和移动 | 稳定,更新可能性不大 | 具有更多平面更新的动态模式 |
平面取向滤光片 | 水平和垂直过滤器选项 | 没有过滤选项 |
命中测试 | 与预期相符的平面 | 针对网格进行命中测试 |
5.6.6 相机框架访问示例
警告
相机帧访问功能被标记为实验性的,因为它不完全支持所有公共 AR Foundation API,并且软件包和 Snapdragon Spaces 服务端的优化目前正在破坏各个版本的向后兼容性。
此示例演示如何检索 RGB 相机帧和图像处理的内在属性。有关相机框架访问以及 AR Foundation 的 AR 相机管理器
组件功能的基本信息,请参阅 Unity 文档(打开新窗口)。为了使用此功能,必须在项目设置 > XR 插件管理 > OpenXR(> Android 选项卡)
下的 OpenXR 插件设置中启用它。此外,此示例需要在位于项目设置 > 播放器 > Android > 脚本编译
下的 Android 播放器设置中允许“不安全”代码。
5.6.6.1 示例如何工作
将 AR 相机管理器
组件添加到 AR Session Origin > AR
相机游戏对象将启用相机访问子系统。启动后,子系统将从查看器设备检索有效的传感器配置。如果找到有效的 YUV420 传感器配置,子系统将选择此配置作为 CPU 相机图像的提供者。从概念上讲,AR 相机管理器
代表单个相机,不会同时管理多个传感器。
示例场景由两个面板组成:
- 相机馈送面板显示来自设备相机的最新 CPU 图像,并带有暂停和恢复按钮
- 相机信息面板,枚举设备相机的各种属性
5.6.6.1 检索 CPU 映像
每次子系统有新的相机帧可用时,AR 相机管理器
都会触发一个frameReceived
事件。订阅此事件允许其他脚本尽早检索最新的相机帧并对其执行操作。一旦相机帧可用,相机管理器的 TryAcquireLatestCpuImage
函数将返回一个 XRCpuImage
对象,该对象表示来自所选设备相机的单个原始图像。该图像的原始像素数据可以使用 XRCpuImage
的 Convert
函数提取,该函数返回 NativeArray<byte>
。
重要提示
转换后必须显式处置XRCpuImage
对象。为此,请使用XRCpuImage
的Dispose
函数。未能处理XRCpuImage
对象将泄漏内存,直到相机访问子系统被销毁。
如果在转换之前分配NativeArray<byte>
,则在复制或操作之后也必须释放该缓冲区。为此,请使用NativeArray<T>
的Dispose
函数。未能处理NativeArray<byte>
将泄漏内存,直到相机访问子系统被销毁。
有关如何使用frameReceived
、TryAcquireLatestCpuImage
和XRCpuImage
的详细信息,请参阅Unity文档(打开新窗口)。
下面的示例代码在触发frameReceived
事件时从AR 相机管理器
请求CPU 图像。如果成功,它会将 XRCpuImage
的原始像素数据直接提取到托管 Texture2D
的 GetRawTextureData<byte>
缓冲区中,然后使用 Apply
函数应用纹理缓冲区。最后,它更新目标 RawImage
中的纹理,使新帧在应用程序的 UI 中可见。
public RawImage CameraRawImage;
private ARCameraManager _cameraManager;
private Texture2D _cameraTexture;
private XRCpuImage _lastCpuImage;
public void Start()
{
_cameraManager.frameReceived += OnFrameReceived;
}
private void OnFrameReceived(ARCameraFrameEventArgs args)
{
_lastCpuImage = new XRCpuImage();
if (!_cameraManager.TryAcquireLatestCpuImage(out _lastCpuImage))
{
return;
}
UpdateCameraTexture(_lastCpuImage);
}
private unsafe void UpdateCameraTexture(XRCpuImage image)
{
var format = TextureFormat.RGBA32;
if (_cameraTexture == null || _cameraTexture.width != image.width || _cameraTexture.height != image.height)
{
_cameraTexture = new Texture2D(image.width, image.height, format, false);
}
var conversionParams = new XRCpuImage.ConversionParams(image, format);
var rawTextureData = _cameraTexture.GetRawTextureData<byte>();
try
{
image.Convert(conversionParams, new IntPtr(rawTextureData.GetUnsafePtr()), rawTextureData.Length);
}
finally
{
image.Dispose();
}
_cameraTexture.Apply();
CameraRawImage.texture = _cameraTexture;
}
AR 相机管理器
支持以下纹理格式:
RGB24
RGBA32
BGRA32
5.6.6.2 检索 YUV 平面数据
Snapdragon Spaces 目前支持 Y’UV420sp
格式,由 Y 缓冲区和交错 2x2 子采样 U/V 缓冲区组成。有关YUV颜色模型的详细信息,请参阅YUV模型维基百科文章(打开新窗口)。
如果不需要RGB转换,可以通过XRCpuImage
的GetPlane
函数检索原始YUV平面数据。这将返回一个 XRCpuImage
.Plane
对象,可以从中读取平面数据。
- Y 平面数据的索引为 0,可以通过
GetPlane(0)
访问 - UV 平面数据的索引为 1,可以通过
GetPlane(1)
访问
有关 XRCpuImage.GetPlane
的详细信息,请参阅 Unity 文档(打开新窗口)。
有关 XRCpuImage.Plane
的详细信息,请参阅 Unity 文档(打开新窗口)。
下面的示例代码在触发frameReceived
事件时从AR 相机管理器
请求CPU 图像。如果成功,它将检索 XRCpuImage 的原始平面数据以对其应用图像处理算法。
private ARCameraManager _cameraManager;
private XRCpuImage _lastCpuImage;
public void Start()
{
_cameraManager.frameReceived += OnFrameReceived;
}
private void OnFrameReceived(ARCameraFrameEventArgs args)
{
_lastCpuImage = new XRCpuImage();
if (!_cameraManager.TryAcquireLatestCpuImage(out _lastCpuImage))
{
return;
}
ImageProcessingAlgorithm(_lastCpuImage);
}
private void ImageProcessingAlgorithm(XRCpuImage image)
{
var yPlane = image.GetPlane(0);
var uvPlane = image.GetPlane(1);
for (int row = 0; row < image.height; row++)
{
for (int col = 0; col < image.width; col++)
{
// Perform image processing here...
}
}
}
5.6.6.3 检索传感器内在参数
相机管理器的 TryGetIntrinsics
函数将返回一个 XRCameraIntrinsics
对象,该对象描述所选传感器的物理特性。有关 XRCameraIntrinsics
的详细信息,请参阅 Unity 文档(打开新窗口)。
下面的示例代码检索所选传感器的内在特性并将其显示在应用程序 UI 中。
public Text[] ResolutionTexts;
public Text[] FocalLengthTexts;
public Text[] PrincipalPointTexts;
private ARCameraManager _cameraManager;
private XRCameraIntrinsics _intrinsics;
private void UpdateCameraIntrinsics()
{
if (!_cameraManager.TryGetIntrinsics(out _intrinsics))
{
Debug.Log("Failed to acquire camera intrinsics.");
return;
}
ResolutionTexts[0].text = _intrinsics.resolution.x.ToString();
ResolutionTexts[1].text = _intrinsics.resolution.y.ToString();
FocalLengthTexts[0].text = _intrinsics.focalLength.x.ToString("#0.00");
FocalLengthTexts[1].text = _intrinsics.focalLength.y.ToString("#0.00");
PrincipalPointTexts[0].text = _intrinsics.principalPoint.x.ToString("#0.00");
PrincipalPointTexts[1].text = _intrinsics.principalPoint.y.ToString("#0.00");
}
5.6.6.3 通过分辨率限制提高性能
无论是由于大图像分辨率还是所使用算法的性质,相机访问操作的计算成本可能很高。对于这种情况,可以在项目设置 > XR 插件管理 > OpenXR(> Android 选项卡)> 相机框架访问(实验)中设置最大垂直分辨率约束。子系统支持的最大垂直分辨率为2160,默认为720。
将 Spaces AR Camera Manager Config 组件添加到 AR Session Origin > AR Camera GameObject 允许在运行时以编程方式更改分辨率约束。为此,请将组件的 MaximumVerticalResolution
属性设置为所需的值。
当使用最大垂直分辨率约束时,子系统会将传入帧缩小 2 的幂,直到满足分辨率约束。根据用于检索相机帧的方法,这将产生不同的结果:
XrCpuImage.Convert
返回缩小的 RGB 图像,这反映在其较小的缓冲区大小上。XrCpuImage.GetPlane
返回一个全分辨率缓冲区,忽略分辨率约束。 Spaces AR Camera Manager 配置组件提供了DownsamplingStride
属性,该属性公开了要应用于指定约束的行
和列
索引的正确步幅。