A API Hit Test permite posicionar itens virtuais em uma visualização do mundo real.
A API WebXR Device foi lançada no outono passado no Chrome 79. Conforme declarado, a implementação da API no Chrome ainda está em andamento. O Chrome tem o prazer de anunciar que parte do trabalho foi concluído. No Chrome 81, dois novos recursos foram lançados:
Este artigo aborda a API WebXR Hit Test, uma maneira de colocar objetos virtuais em uma visualização de câmera do mundo real.
Neste artigo, suponho que você já sabe como criar uma sessão de realidade aumentada e como executar um loop de frame. Se você não conhece esses conceitos, leia os artigos anteriores desta série.
- A realidade virtual chegou à Web
- A realidade virtual chegou à Web, parte II
- RA na Web: você já sabe como usar
Exemplo de sessão de RA imersiva
O código neste artigo é baseado no exemplo do teste de hit do grupo de trabalho da Web imersivo (demo, fonte, em inglês), mas não é idêntico. Neste exemplo, você pode colocar girassóis virtuais em superfícies do mundo real.
Quando você abrir o app pela primeira vez, vai aparecer um círculo azul com um ponto no meio. O ponto é a interseção entre uma linha imaginária do dispositivo até o ponto no ambiente. Ele se move conforme você move o dispositivo. À medida que encontra pontos de interseção, ele parece se encaixar em superfícies como pisos, mesas e paredes. Isso acontece porque o teste de acerto fornece a posição e a orientação do ponto de interseção, mas nada sobre as superfícies em si.
Esse círculo é chamado de retículo, que é uma imagem temporária que ajuda a colocar um objeto na realidade aumentada. Se você tocar na tela, um girassol será colocado na superfície no local e na orientação do retículo independentemente de onde você tocou na tela. O retículo continua se movendo com o dispositivo.
Criar a retícula
Você precisa criar a imagem da retícula, já que ela não é fornecida pelo
navegador ou pela API. O método de carregamento e renderização é específico do framework.
Se você não estiver desenhando diretamente usando o WebGL ou o WebGL2, consulte a documentação do
framework. Por esse motivo, não vou entrar em detalhes sobre como a retícula é
desenhada na amostra. Abaixo, mostro uma linha dele por um motivo: para que, em
exemplos de código posteriores, você saiba a que me refiro quando uso a variável
reticle
.
let reticle = new Gltf2Node({url: 'media/gltf/reticle/reticle.gltf'});
Solicitar uma sessão
Ao solicitar uma sessão, você precisa solicitar 'hit-test'
na
matriz requiredFeatures
, conforme mostrado abaixo.
navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
// Do something with the session
});
Entrar em uma sessão
Em artigos anteriores, apresentei um código para entrar em uma sessão de XR. Eu mostrei uma versão
disso abaixo com algumas adições. Primeiro, adicionei o listener de eventos
select
. Quando o usuário toca na tela, uma flor é colocada na visualização
da câmera com base na pose do retículo. Vou descrever esse ouvinte de evento mais adiante.
function onSessionStarted(xrSession) {
xrSession.addEventListener('end', onSessionEnded);
xrSession.addEventListener('select', onSelect);
let canvas = document.createElement('canvas');
gl = canvas.getContext('webgl', { xrCompatible: true });
xrSession.updateRenderState({
baseLayer: new XRWebGLLayer(session, gl)
});
xrSession.requestReferenceSpace('viewer').then((refSpace) => {
xrViewerSpace = refSpace;
xrSession.requestHitTestSource({ space: xrViewerSpace })
.then((hitTestSource) => {
xrHitTestSource = hitTestSource;
});
});
xrSession.requestReferenceSpace('local').then((refSpace) => {
xrRefSpace = refSpace;
xrSession.requestAnimationFrame(onXRFrame);
});
}
Vários espaços de referência
Observe que o código destacado chama XRSession.requestReferenceSpace()
duas vezes. No começo, isso me pareceu confuso. Perguntei por que o código do teste de acerto não
solicita um frame de animação (iniciando o loop de frames) e por que o loop de frames
parece não envolver testes de acerto. A origem da confusão foi um mal-entendido
dos espaços de referência. Os espaços de referência expressam relações
entre uma origem e o mundo.
Para entender o que esse código está fazendo, imagine que você está vendo este exemplo usando uma plataforma independente e que você tem um fone de ouvido e um controle. Para medir distâncias do controlador, use um frame de referência centralizado no controlador. Mas, para desenhar algo na tela, você usaria coordenadas centradas no usuário.
Neste exemplo, o visualizador e o controlador são o mesmo dispositivo. Mas tenho um problema. O que eu desenho precisa ser estável em relação ao ambiente, mas o 'controlador' que estou usando está se movendo.
Para o desenho de imagem, uso o espaço de referência local
, que oferece estabilidade
em termos de ambiente. Depois de receber isso, inicie o loop de frame chamando
requestAnimationFrame()
.
Para o teste de hit, uso o espaço de referência viewer
, que é baseado na
postura do dispositivo no momento do teste de hit. O rótulo "visualizador" é um pouco confuso
neste contexto, porque estamos falando de um controlador. Faz sentido
pensar no controlador como um leitor eletrônico. Depois de receber isso, chamo xrSession.requestHitTestSource()
, que cria a fonte dos dados do teste
de hit que vou usar ao desenhar.
Como executar um loop de frame
O callback requestAnimationFrame()
também recebe um novo código para processar o teste de hit.
À medida que você move o dispositivo, o retículo precisa se mover com ele para tentar encontrar
superfícies. Para criar a ilusão de movimento, desenhe o retículo em cada frame.
Mas não mostre a retícula se o teste de acerto falhar. Para o retículo que criei
anteriormente, defini a propriedade visible
como false
.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
Para desenhar algo em RA, preciso saber onde o espectador está e para onde ele está
olhando. Então, teste se hitTestSource
e xrViewerPose
ainda são válidos.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
Agora chamo getHitTestResults()
. Ele usa o hitTestSource
como argumento
e retorna uma matriz de instâncias HitTestResult
. O teste de hit pode encontrar
várias plataformas. A primeira na matriz é a mais próxima da câmera.
Na maioria das vezes, você vai usar, mas uma matriz é retornada para casos de uso
avançados. Por exemplo, imagine que sua câmera está apontada para uma caixa em uma mesa em um
piso. É possível que o teste de hit retorne as três superfícies no
array. Na maioria dos casos, será a caixa que me interessa. Se o comprimento da
matriz retornada for 0, ou seja, se nenhum teste de acerto for retornado, continue
em frente. Tente de novo no frame seguinte.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
Por fim, preciso processar os resultados do teste de acerto. O processo básico é o seguinte. Receba
uma pose do resultado do teste de acerto, transforme (mova) a imagem do retículo para a posição
do teste de acerto e defina a propriedade visible
como verdadeira. A pose representa a
posição de um ponto em uma superfície.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.matrix = pose.transform.matrix;
reticle.visible = true;
}
}
// Draw to the screen
}
Colocar um objeto
Um objeto é colocado em RA quando o usuário toca na tela. Já adicionei um manipulador de eventos select
à sessão. Veja acima.
O importante nessa etapa é saber onde colocá-lo. Como a retícula em movimento fornece uma fonte constante de testes de acerto, a maneira mais simples de colocar um objeto é desenhá-lo no local da retícula no último teste de acerto.
function onSelect(event) {
if (reticle.visible) {
// The reticle should already be positioned at the latest hit point,
// so we can just use its matrix to save an unnecessary call to
// event.frame.getHitTestResults.
addARObjectAt(reticle.matrix);
}
}
Conclusão
A melhor maneira de entender isso é seguir o código de exemplo ou testar o codelab. Espero ter fornecido informações suficientes para que você entenda os dois.
Ainda não terminamos de criar APIs da Web imersiva, nem de longe. Publicaremos novos artigos aqui à medida que progredirmos.
Foto de Daniel Frank no Unsplash