Usar profundidade no app Android AR Foundation

A API Depth ajuda a câmera de um dispositivo a entender o tamanho e a forma dos objetos reais em uma cena. Ele usa a câmera para criar imagens ou mapas de profundidade, adicionando uma camada de realismo de RA aos seus apps. É possível usar as informações fornecidas por uma imagem de profundidade para que objetos virtuais apareçam com precisão na frente ou atrás de objetos do mundo real, permitindo experiências do usuário imersivas e realistas.

As informações de profundidade são calculadas com base no movimento e podem ser combinadas com as informações de um sensor de profundidade de hardware, como um sensor de tempo de voo (ToF), se disponível. Um dispositivo não precisa de um sensor ToF para oferecer suporte à API Depth.

Pré-requisitos

Verifique se você entendeu os conceitos fundamentais de RA e como configurar uma sessão do ARCore antes de continuar.

Configurar o app para ser Depth Required ou Depth Optional (somente Android)

Se o app precisa de suporte para a API Depth, porque uma parte essencial da experiência de RA depende da profundidade ou porque não há um substituto otimizado para as partes que usam esse recurso, você pode optar por restringir a distribuição do app na Google Play Store a dispositivos com suporte à API Depth.

Transforme seu app em Depth Required

Navegue para Edit > Project Settings > XR Plug-in Management > ARCore.

Depth é definido como Required por padrão.

Transforme seu app em Depth Optional

  1. Navegue para Edit > Project Settings > XR Plug-in Management > ARCore.

  2. No menu suspenso Depth, selecione Optional para definir um app como "Profundidade" opcional.

Ativar profundidade

Para economizar recursos, o ARCore não ativa a API Depth por padrão. Para aproveitar a profundidade em dispositivos com suporte, é necessário adicionar manualmente o componente AROcclusionManager ao objeto de jogo da Câmera RA com os componentes Camera e ARCameraBackground. Consulte Oclusão automática (link em inglês) na documentação do Unity para saber mais.

Em uma nova sessão do ARCore, verifique se o dispositivo do usuário oferece suporte à profundidade e à API Depth da seguinte maneira:

// Reference to AROcclusionManager that should be added to the AR Camera
// game object that contains the Camera and ARCameraBackground components.
var occlusionManager = …

// Check whether the user's device supports the Depth API.
if (occlusionManager.descriptor?.supportsEnvironmentDepthImage)
{
    // If depth mode is available on the user's device, perform
    // the steps you want here.
}

Conseguir imagens de profundidade

Acesse a imagem de profundidade do ambiente mais recente do AROcclusionManager.

// Reference to AROcclusionManager that should be added to the AR Camera
// game object that contains the Camera and ARCameraBackground components.
var occlusionManager = …

if (occlusionManager.TryAcquireEnvironmentDepthCpuImage(out XRCpuImage image))
{
    using (image)
    {
        // Use the texture.
    }
}

É possível converter a imagem bruta da CPU em um RawImage para ter mais flexibilidade. Um exemplo de como fazer isso pode ser encontrado nos exemplos de ARFoundation do Unity (link em inglês).

Entender os valores de profundidade

Considerando o ponto A na geometria real observada e um ponto 2D a que representa o mesmo ponto na imagem de profundidade, o valor fornecido pela API Depth em a é igual ao comprimento de CA projetado no eixo principal. Ela também pode ser chamada de coordenada z de A em relação à origem C da câmera. Ao trabalhar com a API Depth, é importante entender que os valores de profundidade não são o comprimento da CA de raio em si, mas a projeção dela.

Oculte objetos virtuais e visualize dados de profundidade

Confira a postagem do blog do Unity (link em inglês) para ter uma visão geral de alto nível dos dados de profundidade e como eles podem ser usados para ocultar imagens virtuais. Além disso, os exemplos de ARFoundation do Unity (em inglês) demonstram a oclusão de imagens virtuais e a visualização de dados de profundidade.

É possível renderizar a oclusão usando a renderização em duas passagens ou a renderização de passagem direta por objeto. A eficiência de cada abordagem depende da complexidade do cenário e de outras considerações específicas do app.

Renderização por objeto, passagem para frente

A renderização de passagem direta por objeto determina a oclusão de cada pixel do objeto no sombreador do material. Se os pixels não estiverem visíveis, eles serão cortados, geralmente usando a mistura alfa, simulando a oclusão no dispositivo do usuário.

Renderização em duas etapas

Com a renderização em duas etapas, a primeira passagem renderiza todo o conteúdo virtual em um buffer intermediário. O segundo passe combina a cena virtual com o segundo plano com base na diferença entre a profundidade do mundo real e a profundidade da cena virtual. Essa abordagem não exige outro trabalho de sombreador específico do objeto e geralmente produz resultados de aparência mais uniforme do que o método de passagem direta.

Extrair distância de uma imagem de profundidade

Para usar a API Depth para fins que não sejam a ocultação de objetos virtuais ou a visualização de dados de profundidade, extraia informações da imagem de profundidade.

Texture2D _depthTexture;
short[] _depthArray;

void UpdateEnvironmentDepthImage()
{
  if (_occlusionManager &&
        _occlusionManager.TryAcquireEnvironmentDepthCpuImage(out XRCpuImage image))
    {
        using (image)
        {
            UpdateRawImage(ref _depthTexture, image, TextureFormat.R16);
            _depthWidth = image.width;
            _depthHeight = image.height;
        }
    }
  var byteBuffer = _depthTexture.GetRawTextureData();
  Buffer.BlockCopy(byteBuffer, 0, _depthArray, 0, byteBuffer.Length);
}

// Obtain the depth value in meters at a normalized screen point.
public static float GetDepthFromUV(Vector2 uv, short[] depthArray)
{
    int depthX = (int)(uv.x * (DepthWidth - 1));
    int depthY = (int)(uv.y * (DepthHeight - 1));

    return GetDepthFromXY(depthX, depthY, depthArray);
}

// Obtain the depth value in meters at the specified x, y location.
public static float GetDepthFromXY(int x, int y, short[] depthArray)
{
    if (!Initialized)
    {
        return InvalidDepthValue;
    }

    if (x >= DepthWidth || x < 0 || y >= DepthHeight || y < 0)
    {
        return InvalidDepthValue;
    }

    var depthIndex = (y * DepthWidth) + x;
    var depthInShort = depthArray[depthIndex];
    var depthInMeters = depthInShort * MillimeterToMeter;
    return depthInMeters;
}

O que vem em seguida?