Compila una app de realidad aumentada (RA) con la API de WebXR Device

1. Antes de comenzar

En este codelab, se analiza un ejemplo de cómo compilar una app web de RA. Se utiliza JavaScript para renderizar modelos 3D que se muestran como si existieran en el mundo real.

Usarás la API de WebXR Device, que combina funciones de RA y realidad virtual (RV). Te enfocarás en las extensiones de RA para la API de WebXR Device a fin de crear una app de RA sencilla que se ejecute en la Web interactiva.

¿Qué es la RA?

La RA es un término que se suele usar para describir la combinación de gráficos generados por computadora con el mundo real. En el caso de la RA basada en teléfonos, significa colocar de manera convincente este tipo de gráficos en un feed de cámara en vivo. Para que este efecto se mantenga realista a medida que se mueve el teléfono, el dispositivo compatible con RA debe comprender el mundo por el que se desplaza y determinar su pose (posición y orientación) en el espacio 3D. Este proceso puede incluir detectar superficies y estimar la iluminación del entorno.

El uso de la RA se popularizó en las apps después del lanzamiento de ARCore de Google y ARKit de Apple, ya sea para filtros de selfies o juegos basados en esta tecnología.

Qué compilarás

En este codelab, compilarás una app web que coloque un modelo en el mundo real mediante la realidad aumentada. Tu app hará lo siguiente:

  1. Usará los sensores del dispositivo de destino para determinar su posición y orientación en el mundo, y hacer un seguimiento de ellas.
  2. Renderizará un modelo 3D compuesto sobre una vista de cámara en vivo.
  3. Ejecutará pruebas de posicionamiento para colocar objetos sobre las superficies detectadas en el mundo real.

Qué aprenderás

  • Cómo usar la API de WebXR Device
  • Cómo configurar una escena básica de RA
  • Cómo encontrar una superficie con pruebas de posicionamiento de RA
  • Cómo cargar y renderizar un modelo 3D sincronizado con el feed de cámara del mundo real
  • Cómo renderizar sombras en función del modelo 3D

Este codelab se enfoca en las API de RA. Los conceptos y los bloques de código que no son pertinentes se mencionan de forma somera, pero se proporcionan en el código del repositorio correspondiente.

Requisitos

Haz clic en Probar en el dispositivo de RA para realizar el primer paso de este codelab. Si llegas a una página con el mensaje “Your browser does not support AR features with WebXR”, comprueba que tu dispositivo Android tenga instalados los Servicios de Google Play para RA.

2. Configura el entorno de desarrollo

Descarga el código

  1. Haz clic en el siguiente vínculo para descargar el código de este codelab en tu estación de trabajo:

Descargar código fuente

  1. Descomprime el archivo ZIP descargado, que incluye una carpeta raíz (ar-with-webxr-master) con directorios de varios pasos de este codelab, junto con todos los recursos que necesitas.

Las carpetas step-03 y step-04 contienen el estado final deseado de los pasos tres y cuatro de este codelab, así como el resultado final. Están disponibles como referencia.

Todo el trabajo de codificación se realiza en el directorio work.

Instala un servidor web

  1. Puedes usar tu propio servidor web. Si no tienes uno ya configurado, en esta sección se explica cómo configurar Web Server for Chrome.
    Si aún no tienes esa app instalada en tu estación de trabajo, puedes instalarla desde Chrome Web Store.

  1. Después de instalar la app de Web Server for Chrome, ve a chrome://apps y haz clic en el ícono de Web Server:

Ícono de Web Server

Luego, verás este diálogo, que te permitirá configurar tu servidor web local:

Configuración de Web Server de Chrome

  1. Haz clic en Choose folder y selecciona la carpeta ar-with-webxr-master. Esto te permite entregar el trabajo en curso a través de la URL destacada en el diálogo de servidor web, en la sección Web Server URL(s).
  2. En Options (needs restart), selecciona la casilla de verificación Automatically show index.html.
  3. Mueve el interruptor Web Server a la opción STOPPED y, luego, a STARTED nuevamente.Reinicia Web Server de Chrome
  4. Verifica que aparezca al menos una URL del servidor web: http://127.0.0.1:8887 (la URL localhost predeterminada).

Configura la redirección de puertos

Configura tu dispositivo de RA para que acceda al mismo puerto en tu estación de trabajo cuando visites localhost:8887 desde ella.

  1. En tu estación de trabajo de desarrollo, ve a chrome://inspect y haz clic en Port forwarding…: chrome://inspect
  2. Usa el cuadro de diálogo Port forwarding settings para redireccionar el puerto 8887 a localhost:8887.
  3. Selecciona la casilla de verificación Enable port forwarding:

Configura el reenvío de puertos

Verifica la configuración

Sigue estos pasos para probar la conexión:

  1. Conecta tu dispositivo de RA a la estación de trabajo con un cable USB.
  2. En el dispositivo de RA en Chrome, ingresa http://localhost:8887 en la barra de direcciones. Tu dispositivo debe reenviar esta solicitud al servidor web de la estación de trabajo de desarrollo. Deberías ver un directorio de archivos.
  3. En el dispositivo de RA, haz clic en step-03 para cargar el archivo step-03/index.html en el navegador.

Deberías ver una página que contiene el botón Start augmented reality.

Sin embargo, si aparece una página de error que indica Unsupported browser, es probable que tu dispositivo no sea compatible.

Se admite ARCore

No se admite ARCore

La conexión al servidor web ahora debería funcionar con el dispositivo de RA.

  1. Haz clic en Start augmented reality. Es posible que se te solicite instalar ARCore.

Mensaje para instalar ARCore

Verás una solicitud de permisos para usar la cámara la primera vez que ejecutes una app de RA.

Chrome solicita permisos para usar la cámaraDiálogo de permisos

Una vez que esté todo listo, deberías ver una escena de cubos superpuestos sobre un feed de cámara. La comprensión de la escena mejora a medida que la cámara analiza más el mundo, por lo que moverse puede ayudar a estabilizar el proceso.

3. Configura WebXR

En este paso, aprenderás a configurar una sesión de WebXR y una escena básica de RA. La página HTML cuenta con estilo CSS y JavaScript para habilitar la funcionalidad básica de RA, lo que acelera el proceso de configuración y permite que el codelab se enfoque en las funciones de RA.

La página HTML

Deberás crear una experiencia de RA en una página web tradicional con tecnologías web existentes. En esta experiencia, usarás un lienzo de renderización en pantalla completa, por lo que el archivo HTML debe ser sencillo.

Las funciones de RA requieren un gesto del usuario a fin de iniciarse, por lo que hay algunos componentes de Material Design para mostrar el botón Start AR y el mensaje de navegador no compatible.

El archivo index.html que ya está en el directorio work debe verse de la siguiente manera. Este es un subconjunto del contenido real. No copies el código en tu archivo.

<!-- Don't copy this code into your file! -->
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Building an augmented reality application with the WebXR Device API</title>
    <link rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css">
    <script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>

    <!-- three.js -->
    <script src="https://unpkg.com/three@0.123.0/build/three.js"></script>
    <script src="https://unpkg.com/three@0.123.0/examples/js/loaders/GLTFLoader.js"></script>

    <script src="../shared/utils.js"></script>
    <script src="app.js"></script>
  </head>
  <body>
  <!-- Information about AR removed for brevity. -->

  <!-- Starting an immersive WebXR session requires user interaction. Start the WebXR experience with a simple button. -->
  <a onclick="activateXR()" class="mdc-button mdc-button--raised mdc-button--accent">
    Start augmented reality
  </a>

</body>
</html>

Abre el código JavaScript clave

El punto de partida de la app está en app.js. Este archivo proporciona código estándar para configurar una experiencia de RA.

El directorio de trabajo también incluye el código de la app (app.js).

Comprueba la compatibilidad con WebXR y la RA

Antes de que un usuario pueda trabajar con RA, verifica la existencia de navigator.xr y las funciones de XR necesarias. El objeto navigator.xr es el punto de entrada de la API de WebXR Device, por lo que debe existir si el dispositivo es compatible. También verifica que se admita el modo de sesión "immersive-ar".

Si todo está bien, cuando hagas clic en el botón Enter augmented reality, se intentará crear una sesión de XR. De lo contrario, se llamará a onNoXRDevice() (ubicado en shared/utils.js), que mostrará un mensaje para indicar la incompatibilidad con la RA.

Este código ya está presente en app.js, por lo que no es necesario realizar ningún cambio.

(async function() {
  if (navigator.xr && await navigator.xr.isSessionSupported("immersive-ar")) {
    document.getElementById("enter-ar").addEventListener("click", activateXR)
  } else {
    onNoXRDevice();
  }
})();

Solicita una XRSession

Cuando haces clic en Enter augmented Reality, el código llama a activateXR(). Esta acción inicia la experiencia de RA.

  1. Busca la función activateXR() en app.js. Parte del código no se incluye en esta muestra:
activateXR = async () => {
  // Initialize a WebXR session using "immersive-ar".
  this.xrSession = /* TODO */;

  // Omitted for brevity
}

El punto de entrada a WebXR es a través de XRSystem.requestSession(). Usa el modo immersive-ar para permitir la visualización de contenido renderizado en un entorno real.

  1. Inicializa this.xrSession con el modo "immersive-ar":
activateXR = async () => {
  // Initialize a WebXR session using "immersive-ar".
  this.xrSession = await navigator.xr.requestSession("immersive-ar");

  // ...
}

Inicializa un XRReferenceSpace

Un objeto XRReferenceSpace describe el sistema de coordenadas que se usa para los objetos del mundo virtual. El modo 'local' es más adecuado para una experiencia de RA, con un espacio de referencia que tenga un origen cerca del usuario y un seguimiento estable.

Inicializa this.localReferenceSpace en onSessionStarted() con el siguiente código:

this.localReferenceSpace = await this.xrSession.requestReferenceSpace("local");

Define un bucle de animación

  1. Usa el elemento requestAnimationFrame de XRSession para iniciar un bucle de renderización, similar a window.requestAnimationFrame.

En cada fotograma, se llama a onXRFrame con una marca de tiempo y un XRFrame.

  1. Completa la implementación de onXRFrame. Cuando se dibuja un fotograma, agrega este código a la cola de la próxima solicitud:
// Queue up the next draw request.
this.xrSession.requestAnimationFrame(this.onXRFrame);
  1. Agrega código para configurar el entorno de gráficos. Agrega lo siguiente a la parte inferior de onXRFrame:
// Bind the graphics framebuffer to the baseLayer's framebuffer.
const framebuffer = this.xrSession.renderState.baseLayer.framebuffer;
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebuffer);
this.renderer.setFramebuffer(framebuffer);
  1. Para determinar la pose del usuario, usa XRFrame.getViewerPose(). Este objeto XRViewerPose describe la posición y la orientación del dispositivo en el espacio. También contiene un array de XRView, que describe cada punto de vista desde el que se debe renderizar la escena para que se muestre correctamente en el dispositivo actual. Si bien la RV estereoscópica tiene dos vistas (una para cada ojo), los dispositivos de RA solo tienen una.
    La información de pose.views se usa con mayor frecuencia para configurar la matriz de vista y la matriz de proyección de la cámara virtual, lo que afecta la disposición de la escena en 3D. Cuando se configura la cámara, se puede renderizar la escena.
  2. Agrega lo siguiente a la parte inferior de onXRFrame:
// Retrieve the pose of the device.
// XRFrame.getViewerPose can return null while the session attempts to establish tracking.
const pose = frame.getViewerPose(this.localReferenceSpace);
if (pose) {
  // In mobile AR, we only have one view.
  const view = pose.views[0];

  const viewport = this.xrSession.renderState.baseLayer.getViewport(view);
  this.renderer.setSize(viewport.width, viewport.height);

  // Use the view's transform matrix and projection matrix to configure the THREE.camera.
  this.camera.matrix.fromArray(view.transform.matrix);
  this.camera.projectionMatrix.fromArray(view.projectionMatrix);
  this.camera.updateMatrixWorld(true);

  // Render the scene with THREE.WebGLRenderer.
  this.renderer.render(this.scene, this.camera);
}

Pruébalo

Visita work/index.html en el dispositivo de desarrollo para ejecutar la app. Deberías ver el feed de la cámara con cubos flotando en el espacio, cuya perspectiva cambia cuando mueves el dispositivo. El seguimiento mejora a medida que te desplazas, así que explora qué funciona para ti y el dispositivo.

Si tienes problemas para ejecutar la app, consulta las secciones Introducción y Configura el entorno de desarrollo.

4. Agrega un retículo de objetivo

Ahora que tienes configurada una escena básica de RA, es hora de comenzar a interactuar con el mundo real mediante una prueba de posicionamiento. En esta sección, programarás una prueba de posicionamiento y la usarás para encontrar una superficie en el mundo real.

Información sobre las pruebas de posicionamiento

Por lo general, una prueba de posicionamiento es una forma de proyectar una línea recta desde un punto en el espacio en alguna dirección y determinar si se interseca con algún objeto de interés. En este ejemplo, apuntarás con el dispositivo a una ubicación en el mundo real. Imagina un rayo que viaja desde la cámara de tu dispositivo y llega directamente al mundo físico que está frente a él.

La API de WebXR Device te permite saber si este rayo se cruza con algún objeto del mundo real, determinado por las capacidades de RA subyacentes y por la comprensión del mundo.

Explicación de la prueba de posicionamiento

Solicita un elemento XRSession con atributos adicionales

Para realizar pruebas de posicionamiento, se requieren atributos adicionales cuando se solicita XRSession.

  1. En app.js, busca navigator.xr.requestSession.
  2. Agrega los atributos "hit-test" y "dom-overlay" como requiredFeatures de la siguiente manera:
this.xrSession = await navigator.xr.requestSession("immersive-ar", {
  requiredFeatures: ["hit-test", "dom-overlay"]
});
  1. Configura la superposición del DOM. Coloca el elemento document.body sobre la vista de la cámara de RA de la siguiente manera:
this.xrSession = await navigator.xr.requestSession("immersive-ar", {
  requiredFeatures: ["hit-test", "dom-overlay"],
  domOverlay: { root: document.body }
});

Agrega un mensaje de movimiento

ARCore funciona mejor cuando se crea una comprensión adecuada del entorno. Esto se logra mediante un proceso llamado localización y modelado simultáneos (SLAM) en el que se usan puntos del entorno visualmente distintivos para calcular un cambio en las características de ubicación y entorno.

Usa "dom-overlay" del paso anterior para mostrar un mensaje de movimiento en la parte superior de la transmisión de la cámara.

Agrega un elemento <div> a index.html con el ID stabilization. El elemento <div> muestra una animación a los usuarios que representa el estado de estabilización y les indica que se muevan con su dispositivo para mejorar el proceso de SLAM. Se muestra una vez que el usuario está en la RA y se oculta cuando el retículo encuentra una superficie. La controlan las clases <body>.

  <div id="stabilization"></div>

</body>
</html>

Agrega un retículo

Usa un retículo para indicar la ubicación hacia la que apunta la vista del dispositivo.

  1. En app.js, reemplaza la llamada DemoUtils.createCubeScene() en setupThreeJs() por un elemento Three.Scene() vacío.
setupThreeJs() {
  // ...

  // this.scene = DemoUtils.createCubeScene();
  this.scene = DemoUtils.createLitScene();
}
  1. Propaga la escena nueva con un objeto que represente el punto de colisión. La clase Reticle proporcionada controla la carga del modelo de retículo en shared/utils.js.
  2. Agrega Reticle a la escena en setupThreeJs():
setupThreeJs() {
  // ...

  // this.scene = DemoUtils.createCubeScene();
  this.scene = DemoUtils.createLitScene();
  this.reticle = new Reticle();
  this.scene.add(this.reticle);
}

Para realizar una prueba de posicionamiento, usa un XRReferenceSpace nuevo. Este espacio de referencia indica un nuevo sistema de coordenadas desde la perspectiva del usuario para crear un rayo alineado con la dirección de visualización. Este sistema de coordenadas se usa en XRSession.requestHitTestSource(), que puede procesar pruebas de posicionamiento.

  1. Agrega el siguiente código a onSessionStarted() en app.js:
async onSessionStarted() {
  // ...

  // Setup an XRReferenceSpace using the "local" coordinate system.
  this.localReferenceSpace = await this.xrSession.requestReferenceSpace("local");

  // Add these lines:
  // Create another XRReferenceSpace that has the viewer as the origin.
  this.viewerSpace = await this.xrSession.requestReferenceSpace("viewer");
  // Perform hit testing using the viewer as origin.
  this.hitTestSource = await this.xrSession.requestHitTestSource({ space: this.viewerSpace });

  // ...
}
  1. Con hitTestSource, realiza una prueba de posicionamiento en cada fotograma:
    • Si la prueba de posicionamiento no da resultados, es posible que ARCore no haya tenido tiempo suficiente para comprender el entorno. En ese caso, pídele al usuario que mueva el dispositivo con el elemento <div> de estabilización.
    • Si hay resultados, mueve el retículo a esa ubicación.
  2. Modifica onXRFrame para mover el retículo:
onXRFrame = (time, frame) => {
  // ... some code omitted ...
  this.camera.updateMatrixWorld(true);

  // Add the following:
  const hitTestResults = frame.getHitTestResults(this.hitTestSource);

  if (!this.stabilized && hitTestResults.length > 0) {
    this.stabilized = true;
    document.body.classList.add("stabilized");
  }
  if (hitTestResults.length > 0) {
    const hitPose = hitTestResults[0].getPose(this.localReferenceSpace);

    // update the reticle position
    this.reticle.visible = true;
    this.reticle.position.set(hitPose.transform.position.x, hitPose.transform.position.y, hitPose.transform.position.z)
    this.reticle.updateMatrixWorld(true);
  }
  // More code omitted.
}

Agrega un comportamiento cuando se presione la pantalla

Una XRSession puede emitir eventos en función de la interacción del usuario a través del evento select, que representa la acción principal. En WebXR en dispositivos móviles, la acción principal es presionar la pantalla.

  1. Agrega un objeto de escucha de eventos select a la parte inferior de onSessionStarted:
this.xrSession.addEventListener("select", this.onSelect);

En este ejemplo, presionar la pantalla causa que se ubique un girasol en el retículo.

  1. Crea una implementación para onSelect en la clase App:
onSelect = () => {
  if (window.sunflower) {
    const clone = window.sunflower.clone();
    clone.position.copy(this.reticle.position);
    this.scene.add(clone);
  }
}

Prueba la app

Creaste un retículo que puedes apuntar con tu dispositivo mediante pruebas de posicionamiento. Cuando presiones la pantalla, deberías poder colocar un girasol en la ubicación que indica el retículo.

  1. Cuando ejecutes tu app, deberías poder ver una retículo que traza la superficie del piso. De lo contrario, intenta moverte lentamente con el teléfono.
  2. Cuando veas el retículo, presiónalo. Debería aparecer un girasol sobre él. Es posible que debas moverte un poco para que la plataforma de RA subyacente pueda detectar mejor las superficies del mundo real. Las superficies con poca iluminación y sin atributos disminuyen la calidad de la comprensión de la escena y aumentan las probabilidades de que no aparezca el retículo. Si tienes algún problema, revisa el código step-04/app.js para ver un ejemplo de este paso en funcionamiento.

5. Agrega sombras

Crear una escena realista incluye elementos como una iluminación apropiada y sombras en objetos digitales que agregan realismo y una experiencia envolvente.

three.js controla las luces y las sombras. Puedes especificar qué luces deben proyectar sombras, qué materiales deben recibir y renderizar estas sombras y qué mallas pueden proyectar sombras. La escena de esta app contiene una luz que proyecta una sombra y una superficie plana para renderizar solo sombras.

  1. Habilita las sombras en WebGLRenderer de three.js. Después de crear el renderizador, configura los siguientes valores en su shadowMap:
setupThreeJs() {
  ...
  this.renderer = new THREE.WebGLRenderer(...);
  ...
  this.renderer.shadowMap.enabled = true;
  this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  ...
}

La escena de ejemplo creada en DemoUtils.createLitScene() contiene un objeto llamado shadowMesh, una superficie horizontal plana que solo renderiza sombras. Inicialmente, esta superficie tiene una posición Y de 10,000 unidades. Cuando aparezca un girasol, mueve la shadowMesh para que tenga la misma altura que la superficie del mundo real, de modo que la sombra de la flor se renderice sobre el suelo real.

  1. En onSelect, después de agregar clone a la escena, agrega este código para cambiar la posición del plano de la sombra:
onSelect = () => {
  if (window.sunflower) {
    const clone = window.sunflower.clone();
    clone.position.copy(this.reticle.position);
    this.scene.add(clone);

    const shadowMesh = this.scene.children.find(c => c.name === "shadowMesh");
    shadowMesh.position.y = clone.position.y;
  }
}

Pruébalo

Cuando coloques un girasol, deberías poder ver que proyecta una sombra. Si tienes algún problema, revisa el código final/app.js para ver un ejemplo de este paso en funcionamiento.

6. Recursos adicionales

¡Felicitaciones! Llegaste al final de este codelab sobre RA con WebXR.

Más información