웹에 도입된 가상 현실 2부

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

Joe Medley
Joe Medley

최근에 WebXR Device API의 기본 개념을 소개한 가상 현실을 웹에 제공을 게시했습니다. XR 세션 요청, 시작, 종료에 관한 안내도 제공했습니다.

이 도움말에서는 프레임 루프에 관해 설명합니다. 프레임 루프는 사용자 에이전트가 제어하는 무한 루프이며, 프레임 루프에서는 콘텐츠가 반복적으로 화면에 그려집니다. 콘텐츠는 프레임이라는 개별 블록에 그려집니다. 프레임이 이어지면 움직이는 듯한 착각을 일으킵니다.

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

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

이 문서는 WebGL도 아니고 프레임워크 튜토리얼도 아닙니다. Immersive Web Working Group의 몰입형 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. XRWebGLLayer에서 WebGLRenderingContextWebGLFramebuffer를 ('바인딩') 전달합니다.
    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 상태의 개별 기능을 따르세요.

사진: JESHOOTS.COM, Unsplash