A realidade virtual chega à Web

Algumas noções básicas para você se preparar para um espectro de experiências imersivas: realidade virtual, realidade aumentada e tudo mais.

Joe Medley
Joe Medley

As experiências imersivas chegaram à Web no Chrome 79. A API WebXR Device traz a 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 vão oferecer suporte a 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 no meio. 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

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
  • Visualizar produtos na sua casa antes de comprar
  • 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 deste artigo é baseado no exemplo básico do Grupo de trabalho da Web imersiva (demonstração, fonte), mas foi editado para maior clareza e simplicidade.

Parte da criação da especificação do WebXR foi dar forma a medidas de segurança e privacidade para proteger os usuários. Consequentemente, as implementações precisam obedecer a determinados 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 foi projetada para proteger as informações obtidas de sensores e câmeras, que ela precisa 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 inline é usada para apresentar conteúdo no HTML e é usada principalmente para conteúdo de teaser. 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, aguardo 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. Ele passa de navigator para xr para uma instância XRSession. Nas versões anteriores da API, um script precisava solicitar um dispositivo antes de solicitar uma sessão. Agora, o dispositivo é adquirido implicitamente.

Entrar em uma sessão

Depois de receber uma sessão, preciso iniciar e entrar nela. 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 redefinida 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. 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 espectador, o eixo y é 0 no nível do chão e não se move. 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 ao 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 de 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 para o loop de frames é o seguinte:

  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. Posicione o 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.
}

Agora é hora de desenhar algo para o espectador. Essa é uma discussão para a parte II. Antes disso, 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 a desconexão do fone de ouvido ou o controle de outro aplicativo. É por isso que um aplicativo bem comportado precisa monitorar o evento end. Quando isso ocorre, 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ê comece a entender 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