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. Ela usa a câmera para criar imagens de profundidade ou mapas de profundidade, adicionando uma camada de realismo de RA aos seus apps. Você pode usar as informações fornecidas por uma imagem de profundidade para que os objetos virtuais apareçam com precisão em frente ou atrás de objetos do mundo real, possibilitando 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 informações de um sensor de hardware de profundidade, como um sensor de tempo de voo (ToF, na sigla em inglês), se disponível. Um dispositivo não precisa de um sensor ToF para oferecer suporte à API Depth.

Pré-requisitos

Entenda os conceitos básicos 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 exigir suporte à API Depth, seja porque uma parte principal da experiência de RA depende da profundidade ou porque não há um fallback adequado para as partes do app que usam profundidade, você pode restringir a distribuição do app na Google Play Store a dispositivos compatíveis com a API Depth.

Criar seu app Depth Required

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

Depth é definido como Required por padrão.

Criar seu app 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 opcional.

Ativar a profundidade

Para economizar recursos, o ARCore não ativa a API Depth por padrão. Para aproveitar a profundidade em dispositivos com suporte, adicione manualmente o componente AROcclusionManager ao objeto de jogo Câmera de RA com o componente Camera e ARCameraBackground. Consulte Ocultação automática na documentação do Unity para mais informações.

Em uma nova sessão do ARCore, verifique se o dispositivo do usuário oferece suporte à profundidade e à API Depth, conforme mostrado abaixo:

// 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.
}

Adquirir imagens de profundidade

Acesse a imagem de profundidade mais recente do ambiente no 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 maior flexibilidade. Um exemplo de como fazer isso pode ser encontrado nas amostras de ARFoundation do Unity.

Entender os valores de profundidade

Dado o ponto A na geometria do mundo 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. Isso também pode ser chamado de coordenada z de A em relação à origem da câmera C. Ao trabalhar com a API Depth, é importante entender que os valores de profundidade não são o comprimento do raio CA em si, mas a projeção dele.

Ocultar objetos virtuais e visualizar dados de profundidade

Confira a postagem do blog da Unity 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, as amostras de ARFoundation do Unity demonstram como ocultar imagens virtuais e visualizar dados de profundidade.

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

Renderização por objeto e de passagem para frente

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

Renderização em duas etapas

Com a renderização em duas etapas, a primeira renderiza todo o conteúdo virtual em um buffer intermediário. A segunda passagem mescla a cena virtual no plano de fundo com base na diferença entre a profundidade do mundo real e a profundidade da cena virtual. Essa abordagem não requer trabalho adicional de sombreador específico do objeto e geralmente produz resultados mais uniformes do que o método de passagem para frente.

Extrair a distância de uma imagem de profundidade

Para usar a API Depth para outros fins que não sejam ocultar objetos virtuais ou visualizar 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?