웹에 도입된 가상 현실 2부

프레임 루프에 관한 모든 정보

Joe Medley
Joe Medley

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

이 도움말에서는 콘텐츠가 화면에 반복적으로 그려지는 사용자 에이전트 제어 무한 루프인 프레임 루프를 설명합니다. 콘텐츠는 프레임이라는 개별 블록으로 그려집니다. 프레임이 연속되면 움직이는 듯한 착시 현상이 나타납니다.

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

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

이 도움말에서는 Immersive Web Working Group's Immersive VR Session 샘플 (데모, 소스)을 사용하여 프레임 루프의 기본사항을 설명합니다. 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. XRWebGLLayer에서 WebGLFramebufferWebGLRenderingContext에 전달('바인딩')합니다.
    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 호환 Web 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 전달('바인딩')

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
    }
  }
}

XRViewport 객체를 WebGLRenderingContext에 전달

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 Status에서 개별 기능을 팔로우하세요.