# Create an immersive AR session using WebXR

This page will guide you through creating a simple immersive AR application using WebXR.

You'll need [a WebXR-compatible development environment](https://developers.google.com/ar/develop/webxr/environment) to get started.

## Create an HTML page

WebXR requires user interaction to be able to start a session.
Create a button that calls `activateXR()`. Upon loading the page, the user can use this button to start the AR experience.

Make a new file called `index.html` and add the following HTML code to it:

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

## Initialize three.js

Not much will happen when pressing the Start button. To set up a 3D environment, you can use a rendering library to display a scene.

In this example, you will use [`three.js`](https://threejs.org/), a JavaScript 3D rendering library that provides a WebGL renderer. `Three.js` handles rendering, cameras, and scene graphs, making it easier to display 3D content on the web.

> [!NOTE]
> **Note:** Though it is possible to use a different WebGL rendering library, `three.js` makes it simple to follow this WebXR tutorial.

### Create a scene

A 3D environment is generally modelled as a scene. Create a [`THREE.Scene`](https://threejs.org/docs/#api/en/scenes/Scene) that contains AR elements.
The following code allows you to look at an unlit colored box in AR.

Add this code to the bottom of the `activateXR()` function:

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

### Set up rendering using three.js

To be able to view this scene in AR, you'll need a [renderer](https://threejs.org/docs/#api/en/renderers/WebGLRenderer) and a [camera](https://threejs.org/docs/#api/en/cameras/PerspectiveCamera).
The renderer uses WebGL to draw your scene to the screen. The camera describes the viewport from which the scene is viewed.

Add this code to the bottom of the `activateXR()` function:

    // 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;

## Create an XRSession

The entrypoint to WebXR is through [`XRSystem.requestSession()`](https://developer.mozilla.org/en-US/docs/Web/API/XRSystem/requestSession). Use the `immersive-ar` mode to allow rendered content to be viewed in a real-world environment.

An [`XRReferenceSpace`](https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpace)
describes the coordinate system used for objects within the virtual world.
The `'local'` mode is best suited for an AR experience, with a reference space that has an origin near the viewer and stable tracking.

To create an `XRSession` and `XRReferenceSpace`, add this code to the bottom of the `activateXR()` function:

    // 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');

## Render the scene

Now you can render the scene. [`XRSession.requestAnimationFrame()`](https://developer.mozilla.org/en-US/docs/Web/API/XRSession/requestAnimationFrame)
schedules a callback which is executed when the browser is ready to draw a frame.

> [!WARNING]
> **Warning:** Though `Window` has a method of the same name, they are not interchangeable. Use `XRSession.requestAnimationFrame()` during XR sessions.

During the animation frame callback, call [`XRFrame.getViewerPose()`](https://developer.mozilla.org/en-US/docs/Web/API/XRFrame/getViewerPose)
to obtain the viewer's pose relative to the local coordinate space.
This is used to update the in-scene camera, changing how the user views the virtual world before the renderer draws the scene using the updated camera.

Add this code to the bottom of the `activateXR()` function:

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

## Run Hello WebXR

Navigate to the WebXR file on your device. You should be able to view a colored cube from all sides.

## Add a hit test

A common way of interacting with the AR world is through a **hit test**, which
finds an intersection between a ray and real-world geometry.
In Hello WebXR, you'll use a hit test to place a sunflower in the virtual world.

### Remove the demo cube

Remove the [unlit cube](https://developers.google.com/ar/develop/webxr/hello-webxr#create_a_scene) and replace it with a scene that includes lighting:

    const scene = new THREE.Scene();

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

### Use the `hit-test` feature

To initialize hit test functionality, request session with the `hit-test` feature. Find the previous `requestSession()` fragment, and add `hit-test` to it:

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

### Add a model loader

Currently, the scene only contains a colored cube. To make the experience more interesting,
add a model loader, which allows [GLTF models](https://en.wikipedia.org/wiki/GlTF) to be loaded.

In your document's `<head>` tag, add [three.js' `GLTFLoader`](https://threejs.org/docs/#examples/en/loaders/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\>

### Load GLTF models

Use the model loader from the previous step to load a targeting reticle and a sunflower from the web.

Add this code above `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) => {

> [!NOTE]
> **Note:** Error handling is left out of this tutorial for brevity. See [GLTFLoader.load](https://threejs.org/docs/#examples/en/loaders/GLTFLoader.load) for `onProgress` and `onError` handlers.

### Create a hit test source

To calculate intersections with real-world objects, create a `XRHitTestSource` using
[`XRSession.requestHitTestSource()`](https://github.com/immersive-web/hit-test/blob/master/hit-testing-explainer.md#requesting-a-hit-test-source).
The ray used for hit testing has the `viewer` reference space as origin, meaning that the hit test is done from the center of the viewport.

To create a hit test source, add this code after creating the `local` reference space:

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

### Drawing a targeting reticle

To make it clear where the sunflower will be placed, add a targeting reticle to the scene.
This reticle will appear to stick to real-world surfaces, signifying where the sunflower will be anchored.

`XRFrame.getHitTestResults` returns an array of `XRHitTestResult` and exposes intersections with real-world geometry.
Use these intersections to position the targeting reticle on every 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);
    }

### Adding interactions on tap

`XRSession` receives `select` events when the user completes a [primary action](https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API/Inputs#Primary_actions).
In an AR session, this corresponds to a tap on the screen.

Make a new sunflower appear when the user taps on the screen by adding this code during initialization:

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

### Test the hit test

Use your mobile device to navigate to the page. After WebXR builds an understanding of the environment, the reticle should appear on real-world surfaces. Tap the screen to place a sunflower, which can be viewed from all sides.

## Next steps

- Read the [WebXR Device API Specification](https://github.com/immersive-web/webxr).
- Check out [WebXR Reference documentation on MDN web docs](https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API).
- Try out [WebXR Samples](https://immersive-web.github.io/webxr-samples/).
- [Build an augmented reality (AR) app using the WebXR Device API](https://codelabs.developers.google.com/ar-with-webxr) (codelab).