Criar uma sessão de RA imersiva usando o WebXR

Nesta página, você vai aprender a criar um aplicativo de RA simples e imersivo usando o WebXR.

Você precisará de um ambiente de desenvolvimento compatível com WebXR para começar.

Criar uma página HTML

O WebXR exige interação do usuário para iniciar uma sessão. Crie um botão que chame activateXR(). Ao carregar a página, o usuário pode usar esse botão para iniciar a experiência de RA.

Crie um novo arquivo chamado index.html e adicione o seguinte 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>

Inicializar o three.js

Não vai acontecer muita coisa ao pressionar o botão "Start". Para configurar um ambiente 3D, use uma biblioteca de renderização para exibir uma cena.

Neste exemplo, você vai usar three.js, uma biblioteca de renderização 3D em JavaScript que fornece um renderizador WebGL. O Three.js processa a renderização, as câmeras e os gráficos de cena, facilitando a exibição de conteúdo 3D na Web.

Criar uma cena

Um ambiente 3D geralmente é modelado como uma cena. Crie uma THREE.Scene que contenha elementos de RA. O código a seguir permite que você veja uma caixa colorida não iluminada em RA.

Adicione este código à parte de baixo da função 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);

Configurar a renderização usando o three.js

Para visualizar essa cena em RA, você vai precisar de um renderizador e uma câmera. O renderizador usa o WebGL para desenhar a cena na tela. A câmera descreve a janela de visualização a partir da qual a cena é visualizada.

Adicione este código à parte de baixo da função 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;

Criar uma XRSession

O ponto de entrada para o WebXR é pelo XRSystem.requestSession(). Use o modo immersive-ar para permitir que o conteúdo renderizado seja visualizado em um ambiente real.

Um XRReferenceSpace descreve o sistema de coordenadas usado para objetos no mundo virtual. O modo 'local' é mais adequado para uma experiência de RA com um espaço de referência que tem uma origem próxima ao espectador e um acompanhamento estável.

Para criar um XRSession e um XRReferenceSpace, adicione este código à parte de baixo da função 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');

Renderizar a cena

Agora você pode renderizar a cena. XRSession.requestAnimationFrame() programa um callback que é executado quando o navegador está pronto para renderizar um frame.

Durante o callback de frame de animação, chame XRFrame.getViewerPose() para receber a pose do espectador em relação ao espaço de coordenadas local. Isso é usado para atualizar a câmera na cena, mudando a forma como o usuário visualiza o mundo virtual antes que o renderizador desenhe a cena usando a câmera atualizada.

Adicione este código à parte de baixo da função 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);

Executar o Hello WebXR

Navegue até o arquivo WebXR no seu dispositivo. Você vai conseguir ver um cubo colorido de todos os lados.

Adicionar um teste de hit

Uma maneira comum de interagir com o mundo da RA é por meio de um teste de hit, que encontra uma interseção entre um raio e a geometria do mundo real. No Hello WebXR, você vai usar um teste de hit para colocar um girassol no mundo virtual.

Remover o cubo de demonstração

Remova o cubo sem iluminação e substitua-o por uma cena que inclua iluminação:

const scene = new THREE.Scene();

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

Usar o recurso hit-test

Para inicializar a funcionalidade de teste de hit, solicite a sessão com o recurso hit-test. Encontre o fragmento requestSession() anterior e adicione hit-test a ele:

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

Adicionar um carregador de modelo

No momento, a cena só tem um cubo colorido. Para tornar a experiência mais interessante, adicione um carregador de modelo, que permite o carregamento de modelos GLTF.

Na tag <head> do documento, adicione 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>

Carregar modelos glTF

Use o loader de modelo da etapa anterior para carregar uma mira de segmentação e uma flor de girassol da Web.

Adicione este código acima 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) => {

Criar uma origem de teste de acerto

Para calcular interseções com objetos reais, crie um XRHitTestSource usando XRSession.requestHitTestSource(). O raio usado para o teste de hit tem o espaço de referência viewer como origem, o que significa que o teste de hit é feito no centro da janela de visualização.

Para criar uma origem de teste de acerto, adicione este código após criar o espaço de referência 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 });

Desenhar um retículo de mira

Para deixar claro onde o girassol será colocado, adicione um retículo de segmentação à cena. Esse retículo vai parecer preso a superfícies do mundo real, significando onde o girassol vai estar ancorado.

XRFrame.getHitTestResults retorna uma matriz de XRHitTestResult e expõe interseções com geometria do mundo real. Use essas interseções para posicionar o retículo de segmentação em cada 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);
}

Como adicionar interações ao toque

O XRSession recebe eventos select quando o usuário conclui uma ação principal. Em uma sessão de RA, isso corresponde a um toque na tela.

Adicione este código durante a inicialização para que uma nova flor de girassol apareça quando o usuário tocar na tela:

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

Testar o teste de hit

Use seu dispositivo móvel para navegar até a página. Depois que o WebXR entender o ambiente, a retícula vai aparecer nas superfícies do mundo real. Toque na tela para colocar um girassol, que pode ser visto de todos os lados.

Próximas etapas