La API de Hit Test te permite posicionar 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 es un trabajo en curso. Chrome se complace en anunciar que parte del trabajo está terminado. En Chrome 81, se lanzaron dos funciones nuevas:
En este artículo, se explica la API de WebXR Hit Test, una forma de colocar objetos virtuales en la vista de la 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, te recomendamos que leas los artículos anteriores de esta serie.
- La realidad virtual llega a la Web
- La realidad virtual llega a la Web, parte II
- RA web: Es posible que ya sepas cómo usarla
Ejemplo de sesión de RA inmersiva
El código de este artículo se basa en el ejemplo de prueba de detección de impactos del Grupo de trabajo de la Web Immersive (demostración, fuente), pero no es idéntico a él. 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 que se ajusta a superficies como pisos, mesas y paredes. Esto se debe a que la prueba de detección de intersecciones proporciona la posición y la orientación del punto de intersección, pero no información sobre las superficies en sí.
Este círculo se llama retículo, que es una imagen temporal que ayuda a colocar un objeto en realidad aumentada. Si presionas la pantalla, se colocará un girasol en la superficie en la ubicación del retículo y la orientación del punto del retículo, independientemente de dónde hayas presionado la pantalla. La retícula seguirá 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 cargarla y dibujarla 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 entraré 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é código para ingresar a una sesión de XR. A continuación, se muestra una versión de este código 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 posición del retículo. 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);
});
}
Varios espacios de referencia
Observa que el código destacado llama a XRSession.requestReferenceSpace() dos veces. Al principio, me pareció confuso. Pregunté por qué el código de prueba de detección no solicita un fotograma de animación (que inicia el bucle de fotogramas) y por qué el bucle de fotogramas parece no involucrar pruebas de detección. La confusión se debió a una mala interpretación 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, simula que estás viendo esta muestra con un equipo independiente y que tienes auriculares y un controlador. Para medir las distancias desde el controlador, usarías un marco de referencia centrado en el controlador. 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 obtener esto, inicio el bucle de fotogramas llamando a requestAnimationFrame().
Para las pruebas de impacto, uso el espacio de referencia viewer, que se basa en la posición del dispositivo en el momento de la prueba de impacto. La etiqueta "visualizador" es algo confusa en este contexto porque estoy hablando de un controlador. Tiene sentido si piensas en el controlador como un visor electrónico. Después de obtener esto, llamo a xrSession.requestHitTestSource(), que crea la fuente de datos de la prueba de impacto que usaré cuando dibuje.
Cómo ejecutar un bucle de fotogramas
La devolución de llamada requestAnimationFrame() también obtiene código nuevo para controlar las pruebas de detección de aciertos.
A medida que mueves el dispositivo, el retículo debe moverse con él mientras intenta encontrar superficies. Para crear la ilusión de movimiento, vuelve a dibujar la retícula en cada fotograma.
Pero no muestres la retícula si falla la prueba de impacto. Por lo tanto, para la retícula que creé antes, configuré su propiedad visible en 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 algo 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 el 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 que está más cerca de la cámara.
La mayoría de las veces lo usarás, pero se devuelve un array para casos de uso avanzados. Por ejemplo, imagina que tu cámara apunta a una caja sobre una mesa en el piso. Es posible que la prueba de impacto devuelva las tres superficies en el array. En la mayoría de los casos, será la caja que me interesa. Si la longitud del array devuelto es 0, en otras palabras, si no se devuelve ninguna prueba de impacto, 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 impacto. El proceso básico es el siguiente: Obtén una postura del resultado de la prueba de impacto, transforma (mueve) la imagen de la retícula a la posición de la prueba de impacto y, luego, establece su propiedad visible como verdadera. La posición representa la posición 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 la información anterior).
Lo importante en este paso es saber dónde colocarlo. Dado que la retícula en movimiento te proporciona una fuente constante de pruebas de impacto, la forma más sencilla de colocar un objeto es dibujarlo en la ubicación de la retícula en la última prueba de impacto.
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 ejemplo o probar el codelab. Espero haberte brindado suficientes antecedentes para que comprendas ambos conceptos.
Aún no terminamos de crear APIs web envolventes, ni mucho menos. Publicaremos artículos nuevos aquí a medida que avancemos.
Foto de Daniel Frank en Unsplash