开发
开发
选择平台

使用 OVRAnchor 访问场景数据

更新时间: 2024年8月15日
在本页,您将了解如何使用 OVRAnchor 组件直接访问场景数据,重建您自己的场景管理工具。

OVRAnchor 组件是什么?

混合现实实用工具套件在使用场景数据时采用的是最常见的概念:要求加载时,为任何给定场景元素生成 Unity Prefab。这不同于低级 API,低级 API 更接近于系统如何表示和展示场景的核心功能。为了在接收数据时更好地控制 Unity 中发生的情况,您必须使用 OVRAnchor API。

OVRAnchor 组件如何工作?

OVRAnchor 组件是以低级场景功能为中心的轻量包装器。大多数场景 API 功能都是异步的,因此,您可以通过调用非阻塞函数、订阅事件以及将逻辑放入事件回调中来访问它们,也可以使用 C# 的异步/等待功能,这就是 OVRAnchor 的工作方式。
就像 Unity 场景图包含带有 MonoBehaviour(组件)的 GameObject 集合一样,Meta Quest 场景模型也包含带有组件的锚点集合(更多详情,请参阅场景模型的工作原理)。
大多数时间,您要么获取符合某些条件的锚点,要么获取锚点的组件数据。组件是与锚点相关的数据,如平面的 2D 扩展,或体积的 3D 边界框。

获取锚点

您可以使用 OVRAnchor.FetchAnchorsAsync() 查询运行时的锚点。FetchAnchorsAsync 接受 OVRAnchor.FetchOptions 结构,这让您能够过滤符合一系列条件的锚点,具体说明如下:
  • 通过组件获取锚点:每个锚点都有一个或多个组件。您可以获取含有此特定组件的所有已知锚点列表(例如找到具有房间布局组件的所有锚点)。
  • 通过 UUID 获取锚点:每个锚点都有一个 UUID,它可以用来在会话之间引用同一锚点。
有了锚点后,您就可以访问它的组件,并通过这些组件访问它的数据。锚点可以有任意数量的组件,不过这可以提前得知(请参阅常见场景锚点)。
注意:虽然 OVRAnchor API 是异步的,但 Awaiter 的实现不能阻塞主线程(不同于典型的异步功能)。这是因为完成 OVRAnchor 任务的事件仅将在主线程上调用。

房间和子锚点的控制流

如果您在完全不了解环境的情况下开始,您很可能会按照特定的流程来检索场景中的内容。
  1. 找到所有具有 RoomLayout 组件的锚点。
  2. 对于每个锚点,获取 AnchorContainer 组件来访问房间的子锚点。也可以使用 RoomLayout 来直接访问天花板、地板或墙壁。
  3. 遍历子锚点,获取您有兴趣检索其数据的组件。
  4. 如要定位锚点,请启用其 Locatable 组件。然后您可以访问其姿势,并相应地更新对象变换。
  5. 如果您想知道维度,请查询 2D 和/或 3D 边界,并相应地缩放您的 Unity 对象。
代码示意如下:
var anchors = new List<OVRAnchor>();
var result = await OVRAnchor.FetchAnchorsAsync(anchors, new OVRAnchor.FetchOptions
    {
        SingleComponentType = typeof(OVRRoomLayout),
    });

// no rooms - call Space Setup or check Scene permission
if (!result.Success || anchors.Count == 0)
    return;

// get the component to access its data
foreach (var room in anchors)
{
    if (!room.TryGetComponent(out OVRAnchorContainer container))
        continue;

    // use the component helper function to access all child anchors
    await container.FetchChildrenAsync(anchors);
}
注意:FetchAnchorsAsync 只能提供它有权访问的锚点。为了访问场景锚点,用户必须授予应用访问空间数据的权限(请参阅空间数据权限)。如果您尝试在未获得此权限的情况下使用 OVRRoomLayout 组件获取锚点,FetchAnchorsAsync 将返回一个空集。

锚点组件

所有数据都存储在组件中,因此场景数据级组件和 OVRAnchor 之间存在 1 对 1 映射。并非所有锚点都有所有组件,建议使用 OVRAnchor.TryGetComponent() 来查看某个锚点是否有所讨论的组件。
在下面的代码中,您将遍历房间元素,找到第一个地板锚点,将我们的变换与锚点变换相匹配并获得边界平面的大小。
// the transform of the OVRCameraRig's TrackingSpace
Transform trackingSpace;

foreach (var anchor in anchors)
{
    // check that this anchor is the floor
    if (!anchor.TryGetComponent(out OVRSemanticLabels labels) ||
        !labels.Labels.Contains(OVRSceneManager.Classification.Floor)))
    {
        continue;
    }

    // enable locatable/tracking
    if (!anchor.TryGetComponent(out OVRLocatable locatable))
        continue;
    await locatable.SetEnabledAsync(true);

    // localize the anchor
    locatable.TryGetSceneAnchorPose(out var pose);
    this.transform.SetPositionAndRotation(
        pose.ComputeWorldPosition(trackingSpace).GetValueOrDefault(),
        pose.ComputeWorldRotation(trackingSpace).GetValueOrDefault()
    );

    // get the floor dimensions
    anchor.TryGetComponent(out OVRBounded2D bounded2D);
    var size = bounded2D.BoundingBox.size;

    // only interested in the first floor anchor
    break;
}
为了防止姿势变换影响原始组件数据,您应该有一个只应用姿势的父 Unity GameObject,并且其中包括平面、体积和网格的子 GameObject。这将允许您在对象变换上使用组件数据。
// you should previously have set this object's transform using the OVRLocatable pose
var parent = this.gameObject;

// create a child Unity game object
var plane = GameObject.CreatePrimitive(PrimitiveType.Cube);
plane.transform.SetParent(parent.transform, false);

// set the object transform to the bounds
anchor.TryGetComponent(out OVRBounded2D bounded2D);
plane.transform.localScale = new Vector3(
    bounded2D.BoundingBox.size.x,
    bounded2D.BoundingBox.size.y,
    0.01f);

查询场景数据的时间

OVRSceneManager Prefab 将在调用其 LoadSceneModel() 函数时加载数据,这通常是应用启动时。但是,在应用的生命周期内,随时都可以查询场景模型数据。
在调用空间设置/场景捕捉时,场景模型会发生变化。应用可能会触发该进程,从而导致要加载新的场景数据载。空间设置进程会暂停一个应用,因此可以将新场景数据的查询限制在调用 OnAppPause 方法时。

详细了解

现在您已经学会如何使用低级 OVRAnchor 组件访问场景模型数据,您已经拥有在 Unity 中创建场景管理工具的所有必要工具。
  • 如需查看使用 OVRAnchor API 创建场景管理工具的代码示例,请查阅我们的自定义场景管理工具示例
  • 如要查看使用场景的更多示例,请查阅我们的示例
  • 如要了解如何利用权限保护用户隐私,请参阅空间数据权限
  • 如要了解如何实际使用场景,请查看我们的最佳实践