Виртуальная реальность приходит в Интернет, часть II

Все о цикле кадров

Джо Медли
Joe Medley

Недавно я опубликовал статью «Виртуальная реальность приходит в Интернет» , в которой представлены основные концепции API устройств WebXR . Я также предоставил инструкции по запросу, входу и завершению сеанса XR.

В этой статье описывается цикл кадров, который представляет собой бесконечный цикл, управляемый пользовательским агентом, в котором контент неоднократно выводится на экран. Содержимое рисуется в отдельных блоках, называемых кадрами. Последовательность кадров создает иллюзию движения.

Чем не является эта статья

WebGL и WebGL2 — единственные средства рендеринга контента во время цикла кадров в приложении WebXR. К счастью, многие фреймворки предоставляют уровень абстракции поверх WebGL и WebGL2. К таким фреймворкам относятся Three.js , babylonjs и PlayCanvas , а A-Frame и React 360 были разработаны для взаимодействия с WebXR.

Эта статья не является ни руководством по WebGL, ни руководством по фреймворку. В нем объясняются основы цикла фреймов с использованием примера Immersive VR Session от Immersive Web Workshop ( демо , исходный код ). Если вы хотите погрузиться в WebGL или один из фреймворков, в Интернете можно найти постоянно растущий список статей.

Игроки и игра

Пытаясь понять цикл кадра, я постоянно терялся в деталях. В игре много объектов, и некоторые из них названы только по ссылочным свойствам других объектов. Чтобы помочь вам понять это, я опишу объекты, которые называю «игроками». Затем я опишу, как они взаимодействуют, что я называю «игрой».

Игроки

XRViewerPose

Поза — это положение и ориентация чего-либо в трехмерном пространстве. И у зрителей, и у устройств ввода есть поза, но нас интересует именно поза зрителя. И у зрителя, и у устройства ввода есть атрибут 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.

Обратите внимание на связь между XRWebGLLayer и WebGLRenderingContext . Один соответствует устройству зрителя, а другой — веб-странице. WebGLFramebuffer и XRViewport передаются от первого ко второму.

Отношения между XRWebGLLayer и WebGLRenderingContext
Отношения между XRWebGLLayer и WebGLRenderingContext

Игра

Теперь, когда мы знаем, кто эти игроки, давайте посмотрим на игру, в которую они играют. Это игра, которая начинается заново с каждым кадром. Напомним, что кадры являются частью цикла обработки кадров , скорость которого зависит от базового оборудования. Для приложений VR частота кадров в секунду может составлять от 60 до 144. AR для Android работает со скоростью 30 кадров в секунду. Ваш код не должен предполагать какую-либо конкретную частоту кадров.

Основной процесс цикла кадра выглядит следующим образом:

  1. Вызовите XRSession.requestAnimationFrame() . В ответ пользовательский агент вызывает XRFrameRequestCallback , определенный вами.
  2. Внутри вашей функции обратного вызова:
    1. Снова вызовите XRSession.requestAnimationFrame() .
    2. Получите позу зрителя.
    3. Передайте («привяжите») WebGLFramebuffer из XRWebGLLayer в WebGLRenderingContext .
    4. Выполните итерацию по каждому объекту XRView , извлекая его XRViewport из XRWebGLLayer и передавая его в WebGLRenderingContext .
    5. Нарисуйте что-нибудь во фреймбуфере.

Поскольку шаги 1 и 2а были рассмотрены в предыдущей статье, я начну с шага 2б.

Получите позу зрителя

Наверное, это само собой разумеется. Чтобы нарисовать что-нибудь в 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 события.

Короткий обход

Для следующего шага потребуются объекты, созданные во время настройки сеанса . Напомним, что я создал холст и поручил ему создать XR-совместимый контекст рендеринга Web GL, который я получил, вызвав canvas.getContext() . Все рисование выполняется с использованием API WebGL, API WebGL2 или платформы на основе WebGL, такой как Three.js. Этот контекст был передан объекту сеанса через updateRenderState() вместе с новым экземпляром XRWebGLLayer .

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 предоставляет кадровый буфер для WebGLRenderingContext , специально предназначенный для использования с WebXR и заменяющий кадровый буфер по умолчанию для контекстов рендеринга. На языке 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
    }
  }
}

ВебGLRenContext

При написании этой статьи у меня возник спор с несколькими коллегами по поводу названия объекта webGLRenContext . В примерах сценариев и большей части кода WebXR эта переменная просто называется gl . Когда я работал над пониманием образцов, я все время забывал, о чем говорит gl . Я назвал его webGLRenContext , чтобы напомнить вам, пока вы изучаете, что это экземпляр WebGLRenderingContext .

Причина в том, что использование gl позволяет именам методов выглядеть так же, как их аналоги в API OpenGL ES 2.0, используемом для создания виртуальной реальности на компилируемых языках. Этот факт очевиден, если вы пишете VR-приложения с использованием OpenGL, но сбивает с толку, если вы новичок в этой технологии.

Нарисуйте что-нибудь во фреймбуфере

Если вы действительно амбициозны, вы можете использовать WebGL напрямую, но я не рекомендую этого делать. Гораздо проще использовать один из фреймворков , перечисленных вверху .

Заключение

Это не конец обновлений и статей WebXR. Вы можете найти справочник по всем интерфейсам и участникам WebXR на MDN. Чтобы узнать о предстоящих улучшениях самих интерфейсов, следите за отдельными функциями в Chrome Status .

Фото JESHOOTS.COM на Unsplash