Créer une session de RA immersive à l'aide de WebXR

Cette page vous explique comment créer une application de RA immersive simple à l'aide de WebXR.

Pour commencer, vous avez besoin d'un environnement de développement compatible avec WebXR.

Créer une page HTML

WebXR nécessite une interaction de l'utilisateur pour pouvoir démarrer une session. Créez un bouton qui appelle activateXR(). Une fois la page chargée, l'utilisateur peut utiliser ce bouton pour lancer l'expérience RA.

Créez un fichier appelé index.html et ajoutez-y le code HTML suivant:

<!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>

Initialiser three.js

Il ne se passera pas grand-chose lorsque l'on appuie sur le bouton Démarrer. Pour configurer un environnement 3D, vous pouvez utiliser une bibliothèque de rendu pour afficher une scène.

Dans cet exemple, vous allez utiliser three.js, une bibliothèque de rendu 3D JavaScript qui fournit un moteur de rendu WebGL. Three.js gère le rendu, les caméras et les graphes de scène, ce qui facilite l'affichage de contenus 3D sur le Web.

Créer une scène

Un environnement 3D est généralement modélisé sous forme de scène. Créez un THREE.Scene contenant des éléments RA. Le code suivant vous permet d'afficher une boîte colorée non éclairée en RA.

Ajoutez ce code en bas de la fonction 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);

Configurer le rendu à l'aide de Three.js

Pour afficher cette scène en RA, vous avez besoin d'un moteur de rendu et d'une caméra. Le moteur de rendu utilise WebGL pour dessiner votre scène à l'écran. La caméra décrit la fenêtre d'affichage à partir de laquelle la scène est vue.

Ajoutez ce code en bas de la fonction 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;

Créer une XRSession

Le point d'entrée vers WebXR passe par XRSystem.requestSession(). Utilisez le mode immersive-ar pour pouvoir visualiser le rendu dans un environnement réel.

Un XRReferenceSpace décrit le système de coordonnées utilisé pour les objets du monde virtuel. Le mode 'local' convient mieux à une expérience de RA, car il offre un suivi stable et un espace de référence initial proche du spectateur.

Pour créer un XRSession et un XRReferenceSpace, ajoutez le code suivant en bas de la fonction 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');

Rendre la scène

Vous pouvez maintenant effectuer le rendu de la scène. XRSession.requestAnimationFrame() planifie un rappel qui est exécuté lorsque le navigateur est prêt à dessiner un frame.

Lors du rappel du frame d'animation, appelez XRFrame.getViewerPose() pour obtenir la position de l'utilisateur par rapport à l'espace de coordonnées local. Cette méthode permet de mettre à jour la caméra dans la scène, ce qui modifie la façon dont l'utilisateur voit le monde virtuel avant que le moteur de rendu ne dessine la scène à l'aide de la caméra mise à jour.

Ajoutez ce code en bas de la fonction 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);

Exécuter Hello WebXR

Accédez au fichier WebXR sur votre appareil. Vous devriez voir un cube coloré de tous les côtés.

Ajouter un test de positionnement

Une méthode courante d'interaction avec le monde de la RA consiste à utiliser un test de positionnement, qui permet de trouver l'intersection entre un rayon et une géométrie du monde réel. Dans Hello WebXR, vous allez utiliser un test de positionnement pour placer un tournesol dans le monde virtuel.

Retirer le cube de démonstration

Supprimez le cube non éclairé et remplacez-le par une scène qui inclut un éclairage:

const scene = new THREE.Scene();

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

Utiliser la fonctionnalité hit-test

Pour initialiser la fonctionnalité de test de positionnement, demandez une session avec la fonctionnalité hit-test. Recherchez le fragment requestSession() précédent et ajoutez-lui hit-test:

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

Ajouter un chargeur de modèle

Actuellement, la scène ne contient qu'un cube coloré. Pour rendre l'expérience plus intéressante, ajoutez un chargeur de modèle, qui permet de charger des modèles glTF.

Dans la balise <head> de votre document, ajoutez le 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>

Charger des modèles glTF

Utilisez le chargeur de modèle de l'étape précédente pour charger un réticule de ciblage et un tournesol à partir du Web.

Ajoutez ce code au-dessus 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) => {

Créer une source de test de coup

Pour calculer les intersections avec des objets réels, créez un XRHitTestSource à l'aide de XRSession.requestHitTestSource(). Le rayon utilisé pour le test de collision a l'espace de référence viewer comme origine, ce qui signifie que le test de collision est effectué à partir du centre de la fenêtre d'affichage.

Pour créer une source de test de positionnement, ajoutez ce code après avoir créé l'espace de référence 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 });

Dessiner un réticule de ciblage

Pour indiquer clairement où le tournesol sera placé, ajoutez un réticule de ciblage à la scène. Ce réticule semble adhérer aux surfaces du monde réel, indiquant où le tournesol sera ancré.

XRFrame.getHitTestResults renvoie un tableau de XRHitTestResult et expose les intersections avec la géométrie réelle. Utilisez ces intersections pour positionner le réticule de ciblage sur chaque frame.

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);
}

Ajouter des interactions lorsque l'utilisateur appuie sur l'écran

XRSession reçoit des événements select lorsque l'utilisateur effectue une action principale. Dans une session de RA, cela correspond à un appui sur l'écran.

Pour qu'un nouveau tournesol apparaisse lorsque l'utilisateur appuie sur l'écran, ajoutez ce code lors de l'initialisation:

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);
  }
});

Tester le test de positionnement

Accédez à la page avec votre appareil mobile. Une fois que WebXR a compris l'environnement, le réticule doit apparaître sur les surfaces du monde réel. Appuyez sur l'écran pour placer un tournesol, qui peut être vu de tous les côtés.

Étapes suivantes