使用空间容器进行构建

适用的 XR 设备
本指南可帮助您为以下类型的 XR 设备打造优质体验。
扩展现实头戴设备
有线扩展现实眼镜

空间容器为 OpenXR 带来了体积窗口。空间容器由 OpenXR 1.1 中的 XR_EXT_spatial_container 和 XR_EXT_spatial_container_self_rendering 扩展定义,是用户空间中由系统管理的 3D 体积区域,您的应用可在其中使用标准 OpenXR 合成层渲染 3D 内容。

传统上,OpenXR 会话会声明对用户整个视野范围的独占访问权限。借助空间容器,您的应用可以在用户环境中的有界 3D 框内呈现 3D OpenXR 内容,或随时过渡到无界的全屏沉浸式体验。

关于空间容器

空间容器是由 Android XR 系统管理并由 OpenXR 应用渲染的有界或无界 3D 区域:

  • 有界模式:应用建议 3D 尺寸(宽度、高度和深度,以米为单位),系统在用户环境中分配一个 3D 边界框。您的应用会在该空间内渲染立体 3D 内容,系统会将渲染的内容裁剪到 3D 盒子的边界。
  • 沉浸模式:空间容器会扩展到无限边界,让您的应用占据用户的整个视野范围,就像传统的全屏 OpenXR 会话一样。
在全沉浸空间模式下同时运行的三个空间容器:一个橙色的锁定在地板上的 3D 地形容器、一个带有 2D 界面的蓝色 3D 鸭子模型,以及一个绿色的 2D 界面容器。
图 1. 全沉浸空间模式下的多个空间容器:一个锁定在地板上的 3D 地形体(左)、一个与 2D 界面配对的 3D 鸭子体(中)和一个 2D 界面容器(右)。

用户如何与空间容器互动

Android XR 系统负责管理空间容器的窗口控件及其在物理空间中的位置,从而在各个应用中提供一致的互动模型:

  • 移动和重新定位:用户可以使用手势(例如捏合)或动作控制器抓取空间容器的系统手柄,以便在 3D 空间中重新定位。
  • 最小化和关闭:系统窗口控件可让用户最小化或关闭单个空间容器。当用户关闭容器时,系统会通知您的应用,以便其清理资源。
  • 通过场景理解锁定到地面:当您的应用声明并请求 android.permission.SCENE_UNDERSTANDING_COARSE 权限时,系统会自动启用表面吸附功能,以便用户将空间容器放置并锁定到物理地面,而无需进行额外的应用配置,如图 1 中的橙色地形容器所示。
  • 直接 3D 互动:当容器处于焦点状态且可互动时,用户可以使用手部追踪、眼动追踪或 6DoF 控制器直接与体积内的 3D 内容互动。
用户通过手部追踪功能与蓝色空间容器内的 3D 鸭子互动。
图 2. 用户使用手部追踪功能与有界空间容器内的 3D 内容进行互动。
  • 在有界模式和沉浸模式之间切换:用户或您的应用可以在有界 3D 框和无界全屏沉浸式视图之间切换容器,而无需重启 OpenXR 会话。
从有界空间容器过渡到无界全屏沉浸式视图后的 3D 鸭子场景。
图 3. 从有界模式过渡到沉浸模式后的空间容器,填满用户的视野范围。
  • 随行跟随模式:由于系统会控制每个空间容器的坐标空间 (xrCreateSpatialContainerSpaceEXT) 的姿态,因此用户可以从系统窗口控件中启用跟随模式,这样有界 3D 容器就会在用户四处走动时自动跟随他们并保持在他们的视野范围内,而无需在应用中进行任何额外配置。

如何在 Android XR 上使用空间容器

使用空间容器构建应用时,请围绕容器的边界、状态变化和背景混合模式设计场景。

使 3D 内容适应容器边界

在有界模式下,空间容器空间 (xrCreateSpatialContainerSpaceEXT) 的原点 (0, 0, 0) 位于 3D 边界框的中心。由于系统会管理容器空间在用户环境中的姿态,因此您的应用会相对于 (0, 0, 0) 渲染其 3D 内容,而系统会处理用户重新定位和跟随模式。系统会剪裁在框的宽度、高度或深度之外渲染的所有几何图形,以便内容保持在其分配的体积内。

您的应用应监听来自运行时的边界更改事件,并缩放或重新定位 3D 场景,以便关键内容适合容器的当前尺寸。当容器过渡到沉浸模式时,边界会变为无限,您可以将场景恢复为完整的 1:1 世界比例。

响应容器状态变化

每个空间容器都会报告自己的状态,以便您的应用做出相应反应,而不仅仅是在应用级别管理可见性和焦点:

  • 可见性:指示容器当前是显示还是隐藏。在容器隐藏时暂停动画、音频或模拟更新。您的应用还可以请求显示或隐藏容器。
  • 可交互性:指示容器是否处于焦点状态,以及是否正在接收来自手部追踪、控制器或视线追踪的用户输入。在焦点发生变化时启用或停用互动式突出显示和输入处理。
  • 边界模式:指示容器当前是处于 XR_SPATIAL_CONTAINER_BOUNDS_MODE_BOUNDED_EXT(剪裁到 3D 框)还是 XR_SPATIAL_CONTAINER_BOUNDS_MODE_IMMERSIVE_EXT(无边界全屏)模式。在模式发生变化时,更新场景比例并切换环境视觉效果(例如天空盒或透视)。您的应用还可以随时请求在有界模式和沉浸模式之间切换。

在有界模式下使用 Alpha 混合透明度

在有界模式下,透明背景可让用户在 3D 模型周围看到其物理环境和其他窗口。将相机或视口配置为透明背景,并使用 Alpha 混合环境混合模式。切换到沉浸模式时,您可以选择保持透视模式处于启用状态,也可以切换到不透明的天空盒环境。

检查功能可用性并配置清单

空间容器需要支持空间 API 级别 4 或更高级别的 Android XR 设备。请勿直接检查 Android API 级别,而是通过调用 xrEnumerateInstanceExtensionProperties 来验证 XR_EXT_spatial_container 和 XR_EXT_spatial_container_self_rendering 是否可用,并使用 XrSystemSpatialContainerPropertiesEXT 调用 xrGetSystemProperties 来检查 supportsBounded 和 supportsImmersive,从而查询 OpenXR 运行时以确定是否支持空间容器。

在构建使用空间容器的应用之前,请在 AndroidManifest.xml 中配置 activity 启动模式和任何特定于功能的权限。

在 AndroidManifest.xml 中启用全沉浸空间模式

空间容器需要全沉浸空间托管模式,即使对于独立 OpenXR 应用也是如此。传统的全屏 OpenXR 应用会声明 XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED,以在没有系统窗口管理的情况下独占显示屏。相比之下,使用空间容器的应用在全沉浸空间模式下依赖 Android XR 系统合成器来管理 3D 有界体、窗口控件和空间放置,并且必须将 android.window.PROPERTY_XR_ACTIVITY_START_MODE 设置为 XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application ...>
        <!-- Spatial Containers require Full Space Managed mode -->
        <property
            android:name="android.window.PROPERTY_XR_ACTIVITY_START_MODE"
            android:value="XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED" />

        <activity android:name=".MainActivity" ...>
            <!-- Optional hint declaring which bounds modes the activity uses -->
            <property
                android:name="org.khronos.openxr.PROPERTY_ACTIVITY_HINT_XR_SPATIAL_CONTAINER_USED_BOUNDS_MODES"
                android:value="XR_SPATIAL_CONTAINER_BOUNDS_MODE_BOUNDED_EXT|XR_SPATIAL_CONTAINER_BOUNDS_MODE_IMMERSIVE_EXT" />
        </activity>
    </application>
</manifest>

声明和请求功能专属权限

创建和渲染基本空间容器不需要危险的运行时权限。不过,特定功能需要声明和请求相应的 Android XR 权限:

  • 如需使用场景理解功能将空间容器锁定并锚定到物理地板,请在运行时声明并请求危险的 android.permission.SCENE_UNDERSTANDING_COARSE 权限。
  • 如需在 OpenXR 中跟踪用户的头部姿势,请声明并请求 android.permission.HEAD_TRACKING。
<uses-permission android:name="android.permission.HEAD_TRACKING" />
<uses-permission android:name="android.permission.SCENE_UNDERSTANDING_COARSE" />

如需详细了解如何在运行时声明和请求危险的 XR 权限,请参阅了解 XR 权限。

在项目中启用 Spatial Containers

您可以使用 Unity、Godot 或直接在 C++ 中使用 OpenXR 构建空间容器。选择一个标签页,查看您首选环境的前提条件、编辑器设置和代码示例:

Unity

前提条件

配置您的项目

  1. 依次前往 Edit > 项目设置 > XR Plug-in Management > OpenXR。
  2. 点击 Android XR (Extensions): Session Management 旁边的设置 ,然后清除 Immersive XR。
  3. 在 OpenXR Feature Groups(OpenXR 功能组)列表中,启用 Spatial Container(空间容器)。

在 C# 中使用 XRSpatialContainerFeature 查询边界、响应容器事件,以及在有界模式和沉浸模式之间切换:

void OnEnable()
{
    XRSpatialContainerFeature.OnSpatialContainerBoundsChanged += OnBoundsChanged;
}

private void OnBoundsChanged(Vector3 bounds, bool infiniteBounds)
{
    float minScale = Mathf.Min(bounds.x, bounds.y, bounds.z);
    transform.localScale = infiniteBounds ? Vector3.one : Vector3.one * minScale;
}

public void SetImmersiveMode(bool immersive)
{
    XRSpatialContainerFeature.TryRequestBoundsMode(immersive);
}

如需了解完整的项目设置步骤、清单配置和权限处理,请参阅在 Unity 中使用空间容器。

Godot

前提条件

配置您的项目

  1. 依次前往 Project > 项目设置,启用 Advanced Settings,然后依次前往 General > XR > OpenXR > Extensions。
  2. 在空间容器子部分下,选择已启用,然后配置边界模式和边界。
  3. 依次前往项目 > 导出…> Android,然后选择 Android XR Features 下的 Use Experimental Features。

在 GDScript 中使用 OpenXRSpatialContainerExtension 单例来响应容器边界和模式更改:

func _ready() -> void:
    if OpenXRSpatialContainerExtension.is_enabled():
        OpenXRSpatialContainerExtension.spatial_container_bounds_changed.connect(
            _on_bounds_changed
        )

func _on_bounds_changed(_rid: RID, bounds: Vector3) -> void:
    scale = Vector3.ONE * min(bounds.x, bounds.y, bounds.z)

func set_immersive_mode() -> void:
    OpenXRSpatialContainerExtension.request_main_spatial_container_bounds_mode(
        OpenXRSpatialContainerState.IMMERSIVE
    )

如需查看完整的项目设置、导出权限和 GDScript 示例,请参阅在 Godot 中使用空间容器。

OpenXR

前提条件

在创建 XrInstance 时启用 XR_EXT_spatial_container 和 XR_EXT_spatial_container_self_rendering,然后创建并渲染空间容器:

// 1. Enable the Spatial Container extensions during xrCreateInstance
const char* enabledExtensions[] = {
    XR_EXT_SPATIAL_CONTAINER_EXTENSION_NAME,
    XR_EXT_SPATIAL_CONTAINER_SELF_RENDERING_EXTENSION_NAME,
};

// 2. Configure and create a self-rendering spatial container
XrSpatialContainerConfigurationSelfRenderingEXT selfRenderingConfig{
    .type = XR_TYPE_SPATIAL_CONTAINER_CONFIGURATION_SELF_RENDERING_EXT,
    .next = nullptr,
    .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
    .suggestedBounds = {1.5f, 1.5f, 1.5f}, // Width, height, depth in meters
};

XrSpatialContainerCreateInfoEXT createInfo{
    .type = XR_TYPE_SPATIAL_CONTAINER_CREATE_INFO_EXT,
    .next = &selfRenderingConfig,
    .spatialContainerConfig =
        reinterpret_cast<const XrSpatialContainerConfigurationBaseHeaderEXT*>(
            &selfRenderingConfig),
};

XrSpatialContainerEXT spatialContainer = XR_NULL_HANDLE;
xrCreateSpatialContainerEXT(session, &createInfo, &spatialContainer);

// 3. Request bounded mode and make the container visible
xrRequestSpatialContainerBoundsModeEXT(
    spatialContainer, XR_SPATIAL_CONTAINER_BOUNDS_MODE_BOUNDED_EXT);
xrRequestSpatialContainerVisibilityEXT(spatialContainer, XR_TRUE);

另请参阅


OpenXR™ 和 OpenXR 徽标是 The Khronos Group Inc. 拥有的商标,并在中国、欧盟、日本和英国注册为商标。