fnwinter.github.io blog

OpenXR

What is OpenXR? :eyeglasses:

  • OpenXR은 AR(증강 현실) 및 VR(가상 현실(총칭하여 XR)) 플랫폼 및 장치에 대한 고성능 액세스를 제공하는 로열티 없는 개방형 표준입니다.
  • 초창기 VR을 개발하면서 여러 회사들이 각자 자신만의 VR 환경을 개발합니다.
  • Oculus, Vive, Steam 등 여러 회사들이 각자 하드웨어를 만들면서 또한 소프트웨어 플랫폼도 구현을 합니다.
  • 그런데 이렇게 많은 회사들이 각자 개발하다 보니까, 애플리케이션 제작 회사에서는 출시하고자 하는 플랫폼에 맞게 여러 버전을 출시해야 되고 추가적으로 구현해야 될 일이 많아져서 개발에 어려움을 겪습니다.

  • OpenXR 이전의 상황 Before

  • OpenXR에 맞게 구현 된 상황 After

  • 그래서 여러 벤더들이 모여 Khronos에서 VR 더불어 AR 및 MR 환경을 아우르는 OpenXR 표준을 만들게 됩니다. 그 결과로 OpenXR 스펙을 준수하는 하드웨어나 소프트웨어 플랫폼은 애플리케이션이 똑같이 OpenXR API를 사용할 경우 별도의 수정 없이(?) 동작할 수 있습니다.

OpenXR에서 지원하는 기능

  • OpenXR은 크게 2가지를 지원합니다. 하나는 Display 관련 된 기능이고, 다른 하나는 Input 관련 된 기능입니다.
  • Display 관련 기능은 Rendering 시에 Texture의 SwapChain을 관리하는 부분과, 그리고 양안 렌더링을 위한 기능, 여러 레이어를 합성해서 Compositing 을 하는 기능이 있습니다.
  • Input 관련 된 기능은, 하드웨어에서 올라오는 Input 메시지를 OpenXR에 맞게 변환하고 이를 애플리케이션에 전달해 주는 기능을 합니다.

OpenXR 개발하기 :computer:

  • OpenXR는 구현체가 아니라 Spec입니다. 그래서 실제 동작하기 위해서는 Runtime이라는 구현체가 필요합니다. Runtime을 실제로는 VR / AR 플랫폼 회사에서 또는 게임 엔지 회사에서 개발하기 때문에 실제로 조금씩 다 다르게 구현 됩니다. 만약 Unity나 Unreal 엔진 같이 유명한 엔진을 사용한다면, OpenXR plugin 을 추가해서 개발하면 됩니다. 만약 그런 엔진을 사용하지 않는다면, OpenXR Runtime을 선택하고, 선택한 Runtime 에 맞춰서 OpenXR API를 호출해 개발할 수 있습니다.

OpenXR Spec 분석 :microscope:

  • OpenXR Specification 문서는 여기에 있습니다. :link: Link
  • ReferenceGuide :link: Link
  • ReferenceGuide
  • ReferenceGuide 를 통해서 간단하게 OpenXR이 동작하는 방법을 설명해 보겠습니다.
  • :art: Application이 시작하면 OpenXR Loader를 통해서 API를 로드하고 xrCreateInstance를 호출해 Instance를 생성합니다.
  • 그리고 xrCreateSession 통해서 Session을 만듭니다. Session은 Application Life cycle과 같이 동작하는데 Application이 hide 또는 background로 가게 되서 rendering이 필요 없다면, xrEndSession으로 멈추고 다시 rendering을 시작하게 되면 xrBeginSession으로 다시 session을 시작하면 됩니다.
  • session이 시작하면 OpenXR Runtime을 frame을 rendering 합니다.
  • frame을 rendering 하기 위해서는 xrWaitFrame 을 통해서 이전 프레임이 frame buffer에 보여질때 까지 기다립니다. xrWaitFrame이 끝나면 새로운 frame을 그리기 위해서 xrBeginFrame을 호출 합니다.
  • 새로운 frame을 그리기 위해서는 OpenXR Runtime에 새로운 texture image를 요청합니다.
  • xrAcquireSwapchainImage를 통해서 양안 렌더링을 하기 위해 2장이 texture iamges를 요청합니다.
  • xrLocateViews와 xrLocaeSpace를 통해서 각 카메라의 view/projection matrix를 업데이트합니다.
  • 그리고 xrAcquireSwapchainImage 에서 받은 Texture를 framebuffer에 bind 해서, OpenGL 또는 Vulkan API를 통해서 화면을 그립니다.
  • 다음 frame을 그리기 위해서 xrReleaseSwapchainImage를 호출하고, 마지막으로 xrEndFrame을 통해서 frame buffer로 화면을 내보냅니다.
  • Application을 종료할 때는 Session을 종료하기 위해서 xrDestroySession 을 호출하고, Instance를 삭제하기 위해 xrDestroyInstance 를 호출 합니다.
  • 지금까지 Display 관련 OpenXR rendering cycle을 알아 보았습니다.
  • :joystick: 이제는 Action system이라고 하는 Input system을 알아 보겠습니다.

// Create an action set
XrActionSetCreateInfo actionSetInfo{XR_TYPE_ACTION_SET_CREATE_INFO};
strcpy(actionSetInfo.actionSetName, "gameplay");
strcpy(actionSetInfo.localizedActionSetName, "Gameplay");
actionSetInfo.priority = 0;
XrActionSet inGameActionSet;
CHK_XR(xrCreateActionSet(instance, &actionSetInfo, &inGameActionSet));

// create a "teleport" input action
XrActionCreateInfo actioninfo{XR_TYPE_ACTION_CREATE_INFO};
strcpy(actioninfo.actionName, "teleport");
actioninfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
strcpy(actioninfo.localizedActionName, "Teleport");
XrAction teleportAction;
CHK_XR(xrCreateAction(inGameActionSet, &actioninfo, &teleportAction));

// create a "player_hit" output action
XrActionCreateInfo hapticsactioninfo{XR_TYPE_ACTION_CREATE_INFO};
strcpy(hapticsactioninfo.actionName, "player_hit");
hapticsactioninfo.actionType = XR_ACTION_TYPE_VIBRATION_OUTPUT;
strcpy(hapticsactioninfo.localizedActionName, "Player hit");
XrAction hapticsAction;
CHK_XR(xrCreateAction(inGameActionSet, &hapticsactioninfo, &hapticsAction));

XrPath triggerClickPath, hapticPath;
CHK_XR(xrStringToPath(instance, "/user/hand/right/input/trigger/click", &triggerClickPath));
CHK_XR(xrStringToPath(instance, "/user/hand/right/output/haptic", &hapticPath))

XrPath interactionProfilePath;
CHK_XR(xrStringToPath(instance, "/interaction_profiles/vendor_x/profile_x", &interactionProfilePath));

XrActionSuggestedBinding bindings[2];
bindings[0].action = teleportAction;
bindings[0].binding = triggerClickPath;
bindings[1].action = hapticsAction;
bindings[1].binding = hapticPath;

XrInteractionProfileSuggestedBinding suggestedBindings{XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING};
suggestedBindings.interactionProfile = interactionProfilePath;
suggestedBindings.suggestedBindings = bindings;
suggestedBindings.countSuggestedBindings = 2;
CHK_XR(xrSuggestInteractionProfileBindings(instance, &suggestedBindings));

XrSessionActionSetsAttachInfo attachInfo{XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO};
attachInfo.countActionSets = 1;
attachInfo.actionSets = &inGameActionSet;
CHK_XR(xrAttachSessionActionSets(session, &attachInfo));

// application main loop
while (1)
{
    // sync action data
    XrActiveActionSet activeActionSet{inGameActionSet, XR_NULL_PATH};
    XrActionsSyncInfo syncInfo{XR_TYPE_ACTIONS_SYNC_INFO};
    syncInfo.countActiveActionSets = 1;
    syncInfo.activeActionSets = &activeActionSet;
    CHK_XR(xrSyncActions(session, &syncInfo));

    // query input action state
    XrActionStateBoolean teleportState{XR_TYPE_ACTION_STATE_BOOLEAN};
    XrActionStateGetInfo getInfo{XR_TYPE_ACTION_STATE_GET_INFO};
    getInfo.action = teleportAction;
    CHK_XR(xrGetActionStateBoolean(session, &getInfo, &teleportState));

    if (teleportState.changedSinceLastSync && teleportState.currentState)
    {
        // fire haptics using output action
        XrHapticVibration vibration{XR_TYPE_HAPTIC_VIBRATION};
        vibration.amplitude = 0.5;
        vibration.duration = 300;
        vibration.frequency = 3000;
        XrHapticActionInfo hapticActionInfo{XR_TYPE_HAPTIC_ACTION_INFO};
        hapticActionInfo.action = hapticsAction;
        CHK_XR(xrApplyHapticFeedback(session, &hapticActionInfo, (const XrHapticBaseHeader*)&vibration));
    }
}
  • 위의 코드처럼 xrCreateActionSet 으로 ActionSet을 만듭니다. 그리고 xrCreateAction 함수로 action 을 만듭니다. 그리고 xrPath를 통해서 xrPath를 만들고 xrSuggestInteractionProfileBindings 함수를 통해서 Action와 Path를 Bind 합니다.
  • 그리고 Application을 loop 에서 xrGetActionStateBoolean 를 통해서 Action Event가 발생했는지 확인을 합니다.

OpenXR Runtime (Monado)

  • Unity / Unreal 같은 경우엔 자체적으로 Runtime을 제공해 줍니다. 만약 OpenXR을 지원하지 않는 엔진이나 OpenGL이나 Vulkan을 사용해 밑바닥부터 개발하고자 한다면, Monado라는 Open source runtime이 있습니다.
  • Monado는 Collabora에서 개발하고 있는 Open Source / Boost Software License 1.0 의 OpenXR Runtime입니다. :link: Link
  • Monado를 OpenXR Runtime으로 선택하고, OpenXR API를 통해 XR 환경을 개발할 수 있습니다.
  • 현재 Monado Runtime을 지원하는 엔진는 godot 3D 엔진이 있습니다.
comments powered by Disqus