Todo sobre el bucle de fotogramas
Hace poco, publiqué Virtual reality comes to the web, un artículo en el que se presentaban los conceptos básicos detrás de la API de WebXR Device. También proporcioné instrucciones para solicitar, ingresar y finalizar una sesión de XR.
En este artículo, se describe el bucle de fotogramas, que es un bucle infinito controlado por el agente de usuario en el que el contenido se dibuja repetidamente en la pantalla. El contenido se dibuja en bloques discretos llamados fotogramas. La sucesión de fotogramas crea la ilusión de movimiento.
Qué no es este artículo
WebGL y WebGL2 son los únicos medios para renderizar contenido durante un bucle de fotogramas en una app de WebXR. Afortunadamente, muchos frameworks proporcionan una capa de abstracción sobre WebGL y WebGL2. Entre estos frameworks, se incluyen three.js, babylonjs y PlayCanvas, mientras que A-Frame y React 360 se diseñaron para interactuar con WebXR.
En este artículo, se explican los conceptos básicos de un bucle de fotogramas con la muestra de sesión de RV inmersiva del Grupo de trabajo de la Web Inmersiva (demostración, código fuente). Si quieres profundizar en WebGL o en uno de los frameworks, hay una lista cada vez más grande de recursos en línea.
Los jugadores y el juego
Cuando intentaba comprender el bucle de fotogramas, me perdía en los detalles. Hay muchos objetos en juego, y algunos de ellos solo se nombran por propiedades de referencia en otros objetos. Para ayudarte a entenderlo, describiré los objetos, a los que llamaré "jugadores". Luego, describiré cómo interactúan, a lo que llamo "el juego".
Los jugadores
XRViewerPose
Una postura es la posición y la orientación de algo en el espacio 3D. Tanto los visores como los dispositivos de entrada tienen una postura, pero aquí nos interesa la postura del visor. Las poses del dispositivo de entrada y del visualizador tienen un atributo transform que describe su posición como un vector y su orientación como un cuaternión en relación con el origen. El origen se especifica según el tipo de espacio de referencia solicitado cuando se llama a XRSession.requestReferenceSpace().
Los espacios de referencia tardan un poco en explicarse. Los abordo en detalle en Realidad aumentada. La muestra que uso como base para este artículo usa un espacio de referencia 'local', lo que significa que el origen se encuentra en la posición del usuario en el momento de la creación de la sesión sin un piso bien definido, y su posición precisa puede variar según la plataforma.
XRView
Una vista corresponde a una cámara que observa la escena virtual. Una vista también tiene un atributo transform que describe su posición como un vector y su orientación.
Se proporcionan como un par de vector y cuaternión, y como una matriz equivalente. Puedes usar cualquiera de las representaciones según la que mejor se adapte a tu código. Cada vista corresponde a una pantalla o a una parte de una pantalla que usa un dispositivo para presentar imágenes al usuario. Los objetos XRView se muestran en un array del objeto XRViewerPose. La cantidad de vistas en el array varía. En los dispositivos móviles, una escena de RA tiene una vista, que puede cubrir o no la pantalla del dispositivo.
Por lo general, los visores tienen dos vistas, una para cada ojo.
XRWebGLLayer
Las capas proporcionan una fuente de imágenes de mapa de bits y descripciones de cómo se deben renderizar esas imágenes en el dispositivo. Esta descripción no explica con exactitud lo que hace este reproductor. Lo considero un intermediario entre un dispositivo y un WebGLRenderingContext. MDN tiene una opinión muy similar y afirma que proporciona una vinculación entre ambos. Por lo tanto, proporciona acceso a los demás jugadores.
En general, los objetos WebGL almacenan información de estado para renderizar gráficos en 2D y 3D.
WebGLFramebuffer
Un búfer de fotogramas proporciona datos de imagen al WebGLRenderingContext. Después de recuperarlo del XRWebGLLayer, lo pasas al WebGLRenderingContext actual. Además de llamar a bindFramebuffer() (más información al respecto más adelante), nunca accederás a este objeto directamente. Solo lo pasarás de XRWebGLLayer a WebGLRenderingContext.
XRViewport
Un viewport proporciona las coordenadas y las dimensiones de una región rectangular en el WebGLFramebuffer.
WebGLRenderingContext
Un contexto de renderización es un punto de acceso programático para un lienzo (el espacio en el que dibujamos). Para ello, necesita un WebGLFramebuffer y un XRViewport.
Observa la relación entre XRWebGLLayer y WebGLRenderingContext. Uno corresponde al dispositivo del usuario y el otro a la página web.
WebGLFramebuffer y XRViewport se pasan del primero al segundo.
XRWebGLLayer y WebGLRenderingContext
El juego
Ahora que sabemos quiénes son los jugadores, veamos el juego que juegan. Es un juego que comienza de nuevo con cada fotograma. Recuerda que los fotogramas forman parte de un bucle de fotogramas que se produce a una velocidad que depende del hardware subyacente. En el caso de las aplicaciones de RV, los fotogramas por segundo pueden variar entre 60 y 144. La RA para Android se ejecuta a 30 fotogramas por segundo. Tu código no debe suponer ninguna velocidad de fotogramas en particular.
El proceso básico del bucle de fotogramas se ve de la siguiente manera:
- Llamar a
XRSession.requestAnimationFrame()En respuesta, el agente de usuario invoca elXRFrameRequestCallback, que tú defines. - Dentro de la función de devolución de llamada, haz lo siguiente:
- Vuelve a llamar a
XRSession.requestAnimationFrame(). - Obtén la postura del usuario.
- Pasa (víncula) el
WebGLFramebufferdelXRWebGLLayeralWebGLRenderingContext. - Itera sobre cada objeto
XRView, recupera suXRViewportdelXRWebGLLayery pásalo alWebGLRenderingContext. - Dibuja algo en el búfer de fotogramas.
- Vuelve a llamar a
Como los pasos 1 y 2a se explicaron en el artículo anterior, comenzaré con el paso 2b.
Cómo obtener la postura del usuario
Probablemente no hace falta decirlo. Para dibujar cualquier cosa en RA o RV, necesito saber dónde está el usuario y hacia dónde mira. La posición y la orientación del visor se proporcionan a través de un objeto XRViewerPose. Para obtener la posición del visualizador, llamo a XRFrame.getViewerPose() en el fotograma de animación actual. Le paso el espacio de referencia que adquirí cuando configuré la sesión. Los valores que devuelve este objeto siempre son relativos al espacio de referencia que solicité cuando ingresé a la sesión actual. Como recordarás, tengo que pasar el espacio de referencia actual cuando solicito la postura.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
if (xrViewerPose) {
// Render based on the pose.
}
}
Hay una sola posición del usuario que representa su posición general, es decir, la cabeza del usuario o la cámara del teléfono.
La pose le indica a tu aplicación dónde está el usuario. La renderización de imágenes real usa objetos XRView, que explicaré en un momento.
Antes de continuar, pruebo si se devolvió la posición del visualizador en caso de que el sistema pierda el seguimiento o bloquee la posición por motivos de privacidad. El seguimiento es la capacidad del dispositivo XR de saber dónde se encuentran él y sus dispositivos de entrada en relación con el entorno. El seguimiento se puede perder de varias maneras y varía según el método que se use. Por ejemplo, si se usan las cámaras del teléfono o los auriculares para hacer un seguimiento del dispositivo, es posible que este pierda la capacidad de determinar dónde se encuentra en situaciones con poca luz o sin luz, o si las cámaras están cubiertas.
Un ejemplo de bloqueo de la postura por motivos de privacidad es si el visor muestra un diálogo de seguridad, como un mensaje de permiso. En ese caso, el navegador puede dejar de proporcionar posturas a la aplicación mientras esto sucede. Pero ya llamé a XRSession.requestAnimationFrame() para que, si el sistema se puede recuperar, continúe el bucle de fotogramas. De lo contrario, el agente de usuario finalizará la sesión y llamará al controlador de eventos end.
Un pequeño desvío
El siguiente paso requiere objetos creados durante la configuración de la sesión.
Recuerda que creé un lienzo y le indiqué que creara un contexto de renderización de WebGL compatible con XR, que obtuve llamando a canvas.getContext(). Todo el dibujo se realiza con la API de WebGL, la API de WebGL2 o un framework basado en WebGL, como Three.js. Este contexto se pasó al objeto de sesión con updateRenderState(), además de una instancia nueva de 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)
});
Pasa (víncula) el WebGLFramebuffer
El XRWebGLLayer proporciona un búfer de fotogramas para el WebGLRenderingContext proporcionado específicamente para usar con WebXR y reemplazar el búfer de fotogramas predeterminado de los contextos de renderización. En el lenguaje de WebGL, esto se denomina "enlace".
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
}
}
Itera sobre cada objeto XRView
Después de obtener la pose y vincular el búfer de fotogramas, es momento de obtener los puertos de visualización. XRViewerPose contiene un array de interfaces XRView, cada una de las cuales representa una pantalla o una parte de una pantalla. Contienen información necesaria para renderizar contenido que se posiciona correctamente para el dispositivo y el usuario, como el campo de visión, el desplazamiento de los ojos y otras propiedades ópticas.
Como dibujo para dos ojos, tengo dos vistas, por las que itero y dibujo una imagen separada para cada una.
Cuando implemente la realidad aumentada basada en el teléfono, solo tendré una vista, pero seguiré usando un bucle. Aunque puede parecer inútil iterar en una sola vista, hacerlo te permite tener una sola ruta de renderización para un espectro de experiencias inmersivas. Esta es una diferencia importante entre WebXR y otros sistemas inmersivos.
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
}
}
}
Pasa el objeto XRViewport a WebGLRenderingContext
Un objeto XRView hace referencia a lo que se puede observar en una pantalla. Sin embargo, para dibujar en esa vista, necesito coordenadas y dimensiones específicas para mi dispositivo. Al igual que con el búfer de fotogramas, los solicito desde XRWebGLLayer y los paso a 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
}
}
}
El objeto webGLRenContext
Por escrito, tuve un debate con algunos colegas sobre el nombre del objeto webGLRenContext. Las secuencias de comandos de muestra y la mayoría del código de WebXR llaman a esta variable gl. Cuando intentaba comprender las muestras, olvidaba a qué se refería gl. La llamé webGLRenContext para recordarte que, mientras aprendes, esta es una instancia de WebGLRenderingContext.
El motivo es que usar gl permite que los nombres de los métodos se parezcan a sus contrapartes en la API de OpenGL ES 2.0, que se usa para crear VR en lenguajes compilados. Este hecho es obvio si escribiste apps de RV con OpenGL, pero confuso si es la primera vez que usas esta tecnología.
Cómo dibujar algo en el búfer de fotogramas
Si te sientes muy ambicioso, puedes usar WebGL directamente, pero no lo recomiendo. Es mucho más sencillo usar uno de los frameworks que se indican en la parte superior.
Conclusión
Este no es el final de las actualizaciones ni los artículos sobre WebXR. Puedes encontrar una referencia para todas las interfaces y los miembros de WebXR en MDN. Para conocer las próximas mejoras en las interfaces, sigue las funciones individuales en ChromeStatus.