A realidade virtual chega à Web

Confira alguns princípios básicos para prepará-lo para um espectro de 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 traz a realidade virtual e o suporte à realidade aumentada chega ao Chrome 81. Embora uma atualização da API GamePad estenda 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 Helio do Magic Leap, entre outros.

Este artigo dá início a uma série sobre a Web imersiva. Esta parcela aborda a configuração de um aplicativo WebXR básico, bem como a entrada e a saída de uma sessão XR. Os próximos artigos vão abordar o loop de frames (o ponto de trabalho da experiência do WebXR), os detalhes da realidade aumentada e a API WebXR Hit Test, um meio de detecção de superfícies em uma sessão de RA. A menos que indicado de outra forma, tudo o que for abordado neste artigo e nos próximos artigos se aplica igualmente a RA e RV.

O que é a Web imersiva?

Embora usemos dois termos para descrever experiências imersivas (realidade aumentada e realidade virtual), muitos pensam nelas em um espectro da realidade completa até completamente virtual, com graus de imersão no meio. O "X" em XR reflete esse pensamento como uma espécie de variável algébrica que representa qualquer coisa no espectro de experiências imersivas.

Um gráfico ilustrando o espectro de experiências visuais, de realidade completa a totalmente imersiva.
O espectro de experiências imersivas

Confira 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
  • Conferir os produtos na sua casa antes de comprá-los
  • Arte imersiva
  • Algo legal que ninguém pensou ainda

Conceitos e uso

Vou explicar os fundamentos do uso da API WebXR Device. Se você precisar de mais detalhes do que eu forneci, confira os exemplos do WebXR do Immersive Web Working Group ou os materiais de referência em crescimento da MDN (links em inglês). Caso você já conheça as versões anteriores da API WebXR Device, confira todo esse material. Houve mudanças.

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

Parte da criação da especificação WebXR foi desenvolver as medidas de segurança e privacidade para proteger os usuários. Consequentemente, as implementações precisam aderir a determinados requisitos. Uma página da Web ou um app precisa estar ativo e focado antes de solicitar informações sensíveis ao visualizador. As páginas da Web ou os apps precisam ser exibidos por HTTPS. A API em si foi projetada para proteger informações recebidas de sensores e câmeras, necessárias para o funcionamento.

Solicitar uma sessão

A entrada em uma sessão XR requer um gesto do usuário. Para isso, use a detecção de recursos para testar XRSystem (via navigator.xr) e faça uma chamada para XRSystem.isSessionSupported(). Lembre-se de que, nas versões 79 e 80 do Chrome, o objeto XRSystem era chamado de XR.

No exemplo abaixo, eu 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 inline destina-se à apresentação de conteúdo em HTML e é usada principalmente para conteúdo de teaser. O exemplo de Sessão de RA imersiva (link em inglês) demonstra isso. Vou explicar isso em um artigo posterior.

Depois de saber que as sessões de realidade virtual têm suporte, ativo um botão que permite adquirir 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 nesse código. Ele muda de navigator para xr para uma instância de XRSession. Nas primeiras versões da API, um script precisava solicitar um dispositivo antes de uma sessão. Agora, o dispositivo é adquirido implicitamente.

Insira uma sessão

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

Também vou precisar de um elemento <canvas> para desenhar minha cena. Ele precisa ser um WebGLRenderingContext ou WebGL2RenderingContext compatível com XR. Todo o desenho é feito usando esses elementos ou um framework baseado em WebGL, como o Three.js.

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

Quando estou em uma sessão, preciso de uma forma de determinar onde as coisas estão na realidade virtual. Preciso 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 se espera que ele se mova. Há outros tipos de espaços de referência, mas esse é um tópico mais complicado do que eu posso abordar aqui. Salvo o espaço de referência em uma variável porque vou precisar dele quando 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 frames é um loop infinito controlado pelo user agent em que o conteúdo é desenhado repetidamente na tela. O conteúdo é desenhado em blocos discretos chamados de frames. A sucessão de frames cria a ilusão de movimento. Para aplicativos de RV, os quadros por segundo podem variar de 60 a 144. A RA no Android é executada a 30 quadros por segundo. Seu código não deve presumir nenhum frame rate específico.

O processo básico para o loop de frames é:

  1. Chame XRSession.requestAnimationFrame(). Em resposta, o user agent invoca o XRFrameRequestCallback, que é definido por você.
  2. Dentro da função de callback:
    1. Chame XRSession.requestAnimationFrame() de novo.
    2. Identifique a pose do espectador.
    3. Transmita ("bind") o WebGLFramebuffer do XRWebGLLayer para o WebGLRenderingContext.
    4. Itere em cada objeto XRView, recuperando o XRViewport do XRWebGLLayer e transmitindo-o para o WebGLRenderingContext.
    5. Desenhe algo no framebuffer.

O restante deste artigo descreve as etapas 1 e parte da etapa 2, como configurar e chamar o 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: uma DOMHighResTimeStamp e uma instância de XRFrame. O objeto XRFrame fornece as informações necessárias para renderizar um único frame para a exibição. 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 até lá, vou mostrar como encerrar uma sessão.

Encerrar a sessão

Uma sessão imersiva pode ser encerrada por vários motivos, incluindo ser encerrada com seu próprio código usando uma chamada para XRSession.end(). Outras causas incluem a desconexão do fone de ouvido ou outro aplicativo assumindo o controle. É 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. Não é possível retomar uma sessão imersiva finalizada. Para voltar à experiência imersiva, meu app precisa iniciar uma nova sessão.

Lembre-se de Como entrar em uma sessão que, 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 de o usuário entrar 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 Web XR ou RA. Espero ter dado a você o suficiente para começar a entender o código por conta própria e para começar a experimentar. No próximo artigo, vou explicar o loop de frames, que é onde o conteúdo é desenhado na tela.

Foto de JESHOOTS.COM no Unsplash