Criar um app de realidade aumentada (RA) usando a API WebXR Device

1. Antes de começar

Este codelab aborda um exemplo de como criar um app da Web de RA. Ele usa JavaScript para renderizar modelos 3D como no mundo real.

Use a API WebXR Device, que combina funcionalidades de RA e realidade virtual (RV). Você pode utilizar as extensões de RA na API WebXR Device para criar um app de RA simples que será executado na Web interativa.

O que é RA?

RA é um termo geralmente usado para descrever a combinação do mundo real com gráficos criados por computador. No caso da RA baseada em smartphone, isso significa posicionar elementos gráficos convincentes em um feed de câmera ao vivo. Para que esse efeito permaneça realista conforme o smartphone se move, o dispositivo com RA precisa entender a movimentação do dispositivo e determinar a posição dele (posição e orientação) no espaço 3D. Isso pode incluir a detecção de superfícies e estimativas da iluminação do ambiente.

A RA passou a ser muito usada em apps após o lançamento do ARCore e do ARKit da Apple, seja para filtros de selfie ou jogos baseados em RA.

O que você vai criar

Neste codelab, você criará um app da Web que projeta um modelo no mundo real usando a realidade aumentada. Esse app vai:

  1. usar os sensores do dispositivo de destino para determinar e rastrear a posição e a orientação dele no mundo real;
  2. renderizar um modelo 3D baseado em uma visualização da câmera em tempo real;
  3. fazer testes de hit para colocar objetos em superfícies descobertas no mundo real.

O que você vai aprender

  • Como usar a API WebXR Device
  • Como configurar uma cena de RA básica
  • Como encontrar uma superfície usando testes de hit de RA
  • Como carregar e renderizar um modelo 3D sincronizado com o feed da câmera do mundo real
  • Como renderizar sombras com base no modelo 3D

Este codelab é focado em APIs de RA. Resumiremos conceitos e blocos de código sem relevância no código do repositório correspondente.

Pré-requisitos

Clique em Testar no seu dispositivo de RA para usar a primeira etapa deste codelab. Se você receber uma página com a mensagem "Seu navegador não tem recursos de RA", verifique se o Google Play Services para RA está instalado no dispositivo Android.

2. Configurar seu ambiente de desenvolvimento

Fazer o download do código

  1. Clique no link abaixo para fazer o download de todo o código deste codelab na sua estação de trabalho:

  1. Descompacte o arquivo ZIP transferido por download. Isso descompacta uma pasta raiz (ar-with-webxr-master), que contém diretórios de várias etapas deste codelab com todos os recursos necessários.

As pastas step-03 e step-04 contêm o estado final desejado da terceira e quarta etapas deste codelab, bem como o resultado final. Elas servem como referência.

Todo o trabalho de codificação vai ser feito no diretório work.

Instalar o servidor da Web

  1. É possível usar seu próprio servidor da Web. Se você ainda não tiver um configurado, veja nesta seção como configurar o servidor da Web para Chrome.
    Se você ainda não tiver o app instalado na sua estação de trabalho, poderá instalá-lo pela Chrome Web Store.

  1. Depois de instalar o app do servidor da Web para Chrome, acesse chrome://apps e clique no ícone do servidor da Web:

Ícone do servidor da Web

Você verá esta caixa de diálogo, que permite configurar seu servidor da Web local:

Configurar o servidor da Web do Chrome

  1. Clique em Escolher pasta e selecione a pasta ar-with-webxr-master. Isso permite que seu trabalho seja detalhado pelo URL destacado na caixa de diálogo do servidor da Web (na seção URLs do servidor da Web).
  2. Em Opções (precisa ser reiniciado), marque a caixa de seleção Mostrar automaticamente index.html.
  3. Alterne o Servidor da Web para Parado e depois para Iniciado.Reiniciar o servidor da Web do Chrome
  4. Verifique se pelo menos um URL do servidor da Web é exibido: http://127.0.0.1:8887, que é o URL do host local padrão.

Configurar o encaminhamento de portas

Configure seu dispositivo de RA para que ele acesse a mesma porta na estação de trabalho quando você abrir o localhost:8887.

  1. Na estação de trabalho de desenvolvimento, acesse chrome://inspect e clique em Encaminhamento de portas...: chrome://inspect
  2. Use a caixa de diálogo Configurações de encaminhamento de portas para encaminhar a porta 8887 para localhost:8887.
  3. Marque a caixa de seleção Ativar encaminhamento de portas:

Configurar o encaminhamento de portas

Verificar sua configuração

Testar sua conexão:

  1. Conecte seu dispositivo de RA à estação de trabalho usando um cabo USB.
  2. No dispositivo de RA no Chrome, digite http://localhost:8887 na barra de endereço. Seu dispositivo de RA deve encaminhar essa solicitação ao seu servidor da Web da estação de trabalho de desenvolvimento. Você verá um diretório de arquivos.
  3. No dispositivo de RA, clique em step-03 para carregar o arquivo step-03/index.html no navegador.

Você verá uma página que contém um botão Iniciar realidade aumentada.

Caso apareça a página de erro Navegador incompatível, é provável que o dispositivo não seja aceito.

O ARCore é compatível

O ARCore não é compatível

A conexão com o servidor da Web funcionará com seu dispositivo de RA.

  1. Clique em Iniciar realidade aumentada. Talvez seja necessário instalar o ARCore.

Instalar a solicitação do ARCore

Você verá uma solicitação de permissões da câmera na primeira vez que executar um app de RA.

Permissões de câmera solicitadas pelo ChromeCaixa de diálogo de permissões

Quando estiver tudo certo, você verá uma cena de cubos sobreposta no topo do feed da câmera. A compreensão de cena melhora conforme mais paisagens são analisadas pela câmera. Dessa forma, movimentar-se pode ajudar a estabilizar o quadro.

3. Configurar o WebXR

Nesta etapa, você aprenderá a configurar uma sessão do WebXR e uma cena de RA básica. A página HTML tem o estilo CSS e o JavaScript para ativar a funcionalidade básica de RA. Isso acelera o processo de configuração, permitindo que o codelab se concentre nos recursos de RA.

Página HTML

Você cria uma experiência de RA em uma página da Web tradicional usando as tecnologias atuais da Web. Nesta experiência, use uma tela de renderização em tela cheia. Portanto, o arquivo HTML não precisa ter muita complexidade.

Os recursos de RA exigem um gesto do usuário para iniciar. Portanto, há alguns componentes do Material Design para exibir o botão Iniciar RA e a mensagem do navegador incompatível.

O arquivo index.html que já está no diretório work precisa ser semelhante ao arquivo abaixo. Ele é um subconjunto do conteúdo real, então não copie esse código no seu arquivo.

<!-- Don't copy this code into your file! -->
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Building an augmented reality application with the WebXR Device API</title>
    <link rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css">
    <script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>

    <!-- three.js -->
    <script src="https://unpkg.com/three@0.123.0/build/three.js"></script>
    <script src="https://unpkg.com/three@0.123.0/examples/js/loaders/GLTFLoader.js"></script>

    <script src="../shared/utils.js"></script>
    <script src="app.js"></script>
  </head>
  <body>
  <!-- Information about AR removed for brevity. -->

  <!-- Starting an immersive WebXR session requires user interaction. Start the WebXR experience with a simple button. -->
  <a onclick="activateXR()" class="mdc-button mdc-button--raised mdc-button--accent">
    Start augmented reality
  </a>

</body>
</html>

Abrir o código JavaScript principal

O ponto de partida do seu app é o app.js. Esse arquivo contém um código clichê para configurar uma experiência de RA.

Seu diretório de trabalho também inclui o código do app (app.js).

Verificar a compatibilidade com WebXR e RA

Antes que um usuário possa usar a RA, verifique a existência do navigator.xr e os recursos XR necessários. O objeto navigator.xr é o ponto de entrada da API WebXR Device, então ele precisa existir se o dispositivo for compatível. Além disso, verifique se o modo de sessão "immersive-ar" é aceito.

Se tudo estiver certo, clicar no botão Abrir realidade aumentada tentará gerar uma sessão de XR. Caso contrário, o método onNoXRDevice() será chamado (em shared/utils.js), que exibirá uma mensagem indicando que não há compatibilidade com RA.

Esse código já está presente no app.js. Por isso, não é necessário fazer nenhuma alteração.

(async function() {
  if (navigator.xr && await navigator.xr.isSessionSupported("immersive-ar")) {
    document.getElementById("enter-ar").addEventListener("click", activateXR)
  } else {
    onNoXRDevice();
  }
})();

Solicitar uma XRSession

Quando você clica em Abrir realidade aumentada, o código chama activateXR(). Isso inicia a experiência de RA.

  1. Localize a função activateXR() no app.js. Parte do código foi omitida:
activateXR = async () => {
  // Initialize a WebXR session using "immersive-ar".
  this.xrSession = /* TODO */;

  // Omitted for brevity
}

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.

  1. Inicialize this.xrSession usando o modo "immersive-ar":
activateXR = async () => {
  // Initialize a WebXR session using "immersive-ar".
  this.xrSession = await navigator.xr.requestSession("immersive-ar");

  // ...
}

Inicializar um XRReferenceSpace

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.

Inicialize o this.localReferenceSpace no onSessionStarted() com o seguinte código:

this.localReferenceSpace = await this.xrSession.requestReferenceSpace("local");

Definir um loop de animação

  1. Use o requestAnimationFrame de XRSession para iniciar um loop de renderização, semelhante a window.requestAnimationFrame.

Em cada frame, o onXRFrame é chamado com um carimbo de data/hora e um XRFrame.

  1. Conclua a implementação do onXRFrame. Quando um frame for criado, enfileire a próxima solicitação adicionando:
// Queue up the next draw request.
this.xrSession.requestAnimationFrame(this.onXRFrame);
  1. Adicione um código para configurar o ambiente gráfico. Inclua no fim de onXRFrame:
// Bind the graphics framebuffer to the baseLayer's framebuffer.
const framebuffer = this.xrSession.renderState.baseLayer.framebuffer;
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebuffer);
this.renderer.setFramebuffer(framebuffer);
  1. Para determinar a posição do espectador, use XRFrame.getViewerPose(). Este XRViewerPose descreve a posição e a orientação do dispositivo no espaço. Ele também contém uma matriz de XRViews, que mostra todos os pontos de vista onde a cena precisa ser renderizada para ser exibida corretamente no dispositivo atual. Embora a RV estereoscópica tenha duas visualizações, uma para cada olho, os dispositivos de RA só têm uma.
    : as informações no pose.views são mais usadas para configurar a matriz de visualização e a de projeção da câmera virtual. Isso afeta o layout da cena em 3D. Quando a câmera é configurada, a cena pode ser renderizada.
  2. Inclua no fim de onXRFrame:
// Retrieve the pose of the device.
// XRFrame.getViewerPose can return null while the session attempts to establish tracking.
const pose = frame.getViewerPose(this.localReferenceSpace);
if (pose) {
  // In mobile AR, we only have one view.
  const view = pose.views[0];

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

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

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

Realizar o teste

Execute o app. No dispositivo de desenvolvimento, acesse work/index.html. Você verá o feed da câmera com cubos flutuando no espaço em que a perspectiva muda à medida que o dispositivo se move. O rastreamento melhora o movimento. Por isso, explore o que funciona para você e seu aparelho.

Se você tiver problemas para executar o app, consulte as seções Introdução e Configurar seu ambiente de desenvolvimento.

4. Adicionar um retículo de foco

Com um cenário básico de RA configurado, é hora de começar a interagir com o mundo real usando um teste de hit. Nesta seção, você vai programar um teste de hit e usá-lo para encontrar uma superfície no mundo real.

Entender o teste de hit

Um teste de hit geralmente é uma maneira de converter uma linha reta de um ponto no espaço em alguma direção e determinar se ela cruza com objetos de interesse. Neste exemplo, o dispositivo é apontado para um local no mundo real. Imagine um raio indo da câmera do seu dispositivo direto para o mundo físico na frente dele.

Com a API WebXR Device, você pode saber se esse raio cruzou qualquer objeto no mundo real, determinado pelos recursos de RA subjacentes e pela compreensão do mundo.

Explicação do teste de hit

Solicitar uma XRSession com recursos extras

Para realizar testes de hit, são necessários recursos adicionais ao solicitar a XRSession.

  1. No app.js, localize navigator.xr.requestSession.
  2. Adicione os recursos "hit-test" e "dom-overlay" como requiredFeature da seguinte maneira:
this.xrSession = await navigator.xr.requestSession("immersive-ar", {
  requiredFeatures: ["hit-test", "dom-overlay"]
});
  1. Configure a sobreposição DOM. Sobreponha o elemento document.body na visualização da câmera de RA da seguinte maneira:
this.xrSession = await navigator.xr.requestSession("immersive-ar", {
  requiredFeatures: ["hit-test", "dom-overlay"],
  domOverlay: { root: document.body }
});

Adicionar uma solicitação de movimento

O ARCore funciona melhor quando há o entendimento adequado do ambiente. Isso é viabilizado por um processo chamado localização e mapeamento simultâneos (SLAM, na sigla em inglês) em que pontos de recursos visualmente distintos são usados para calcular uma mudança nas características de localização e do ambiente.

Use o "dom-overlay" da etapa anterior para exibir uma solicitação de movimento na parte superior do stream da câmera.

Adicione um <div> ao index.html com stabilization de ID. Esse <div> exibe uma animação para os usuários que representa o status de estabilização e solicita que eles se movam com o dispositivo para melhorar o processo de SLAM. A animação é exibida quando o usuário está na RA e ocultada quando o retículo encontra uma superfície, controlado pelas classes <body>.

  <div id="stabilization"></div>

</body>
</html>

Adicionar um retículo

Use um retículo para indicar o local para que a visualização do dispositivo está apontada.

  1. No app.js, substitua a chamada DemoUtils.createCubeScene() no setupThreeJs() por um Three.Scene() vazio.
setupThreeJs() {
  // ...

  // this.scene = DemoUtils.createCubeScene();
  this.scene = DemoUtils.createLitScene();
}
  1. Preencha a nova cena com um objeto que representa o ponto de colisão. A classe Reticle indicada gerencia o carregamento do modelo de retículo no shared/utils.js.
  2. Adicione o Reticle à cena no setupThreeJs():
setupThreeJs() {
  // ...

  // this.scene = DemoUtils.createCubeScene();
  this.scene = DemoUtils.createLitScene();
  this.reticle = new Reticle();
  this.scene.add(this.reticle);
}

Para realizar um teste de hit, use um novo XRReferenceSpace. Esse espaço de referência indica um novo sistema de coordenadas com base na perspectiva do visualizador para criar um raio alinhado à direção de visualização. Esse sistema de coordenadas é usado na XRSession.requestHitTestSource(), que pode calcular testes de hit.

  1. Adicione o seguinte a onSessionStarted() no app.js:
async onSessionStarted() {
  // ...

  // Setup an XRReferenceSpace using the "local" coordinate system.
  this.localReferenceSpace = await this.xrSession.requestReferenceSpace("local");

  // Add these lines:
  // Create another XRReferenceSpace that has the viewer as the origin.
  this.viewerSpace = await this.xrSession.requestReferenceSpace("viewer");
  // Perform hit testing using the viewer as origin.
  this.hitTestSource = await this.xrSession.requestHitTestSource({ space: this.viewerSpace });

  // ...
}
  1. Usando esse hitTestSource, execute um teste de hit em cada quadro:
    • Se não houver resultados para o teste de hit, isso significa que o ARCore não teve tempo suficiente para entender o ambiente. Nesse caso, solicite que o usuário mova o dispositivo usando o <div> de estabilização.
    • Se houver resultados, movimente o retículo para esse local.
  2. Modifique onXRFrame para deslocar o retículo:
onXRFrame = (time, frame) => {
  // ... some code omitted ...
  this.camera.updateMatrixWorld(true);

  // Add the following:
  const hitTestResults = frame.getHitTestResults(this.hitTestSource);

  if (!this.stabilized && hitTestResults.length > 0) {
    this.stabilized = true;
    document.body.classList.add("stabilized");
  }
  if (hitTestResults.length > 0) {
    const hitPose = hitTestResults[0].getPose(this.localReferenceSpace);

    // update the reticle position
    this.reticle.visible = true;
    this.reticle.position.set(hitPose.transform.position.x, hitPose.transform.position.y, hitPose.transform.position.z)
    this.reticle.updateMatrixWorld(true);
  }
  // More code omitted.
}

Adicionar comportamento de toque na tela

Uma XRSession pode emitir eventos com base na interação do usuário com base no evento select, que representa a ação principal. No WebXR em dispositivos móveis, a ação principal é um toque na tela.

  1. Adicione um listener de eventos select na parte inferior de onSessionStarted:
this.xrSession.addEventListener("select", this.onSelect);

Neste exemplo, um toque de tela faz com que um girassol seja colocado no retículo.

  1. Crie uma implementação para onSelect na classe App.
onSelect = () => {
  if (window.sunflower) {
    const clone = window.sunflower.clone();
    clone.position.copy(this.reticle.position);
    this.scene.add(clone);
  }
}

Testar o app

Você criou um retículo que pode ser usado com testes de hit. Ao tocar na tela, um girassol será posicionado no local designado pelo retículo.

  1. Quando você executar o app, verá um retículo que acompanha a superfície do chão. Se não, tente girar o smartphone lentamente.
  2. Quando vir o retículo, toque nele. Um girassol deve aparecer sobre ele. Talvez seja necessário se mover um pouco para que a plataforma de RA subjacente consiga detectar melhor as superfícies do mundo real. Pouca iluminação e superfícies sem recursos diminuem a qualidade do entendimento da cena e aumentam a chance de nenhum hit ser encontrado. Se você encontrar algum problema, confira o código step-04/app.js para ver um exemplo prático desta etapa.

5. Adicionar sombras

Criar uma cena realista envolve elementos como iluminação e sombras adequadas em objetos digitais que adicionam realismo e imersão.

A iluminação e as sombras são processadas pelo three.js. Você pode especificar quais luzes devem projetar sombras, quais materiais devem receber e renderizar essas sombras e quais malhas podem criá-las. A cena desse app contém uma luz que gera uma sombra e uma superfície plana para renderizar apenas sombras.

  1. Ative as sombras no WebGLRenderer do three.js. Depois de criar o renderizador, defina os seguintes valores no shadowMap:
setupThreeJs() {
  ...
  this.renderer = new THREE.WebGLRenderer(...);
  ...
  this.renderer.shadowMap.enabled = true;
  this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  ...
}

O exemplo de cena criado no DemoUtils.createLitScene() contém um objeto chamado shadowMesh, que é uma superfície plana horizontal que renderiza somente sombras. Inicialmente, essa superfície tem uma posição Y de 10.000 unidades. Depois que um girassol é colocado, mova o shadowMesh para a mesma altura da superfície real de modo que a sombra da flor seja renderizada sobre o solo do mundo real.

  1. Em onSelect, depois de adicionar clone à cena, inclua um código para reposicionar o plano de sombra:
onSelect = () => {
  if (window.sunflower) {
    const clone = window.sunflower.clone();
    clone.position.copy(this.reticle.position);
    this.scene.add(clone);

    const shadowMesh = this.scene.children.find(c => c.name === "shadowMesh");
    shadowMesh.position.y = clone.position.y;
  }
}

Realizar o teste

Ao colocar um girassol, você deve vê-lo lançando uma sombra. Em caso de problemas, confira o código final/app.js para ver um exemplo prático desta etapa.

6. Outros recursos

Parabéns! Você chegou ao fim deste codelab sobre RA usando o WebXR.

Saiba mais