La API de Hit Test te permite colocar elementos virtuales en una vista del mundo real.
La API de WebXR Device se lanzó el otoño pasado en Chrome 79. Como se indicó en ese momento, la implementación de la API en Chrome está en curso. Chrome se complace en anunciar que parte del trabajo ya está terminado. En Chrome 81, llegaron dos funciones nuevas:
En este artículo, se explica la API de prueba de posicionamiento de WebXR, un medio para colocar objetos virtuales en una vista de cámara del mundo real.
En este artículo, se supone que ya sabes cómo crear una sesión de realidad aumentada y cómo ejecutar un bucle de fotogramas. Si no conoces estos conceptos, deberías leer los artículos anteriores de esta serie.
- La realidad virtual llega a la Web
- La realidad virtual llega a la Web, parte II
- Web AR: you may already know how to use it
La muestra de sesión de RA envolvente
El código de este artículo se basa en el que se encuentra en el ejemplo de prueba de posicionamiento del Immersive Web Working Group (demostración, fuente), pero no es idéntico. Este ejemplo te permite colocar girasoles virtuales en superficies del mundo real.
Cuando abras la app por primera vez, verás un círculo azul con un punto en el medio. El punto es la intersección entre una línea imaginaria que va desde tu dispositivo hasta el punto en el entorno. Se mueve a medida que mueves el dispositivo. A medida que encuentra puntos de intersección, parece ajustarse a superficies como pisos, mesas y paredes. Esto se debe a que las pruebas de posicionamiento proporcionan la posición y la orientación del punto de intersección, pero no brindan información sobre las superficies en sí.
Este círculo se llama retícula, que es una imagen temporal que ayuda a colocar un objeto en la realidad aumentada. Si presionas la pantalla, se coloca un girasol en la superficie en la ubicación y orientación del punto del retículo, independientemente de dónde hayas presionado la pantalla. La retícula sigue moviéndose con tu dispositivo.
Crea la retícula
Debes crear la imagen de la retícula por tu cuenta, ya que el navegador o la API no la proporcionan. El método para cargarlo y dibujarlo es específico del framework.
Si no lo dibujas directamente con WebGL o WebGL2, consulta la documentación de tu framework. Por este motivo, no entraremos en detalles sobre cómo se dibuja la retícula en la muestra. A continuación, muestro una línea de este código por un solo motivo: para que, en
las muestras de código posteriores, sepas a qué me refiero cuando uso la variable
reticle.
let reticle = new Gltf2Node({url: 'media/gltf/reticle/reticle.gltf'});
Solicita una sesión
Cuando solicites una sesión, debes solicitar 'hit-test' en el array requiredFeatures, como se muestra a continuación.
navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
// Do something with the session
});
Cómo ingresar a una sesión
En artículos anteriores, presenté el código para ingresar a una sesión de RE. A continuación, se muestra
una versión de esto con algunas adiciones. Primero, agregué el objeto de escucha de eventos select. Cuando el usuario presione la pantalla, se colocará una flor en la vista de la cámara según la pose de la retícula. Describiré ese objeto de escucha de eventos más adelante.
function onSessionStarted(xrSession) {
xrSession.addEventListener('end', onSessionEnded);
xrSession.addEventListener('select', onSelect);
let canvas = document.createElement('canvas');
gl = canvas.getContext('webgl', { xrCompatible: true });
xrSession.updateRenderState({
baseLayer: new XRWebGLLayer(session, gl)
});
xrSession.requestReferenceSpace('viewer').then((refSpace) => {
xrViewerSpace = refSpace;
xrSession.requestHitTestSource({ space: xrViewerSpace })
.then((hitTestSource) => {
xrHitTestSource = hitTestSource;
});
});
xrSession.requestReferenceSpace('local').then((refSpace) => {
xrRefSpace = refSpace;
xrSession.requestAnimationFrame(onXRFrame);
});
}
Múltiples espacios de referencia
Observa que el código destacado llama a XRSession.requestReferenceSpace() dos veces. Al principio, esto me pareció confuso. Pregunté por qué el código de la prueba de posicionamiento no solicita un fotograma de animación (que inicia el bucle de fotogramas) y por qué el bucle de fotogramas parece no incluir pruebas de posicionamiento. La fuente de la confusión fue un
malentendido de los espacios de referencia. Los espacios de referencia expresan relaciones
entre un origen y el mundo.
Para comprender lo que hace este código, imagina que estás viendo esta muestra con un equipo independiente y que tienes un casco y un control. Para medir las distancias desde el control, usarías un marco de referencia centrado en el control. Sin embargo, para dibujar algo en la pantalla, usarías coordenadas centradas en el usuario.
En este ejemplo, el visor y el controlador son el mismo dispositivo. Pero tengo un problema. Lo que dibujo debe ser estable con respecto al entorno, pero el "controlador" con el que dibujo se mueve.
Para dibujar imágenes, uso el espacio de referencia local, que me brinda estabilidad
en términos del entorno. Después de obtenerlo, inicio el bucle de fotogramas llamando a requestAnimationFrame().
Para las pruebas de posicionamiento, uso el espacio de referencia viewer, que se basa en la
postura del dispositivo en el momento de la prueba de posicionamiento. La etiqueta "visualizador" es un poco
confusa en este contexto porque me refiero a un controlador. Tiene sentido si piensas en el controlador como un visor electrónico. Después de obtenerlo, llamo a xrSession.requestHitTestSource(), que crea la fuente de datos de prueba de posicionamiento
que usaré cuando dibuje.
Ejecuta un bucle de fotogramas
La devolución de llamada requestAnimationFrame() también obtiene un código nuevo para controlar la prueba de posicionamiento.
A medida que mueves el dispositivo, la retícula debe moverse con él mientras intenta encontrar
superficies. Para crear la ilusión de movimiento, vuelve a dibujar la retícula en cada fotograma.
Sin embargo, no muestres la retícula si falla la prueba de posicionamiento. Por lo tanto, para la retícula que creé
antes, configuré su propiedad visible como false.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
Para dibujar cualquier cosa en RA, necesito saber dónde está el usuario y hacia dónde
mira. Por lo tanto, pruebo que hitTestSource y xrViewerPose sigan siendo válidos.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
Ahora llamo a getHitTestResults(). Toma hitTestSource como argumento
y devuelve un array de instancias de HitTestResult. La prueba de posicionamiento puede encontrar
varias superficies. El primero del array es el más cercano a la cámara.
La mayoría de las veces, usarás este elemento, pero se muestra un array para casos de uso
avanzados. Por ejemplo, imagina que la cámara apunta a una caja sobre una mesa en un
piso. Es posible que la prueba de posicionamiento devuelva las tres superficies en el array. En la mayoría de los casos, será el cuadro que me interesa. Si la longitud del array que se muestra es 0, es decir, si no se muestra ninguna prueba de posicionamiento, continúa. Vuelve a intentarlo en el siguiente fotograma.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
Por último, debo procesar los resultados de la prueba de posicionamiento. El proceso básico es el siguiente: Obtén una pose del resultado de la prueba de posicionamiento, transforma (mueve) la imagen de la retícula a la posición de la prueba de posicionamiento y, luego, establece su propiedad visible como verdadera. La pose representa la pose de un punto en una superficie.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.matrix = pose.transform.matrix;
reticle.visible = true;
}
}
// Draw to the screen
}
Cómo colocar un objeto
Se coloca un objeto en RA cuando el usuario presiona la pantalla. Ya agregué un controlador de eventos select a la sesión. (Consulta más arriba).
Lo importante en este paso es saber dónde colocarlo. Dado que la retícula en movimiento te brinda una fuente constante de pruebas de posicionamiento, la forma más sencilla de colocar un objeto es dibujarlo en la ubicación de la retícula en la última prueba de posicionamiento.
function onSelect(event) {
if (reticle.visible) {
// The reticle should already be positioned at the latest hit point,
// so we can just use its matrix to save an unnecessary call to
// event.frame.getHitTestResults.
addARObjectAt(reticle.matrix);
}
}
Conclusión
La mejor manera de comprender esto es revisar el código de muestra o probar el codelab. Espero haberte dado suficiente información para que comprendas ambos.
Aún no terminamos de crear APIs web envolventes, ni mucho menos. Publicaremos artículos nuevos aquí a medida que avancemos.