Всё о петле кадра
Недавно я опубликовал статью «Виртуальная реальность выходит в интернет» , в которой познакомился с основными понятиями WebXR Device API . Я также предоставил инструкции по запросу, началу и завершению XR-сессии.
В этой статье описывается циклическая отрисовка кадров, представляющая собой бесконечный цикл, управляемый пользовательским агентом, в котором контент многократно отрисовывается на экране. Контент отрисовывается дискретными блоками, называемыми кадрами. Последовательность кадров создает иллюзию движения.
В этой статье не рассматривается, чем она не является.
WebGL и WebGL2 — единственные средства рендеринга контента во время циклической отрисовки кадров в WebXR-приложении. К счастью, многие фреймворки предоставляют уровень абстракции поверх WebGL и WebGL2. К таким фреймворкам относятся three.js , babylonjs и PlayCanvas , а A-Frame и React 360 были разработаны для взаимодействия с WebXR.
В этой статье объясняются основы циклической смены кадров на примере сессии иммерсивной виртуальной реальности от рабочей группы по иммерсивному веб-технологиям ( демо , исходный код ). Если вы хотите углубиться в WebGL или один из фреймворков, в интернете есть постоянно пополняющийся список ресурсов.
Игроки и игра
Пытаясь разобраться в цикле кадров, я постоянно терялся в деталях. В игре задействовано множество объектов, и некоторые из них названы только по ссылочным свойствам других объектов. Чтобы вам было проще разобраться, я опишу объекты, которые я называю «игроками». Затем я опишу, как они взаимодействуют, что я называю «игрой».
Игроки
XRViewerPose
A pose is the position and orientation of something in 3D space. Both viewers and input devices have a pose, but it's the viewer's pose we're concerned with here. Both viewer and input device poses have a transform attribute describing its position as a vector and its orientation as a quaternion relative to the origin. The origin is specified based on the requested reference space type when calling XRSession.requestReferenceSpace() .
Понятие «пространство отсчета» требует некоторого объяснения. Я подробно рассматриваю его в статье «Дополненная реальность» . В качестве основы для этой статьи я использую 'local' пространство отсчета, что означает, что начало координат находится в точке, где находится зритель в момент создания сессии, без четко определенной плоскости, и его точное положение может варьироваться в зависимости от платформы.
XRView
Представление (view) соответствует камере, наблюдающей за виртуальной сценой. Представление также имеет атрибут transform , описывающий его положение в виде вектора и ориентацию. Эти параметры предоставляются как в виде пары вектор/кватернион, так и в виде эквивалентной матрицы; вы можете использовать любое представление в зависимости от того, какое лучше подходит для вашего кода. Каждое представление соответствует дисплею или части дисплея, используемой устройством для отображения изображения зрителю. Объекты XRView возвращаются в виде массива из объекта XRViewerPose . Количество представлений в массиве варьируется. На мобильных устройствах сцена дополненной реальности имеет одно представление, которое может покрывать или не покрывать экран устройства. Гарнитуры обычно имеют два представления, по одному для каждого глаза.
XRWebGLLayer
Слои предоставляют источник растровых изображений и описания того, как эти изображения должны отображаться на устройстве. Это описание не совсем точно отражает функцию данного плеера. Я склонен рассматривать его как посредника между устройством и WebGLRenderingContext . MDN придерживается примерно той же точки зрения, утверждая, что он «обеспечивает связь» между ними. Таким образом, он предоставляет доступ к другим плеерам.
В целом, объекты WebGL хранят информацию о состоянии для рендеринга 2D и 3D графики.
WebGLFramebuffer
Кадровый буфер предоставляет данные изображения в WebGLRenderingContext . После получения этих данных из XRWebGLLayer , вы передаете их в текущий WebGLRenderingContext . За исключением вызова bindFramebuffer() (подробнее об этом позже), вы никогда не будете обращаться к этому объекту напрямую. Вы просто передадите его из XRWebGLLayer в WebGLRenderingContext.
XRViewport
Область просмотра предоставляет координаты и размеры прямоугольной области в WebGLFramebuffer .
WebGLRenderingContext
Контекст рендеринга — это программная точка доступа к холсту (пространству, на котором мы рисуем). Для этого ему необходимы как WebGLFramebuffer , так и XRViewport.
Обратите внимание на взаимосвязь между XRWebGLLayer и WebGLRenderingContext . Один соответствует устройству пользователя, а другой — веб-странице. WebGLFramebuffer и XRViewport передаются из первого во второй.

XRWebGLLayer и WebGLRenderingContextИгра
Теперь, когда мы знаем, кто игроки, давайте посмотрим, в какую игру они играют. Это игра, которая начинается заново с каждым кадром. Напомним, что кадры являются частью цикла обработки кадров , который происходит со скоростью, зависящей от используемого оборудования. Для VR-приложений частота кадров в секунду может составлять от 60 до 144. AR для Android работает со скоростью 30 кадров в секунду. Ваш код не должен предполагать какую-либо конкретную частоту кадров.
Основной процесс обработки кадров выглядит следующим образом:
- Вызовите
XRSession.requestAnimationFrame(). В ответ пользовательский агент вызоветXRFrameRequestCallback, который определен вами. - Внутри вашей функции обратного вызова:
- Вызовите
XRSession.requestAnimationFrame()еще раз. - Определите позу зрителя.
- Передайте ('bind')
WebGLFramebufferизXRWebGLLayerвWebGLRenderingContext. - Пройдитесь по каждому объекту
XRView, извлеките егоXRViewportизXRWebGLLayerи передайте его вWebGLRenderingContext. - Нарисуйте что-нибудь в буфере кадра.
- Вызовите
Поскольку шаги 1 и 2а были рассмотрены в предыдущей статье, я начну с шага 2б.
Определите позу зрителя.
Вероятно, это само собой разумеется. Чтобы что-либо нарисовать в дополненной или виртуальной реальности, мне нужно знать, где находится зритель и куда он смотрит. Положение и ориентация зрителя предоставляются объектом 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 .
Небольшой объезд
Следующий шаг требует объектов, созданных во время настройки сессии . Напомним, что я создал холст и дал указание создать контекст рендеринга WebGL, совместимый с XR, который я получил, вызвав метод 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)
});
Передайте ('bind') 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
}
}
}
webGLRenContext
В процессе написания статьи у меня возник спор с несколькими коллегами по поводу названия объекта webGLRenContext . В примерах скриптов и большей части кода WebXR эта переменная называется gl . Когда я пытался разобраться в примерах, я постоянно забывал, что означает gl . Я назвал его webGLRenContext , чтобы напоминать вам во время обучения, что это экземпляр WebGLRenderingContext .
Причина в том, что использование gl позволяет именам методов выглядеть так же, как и их аналоги в API OpenGL ES 2.0, используемом для создания VR-приложений на компилируемых языках. Этот факт очевиден, если вы писали VR-приложения с использованием OpenGL, но может сбить с толку, если вы совершенно не знакомы с этой технологией.
Нарисуйте что-нибудь в буфере кадра
Если вы настроены решительно, можете использовать WebGL напрямую, но я этого не рекомендую. Гораздо проще использовать один из фреймворков, перечисленных вверху .
Заключение
Это не конец обновлений и статей о WebXR. Справочную информацию по всем интерфейсам и участникам WebXR можно найти на MDN. Чтобы узнать о предстоящих улучшениях самих интерфейсов, следите за обновлениями отдельных функций в Chrome Status .