OpenXR 크로스플랫폼 XR 표준 완전가이드
TL;DR
- OpenXR은 XR 앱과 벤더 런타임 사이의 공통 API를 제공해 플랫폼 파편화를 줄인다.
- Loader, Runtime, Session State Machine, Frame Loop, Action System을 이해해야 실제 디버깅과 성능 튜닝이 가능하다.
- Meta, Valve, Microsoft, Google, Pico가 사실상 OpenXR 표준 생태계로 수렴했고 Apple만 독자 노선을 유지한다.
1. 개념
OpenXR은 VR·AR·MR 앱이 단일 API로 다양한 헤드셋 런타임을 호출하도록 만든 Khronos 표준이다. 앱은 Loader를 통해 활성 Runtime에 연결되고, 세션·입력·공간·프레임 제출을 일관된 방식으로 다룬다.
2. 배경
초기 XR 시장은 Oculus SDK, OpenVR, WMR, Daydream, Wave처럼 플랫폼별 API가 분리돼 있었다. 개발사는 기기별로 입력·렌더링·세션 로직을 반복 구현해야 했고, 이 비용이 생태계 확장에 큰 마찰이 됐다.
3. 이유
OpenXR을 제대로 이해해야 런타임별 차이, predicted display time, reference space, extension 지원 여부, OpenVR/Oculus SDK 마이그레이션 같은 실무 이슈를 정확히 처리할 수 있다.
4. 특징
- Loader–API Layer–Runtime으로 분리된 표준 아키텍처
- 세션 상태 머신, 프레임 루프, swapchain, composition layer 중심 설계
- Action System과 interaction profile 기반 입력 추상화
- KHR/EXT/vendor extension으로 기능 확장
- Unity, Unreal, Godot, WebXR와 연결되는 광범위한 엔진 생태계
5. 상세 내용
OpenXR 크로스플랫폼 XR 표준 완전가이드
작성일: 2026-04-29 카테고리: XR / Graphics / Khronos / Game Dev 포함 내용: Khronos Group, OpenXR Loader, API Layers, Runtime, XrInstance, XrSession, XrSpace, XrAction, XrSwapchain, Reference Spaces (VIEW/LOCAL/STAGE/LOCAL_FLOOR/UNBOUNDED), Composition Layers, xrWaitFrame, xrBeginFrame, xrEndFrame, xrPollEvent, Session State Machine, Predicted Display Time, Late Latching, Foveated Rendering, Application SpaceWarp, Multiview, Action System, Interaction Profile, Suggested Bindings, Graphics Binding (Vulkan/D3D11/D3D12/OpenGL/OpenGL ES), Project Treble 비교, Monado, Meta Quest Horizon OS, SteamVR, WMR/HoloLens 2, Pico, Varjo, HTC Vive, Magic Leap 2, Lynx, Snap Spectacles, Apple visionOS 부재, OpenVR/Oculus SDK 마이그레이션, Unity OpenXR Plugin, XR Interaction Toolkit, Meta XR SDK, Unreal Meta XR Plugin, Godot 4 OpenXR, BabylonJS/Three.js WebXR, StereoKit, Bevy XR, Android XR (Google), CTS, OpenXR 1.1, Privacy Sandbox, Snapdragon Spaces
1. OpenXR이란?
핵심 개념
┌─────────────────────────────────────────────────────────────────┐
│ OpenXR 한 줄 정의 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ "Khronos가 관리하는 royalty-free 개방 표준 — VR/AR/MR(통칭 XR) │
│ 하드웨어와 소프트웨어를 분리하는 크로스플랫폼 C API" │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ App │ │
│ │ │ 단일 OpenXR API │ │
│ │ ▼ │ │
│ │ Loader (Khronos 제공, 앱에 번들) │ │
│ │ ├── API Layers (validation/dump/...) │ │
│ │ └── Runtime (Meta/SteamVR/Monado/MSFT/Pico/...) │ │
│ │ │ │
│ │ Write once, run on any conformant runtime │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
XR / VR / AR / MR 관계
| 약어 | 풀 이름 | 의미 |
|---|---|---|
| VR | Virtual Reality | 물리 세계 차단, 디지털 세계 몰입 |
| AR | Augmented Reality | 물리 세계 위에 디지털 오버레이 |
| MR | Mixed Reality | 물리/디지털 실시간 상호작용, 공간 앵커 |
| XR | eXtended Reality | VR/AR/MR 전체를 포괄하는 우산 용어 |
왜 “OpenXR”이지 “OpenVR”이 아닌가?
- OpenVR은 이미 Valve의 등록상표: 2016년부터 SteamVR용으로 Valve가 사용 중
- 범위 차이: OpenXR은 VR뿐 아니라 AR, MR, 미래의 어떤 형태도 포괄
- 컨소시엄 중립성: 다양한 회원사(Oculus VR, Microsoft MR, Magic Leap AR)의 이해를 모두 아우름
Khronos Group의 어원
Χρόνος (Chrónos) — 그리스 신화의 “측정 가능한 선형 시간”의 신. 그리스인들은 시간을 χρόνος(연대기적 시간)와 καιρός(결정적 순간)로 구분했다. Khronos Group은 전자를 택했는데, 이는 “실시간(real-time) 컴퓨팅과 그래픽 처리”를 다루는 조직 성격에 정확히 부합한다 — 시간이 곧 프레임, 렌더링, 지연(latency)의 언어이기 때문이다.
2000년 설립. 비영리 개방 컨소시엄. 주요 표준: OpenGL(이관 1992), OpenGL ES(2003), WebGL(2011), OpenCL(2009), Vulkan(2016), SPIR-V(2015), glTF(2015), OpenXR(2019).
2. 등장 배경 (Pre-OpenXR Fragmentation)
2.1 2013~2017 파편화 시대
┌─────────────────────────────────────────────────────────────────┐
│ "왜 또 다른 표준이 필요했는가" │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 앱이 동시 지원해야 했던 SDK들: │
│ │
│ Oculus PC SDK (LibOVR) — Rift, Rift S용 │
│ Oculus Mobile SDK (VrApi) — Gear VR, Quest용 │
│ OVRPlugin (Unity) — 위 둘을 Unity에서 통합 │
│ OpenVR / SteamVR (Valve) — Vive, Index, WMR (SteamVR 종속)│
│ Windows Mixed Reality API — HoloLens, WMR HMD │
│ Google VR SDK (Daydream) — Android Daydream (2019 종료) │
│ Vive Wave SDK (HTC) — Vive Focus 시리즈 │
│ PSVR SDK (Sony) — PSVR 콘솔 전용 │
│ OSVR (Razer) — 2015 시도, 실패 │
│ │
│ → 개발사는 N배 구현 비용 │
│ → 하드웨어 벤더가 자사 런타임에 개발자 가두기 경쟁 │
│ → 콘텐츠 부족 → 사용자 부족 → 개발자 외면 악순환 │
└─────────────────────────────────────────────────────────────────┘
2.2 OpenVR의 한계
Valve의 OpenVR은 다수 HMD를 지원했지만 SteamVR 런타임이 항상 중간에 존재해야 했다. “Open”이라는 이름과 달리 Valve가 런타임 계층을 통제했다.
2.3 OSVR 실패의 교훈
Razer + Sensics가 2015년 시작한 OSVR(Open Source Virtual Reality)은 “VR의 Android”를 표방했지만 콘텐츠 부재, 하드웨어 정밀도 부족, 소프트웨어 미성숙으로 실패. Razer는 OSVR을 포기하고 Khronos OpenXR 창립 회원으로 합류했다 — 중립적 대형 컨소시엄 없이 오픈 표준 성공이 어렵다는 교훈.
3. 역사적 기원 (Historical Origins)
| 연도 | 사건 |
|---|---|
| 2016 초 | Khronos 회원 면대면 회의에서 XR API 파편화 해결 탐색 그룹 형성 |
| 2016.03~04 | Oculus Rift CV1 / HTC Vive 출시 — 두 PC VR 플랫폼이 완전히 다른 SDK |
| 2017.02.27 (GDC 2017) | Khronos가 OpenXR Working Group 공식 설립 발표. AMD, Intel, ARM, NVIDIA, Epic Games, Unity, Oculus/Facebook, Microsoft, Sony, Qualcomm, HTC 등 |
| 2019.03.18 | OpenXR 0.90 Provisional Specification 공개 |
| 2019.07.29 (SIGGRAPH 2019) | OpenXR 1.0 공식 비준 + 공개 릴리스. Monado(Linux), WMR, Oculus Rift 즉시 구현체 제공 |
| 2020.06 | Valve, SteamVR OpenXR Developer Preview 공개. CTS 95% 통과 |
| 2020.11 | Microsoft, OpenXR 1.0 Conformant 인증 (HoloLens 2 + WMR) |
| 2021.02 | Valve SteamVR, OpenXR 1.0 Conformant 등재 |
| 2021.07.23 | Meta “All In on OpenXR” 공식 선언. Oculus 독자 API 단계적 종료 발표 |
| 2022.08.31 | Oculus Mobile SDK(VrApi) + Oculus PC API 완전 지원 종료(Unsupported) |
| 2022 | PICO (ByteDance), OpenXR 1.0 Conformant Runtime 제공 |
| 2024.04.15 | OpenXR 1.1 릴리스 — Local Floor, Palm Pose, Quad Views 등 5개 Extension을 Core로 승격. 연간 정기 릴리스 사이클 선언 |
| 2024.02 | Apple Vision Pro 출시 — OpenXR 미지원. RealityKit + CompositorServices 독자 스택 |
| 2024.07 | Varjo XR-4 시리즈 OpenXR 1.0 Conformant |
| 2024.12.12 | Google이 Android XR 발표 — OpenXR 1.1 기반. Samsung Galaxy XR (Project Moohan) 첫 출시 기기 |
| 2024.11 | Pico 4 Ultra OpenXR 1.1 Conformant |
| 2025~ | OpenXR이 사실상 업계 표준화. Apple만 미참여 |
SIGGRAPH 2019 발언:
- Brent Insko (Intel, OpenXR WG 의장): “Now is the time for software developers to start putting OpenXR to work.”
- Don Box (Microsoft Technical Fellow): “The mobile era was defined by closed ecosystems. With mixed reality, the next wave must be open.”
4. 용어 사전 (Terminology Dictionary)
| 용어 | 풀 이름 | 의미 |
|---|---|---|
| OpenXR | — | “Open eXtended Reality”. XR 우산 표준. |
| Khronos Group | — | 그리스어 Chrónos(시간)에서 유래. 2000년 설립 비영리 개방 컨소시엄 |
| CTS | Conformance Test Suite | 런타임이 사양을 올바르게 구현했는지 검증하는 자동/수동 테스트 |
| Loader | OpenXR Loader | 앱과 함께 배포되는 벤더 중립 라이브러리. Active Runtime 탐색·로드 |
| Runtime | OpenXR Runtime | 벤더(Meta/Valve/MSFT 등) 제공 구현. 트래킹, 컴포지터, 렌즈 왜곡 보정 등 |
| API Layer | — | 앱과 런타임 사이 선택적 미들웨어. validation, debug, profiling |
| XrInstance | — | 런타임 최상위 핸들. xrCreateInstance로 생성 |
| XrSession | — | 그래픽스 바인딩된 렌더링 세션. 상태 머신 동반 |
| XrSpace | — | 3D 좌표계. Reference Space 또는 Action Space |
| XrAction / XrActionSet | — | 의미적 입력 단위. 하드웨어 추상화 |
| XrSwapchain | — | 런타임 관리 텍스처 배열 |
| Reference Space | VIEW / LOCAL / STAGE / LOCAL_FLOOR / UNBOUNDED | 좌표계 종류 |
| Composition Layer | Projection / Quad / Cylinder / Equirect / Cube | xrEndFrame에 제출하는 렌더링 단위 |
| Interaction Profile | — | 컨트롤러 표준 경로 (/interaction_profiles/oculus/touch_controller 등) |
| Suggested Binding | — | 앱이 제안하는 Action↔하드웨어 매핑. 런타임이 사용자 리바인딩 가능 |
| Graphics Binding | — | xrCreateSession 시 GPU 디바이스 선언. Vulkan/D3D11/D3D12/GL/GLES |
| predictedDisplayTime | — | xrWaitFrame이 반환하는 미래 프레임 표시 시각. 헤드 포즈 예측에 사용 |
| Late Latching | — | GPU 큐 직전에 최신 포즈로 재투영 (motion-to-photon ↓) |
| ATW / ASW | Asynchronous Time Warp / SpaceWarp | 컴포지터가 누락 프레임을 보간 |
| Foveated Rendering | FFR / ETFR | 시야 중심부만 고해상도 렌더링 |
| Multiview | — | 두 눈 동시 렌더링 (single pass stereo) |
| Monado | — | Collabora 주도 오픈소스 OpenXR 런타임. Android XR/Pico/CloudXR/Snapdragon Spaces 기반 |
| WebXR | — | W3C JavaScript API. 브라우저가 내부적으로 OpenXR을 호출 (Chromium) |
5. 3계층 아키텍처
5.1 전체 구조
┌──────────────────────────────────────────────────┐
│ Application (앱 / 게임 엔진) │
└──────────┬───────────────────────────────────────┘
│ OpenXR C API
▼
┌──────────────────────────────────────────────────┐
│ OpenXR Loader (Khronos) │
│ - 앱과 함께 배포 (또는 시스템 설치) │
│ - Trampoline functions: 앱 → 첫 레이어 라우팅 │
│ - Per-instance dispatch table │
└──────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ API Layer Chain (선택적, 0개 이상) │
│ XR_APILAYER_LUNARG_core_validation │
│ XR_APILAYER_LUNARG_api_dump │
│ XR_APILAYER_LUNARG_best_practices │
└──────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ Runtime Terminator (마지막 레이어) │
└──────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ XR Runtime (벤더별) │
│ Meta Quest / SteamVR / Monado / WMR / │
│ Pico / Varjo / Magic Leap / Android XR / ... │
└──────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ Hardware Driver / OS / HMD │
└──────────────────────────────────────────────────┘
5.2 Active Runtime 선택 메커니즘
Windows
HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenXR\1
ActiveRuntime = "C:\...\oculus_openxr_64.json"
HKLM\...\AvailableRuntimes
oculus_openxr.json = 0
steamxr.json = 0
Linux (XDG)
$XDG_CONFIG_HOME/openxr/1/active_runtime.json
/etc/xdg/openxr/1/active_runtime.json ← 배포판 기본
Android
<!-- AndroidManifest.xml: 런타임 패키지 발견을 위한 Intent Category -->
<category android:name="org.khronos.openxr.intent.category.IMMERSIVE_HMD"/>
ContentProvider 조회: org.khronos.openxr.runtime_broker(사용자 설치) → org.khronos.openxr.system_runtime_broker(시스템). 폴백: /vendor/etc/openxr/1/active_runtime.json.
환경변수 오버라이드 (전 플랫폼)
export XR_RUNTIME_JSON=/path/to/runtime/manifest.json
5.3 Runtime Manifest 예시
{
"file_format_version": "1.0.0",
"runtime": {
"name": "Monado",
"library_path": "/usr/lib/libopenxr_monado.so",
"functions": {
"xrCreateInstance": "monado_xrCreateInstance"
},
"instance_extensions": [
{"name": "XR_KHR_vulkan_enable2", "extension_version": 2}
]
}
}
5.4 API Layer Manifest
{
"file_format_version": "1.0.0",
"api_layer": {
"name": "XR_APILAYER_LUNARG_core_validation",
"library_path": "./XrApiLayer_core_validation.so",
"api_version": "1.0",
"implementation_version": "1",
"enable_environment": "XR_ENABLE_CORE_VALIDATION",
"disable_environment": "XR_DISABLE_CORE_VALIDATION"
}
}
활성화:
export XR_ENABLE_API_LAYERS=XR_APILAYER_LUNARG_core_validation:XR_APILAYER_LUNARG_api_dump
6. 객체 모델 (Object Handles)
XrInstance ← 런타임 연결
└── XrSession ← 그래픽스 바인딩 세션
├── XrSwapchain[] ← 그래픽스 API 텍스처 배열
├── XrSpace[] ← 좌표계
│ ├── Reference Space (VIEW/LOCAL/STAGE/LOCAL_FLOOR/UNBOUNDED)
│ └── Action Space (컨트롤러 포즈 등)
├── XrActionSet[]
│ └── XrAction[] (boolean/float/vector2f/pose/vibration)
└── XrSpatialAnchor[] ← 확장
6.1 XrInstance 생성
XrApplicationInfo appInfo = {
.applicationName = "MyXRApp",
.applicationVersion = 1,
.engineName = "MyEngine",
.engineVersion = 1,
.apiVersion = XR_CURRENT_API_VERSION
};
const char* extensions[] = {
"XR_KHR_vulkan_enable2",
"XR_EXT_hand_tracking",
};
XrInstanceCreateInfo ci = {
.type = XR_TYPE_INSTANCE_CREATE_INFO,
.applicationInfo = appInfo,
.enabledExtensionCount = 2,
.enabledExtensionNames = extensions
};
XrInstance instance;
xrCreateInstance(&ci, &instance);
XrSystemGetInfo sysInfo = {
.type = XR_TYPE_SYSTEM_GET_INFO,
.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY
};
XrSystemId systemId;
xrGetSystem(instance, &sysInfo, &systemId);
6.2 XrSession 생성 (Graphics Binding 필수)
XrGraphicsBindingVulkan2KHR vkBinding = {
.type = XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR,
.instance = vkInstance,
.physicalDevice = vkPhysicalDevice, // 반드시 런타임 권장 디바이스
.device = vkDevice,
.queueFamilyIndex = graphicsQueueFamily,
.queueIndex = 0
};
XrSessionCreateInfo sessionCI = {
.type = XR_TYPE_SESSION_CREATE_INFO,
.next = &vkBinding, // ★ next chain으로 그래픽스 바인딩
.systemId = systemId
};
XrSession session;
xrCreateSession(instance, &sessionCI, &session);
런타임 권장 GPU 디바이스 획득:
PFN_xrGetVulkanGraphicsDevice2KHR xrGetVulkanGraphicsDevice2KHR;
xrGetInstanceProcAddr(instance, "xrGetVulkanGraphicsDevice2KHR",
(PFN_xrVoidFunction*)&xrGetVulkanGraphicsDevice2KHR);
XrVulkanGraphicsDeviceGetInfoKHR info = {
.type = XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR,
.systemId = systemId,
.vulkanInstance = vkInstance
};
VkPhysicalDevice physDevice;
xrGetVulkanGraphicsDevice2KHR(instance, &info, &physDevice);
// 반드시 이 physical device로 VkDevice 생성
6.3 Graphics Binding 종류
| API | 바인딩 구조체 | Extension |
|---|---|---|
| Vulkan | XrGraphicsBindingVulkan2KHR |
XR_KHR_vulkan_enable2 |
| OpenGL (Win32) | XrGraphicsBindingOpenGLWin32KHR |
XR_KHR_opengl_enable |
| OpenGL ES (Android) | XrGraphicsBindingOpenGLESAndroidKHR |
XR_KHR_opengl_es_enable |
| Direct3D 11 | XrGraphicsBindingD3D11KHR |
XR_KHR_D3D11_enable |
| Direct3D 12 | XrGraphicsBindingD3D12KHR |
XR_KHR_D3D12_enable |
| Metal | XrGraphicsBindingMetalKHR |
(Apple 미참여로 Khronos 공식 미존재) |
클립 공간 주의: Vulkan = +Y down, near=0, far=1; D3D11/D3D12 = +Y up, near=0, far=1.
7. Session State Machine (가장 중요)
xrCreateSession()
│
▼
┌──────────────┐
│ UNKNOWN │
└──────┬───────┘
│ runtime ready
▼
┌──→ ┌─────────┐ ←─ xrEndSession 완료
│ │ IDLE │ (재진입)
│ └────┬────┘
│ │ 런타임이 RUNTIME_READY 알림
│ ▼
│ ┌─────────┐
│ │ READY │ ★ xrBeginSession() 호출 시점
│ └────┬────┘
│ │ xrBeginSession()
│ ▼
│ ┌────────────────┐
│ │ SYNCHRONIZED │ shouldRender=false 가능
│ └────┬───────────┘
│ │ 가시화
│ ▼
│ ┌─────────┐
│ │ VISIBLE │ 렌더링은 하나 입력은 받지 않음
│ └────┬────┘
│ │ 포커스 획득
│ ▼
│ ┌─────────┐
│ │ FOCUSED │ ★ 정상 게임 루프 (입력 + 렌더)
│ └────┬────┘
│ │ 런타임이 종료 요청
│ ▼
│ ┌──────────┐
└─────│ STOPPING │ ★ 즉시 xrEndSession() 호출
└──┬───────┘
│ 손실 / 재시작
▼
┌────────────────┐ ┌─────────┐
│ LOSS_PENDING │ → │ EXITING │ → 종료
└────────────────┘ └─────────┘
7.1 상태별 의미
| 상태 | 앱이 해야 할 일 |
|---|---|
IDLE |
CPU/GPU 최소화. 이벤트 폴링 계속 |
READY |
즉시 xrBeginSession() 호출 |
SYNCHRONIZED |
프레임 루프 실행. shouldRender=false면 렌더 생략 가능 |
VISIBLE |
렌더링 + 레이어 제출. 입력 처리 X |
FOCUSED |
정상 게임 루프: 렌더 + xrSyncActions + 입력 |
STOPPING |
즉시 xrEndSession() 호출. 프레임 루프 중단 |
LOSS_PENDING |
xrDestroySession() 후 선택적 재생성 |
EXITING |
프로세스 종료 또는 비-XR UI 전환 |
7.2 이벤트 폴링 패턴
void PollEvents(XrInstance instance, XrSession session, bool *running, bool *quit) {
XrEventDataBuffer event = {.type = XR_TYPE_EVENT_DATA_BUFFER};
while (xrPollEvent(instance, &event) == XR_SUCCESS) {
switch (event.type) {
case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
XrEventDataSessionStateChanged *e = (void*)&event;
switch (e->state) {
case XR_SESSION_STATE_READY: {
XrSessionBeginInfo bi = {
.type = XR_TYPE_SESSION_BEGIN_INFO,
.primaryViewConfigurationType =
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO
};
xrBeginSession(session, &bi);
*running = true;
break;
}
case XR_SESSION_STATE_STOPPING:
xrEndSession(session); *running = false; break;
case XR_SESSION_STATE_LOSS_PENDING:
case XR_SESSION_STATE_EXITING:
*quit = true; break;
}
break;
}
case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING:
*quit = true; break;
case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING:
// 재센터 → 새 기준 적용 시점 이후 xrLocateSpace 결과 변화
break;
case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED:
// 컨트롤러 교체. xrGetCurrentInteractionProfile() 재호출
break;
}
event.type = XR_TYPE_EVENT_DATA_BUFFER;
}
}
제약: xrBeginSession은 READY에서만, xrEndSession은 STOPPING에서만 호출 가능. 다른 상태에서 호출 시 에러.
8. Frame Loop
8.1 왜 Predicted Display Time이 핵심인가
머리 움직임 → 센서(~1ms) → CPU 게임루프(~3-5ms) → GPU 렌더(~8-10ms)
→ 컴포지터 합성(~2ms) → 디스플레이 스캔아웃(~1ms) → 포톤
│
motion-to-photon ←──┘
90Hz = 11.1ms 예산. 20ms 초과 시 멀미. 따라서 현재 헤드 포즈가 아닌 디스플레이 시점의 예측 포즈로 렌더해야 한다.
8.2 표준 프레임 루프
while (running) {
// 1) 이벤트
PollEvents(instance, session, &running, &quit);
// 2) 프레임 동기화
XrFrameState fs = {.type = XR_TYPE_FRAME_STATE};
xrWaitFrame(session, NULL, &fs);
// fs.predictedDisplayTime, fs.predictedDisplayPeriod, fs.shouldRender
// 3) 프레임 시작
xrBeginFrame(session, NULL);
XrCompositionLayerBaseHeader *layers[2] = {0};
uint32_t layerCount = 0;
if (fs.shouldRender) {
// 4) 뷰 포즈 — 반드시 predictedDisplayTime 사용
XrViewLocateInfo vli = {
.type = XR_TYPE_VIEW_LOCATE_INFO,
.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
.displayTime = fs.predictedDisplayTime, // ★
.space = appSpace
};
XrView views[2];
XrViewState vs = {.type = XR_TYPE_VIEW_STATE};
uint32_t vc = 2;
xrLocateViews(session, &vli, &vs, vc, &vc, views);
// 5) Swapchain 획득 → 렌더 → 해제
for (int eye = 0; eye < 2; eye++) {
uint32_t imgIdx;
xrAcquireSwapchainImage(swapchains[eye], NULL, &imgIdx);
XrSwapchainImageWaitInfo wi = {
.type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO,
.timeout = XR_INFINITE_DURATION
};
xrWaitSwapchainImage(swapchains[eye], &wi);
RenderEyeView(eye, imgIdx, views[eye], fs.predictedDisplayTime);
xrReleaseSwapchainImage(swapchains[eye], NULL);
}
// 6) Composition Layer 구성
XrCompositionLayerProjectionView projViews[2] = {
{.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW,
.pose = views[0].pose, // ★ 렌더 시 사용한 포즈와 동일
.fov = views[0].fov,
.subImage = {swapchains[0], {{0,0},{w,h}}, 0}},
{ /* right eye */ }
};
static XrCompositionLayerProjection projLayer = {
.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION,
.space = appSpace, // ★ world-locked space (LOCAL/STAGE)
.viewCount = 2,
.views = projViews
};
layers[layerCount++] = (XrCompositionLayerBaseHeader*)&projLayer;
}
// 7) 프레임 제출
XrFrameEndInfo fei = {
.type = XR_TYPE_FRAME_END_INFO,
.displayTime = fs.predictedDisplayTime, // ★ 동일 값
.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE,
.layerCount = layerCount,
.layers = layers
};
xrEndFrame(session, &fei);
}
핵심 규칙:
xrWaitFrame이 반환한predictedDisplayTime을xrLocateViews,xrEndFrame에 동일하게 사용xrLocateViews로 받은 pose를 그대로XrCompositionLayerProjectionView.pose에 전달XrCompositionLayerProjection.space는 반드시 world-locked (LOCAL/STAGE 등). VIEW를 사용하면 ATW가 망가짐
8.3 Late Latching / ATW / ASW
Late Latching (Meta): GPU 커맨드 버퍼 큐에 쌓인 후, GPU 실행 직전 최신 포즈로 원자적으로 latch. ~15ms 지연 감소. Phase Sync(앱 빠를 때 효과)와 상보적.
ATW (Asynchronous Time Warp): 컴포지터가 디스플레이 직전 최신 헤드 트래킹으로 이미지를 재투영(reproject) → 누락 프레임도 보간.
ASW (Application SpaceWarp, XR_FB_space_warp): 앱이 36 FPS로 렌더, 컴포지터가 motion vector + depth buffer로 중간 프레임 합성하여 72 FPS 표시. GPU 최대 70% 절감. Vulkan + Multiview 필수, Unity URP/HDRP 필요.
8.4 Composition Layer 종류
| 레이어 | 용도 |
|---|---|
Projection |
표준 스테레오 3D 렌더링. 거의 모든 앱의 메인 |
Quad |
3D 공간의 2D 사각형. UI/HUD/자막에 최적 (단안 렌더로 샤프) |
Cylinder |
원통 곡면 UI |
Equirect |
360° 등장방형 (스카이박스, 360 비디오) |
Cube |
큐브맵 (스카이박스). XR_KHR_composition_layer_cube |
핵심 장점: 각 레이어가 독립 해상도 → 3D 씬 해상도 낮추고 UI Quad는 고해상도 유지 가능.
9. Reference Spaces
VIEW 원점: 헤드(좌우 눈 중점), 헤드와 함께 이동
용도: 헬멧 HUD
★ Projection layer에 사용 금지 (ATW 파괴)
LOCAL 원점: 앱 시작 시 헤드 위치 (Y축 중력 기준 수직)
재센터 시 수평만 리셋, Y 유지. 바닥 정보 없음
용도: 착석형
STAGE 원점: 물리적 바닥 중앙 (Y=0이 바닥)
사전 룸스케일 캘리브레이션 필요
xrGetReferenceSpaceBoundsRect()로 플레이 영역 조회
용도: 룸스케일
LOCAL_FLOOR LOCAL + 추정 바닥 높이 (캘리브레이션 없이 사용 가능)
OpenXR 1.1 코어 (이전: XR_EXT_local_floor)
UNBOUNDED STAGE 범위 초과 (건물/야외). 원점 표류 가능
AR/MR 세계 규모. XR_MSFT_unbounded / XR_ANDROID_unbounded 등
9.1 공간 조회
XrSpaceLocation loc = {.type = XR_TYPE_SPACE_LOCATION};
xrLocateSpace(leftHandSpace, // 조회 대상
localSpace, // 기준
fs.predictedDisplayTime,
&loc);
if ((loc.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) &&
(loc.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)) {
// loc.pose.position, loc.pose.orientation 사용
}
좌표계: Right-handed, +Y up, -Z forward. 쿼터니언은 (x, y, z, w) 순서.
10. Action System
10.1 왜 직접 버튼 폴링이 아닌가
기존 SDK는 GetButtonState(OCULUS_A_BUTTON) 같은 하드웨어 직접 폴링이었다. 두 가지 문제:
- 하드웨어 종속: 컨트롤러 바뀌면 코드 수정
- 리바인딩 불가: 사용자가 버튼 배치 변경 못 함
OpenXR Action System은 앱이 의미적 액션(grab, teleport)을 정의하고, 런타임이 하드웨어 매핑을 관리한다.
10.2 전체 흐름
[앱 시작 시 1회]
xrCreateActionSet() → "gameplay"
xrCreateAction() → "grab" / "teleport" / "menu" / "palm-pose"
xrSuggestInteractionProfileBindings() per profile
/interaction_profiles/oculus/touch_controller
/interaction_profiles/valve/index_controller
/interaction_profiles/khr/simple_controller ← fallback
xrAttachSessionActionSets()
[매 프레임]
xrSyncActions()
xrGetActionStateBoolean / Float / Vector2f / Pose
10.3 핵심 구현
// ActionSet
XrActionSetCreateInfo asci = {
.type = XR_TYPE_ACTION_SET_CREATE_INFO,
.priority = 0
};
strcpy(asci.actionSetName, "gameplay");
strcpy(asci.localizedActionSetName, "Gameplay");
XrActionSet gameplay;
xrCreateActionSet(instance, &asci, &gameplay);
// Subaction paths (좌/우 손 구분)
XrPath handPaths[2];
xrStringToPath(instance, "/user/hand/left", &handPaths[0]);
xrStringToPath(instance, "/user/hand/right", &handPaths[1]);
// Action: float "grab"
XrActionCreateInfo aci = {
.type = XR_TYPE_ACTION_CREATE_INFO,
.actionType = XR_ACTION_TYPE_FLOAT_INPUT,
.countSubactionPaths = 2,
.subactionPaths = handPaths
};
strcpy(aci.actionName, "grab-object");
strcpy(aci.localizedActionName, "Grab Object");
XrAction grabAction;
xrCreateAction(gameplay, &aci, &grabAction);
// Suggested Bindings
XrPath profile;
xrStringToPath(instance,
"/interaction_profiles/khr/simple_controller", &profile);
XrPath grabL, grabR;
xrStringToPath(instance, "/user/hand/left/input/select/click", &grabL);
xrStringToPath(instance, "/user/hand/right/input/select/click", &grabR);
XrActionSuggestedBinding bindings[] = {
{grabAction, grabL}, {grabAction, grabR},
};
XrInteractionProfileSuggestedBinding sb = {
.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING,
.interactionProfile = profile,
.countSuggestedBindings = 2,
.suggestedBindings = bindings
};
xrSuggestInteractionProfileBindings(instance, &sb);
// Attach (세션당 1회)
XrSessionActionSetsAttachInfo ai = {
.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO,
.countActionSets = 1,
.actionSets = &gameplay
};
xrAttachSessionActionSets(session, &ai);
// 매 프레임
XrActiveActionSet active = {.actionSet = gameplay, .subactionPath = XR_NULL_PATH};
XrActionsSyncInfo si = {
.type = XR_TYPE_ACTIONS_SYNC_INFO,
.countActiveActionSets = 1,
.activeActionSets = &active
};
xrSyncActions(session, &si);
XrActionStateGetInfo gi = {
.type = XR_TYPE_ACTION_STATE_GET_INFO,
.action = grabAction,
.subactionPath = handPaths[0]
};
XrActionStateFloat st = {.type = XR_TYPE_ACTION_STATE_FLOAT};
xrGetActionStateFloat(session, &gi, &st);
if (st.isActive && st.currentState > 0.5f) {
// 왼손 잡기
}
10.4 Interaction Profile 경로 구조
/interaction_profiles/<vendor>/<device>
/user/hand/<left|right>/input/<component>/<type>
/output/haptic
/interaction_profiles/khr/simple_controller (fallback 표준)
/interaction_profiles/oculus/touch_controller (Quest)
/interaction_profiles/valve/index_controller (Index)
/interaction_profiles/microsoft/motion_controller (WMR)
/interaction_profiles/htc/vive_controller (Vive)
/interaction_profiles/ext/eye_gaze_interaction (시선)
OpenXR 1.1에서 13개 추가 인터랙션 프로파일이 코어로 편입.
11. Extension 메커니즘
11.1 네이밍 컨벤션
| 접두사 | 의미 |
|---|---|
XR_KHR_* |
Khronos 비준 (코어와 동일 라이선스) |
XR_EXT_* |
멀티벤더 합의 |
XR_FB_* / XR_META_* |
Meta (Facebook) |
XR_MSFT_* |
Microsoft |
XR_VALVE_* |
Valve |
XR_HTC_* |
HTC |
XR_BD_* |
ByteDance (Pico) |
XR_ML_* |
Magic Leap |
XR_VARJO_* |
Varjo |
XR_NV_* |
NVIDIA |
XR_ULTRALEAP_* |
Ultraleap |
XR_ANDROID_* |
Android XR |
11.2 Extension 활성화 패턴
// 1) 사용 가능 확장 열거 (two-call idiom)
uint32_t extCount;
xrEnumerateInstanceExtensionProperties(NULL, 0, &extCount, NULL);
XrExtensionProperties *props = malloc(extCount * sizeof(*props));
for (uint32_t i = 0; i < extCount; i++)
props[i].type = XR_TYPE_EXTENSION_PROPERTIES;
xrEnumerateInstanceExtensionProperties(NULL, extCount, &extCount, props);
// 2) 지원 여부 확인
bool hasHandTracking = false;
for (uint32_t i = 0; i < extCount; i++)
if (strcmp(props[i].extensionName, XR_EXT_HAND_TRACKING_EXTENSION_NAME) == 0)
hasHandTracking = true;
// 3) xrCreateInstance에서 활성화
const char* exts[] = {"XR_KHR_vulkan_enable2", "XR_EXT_hand_tracking"};
ci.enabledExtensionCount = 2;
ci.enabledExtensionNames = exts;
11.3 주요 Extension
| Extension | 용도 |
|---|---|
XR_KHR_vulkan_enable2 |
Vulkan 그래픽스 바인딩 |
XR_KHR_D3D12_enable |
D3D12 바인딩 |
XR_EXT_hand_tracking |
26 관절 손 추적 |
XR_EXT_eye_gaze_interaction |
시선 추적 (인터랙션 용도) |
XR_FB_passthrough |
Meta Quest 패스스루 카메라 |
XR_FB_spatial_entity |
Meta 공간 앵커 |
XR_FB_body_tracking |
Meta 전신 추적 |
XR_FB_face_tracking2 |
Meta 안면 추적 |
XR_FB_foveation / XR_FB_foveation_configuration |
FFR |
XR_FB_space_warp |
Application SpaceWarp |
XR_MSFT_spatial_anchor |
Microsoft 공간 앵커 |
XR_MSFT_scene_understanding |
HoloLens 환경 메시 |
XR_MSFT_unbounded_reference_space |
무제한 공간 |
XR_KHR_composition_layer_depth |
Depth 레이어 (재투영 향상) |
XR_EXT_performance_settings |
성능 레벨 힌트 |
XR_HTC_facial_tracking |
HTC 표정 추적 |
11.4 Extension 함수 동적 로드
PFN_xrCreateHandTrackerEXT xrCreateHandTrackerEXT = NULL;
xrGetInstanceProcAddr(instance, "xrCreateHandTrackerEXT",
(PFN_xrVoidFunction*)&xrCreateHandTrackerEXT);
XrHandTrackerCreateInfoEXT htci = {
.type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT,
.hand = XR_HAND_LEFT_EXT,
.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT
};
XrHandTrackerEXT handTracker;
xrCreateHandTrackerEXT(session, &htci, &handTracker);
12. OpenXR 런타임 생태계
12.1 Conformant Runtime 목록 (2026.04 기준)
| 벤더 | 기기 / 런타임 | OS | OpenXR 버전 |
|---|---|---|---|
| Meta | Quest 1/2/3/3S/Pro (Horizon OS) | Android | 1.1 |
| Meta | Oculus PC (Rift S, Quest Link) | Windows | 1.0+ |
| Valve | SteamVR | Windows / Linux | 1.0 |
| Microsoft | HoloLens 2 + WMR | Windows | 1.0 |
| ByteDance (Pico) | Pico 4 / 4 Ultra / Neo 3 | Android | 1.1 (Ultra) |
| Galaxy XR / Android XR | Android XR | 1.1 | |
| Varjo | XR-3 / XR-4 / Aero | Windows (PC tethered) | 1.0 |
| HTC | VIVE Focus 3 / XR Elite | Android / Windows | 1.0 |
| Magic Leap | Magic Leap 2 | Android (ML OS) | 1.0 |
| Collabora | Monado | Linux / Android | 1.0 |
| NVIDIA | CloudXR | Linux | 1.1 |
| Sony | ELF-SR1/SR2 (Spatial Reality) | Windows | 1.0 |
| Canon | MREAL | Windows | 1.0 |
| Lynx | R-1 / R-2 | Lynx OS (Android 포크) | — |
| Snap | Spectacles | Snap OS | WebXR만 지원 |
12.2 Apple의 부재
Apple Vision Pro (2024.02):
├── visionOS (iPadOS 파생)
├── RealityKit (Swift 씬 그래프)
├── CompositorServices (Metal 직접)
├── ARKit (공간 인식)
└── 컨트롤러 없음 (눈+손+음성)
OpenXR 미참여:
- Khronos Working Group 멤버 아님
- 컨트롤러 미지원 → OpenXR Interaction Profile 모델과 불일치
- 수직통합 차별화 전략 (OpenGL→Metal과 동일)
WebXR은 Safari에서 지원 (OpenXR 미경유, 자체 변환)
Unity/Unreal은 별도 PolySpatial 등 컴패티빌리티 레이어로 지원
12.3 Monado — 오픈소스 OpenXR 런타임의 부상
Collabora 주도 오픈소스 구현체. 2024년 이후 산업 채택 폭증:
- Google Android XR (2024.12) — Monado 기반
- Pico (Neo3, Pico 4) — Monado 활용
- Qualcomm Snapdragon Spaces — Monado 기반
- NVIDIA CloudXR (2025.06 Conformant) — Monado 기반 Linux 런타임
- Hololight Stream, PortalVR 등
“Mesa state-tracker 철학 계승: API 구현과 하드웨어 추상화를 명확히 분리”
오픈소스 OpenXR 구현체로서 신규 런타임 구축의 사실상 표준 플랫폼이 됨.
13. 게임 엔진 통합
13.1 Unity
[Unity App C#]
↓
[XR Interaction Toolkit (XRI)] ← com.unity.xr.interaction.toolkit
↓
[Unity OpenXR Plugin] ← com.unity.xr.openxr (공식 권장)
또는
[Meta OpenXR] ← com.unity.xr.meta-openxr (Meta 확장)
↓
[XR Plugin Management] ← com.unity.xr.management
↓
[Runtime: Meta / SteamVR / Pico / ...]
| 패키지 | 용도 |
|---|---|
com.unity.xr.openxr |
OpenXR 표준 경로 (모든 신규 권장) |
com.unity.xr.meta-openxr |
Meta 확장 (Passthrough, Body Tracking, Spatial Anchors) |
com.unity.xr.interaction.toolkit v3+ |
입력/인터랙션 추상화 |
com.unity.xr.hands |
손 추적 |
com.unity.xr.oculus |
Deprecated (OVRPlugin 기반 레거시) |
visionOS는 별도: Unity PolySpatial — RealityKit 호출, OpenXR 미경유.
13.2 Unreal Engine
| 단계 | 플러그인 |
|---|---|
| 레거시 | OculusVR Plugin (OVRPlugin 자체 구현, deprecated) |
| 현재 권장 | Epic OpenXR Plugin (UE 4.27+, UE5 기본) + Meta XR Plugin |
| 추가 | OpenXRMsftHandInteraction, OpenXRHandTracking (HoloLens) |
13.3 Godot 4
- OpenXR 코어 내장 (Godot 4.0, 2023). 별도 플러그인 불필요
godot_openxr_vendors플러그인 — Meta/Pico/HTC 전용 확장 래퍼- Meta가 2024년 Godot 기여자 직접 자금 지원 시작 → 4.3에서 Quest 최적화 다수 추가
- WebXR 별도 지원
13.4 기타
| 프레임워크 | 특징 |
|---|---|
| StereoKit | Microsoft 후원 C# XR. OpenXR 위 고수준 추상화. 코드 중심 빠른 프로토타입 |
| BabylonJS / Three.js | W3C WebXR API (브라우저). Chromium이 내부적으로 OpenXR 호출 |
| Bevy XR | Rust. 커뮤니티 bevy_mod_openxr (공식 미내장) |
14. WebXR vs OpenXR
| 항목 | WebXR | OpenXR |
|---|---|---|
| 표준 기관 | W3C | Khronos |
| 언어 | JavaScript | C/C++ |
| 환경 | 브라우저 | Native / 게임 엔진 |
| 배포 | URL (설치 X) | APK / 설치형 |
| 성능 | 브라우저 오버헤드 | Native |
| 기능 범위 | 서브셋 | 풀 사양 |
Chromium의 WebXR → OpenXR 변환: device/vr/openxr/ 모듈이 WebXR 세션 요청을 OpenXR API로 변환. Chrome/Edge가 Windows에서 WebXR 지원하는 방식이 이것.
visionOS Safari: Apple 자체 XR API → WebXR (OpenXR 미경유).
15. 대안 비교 (Alternatives)
15.1 OpenXR vs OpenVR (Valve)
| 항목 | OpenVR | OpenXR |
|---|---|---|
| 표준 주체 | Valve 단독 | Khronos 컨소시엄 |
| 대상 | VR 전용 | VR + AR + MR |
| 런타임 | SteamVR 단일 종속 | 로더 + 다중 런타임 |
| 현황 | 유지보수 모드 | 활성, 연간 릴리스 |
| Unity | 공식 지원 종료 | XR Plugin |
OpenComposite / xrizer: OpenVR 호출을 OpenXR로 포워딩 → SteamVR 없이 OpenXR 런타임 직접 실행.
15.2 OpenXR vs Oculus PC/Mobile SDK
| 항목 | Oculus SDK | OpenXR + Meta Extensions |
|---|---|---|
| 포터빌리티 | Quest만 | Quest + Pico + SteamVR + … |
| 신규 기능 | 종료 | Meta Extensions로 |
| Unity 패키지 | com.unity.xr.oculus (legacy) |
com.unity.xr.openxr + com.unity.xr.meta-openxr |
| 에코시스템 | 통합 | OpenXR 코어 + Meta XR Core SDK |
실무 패턴: “OpenXR for portable XR core, vendor SDK for ecosystem” — 이식 가능한 코어는 OpenXR, Meta Avatars/Voice/Presence Platform 같은 에코시스템은 Meta XR SDK.
15.3 OpenXR vs ARKit / ARCore
| 항목 | ARKit / ARCore | OpenXR |
|---|---|---|
| 폼팩터 | 폰 (단일 카메라) | HMD (스테레오 6DoF) |
| 추적 | 디바이스 카메라 | 세계 고정 Reference Space |
| AR vs VR | Phone AR | 풀 VR + AR + MR |
15.4 OpenXR vs 그래픽스 API
핵심: OpenXR은 그래픽스 API가 아니다. XR 런타임 API. 렌더링은 Vulkan/D3D/GL과 페어링하여 사용.
┌─────────────────────────────────────────────┐
│ Application │
├──────────────────┬──────────────────────────┤
│ OpenXR API │ Graphics API │
│ (세션/추적/입력) │ (Vulkan/D3D12/...) │
├──────────────────┴──────────────────────────┤
│ Graphics Binding Extension │
│ XrGraphicsBindingVulkanKHR / D3D12KHR ... │
└─────────────────────────────────────────────┘
16. 상황별 최적 선택
| 시나리오 | 권장 |
|---|---|
| PC + Quest + Pico 크로스플랫폼 | OpenXR (단일 코드베이스) |
| Quest 전용 + 에코시스템 (아바타/소셜) | OpenXR + Meta XR SDK |
| iOS / visionOS | ARKit + RealityKit (OpenXR 미지원) |
| Web 배포 XR | WebXR |
| Android 폰 AR | ARCore |
| Linux 오픈소스 | Monado + OpenXR |
| C# 빠른 MR 프로토타입 | StereoKit |
| HoloLens 2 | OpenXR (MSFT extensions) |
| Android XR (Google, 2026~) | OpenXR |
17. 성능 최적화 베스트 프랙티스
17.1 프레임 예산
| Hz | 예산 | M2P 목표 |
|---|---|---|
| 72 | 13.9ms | < 20ms |
| 90 | 11.1ms | < 20ms |
| 120 | 8.3ms | < 20ms |
17.2 Foveated Rendering
| 종류 | Extension | 설명 |
|---|---|---|
| FFR (Fixed) | XR_FB_foveation, XR_VARJO_foveated_rendering |
화면 중앙 고해상도 / 주변부 저해상도 |
| ETFR (Eye-Tracked) | OpenXR 1.1 코어 (Quad Views, 이전 XR_VARJO_quad_views) |
시선 따라 fovea 이동. Microsoft Flight Simulator 2024 등 |
씬 전환 시점에 FFR 레벨 변경 (씬 중간 변경은 노티스어블). 고대비 텍스트는 프레임 중앙 배치.
// Unity
XRDisplaySubsystem displaySubsystem = ...;
displaySubsystem.SetFoveatedRenderingLevel(FoveatedRenderingLevel.Medium);
17.3 Multiview (Single Pass Stereo)
[2-pass (비효율)] [Multiview (권장)]
DrawCall × N → Left Eye DrawCall × N → 두 눈 동시
DrawCall × N → Right Eye gl_ViewIndex / SV_ViewID로 구분
RenderPass 2개, CPU 2배 RenderPass 1개, CPU 절반
Vulkan: arraySize = 2 스왑체인 + VkRenderPassMultiviewCreateInfo viewMask = 0b11.
17.4 렌더 파이프라인
| 항목 | 권장 |
|---|---|
| 렌더링 | Forward (Deferred는 G-Buffer가 모바일 타일 GPU 대역폭 폭증) |
| MSAA | 4x 베이스라인 (VR aliasing이 멀미 악화) |
| Swapchain Format | xrEnumerateSwapchainFormats 첫 번째 (런타임 선호) |
| UI | Quad Layer 분리 (샤프니스 + 씬 해상도 분리) |
HoloLens 2 예외: Quad Layer 추가 시 post-processing 페널티 → Projection만 사용 권장.
17.5 Application SpaceWarp (XR_FB_space_warp)
앱 36 FPS 렌더 → 컴포지터가 motion vector + depth로 중간 프레임 합성 → 72 FPS 표시
GPU 최대 70% 절감
요구사항: Vulkan + Multiview, Unity URP/HDRP, motion vector + depth buffer 추가 제출.
18. 함정 / 안티패턴
18.1 Predicted Display Time 오용
// WRONG
XrTime now = getCurrentTimeNs();
xrLocateViews(..., now, ...); // 세계가 "수영"
// CORRECT
xrLocateViews(..., fs.predictedDisplayTime, ...);
xrEndFrame(.../* fei.displayTime = */ fs.predictedDisplayTime);
18.2 xrWaitFrame / xrBeginFrame 짝 무결성
// WRONG: xrBeginFrame 누락 → 다음 xrWaitFrame 무한 데드락
for (;;) {
xrWaitFrame(...);
// skip xrBeginFrame ← 절대 금지
render(); xrEndFrame(...);
}
18.3 xrPollEvent 스킵
이벤트 폴링 누락 → STOPPING/LOSS_PENDING 미처리 → 앱 정지/크래시.
18.4 Action Set 미동기화
// 매 프레임 xrSyncActions() 호출 없으면 xrGetActionState* 항상 false
xrSyncActions(session, &si); // ★ 필수
xrGetActionStateBoolean(session, &gi, &state);
18.5 Swapchain Acquire-Wait-Release 순서
xrAcquireSwapchainImage(...); // 인덱스 획득
xrWaitSwapchainImage(...); // 컴포지터 읽기 완료 대기
// ★ 이 사이에서만 렌더링
xrReleaseSwapchainImage(...);
// PITFALL 1: Wait 없이 렌더 → 컴포지터 읽는 중 이미지 덮어쓰기
// PITFALL 2: Release 없이 다시 Acquire → XR_ERROR_CALL_ORDER_INVALID
18.6 미지원 Extension 요청
// xrCreateInstance에서 미지원 extension 요청 시 INSTANCE_CREATE 실패
// → 반드시 xrEnumerateInstanceExtensionProperties로 사전 검사
18.7 좌표계 / 쿼터니언 컨벤션
OpenXR Right-handed, +Y up, -Z forward, quat (x,y,z,w)
Unity Left-handed, +Y up, +Z forward, quat (x,y,z,w)
Unreal Left-handed, +Z up, +X forward
// Unity → OpenXR 변환 (Unity Plugin 내부)
openxrQuat.x = -unityQuat.x;
openxrQuat.y = -unityQuat.y;
openxrQuat.z = unityQuat.z;
openxrQuat.w = unityQuat.w;
18.8 Composition Layer Projection.space에 VIEW 사용 금지
VIEW(헤드 잠금) 공간을 projection layer space로 쓰면 ATW가 망가져 “swimming” 발생. 반드시 LOCAL/STAGE 같은 world-locked.
18.9 SESSION_LOSS_PENDING 미처리
case XR_SESSION_STATE_LOSS_PENDING:
xrDestroySession(session);
// 적절 주기로 xrGetSystem 폴링 → 시스템 재발견 시 새 세션
break;
18.10 Reference Space Recenter 미처리
case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING:
// 재센터 → 기존 holography 위치가 갑자기 이동한 것처럼 보임
// changeTime 이후 새 기준 적용. 필요 시 앵커 재설정
break;
18.11 멀티벤더 Extension 혼용
동일 기능이 XR_FB_*, XR_META_*, XR_VARJO_*로 중복 → KHR/Core 우선, 폴백으로 벤더 버전.
OpenXR 1.1에서 코어 편입:
XR_EXT_local_floor→LOCAL_FLOORXR_VARJO_quad_views→ Stereo FoveatedXR_EXT_palm_pose→ Grip SurfaceXR_KHR_locate_spaces→xrLocateSpaces
19. 마이그레이션 패턴
19.1 OpenVR → OpenXR
| 단계 | 작업 |
|---|---|
| 1 | SteamVR Plugin 제거, OpenXR SDK 추가 |
| 2 | VR_Init() → xrCreateInstance() + xrCreateSession() |
| 3 | WaitGetPoses() → xrWaitFrame() + xrLocateViews() |
| 4 | Submit(L,R) → xrEndFrame() + Projection layer |
| 5 | OpenVR Input → OpenXR Action System |
| 빠른 이행 | OpenComposite / xrizer (앱 무수정 OpenXR 런타임 실행) |
19.2 Oculus SDK → OpenXR (Unity)
// 구: Oculus XR Plugin
// OVRPlugin.GetNodeState() / OVRInput.Get()
// 신: Unity OpenXR Plugin
using UnityEngine.XR;
InputDevice head = InputDevices.GetDeviceAtXRNode(XRNode.Head);
head.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 pos);
// XRI 사용:
XRRayInteractor / XRDirectInteractor / XRSocketInteractor
19.3 Vendor → KHR/Core 프로모션
VENDOR (XR_FB_, XR_MSFT_, XR_NV_)
↓ 충분한 채택
EXT (XR_EXT_) — 멀티벤더 합의
↓ Khronos 비준
KHR / Core
벤더 extension 사용 시 KHR 버전 존재 여부 먼저 확인 후 폴백으로.
20. 검증 & 디버깅
20.1 API Layers
export XR_ENABLE_API_LAYERS=XR_APILAYER_LUNARG_core_validation:XR_APILAYER_LUNARG_api_dump
| Layer | 목적 |
|---|---|
XR_APILAYER_LUNARG_core_validation |
Valid Usage 위반 탐지, 디버그 콜백 |
XR_APILAYER_LUNARG_api_dump |
API 호출 + 파라미터 덤프 |
XR_APILAYER_LUNARG_best_practices |
성능 경고 (비효율 swapchain format 등) |
20.2 디버그 콜백
XrDebugUtilsMessengerCreateInfoEXT dci = {
.type = XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
.messageSeverities= XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
| XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT,
.messageTypes = XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
| XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
.userCallback = myDebugCallback
};
xrCreateDebugUtilsMessengerEXT(instance, &dci, &messenger);
20.3 로더 로깅
export XR_LOADER_DEBUG=all
20.4 GPU 캡처 도구
| 도구 | 지원 |
|---|---|
| RenderDoc | OpenXR + Vulkan 캡처 (외부 메모리 공유 일부 제한) |
| PIX for Windows | D3D12 OpenXR 캡처 |
| Meta ODT (Oculus Debug Tool) | Quest Link 시 퍼포먼스 오버레이, ASW 상태 |
| SteamVR Performance Tool | SteamVR 환경 |
21. 빅테크 전략
| 기업 | 입장 | 핵심 |
|---|---|---|
| Meta | 완전 채택 (2022 독자 API 폐기) | OpenXR + XR_FB_* 200+ 확장. Ron Bessems(Meta) WG 의장 |
| Microsoft | 초기 선도 (2020 Conformant) | HoloLens 2 + WMR 모두 OpenXR. Mesh 협업 통합 |
| Android XR (2024.12) | Monado 기반. Samsung Galaxy XR(Project Moohan) 첫 출시 | |
| Valve | OpenVR → OpenXR 전환 중 | SteamVR 기본은 OpenVR, 신규는 OpenXR 권장 |
| HTC | 채택 | Wave SDK 병행이나 OpenXR 권장 |
| Pico/ByteDance | 채택 (1.1, 2024) | Monado 기반, 북미 진출 |
| Sony PSVR2 | 부분 (PC 모드만) | PS5 콘솔은 독자 API. PC Steam 경유 OpenXR |
| Apple | 미참여 | RealityKit/visionOS 독자 |
| Qualcomm | Snapdragon Spaces 지원 | Monado 기반 AR 개발 |
| NVIDIA | CloudXR Linux Conformant (2025) | Monado 기반 스트리밍 |
21.1 Meta — “All In on OpenXR” (2021)
2021.07.23 v31부터 신규 기능 OpenXR Extension 전용
2021.08.31 Oculus Mobile/PC API → "Compatibility Support"
2022.08.31 ★ Oculus Mobile SDK + Oculus PC API 완전 종료 (Unsupported)
자기가 지배하는 플랫폼에서도 표준 API 강제 → 업계 표준화 사실상 완성. Meta가 OpenXR Working Group 창설(2016) 참여, 의장 직책 보유.
21.2 Google Android XR (2024.12)
2024.12.12 뉴욕 발표
첫 기기: Samsung Project Moohan (Galaxy XR)
SoC: Qualcomm Snapdragon XR2+ Gen 2
AI: Google Gemini 멀티모달 내장
OpenXR: ★ 1.1 완전 지원 (Khronos Conformant 2024.12.08)
도구: Android Studio, Jetpack Compose, Unity, OpenXR
런타임: Monado 기반
파트너: Samsung, Sony, XREAL (Lynx는 계약 파기)
Daydream(2019 종료) 이후 재도전 — 이번엔 OpenXR 표준 + Samsung·Qualcomm 협력으로 단편화 회피 시도.
21.3 OpenXR 1.1 (2024.04.15) 변경
| 코어 승격 | 출처 |
|---|---|
LOCAL_FLOOR reference space |
XR_EXT_local_floor |
| Stereo with Foveated Rendering | XR_VARJO_quad_views |
| Grip Surface (Palm Pose) | XR_EXT_palm_pose |
xrLocateSpaces 배치 함수 |
XR_KHR_locate_spaces |
XrUuid 타입 |
XR_EXT_uuid |
13개 인터랙션 프로파일 추가 (thumb_resting_surfaces, stylus, trigger_curl, trigger_slide 등). 연간 정기 릴리스 사이클 선언.
22. 엔터프라이즈 / 방위산업
22.1 OpenXR이 엔터프라이즈에 중요한 이유
| 문제 | 독자 SDK | OpenXR |
|---|---|---|
| 벤더 락인 | 종속 | 코드 유지하며 기기 교체 |
| 개발 비용 | 플랫폼별 재작성 | 단일 코드베이스 |
| 조달 경쟁 | 단일 공급사 | 다중 벤더 가격 경쟁 |
| 유지보수 | SDK 버전 종속 | 표준 기반 장기 유지 |
22.2 방위·훈련 사례
- 미 국방부(DoD): Mass Virtual의 미 공군 Virtual Hangar → DoD 전반 확산. 의회도서관 2025 보고: “XR 훈련 환경이 국방부 전 영역에 보편화”
- Varjo XR-4 Secure Edition: 핀란드(NATO) 제조, 라디오 모듈 제거, 오프라인 운용. I/ITSEC 2024 주요 전시
- HTC Vive: VIVE Focus + OpenXR 기반 군사·방위 훈련 솔루션 (IT2EC 2024)
22.3 산업 훈련
| 기업 | 적용 |
|---|---|
| Boeing | 항공기 조립·유지보수 훈련. “first-time quality 90% 향상” |
| Volkswagen | 제조 공정 훈련 (선도 기업 사례) |
| Walmart | 직원 온보딩, 위기 대응 훈련 |
| Delta Air Lines | 승무원 훈련 |
| Southern Company | 전력 인프라 안전 훈련 |
2024 트렌드: 단순 몰입에서 정량 훈련 효과 측정(ROI)으로 전환 — 학습 완료율, 실수율 감소, 비용 절감 수치 요구.
23. 경험적 조언 (실무 엔지니어 관점)
- Extension 체크는 Feature Flag처럼: 폴백 경로 필수
- predictedDisplayTime은 신성불가침: 무시하면 반드시 문제 발생
- Swapchain 포맷은 항상 런타임 선호 우선: 첫 번째 반환값
- 멀티벤더 크로스플랫폼은 extension 교집합: Meta/Pico/SteamVR 공통 지원분 파악, 벤더 특화는 폴백
- OpenXR Core만으론 에코시스템 커버 불가: “OpenXR for portability, vendor SDK for ecosystem”
- visionOS는 별개 스택: Unity/Unreal 브리지로도 Shared Space 불가
- Validation Layer는 개발 중 항상 켜라: 한 런타임에서 OK인 코드가 다른 런타임에서 크래시하는 경우 다수
24. 참고 자료
표준 / 공식 문서
- Khronos OpenXR
- OpenXR 1.1 Specification
- OpenXR Loader Design
- OpenXR-CTS
- OpenXR-SDK-Source
- Conformant Products Registry
- OpenXR Extension Support Report
보도 자료
- OpenXR 1.0 Release (2019.07)
- OpenXR 1.1 Release (2024.04)
- OpenXR Working Group 설립 (2017.02)
- Khronos 25주년
튜토리얼 / 가이드
성능 / 최적화
- Meta — Application SpaceWarp
- Meta — Late Latching
- Meta — ETFR
- Unity — Foveated Rendering in OpenXR
- Unity — Application SpaceWarp
게임 엔진
- Unity OpenXR Plugin
- Unity XR Interaction Toolkit 3.0
- Meta — Unity & OpenXR Compatibility
- Meta — Unreal Engine OpenXR
- Godot OpenXR Vendors Plugin
- StereoKit
마이그레이션 / 전환
- Meta — Oculus All In on OpenXR
- Valve OpenVR → OpenXR Transition (UploadVR)
- OpenComposite (znixian)
- GDC 2017 OpenXR 발표 (UploadVR)
런타임 / 에코시스템
- Monado
- Collabora — How Monado Became the Foundation
- Khronos — Monado Upgrade Project
- Android XR for OpenXR
- Android XR Wikipedia
- HTC VIVE OpenXR
- Magic Leap OpenXR Overview
- Pico OpenXR Mobile SDK
- Microsoft OpenXR — Mixed Reality
Apple 부재 / 비교
학술 / 분석
- DeepWiki — OpenXR Runtime Architecture
- Fred Emmott — VR Software Components
- Razer Abandoned OSVR (Tom’s Hardware)
- WebXR Device API Explainer (W3C)