공간 컨테이너로 빌드하기

적용 가능한 XR 기기
이 가이드는 이러한 유형의 XR 기기를 위한 환경을 빌드하는 데 도움이 됩니다.
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 UI가 연결된 파란색 3D 오리 모델, 녹색 2D UI 컨테이너
그림 1. 전체 공간 모드의 여러 공간 컨테이너: 바닥에 고정된 3D 지형 볼륨 (왼쪽), 2D UI와 페어링된 3D 오리 볼륨 (가운데), 2D UI 컨테이너 (오른쪽)

사용자가 공간 컨테이너와 상호작용하는 방식

Android XR 시스템은 공간 컨테이너의 창 컨트롤과 실제 공간에서의 배치를 관리하여 앱 전반에서 일관된 상호작용 모델을 제공합니다.

  • 이동 및 위치 변경: 사용자는 손동작 (예: 꼬집기) 또는 모션 컨트롤러를 사용하여 공간 컨테이너의 시스템 핸들을 잡고 3D 공간에서 위치를 변경할 수 있습니다.
  • 최소화 및 닫기: 시스템 창 컨트롤을 사용하면 사용자가 개별 공간 컨테이너를 최소화하거나 닫을 수 있습니다. 사용자가 컨테이너를 닫으면 시스템은 앱에 알림을 보내 리소스를 정리할 수 있도록 합니다.
  • 장면 이해를 통해 바닥에 고정: 앱에서 android.permission.SCENE_UNDERSTANDING_COARSE 권한을 선언하고 요청하면 시스템에서 자동으로 표면 맞추기를 사용 설정하므로 사용자는 추가 앱 구성 없이 그림 1의 주황색 지형 컨테이너와 같이 공간 컨테이너를 실제 바닥에 배치하고 고정할 수 있습니다.
  • 직접 3D 상호작용: 컨테이너에 포커스가 있고 상호작용이 가능한 경우 사용자는 핸드 트래킹, 시선, 6DoF 컨트롤러를 사용하여 볼륨 내의 3D 콘텐츠와 직접 상호작용할 수 있습니다.
사용자가 핸드 트래킹을 사용하여 경계가 있는 파란색 공간 컨테이너 내에서 3D 오리와 상호작용하고 있습니다.
그림 2. 핸드 트래킹을 사용하여 경계가 있는 공간 컨테이너 내에서 3D 콘텐츠와 상호작용하는 사용자
  • 제한 모드와 몰입형 모드 간 전환: 사용자 또는 앱은 OpenXR 세션을 다시 시작하지 않고도 제한된 3D 상자와 제한되지 않은 전체 화면 몰입형 뷰 간에 컨테이너를 전환할 수 있습니다.
제한된 공간 컨테이너에서 제한되지 않은 전체 화면 몰입형 뷰로 전환한 후의 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 (제한 없는 전체 화면)인지 나타냅니다. 모드가 변경되면 장면 배율을 업데이트하고 환경 시각 요소 (예: 스카이박스 또는 패스 스루)를 전환합니다. 앱은 언제든지 경계 모드와 몰입형 모드 간의 전환을 요청할 수도 있습니다.

제한 모드에서 알파 블렌딩 투명도 사용

제한 모드에서는 투명한 배경을 통해 사용자의 실제 주변 환경과 다른 창이 3D 모델 주변에 계속 표시됩니다. 투명한 배경으로 카메라 또는 표시 영역을 구성하고 알파 블렌드 환경 블렌드 모드를 사용합니다. 몰입형 모드로 전환할 때 패스 스루를 사용 설정된 상태로 유지하거나 불투명한 스카이박스 환경으로 전환할 수 있습니다.

기능 사용 가능 여부 확인 및 매니페스트 구성

공간 컨테이너에는 공간 API 수준 4 이상을 지원하는 Android XR 기기가 필요합니다. Android API 수준을 직접 확인하는 대신 OpenXR 런타임을 쿼리하여 xrEnumerateInstanceExtensionProperties를 호출하여 XR_EXT_spatial_container 및 XR_EXT_spatial_container_self_rendering를 사용할 수 있는지 확인하고 XrSystemSpatialContainerPropertiesEXT를 사용하여 xrGetSystemProperties를 호출하여 supportsBounded 및 supportsImmersive를 검사하여 공간 컨테이너가 지원되는지 확인합니다.

Spatial Containers를 사용하는 앱을 빌드하기 전에 AndroidManifest.xml에서 활동 시작 모드와 기능별 권한을 구성합니다.

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을 직접 사용하여 Spatial Containers로 빌드할 수 있습니다. 탭을 선택하여 선호하는 환경의 기본 요건, 편집기 설정, 코드 예시를 확인하세요.

Unity

기본 요건

프로젝트 구성

  1. Edit(수정) > Project Settings(프로젝트 설정) > XR Plug-in Management(XR 플러그인 관리) > OpenXR로 이동합니다.
  2. Android XR (Extensions): Session Management 옆에 있는 설정 을 클릭하고 Immersive XR을 선택 해제합니다.
  3. OpenXR Feature Groups 목록에서 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(프로젝트) > Project Settings(프로젝트 설정)로 이동하여 Advanced Settings(고급 설정)를 사용 설정하고 General(일반) > XR > OpenXR > Extensions(확장 프로그램)로 이동합니다.
  2. 공간 컨테이너 하위 섹션에서 사용 설정됨을 선택하고 경계 모드 및 경계를 구성합니다.
  3. 프로젝트 > 내보내기… > Android를 선택하고 Android XR 기능에서 실험용 기능 사용을 선택합니다.

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. 소유의 상표이며 중국, 유럽 연합, 일본, 영국에서 상표로 등록되어 있습니다.