Crea una sesión de RA envolvente con WebXR

En esta página, se te explicará cómo crear una aplicación simple y envolvente de RA con WebXR.

Para comenzar, necesitarás un entorno de desarrollo compatible con WebXR.

Crea una página HTML

WebXR requiere la interacción del usuario para poder iniciar una sesión. Crea un botón que llame a activateXR(). Cuando se carga la página, el usuario puede usar este botón para iniciar la experiencia de RA.

Crea un archivo nuevo llamado index.html y agrégale el siguiente código HTML:

<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <title>Hello WebXR!</title>

  <!-- three.js -->
  <script src="https://unpkg.com/three@0.126.0/build/three.js"></script>
</head>
<body>

<!-- Starting an immersive WebXR session requires user interaction.
    We start this one with a simple button. -->
<button onclick="activateXR()">Start Hello WebXR</button>
<script>
async function activateXR() {
  // Add a canvas element and initialize a WebGL context that is compatible with WebXR.
  const canvas = document.createElement("canvas");
  document.body.appendChild(canvas);
  const gl = canvas.getContext("webgl", {xrCompatible: true});

  // To be continued in upcoming steps.
}
</script>
</body>
</html>

Inicializa 3.js

No sucederá mucho al presionar el botón Inicio. Para configurar un entorno 3D, puedes usar una biblioteca de renderización para mostrar una escena.

En este ejemplo, usarás three.js, una biblioteca de renderización en 3D de JavaScript que proporciona un procesador de WebGL. Three.js controla la renderización, las cámaras y los gráficos de escenas, lo que facilita mostrar contenido 3D en la Web.

Crea una escena

Un entorno 3D generalmente se modela como una escena. Crea un THREE.Scene que contenga elementos de RA. El siguiente código te permite ver un cuadro de color sin iluminación en RA.

Agrega este código al final de la función activateXR():

const scene = new THREE.Scene();

// The cube will have a different color on each side.
const materials = [
  new THREE.MeshBasicMaterial({color: 0xff0000}),
  new THREE.MeshBasicMaterial({color: 0x0000ff}),
  new THREE.MeshBasicMaterial({color: 0x00ff00}),
  new THREE.MeshBasicMaterial({color: 0xff00ff}),
  new THREE.MeshBasicMaterial({color: 0x00ffff}),
  new THREE.MeshBasicMaterial({color: 0xffff00})
];

// Create the cube and add it to the demo scene.
const cube = new THREE.Mesh(new THREE.BoxBufferGeometry(0.2, 0.2, 0.2), materials);
cube.position.set(1, 1, 1);
scene.add(cube);

Cómo configurar el procesamiento con Three.js

Para ver esta escena en RA, necesitarás un renderizador y una cámara. El procesador usa WebGL para dibujar la escena en la pantalla. La cámara describe la ventana de visualización desde la que se observa la escena.

Agrega este código al final de la función activateXR():

// Set up the WebGLRenderer, which handles rendering to the session's base layer.
const renderer = new THREE.WebGLRenderer({
  alpha: true,
  preserveDrawingBuffer: true,
  canvas: canvas,
  context: gl
});
renderer.autoClear = false;

// The API directly updates the camera matrices.
// Disable matrix auto updates so three.js doesn't attempt
// to handle the matrices independently.
const camera = new THREE.PerspectiveCamera();
camera.matrixAutoUpdate = false;

Crea una XRSession

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.

Un objeto XRReferenceSpace describe el sistema de coordenadas que se usa para los objetos dentro 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.

Para crear XRSession y XRReferenceSpace, agrega este código al final de la función activateXR():

// Initialize a WebXR session using "immersive-ar".
const session = await navigator.xr.requestSession("immersive-ar");
session.updateRenderState({
  baseLayer: new XRWebGLLayer(session, gl)
});

// A 'local' reference space has a native origin that is located
// near the viewer's position at the time the session was created.
const referenceSpace = await session.requestReferenceSpace('local');

Renderiza la escena

Ahora puedes renderizar la escena. XRSession.requestAnimationFrame() programa una devolución de llamada que se ejecuta cuando el navegador está listo para dibujar un fotograma.

Durante la devolución de llamada del marco de animación, llama a XRFrame.getViewerPose() para obtener la pose del visor en relación con el espacio de coordenadas locales. Se usa para actualizar la cámara en la escena y cambiar la forma en que el usuario ve el mundo virtual antes de que el renderizador dibuje la escena con la cámara actualizada.

Agrega este código al final de la función activateXR():

// Create a render loop that allows us to draw on the AR view.
const onXRFrame = (time, frame) => {
  // Queue up the next draw request.
  session.requestAnimationFrame(onXRFrame);

  // Bind the graphics framebuffer to the baseLayer's framebuffer
  gl.bindFramebuffer(gl.FRAMEBUFFER, session.renderState.baseLayer.framebuffer)

  // Retrieve the pose of the device.
  // XRFrame.getViewerPose can return null while the session attempts to establish tracking.
  const pose = frame.getViewerPose(referenceSpace);
  if (pose) {
    // In mobile AR, we only have one view.
    const view = pose.views[0];

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

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

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

Ejecuta Hello WebXR

Navega al archivo WebXR en tu dispositivo. Deberías poder ver un cubo de color desde todos los lados.

Agrega una prueba de posicionamiento

Una forma común de interactuar con el mundo de RA es mediante una prueba de posicionamiento, que encuentra una intersección entre un rayo y una geometría del mundo real. En Hello WebXR, usarás una prueba de posicionamiento para colocar un girasol en el mundo virtual.

Quita el cubo de demostración

Quita el cubo sin iluminación y reemplázalo por una escena que incluya iluminación:

const scene = new THREE.Scene();

const directionalLight = new THREE.DirectionalLight(0xffffff, 0.3);
directionalLight.position.set(10, 15, 10);
scene.add(directionalLight);

Usa la función hit-test

Para inicializar la funcionalidad de prueba de posicionamiento, solicita una sesión con la función hit-test. Busca el fragmento requestSession() anterior y agrégale hit-test:

const session = await navigator.xr.requestSession("immersive-ar", {requiredFeatures: ['hit-test']});

Cómo agregar un cargador de modelos

Actualmente, la escena solo contiene un cubo de color. Para que la experiencia sea más interesante, agrega un cargador de modelos que permita cargar modelos GLTF.

En la etiqueta <head> de tu documento, agrega three.js' GLTFLoader.

<!-- three.js -->
<script src="https://unpkg.com/three@0.126.0/build/three.js"></script>

<script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script>

Carga modelos GLTF

Usa el cargador de modelos del paso anterior para cargar un retículo de objetivo y un girasol de la Web.

Agrega este código encima de onXRFrame:

const loader = new THREE.GLTFLoader();
let reticle;
loader.load("https://immersive-web.github.io/webxr-samples/media/gltf/reticle/reticle.gltf", function(gltf) {
  reticle = gltf.scene;
  reticle.visible = false;
  scene.add(reticle);
})

let flower;
loader.load("https://immersive-web.github.io/webxr-samples/media/gltf/sunflower/sunflower.gltf", function(gltf) {
  flower = gltf.scene;
});

// Create a render loop that allows us to draw on the AR view.
const onXRFrame = (time, frame) => {

Crea una fuente de prueba de posicionamiento

Para calcular intersecciones con objetos del mundo real, crea un objeto XRHitTestSource con XRSession.requestHitTestSource(). El rayo utilizado para las pruebas de posicionamiento tiene el espacio de referencia viewer como origen, lo que significa que la prueba de posicionamiento se realiza desde el centro del viewport.

Para crear una fuente de prueba de posicionamiento, agrega este código después de crear el espacio de referencia local:

// A 'local' reference space has a native origin that is located
// near the viewer's position at the time the session was created.
const referenceSpace = await session.requestReferenceSpace('local');

// Create another XRReferenceSpace that has the viewer as the origin.
const viewerSpace = await session.requestReferenceSpace('viewer');
// Perform hit testing using the viewer as origin.
const hitTestSource = await session.requestHitTestSource({ space: viewerSpace });

Dibujar un retículo de objetivo

Para dejar en claro dónde colocarás el girasol, agrega un retículo de objetivo a la escena. Este retículo parecerá adherirse a superficies del mundo real, lo que indica dónde estará anclado el girasol.

XRFrame.getHitTestResults muestra un array de XRHitTestResult y expone intersecciones con geometría del mundo real. Usa estas intersecciones para posicionar el retículo de objetivo en cada fotograma.

camera.projectionMatrix.fromArray(view.projectionMatrix);
camera.updateMatrixWorld(true);

const hitTestResults = frame.getHitTestResults(hitTestSource);
if (hitTestResults.length > 0 && reticle) {
  const hitPose = hitTestResults[0].getPose(referenceSpace);
  reticle.visible = true;
  reticle.position.set(hitPose.transform.position.x, hitPose.transform.position.y, hitPose.transform.position.z)
  reticle.updateMatrixWorld(true);
}

Agregando interacciones al presionar

XRSession recibe eventos select cuando el usuario completa una acción principal. En una sesión de RA, esto corresponde a un toque en la pantalla.

Para que aparezca un nuevo girasol cuando el usuario presione la pantalla, agrega este código durante la inicialización:

let flower;
loader.load("https://immersive-web.github.io/webxr-samples/media/gltf/sunflower/sunflower.gltf", function(gltf) {
  flower = gltf.scene;
});

session.addEventListener("select", (event) => {
  if (flower) {
    const clone = flower.clone();
    clone.position.copy(reticle.position);
    scene.add(clone);
  }
});

Prueba la prueba de posicionamiento

Usa tu dispositivo móvil para navegar a la página. Después de que WebXR comprenda el entorno, el retículo debería aparecer en superficies del mundo real. Presiona la pantalla para colocar un girasol, que se puede ver desde todos lados.

Próximos pasos