Crea una sesión de RA envolvente con WebXR

En esta página, se te guiará para crear una aplicación de RA envolvente simple 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>

Cómo inicializar three.js

No sucederá mucho cuando presiones el botón de 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 3D de JavaScript que proporciona un renderizador WebGL. Three.js controla la renderización, las cámaras y los gráficos de escenas, lo que facilita la visualización de contenido 3D en la Web.

Crea una escena

Por lo general, un entorno 3D 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 a la parte inferior 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);

Configura la renderización con three.js

Para poder ver esta escena en RA, necesitarás un renderizador y una cámara. El renderizador usa WebGL para dibujar tu escena en la pantalla. La cámara describe el viewport desde el que se ve la escena.

Agrega este código a la parte inferior 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 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.

Para crear un XRSession y un XRReferenceSpace, agrega este código a la parte inferior 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 marco.

Durante la devolución de llamada del fotograma de animación, llama a XRFrame.getViewerPose() para obtener la pose del usuario en relación con el espacio de coordenadas local. Se usa para actualizar la cámara en la escena, lo que cambia 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 a la parte inferior 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 ver un cubo de color desde todos los lados.

Cómo agregar una prueba de posicionamiento

Una forma común de interactuar con el mundo de la RA es a través de una prueba de intersección, que encuentra una intersección entre un rayo y la 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 hit, solicita la sesión con el atributo hit-test. Busca el fragmento requestSession() anterior y agrégale hit-test:

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

Agrega 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 permite cargar modelos GLTF.

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

<!-- 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 segmentación y un girasol desde la Web.

Agrega este código antes 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 hit

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

Para crear una fuente de prueba de hit, 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 });

Cómo dibujar un retículo de objetivo

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

XRFrame.getHitTestResults muestra un array de XRHitTestResult y expone intersecciones con la geometría del mundo real. Usa estas intersecciones para posicionar el retículo de segmentación 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);
}

Cómo agregar interacciones con el toque

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

Agrega este código durante la inicialización para que aparezca un nuevo girasol cuando el usuario presione la pantalla:

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 las superficies del mundo real. Presiona la pantalla para colocar un girasol, que se puede ver desde todos los lados.

Próximos pasos