웹에 도입된 가상 현실 2부

프레임 루프에 관한 모든 것

Joe Medley
Joe Medley

최근에 WebXR Device API의 기본 개념을 소개하는 웹에 가상 현실이 도입됨이라는 도움말을 게시했습니다. 또한 XR 세션을 요청, 입력, 종료하는 방법도 안내했습니다.

이 도움말에서는 콘텐츠가 화면에 반복적으로 그려지는 사용자 에이전트가 제어하는 무한 루프인 프레임 루프에 관해 설명합니다. 콘텐츠는 프레임이라는 개별 블록으로 그려집니다. 프레임의 연속으로 인해 움직임의 환상이 만들어집니다.

이 도움말에서 다루지 않는 내용

WebGL 및 WebGL2는 WebXR 앱의 프레임 루프 중에 콘텐츠를 렌더링하는 유일한 수단입니다. 다행히 많은 프레임워크가 WebGL 및 WebGL2 위에 추상화 레이어를 제공합니다. 이러한 프레임워크에는 three.js, babylonjs, PlayCanvas가 포함되며 A-FrameReact 360은 WebXR과 상호작용하도록 설계되었습니다.

이 도움말은 WebGL 또는 프레임워크 튜토리얼이 아닙니다. 몰입형 웹 실무 그룹의 몰입형 VR 세션 샘플을 사용하여 프레임 루프의 기본사항을 설명합니다(데모, 소스). WebGL 또는 프레임워크 중 하나를 자세히 알아보려면 인터넷에서 제공하는 다양한 도움말을 참고하세요.

선수 및 경기

프레임 루프를 이해하려고 할 때 세부정보에 계속 집중했습니다. 많은 객체가 작동하며, 그중 일부는 다른 객체의 참조 속성으로만 이름이 지정됩니다. 쉽게 이해할 수 있도록 '플레이어'라고 하는 객체에 대해 설명하겠습니다. 그런 다음 '게임'이라고 부르는 상호작용 방식을 설명하겠습니다

선수

XRViewerPose

포즈는 3D 공간에서 물체의 위치와 방향입니다. 시청자와 입력 장치 모두 자세가 있지만 여기서는 시청자의 자세에 관심이 있습니다. 뷰어 및 입력 기기 포즈에는 모두 벡터로의 위치와 방향을 원점을 기준으로 한 쿼터니언으로 설명하는 transform 속성이 있습니다. 출처는 XRSession.requestReferenceSpace()를 호출할 때 요청된 참조 공간 유형에 따라 지정됩니다.

참조 공간은 설명하기가 조금 어렵습니다. 증강 현실에서 자세히 설명합니다. 이 문서의 기반으로 사용하는 샘플은 'local' 참조 공간을 사용합니다. 즉, 세션 생성 시 출처가 잘 정의된 층 없이 뷰어의 위치에 있으며 정확한 위치는 플랫폼에 따라 다를 수 있습니다.

XRView

뷰는 가상 장면을 보는 카메라에 해당합니다. 뷰에는 벡터와 방향으로 위치를 설명하는 transform 속성도 있습니다. 이는 벡터/쿼터니언 쌍과 등가 행렬로 모두 제공되므로 코드에 가장 적합한 표현을 사용할 수 있습니다. 각 뷰는 기기가 시청자에게 이미지를 표시하는 데 사용하는 디스플레이 또는 디스플레이의 일부에 해당합니다. XRView 객체는 XRViewerPose 객체에서 배열로 반환됩니다. 배열의 뷰 수는 다양합니다. 휴대기기에서 AR 장면에는 하나의 뷰가 있으며, 이 뷰는 기기 화면을 가릴 수도 있고 가리지 않을 수도 있습니다. 헤드셋에는 일반적으로 눈마다 하나씩 두 개의 뷰가 있습니다.

XRWebGLLayer

레이어는 비트맵 이미지의 소스와 이러한 이미지가 기기에서 렌더링되는 방식에 관한 설명을 제공합니다. 이 설명은 이 플레이어의 기능을 정확하게 설명하지 않습니다. 기기와 WebGLRenderingContext 사이의 중간 역할을 한다고 생각합니다. MDN은 두 항목 간에 '연결을 제공'한다고 말하면서 거의 동일한 관점을 취합니다. 따라서 다른 플레이어에게 액세스할 수 있습니다.

일반적으로 WebGL 객체는 2D 및 3D 그래픽을 렌더링하기 위한 상태 정보를 저장합니다.

WebGLFramebuffer

프레임버퍼는 WebGLRenderingContext에 이미지 데이터를 제공합니다. XRWebGLLayer에서 가져온 후 현재 WebGLRenderingContext에 전달하기만 하면 됩니다. bindFramebuffer()를 호출하는 것 외에는(나중에 자세히 설명) 이 객체에 직접 액세스하지 않습니다. XRWebGLLayer에서 WebGLRenderingContext로 전달하기만 하면 됩니다.

XRViewport

표시 영역은 WebGLFramebuffer의 직사각형 영역의 좌표와 크기를 제공합니다.

WebGLRenderingContext

렌더링 컨텍스트는 캔버스 (그리는 공간)의 프로그래매틱 액세스 포인트입니다. 이렇게 하려면 WebGLFramebuffer와 XRViewport가 모두 필요합니다.

XRWebGLLayerWebGLRenderingContext의 관계에 유의하세요. 하나는 뷰어의 기기에 해당하고 다른 하나는 웹페이지에 해당합니다. WebGLFramebufferXRViewport는 전자의 값에서 후자의 값으로 전달됩니다.

XRWebGLLayer와 WebGLRenderingContext 간의 관계
XRWebGLLayerWebGLRenderingContext의 관계

게임

이제 선수를 알았으니 선수가 플레이하는 경기를 살펴보겠습니다. 이 게임은 모든 프레임에서 다시 시작되는 게임입니다. 프레임은 기본 하드웨어에 따라 속도가 달라지는 프레임 루프의 일부입니다. VR 애플리케이션의 경우 초당 프레임은 60~144가 될 수 있습니다. Android용 AR은 초당 30프레임으로 실행됩니다. 코드에서 특정 프레임 속도를 가정해서는 안 됩니다.

프레임 루프의 기본 프로세스는 다음과 같습니다.

  1. XRSession.requestAnimationFrame()를 호출합니다. 이에 따라 사용자 에이전트는 개발자가 정의한 XRFrameRequestCallback를 호출합니다.
  2. 콜백 함수 내에서 다음을 수행합니다.
    1. XRSession.requestAnimationFrame()님에게 다시 전화 걸기
    2. 시청자의 포즈를 취해 보세요.
    3. WebGLFramebufferXRWebGLLayer에서 WebGLRenderingContext에 전달('바인딩')합니다.
    4. XRView 객체를 반복하여 XRWebGLLayer에서 XRViewport를 가져와 WebGLRenderingContext에 전달합니다.
    5. 프레임버퍼에 무언가를 그립니다.

1단계와 2a단계는 이전 도움말에서 다뤘으므로 2b단계부터 시작하겠습니다.

시청자의 포즈 잡기

아마 당연한 일입니다. AR이나 VR로 무언가를 그리려면 시청자가 어디에 있는지, 어디를 보고 있는지 알아야 합니다. 뷰어의 위치와 방향은 XRViewerPose 객체에 의해 제공됩니다. 현재 애니메이션 프레임에서 XRFrame.getViewerPose()를 호출하여 시청자의 포즈를 가져옵니다. 세션을 설정할 때 획득한 참조 공간을 전달합니다. 이 객체에서 반환하는 값은 항상 현재 세션에 시작할 때 요청한 참조 공간을 기준으로 합니다. 기억하시겠지만, 포즈를 요청할 때 현재 참조 공간을 전달해야 합니다.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    // Render based on the pose.
  }
}

사용자의 전반적인 위치를 나타내는 시청자 포즈가 하나 있습니다. 즉, 스마트폰의 경우 시청자의 머리 또는 휴대전화 카메라를 의미합니다. 포즈는 애플리케이션에 뷰어가 있는 위치를 알려줍니다. 실제 이미지 렌더링은 XRView 객체를 사용하며, 이는 잠시 후에 설명하겠습니다.

계속하기 전에 시스템이 추적을 중단하거나 개인 정보 보호로 인해 포즈를 차단하는 경우 시청자 포즈가 반환되었는지 테스트합니다. 추적은 XR 기기의 위치 또는 입력 기기가 환경과 관련하여 어디에 있는지 알 수 있는 기능입니다. 추적은 여러 가지 방법으로 손실될 수 있으며 추적에 사용되는 방법에 따라 다릅니다. 예를 들어 헤드셋이나 휴대전화의 카메라가 추적에 사용되는 경우 조명이 어둡거나 조명이 없는 환경이거나 카메라가 가려져 있으면 기기가 자신의 위치를 파악하지 못할 수 있습니다.

개인 정보 보호를 위해 포즈를 차단하는 예는 헤드셋에 권한 메시지와 같은 보안 대화상자가 표시되는 경우입니다. 이때 브라우저는 애플리케이션에 포즈를 제공하는 것을 중지할 수 있습니다. 하지만 시스템이 복구될 수 있으면 프레임 루프가 계속되도록 이미 XRSession.requestAnimationFrame()를 호출했습니다. 그렇지 않으면 사용자 에이전트가 세션을 종료하고 end 이벤트 핸들러를 호출합니다.

짧은 우회 경로

다음 단계에서는 세션 설정 중에 생성된 객체가 필요합니다. 캔버스를 만들고 canvas.getContext()를 호출하여 가져온 XR 호환 웹 GL 렌더링 컨텍스트를 만들도록 지시했습니다. 모든 그리기는 WebGL API, WebGL2 API 또는 Three.js와 같은 WebGL 기반 프레임워크를 사용하여 실행됩니다. 이 컨텍스트는 XRWebGLLayer의 새 인스턴스와 함께 updateRenderState()를 통해 세션 객체에 전달되었습니다.

let canvas = document.createElement('canvas');
// The rendering context must be based on WebGL or WebGL2
let webGLRenContext = canvas.getContext('webgl', { xrCompatible: true });
xrSession.updateRenderState({
    baseLayer: new XRWebGLLayer(xrSession, webGLRenContext)
  });

WebGLFramebuffer를 전달('bind')합니다.

XRWebGLLayer는 WebXR과 함께 사용하기 위해 특별히 제공되고 렌더링 컨텍스트 기본 프레임버퍼를 대체하는 WebGLRenderingContext의 프레임버퍼를 제공합니다. WebGL 언어에서는 이를 '바인딩'이라고 합니다.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    // Iterate over the views
  }
}

각 XRView 객체 반복

포즈를 가져오고 프레임버퍼를 바인딩한 후에는 뷰포트를 가져와야 합니다. XRViewerPose에는 각각 디스플레이 또는 디스플레이의 일부를 나타내는 XRView 인터페이스의 배열이 포함됩니다. 여기에는 시야, 시야각, 기타 광학 속성과 같이 기기와 뷰어에 올바르게 배치된 콘텐츠를 렌더링하는 데 필요한 정보가 포함됩니다. 두 눈을 그리므로 뷰가 두 개 있으며 이를 통해 루프를 실행하고 각각에 대해 별도의 이미지를 그립니다.

휴대전화 기반 증강 현실을 구현할 때는 뷰가 하나만 있지만 루프는 계속 사용합니다. 하나의 뷰를 반복하는 것이 무의미해 보일 수 있지만 이렇게 하면 단일 렌더링 경로를 통해 다양한 몰입형 경험을 제공할 수 있습니다. 이는 WebXR과 다른 몰입형 시스템의 중요한 차이점입니다.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    for (let xrView of xrViewerPose.views) {
      // Pass viewports to the context
    }
  }
}

WebGLRenderingContext에 XRViewport 객체 전달

XRView 객체는 화면에서 관찰할 수 있는 항목을 나타냅니다. 하지만 이 뷰에 그리려면 기기에 맞는 좌표와 크기가 필요합니다. 프레임버퍼와 마찬가지로 XRWebGLLayer에서 요청하고 WebGLRenderingContext에 전달합니다.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    for (let xrView of xrViewerPose.views) {
      let viewport = glLayer.getViewport(xrView);
      webGLRenContext.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
      // Draw something to the framebuffer
    }
  }
}

webGLRenContext

이 도움말을 작성할 때 webGLRenContext 객체의 이름 지정에 관해 몇몇 동료와 논쟁을 벌였습니다. 샘플 스크립트와 대부분의 WebXR 코드는 이 변수 gl를 호출합니다. 샘플을 이해하기 위해 노력하는 과정에서 gl가 무엇을 나타내는지 계속 잊어버렸습니다. WebGLRenderingContext의 인스턴스임을 학습하는 동안 이를 상기시켜 주기 위해 webGLRenContext라고 이름을 지었습니다.

gl를 사용하면 메서드 이름이 컴파일된 언어로 VR을 만드는 데 사용되는 OpenGL ES 2.0 API의 대응 항목처럼 보일 수 있기 때문입니다. OpenGL을 사용하여 VR 앱을 작성한 적이 있다면 이 사실은 명확하지만 이 기술을 처음 접하는 경우에는 혼란스러울 수 있습니다.

프레임버퍼에 무언가를 그립니다.

야심이 있다면 WebGL을 직접 사용할 수도 있지만 권장하지는 않습니다. 상단에 나열된 프레임워크 중 하나를 사용하는 것이 훨씬 간단합니다.

결론

WebXR 업데이트 또는 도움말은 계속 제공됩니다. MDN에서 모든 WebXR 인터페이스 및 구성원의 참조를 확인할 수 있습니다. 인터페이스 자체에 관한 향후 개선사항은 Chrome 상태에서 개별 기능을 참고하세요.

사진: UnsplashJESHOOTS.COM