A realidade virtual chega à Web

Confira algumas noções básicas que vão preparar você para diversas experiências imersivas: realidade virtual, realidade aumentada e muito mais.

Joe Medley
Joe Medley

As experiências imersivas chegaram à Web no Chrome 79. A API WebXR Device oferece a realidade virtual trazida pela realidade virtual, enquanto o suporte à realidade aumentada chega no Chrome 81. Enquanto uma atualização da API GamePad estende o uso avançado de controles para RV. Outros navegadores serão compatíveis com essas especificações em breve, incluindo Firefox Reality, Oculus Browser, Edge e o navegador Helio da Magic Leap, entre outros.

Este artigo é o primeiro de uma série sobre a Web imersiva. Esta parte aborda como configurar um aplicativo WebXR básico e como entrar e sair de uma sessão de XR. Os próximos artigos vão abordar o loop de frames (o principal recurso da experiência do WebXR), as especificidades da realidade aumentada e a API WebXR Hit Test, uma maneira de detectar superfícies em uma sessão de RA. A menos que indicado de outra forma, tudo o que abordo neste e nos próximos artigos se aplica igualmente à RA e à RV.

O que é a Web imersiva?

Embora usemos dois termos para descrever experiências imersivas (realidade aumentada e realidade virtual), muitas pessoas os consideram em um espectro que vai da realidade completa à completamente virtual, com graus de imersão entre eles. O "X" em XR tem a intenção de refletir esse pensamento sendo uma espécie de variável algébrica que representa qualquer coisa no espectro de experiências imersivas.

Um gráfico que ilustra o espectro de experiências visuais, da realidade completa à imersiva.
O espectro de experiências imersivas

Estes são alguns exemplos de experiências imersivas:

  • Jogos
  • Vídeos em 360°
  • Vídeos tradicionais em 2D (ou 3D) apresentados em ambientes imersivos
  • Compra de imóveis
  • Confira os produtos na sua casa antes de comprá-los
  • Arte imersiva
  • Algo legal que ninguém pensou ainda

Conceitos e uso

Vou explicar alguns conceitos básicos do uso da API WebXR Device. Se você precisar de mais detalhes do que os que eu forneci, confira os exemplos de WebXR do Grupo de Trabalho da Web imersiva ou os materiais de referência do MDN. Se você já conhece as versões anteriores da API WebXR Device, leia todo este material. Houve mudanças.

O código neste artigo é baseado no exemplo barebones do Immersive Web Working Group (demonstração, fonte), mas foi editado para maior clareza e simplicidade.

Parte da criação da especificação do WebXR foi desenvolver medidas de segurança e privacidade para proteger os usuários. Consequentemente, as implementações precisam aderir a certos requisitos. Uma página da Web ou um app precisam estar ativos e focados antes de solicitar qualquer informação sensível do usuário. As páginas da Web ou os apps precisam ser veiculados por HTTPS. A API em si foi projetada para proteger as informações recebidas de sensores e câmeras, necessárias para funcionar.

Solicitar uma sessão

Para entrar em uma sessão de XR, é necessário um gesto do usuário. Para isso, use a detecção de recursos para testar XRSystem (usando navigator.xr) e fazer uma chamada para XRSystem.isSessionSupported(). Nas versões 79 e 80 do Chrome, o objeto XRSystem era chamado de XR.

No exemplo abaixo, indiquei que quero uma sessão de realidade virtual com o tipo de sessão 'immersive-vr'. Os outros tipos de sessão são 'immersive-ar' e 'inline'. Uma sessão in-line serve para apresentar conteúdo em HTML e é usada principalmente para teasers. O exemplo de sessão de RA imersiva demonstra isso. Vou explicar isso em um artigo posterior.

Depois de saber que as sessões de realidade virtual são compatíveis, ativei um botão que permite que eu colete um gesto do usuário.

if (navigator.xr) {
  const supported = await navigator.xr.isSessionSupported('immersive-vr');
  if (supported) {
    xrButton.addEventListener('click', onButtonClicked);
    xrButton.textContent = 'Enter VR';
    xrButton.enabled = supported; // supported is Boolean
  }
}

Depois de ativar o botão, espero por um evento de clique e solicito uma sessão.

let xrSession = null;
function onButtonClicked() {
  if (!xrSession) {
    navigator.xr.requestSession('immersive-vr')
    .then((session) => {
      xrSession = session;
      xrButton.textContent = 'Exit XR';
      onSessionStarted(xrSession);
    });
  } else {
    xrSession.end();
  }
}

Observe a hierarquia de objetos neste código. Ela é movida de navigator para xr para uma instância de XRSession. Nas versões anteriores da API, um script precisava solicitar um dispositivo antes de solicitar uma sessão. Agora, o dispositivo é adquirido implicitamente.

Insira uma sessão

Depois de iniciar uma sessão, preciso iniciá-la e acessá-la. Mas primeiro, preciso configurar algumas coisas. Uma sessão precisa de um manipulador de eventos onend para que o aplicativo ou a página da Web possa ser redefinido quando o usuário sair.

Também vou precisar de um elemento <canvas> para desenhar minha cena. Ele precisa ser um WebGLRenderingContext ou WebGL2RenderingContext compatível com XR. Todas as exibições são feitas usando esses elementos ou um framework baseado em WebGL, como Three.js.

Agora que tenho um lugar para desenhar, preciso de uma fonte de conteúdo para desenhar nele. Para isso, criei uma instância de XRWebGLLayer. Eu o associo à tela chamando XRSession.updateRenderState().

Quando estou em uma sessão, preciso de uma maneira de determinar onde as coisas estão na realidade virtual. Vou precisar de um espaço de referência. Um espaço de referência 'local-floor' é aquele em que a origem está localizada perto do visualizador e o eixo y é 0 no nível do andar, e não é esperado que ele se mova. Há outros tipos de espaços de referência, mas esse é um tópico mais complicado do que posso abordar aqui. Eu salvo o espaço de referência em uma variável porque vou precisar dele quando eu desenhar na tela.

function onSessionStarted(xrSession) {
  xrSession.addEventListener('end', onSessionEnded);

  let canvas = document.createElement('canvas');
  webGLRenContext = canvas.getContext('webgl', { xrCompatible: true });

  xrSession.updateRenderState({
    baseLayer: new XRWebGLLayer(xrSession, webGLRenContext)
  });

  xrSession.requestReferenceSpace('local-floor')
  .then((refSpace) => {
    xrRefSpace = refSpace;
    xrSession.requestAnimationFrame(onXRFrame);
  });
}

Depois de receber um espaço de referência, chamo XRSession.requestAnimationFrame(). Esse é o início da apresentação de conteúdo virtual, que é feito no loop de frames.

Executar um loop de frame

O loop de frame é um loop infinito controlado pelo user agent em que o conteúdo é exibido repetidamente na tela. O conteúdo é exibido em blocos discretos chamados frames. A sucessão de frames cria a ilusão de movimento. Para aplicativos de RV, os frames por segundo podem variar de 60 a 144. A RA para Android é executada a 30 quadros por segundo. Seu código não deve assumir nenhuma taxa de quadros específica.

O processo básico do loop de frame é:

  1. Ligue para a XRSession.requestAnimationFrame(). Em resposta, o agente do usuário invoca o XRFrameRequestCallback, que é definido por você.
  2. Dentro da função de callback:
    1. Ligue para XRSession.requestAnimationFrame() novamente.
    2. Mostre a pose do espectador.
    3. Transmita ('bind') o WebGLFramebuffer do XRWebGLLayer para o WebGLRenderingContext.
    4. Itere cada objeto XRView, recuperando o XRViewport do XRWebGLLayer e transmitindo-o ao WebGLRenderingContext.
    5. Desenhe algo no framebuffer.

O restante deste artigo descreve a etapa 1 e parte da etapa 2, a configuração e a chamada da XRFrameRequestCallback. Os itens restantes da etapa 2 são abordados na Parte II.

O XRFrameRequestCallback

O XRFrameRequestCallback é definido por você. Ele usa dois parâmetros: um DOMHighResTimeStamp e uma instância XRFrame. O objeto XRFrame fornece as informações necessárias para renderizar um único frame na tela. O argumento DOMHighResTimeStamp é para uso futuro.

Antes de fazer qualquer outra coisa, vou solicitar o próximo frame de animação. Como mencionado anteriormente, o tempo dos frames é determinado pelo user agent com base no hardware. Solicitar o próximo frame primeiro garante que o loop de frames continue se algo durante o callback gerar um erro.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  // Render a frame.
}

Neste ponto, é hora de desenhar algo para o espectador. Essa é uma discussão para a parte II. Antes de ir lá, vou mostrar como encerrar uma sessão.

Encerrar a sessão

Uma sessão imersiva pode ser encerrada por vários motivos, incluindo o encerramento pelo seu próprio código por uma chamada para XRSession.end(). Outras causas incluem o fone de ouvido ser desconectado ou outro app assumindo o controle dele. É por isso que um aplicativo bem comportado precisa monitorar o evento end. Quando isso ocorrer, descarte a sessão e os objetos de renderização relacionados. Uma sessão imersiva encerrada não pode ser retomada. Para entrar novamente na experiência imersiva, meu app precisa iniciar uma nova sessão.

Você se lembra que, em Como entrar em uma sessão, durante a configuração, adicionei um manipulador de eventos onend.

function onSessionStarted(xrSession) {
  xrSession.addEventListener('end', onSessionEnded);
  // More setup…
}

No manipulador de eventos, restaure o estado do app antes que o usuário entre em uma sessão.

function onSessionEnded(event) {
  xrSession = null;
  xrButton.textContent = 'Enter VR';
}

Conclusão

Não expliquei tudo o que você precisa para criar um aplicativo de RA ou XR da Web. Espero ter fornecido informações suficientes para que você entenda o código e comece a fazer experimentos. No próximo artigo, vou explicar o loop de frames, que é onde o conteúdo é exibido na tela.

Foto de JESHOOTS.COM no Unsplash