A API Depth ajuda a câmera de um dispositivo a entender o tamanho e o formato dos objetos reais em uma cena. Ele usa a câmera para criar imagens ou mapas de profundidade, adicionando uma camada de realismo em RA aos apps. É possível usar as informações fornecidas por uma imagem de profundidade para fazer com 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
Entenda os conceitos fundamentais de RA. e como configurar uma sessão do ARCore antes de continuar.
Restringir o acesso a dispositivos com suporte a profundidade
Se o app precisar de compatibilidade com a API Depth, seja porque uma parte central do
A experiência de RA depende da profundidade ou porque não há um substituto eficiente para a
partes do app que usam profundidade, você pode optar por restringir a distribuição
app na Google Play Store para
dispositivos compatíveis com a API Depth, adicionando
a linha a seguir ao AndroidManifest.xml
, além do
AndroidManifest.xml
de mudanças descritas nos
Guia Ativar ARCore:
<uses-feature android:name="com.google.ar.core.depth" />
Ativar profundidade
Em uma nova sessão do ARCore, confira se o dispositivo do usuário é compatível com a Profundidade. Nem todos os dispositivos compatíveis com ARCore têm suporte à API Depth devido a restrições de capacidade de processamento. Para economizar recursos, a profundidade é desativada por padrão no ARCore. Ative o modo de profundidade para que o app use a API Depth.
// Check whether the user's device supports the Depth API. int32_t is_depth_supported = 0; ArSession_isDepthModeSupported(ar_session, AR_DEPTH_MODE_AUTOMATIC, &is_depth_supported); // Configure the session for AR_DEPTH_MODE_AUTOMATIC. ArConfig* ar_config = NULL; ArConfig_create(ar_session, &ar_config); if (is_depth_supported) { ArConfig_setDepthMode(ar_session, ar_config, AR_DEPTH_MODE_AUTOMATIC); } CHECK(ArSession_configure(ar_session, ar_config) == AR_SUCCESS); ArConfig_destroy(ar_config);
Obter imagens de profundidade
Chame ArFrame_acquireDepthImage16Bits()
para ver a imagem de profundidade do frame atual.
// Retrieve the depth image for the current frame, if available. ArImage* depth_image = NULL; // If a depth image is available, use it here. if (ArFrame_acquireDepthImage16Bits(ar_session, ar_frame, &depth_image) != AR_SUCCESS) { // No depth image received for this frame. // This normally means that depth data is not available yet. // Depth data will not be available if there are no tracked // feature points. This can happen when there is no motion, or when the // camera loses its ability to track objects in the surrounding // environment. return; }
A imagem retornada fornece o buffer de imagem bruta, que pode ser transmitido a um sombreador de fragmento para uso na GPU para cada objeto renderizado a ser ocultado. Ele é orientado em AR_COORDINATES_2D_OPENGL_NORMALIZED_DEVICE_COORDINATES
e pode ser alterado para AR_COORDINATES_2D_TEXTURE_NORMALIZED
chamando ArFrame_transformCoordinates2d()
. Quando a imagem de profundidade estiver acessível em um sombreador de objeto, essas medições de profundidade poderão ser acessadas diretamente para o tratamento da oclusão.
Entender os valores de profundidade
Ponto A
na geometria real observada e um ponto 2D a
que representam o mesmo ponto na imagem de profundidade, o valor fornecido pelo atributo
A API em a
é igual ao comprimento de CA
projetado no eixo principal.
Também pode ser referida como a coordenada z de A
em relação à câmera.
C
. Ao trabalhar com a API Depth, é importante entender que
os valores de profundidade não são o comprimento da CA
do raio, mas a projeção
sobre ele.
Usar profundidade em sombreadores
Analisar informações de profundidade do frame atual
Use as funções auxiliares DepthGetMillimeters()
e DepthGetVisibility()
em um sombreador de fragmento para acessar as informações de profundidade da posição atual da tela. Em seguida, use essas informações para ocultar seletivamente partes do objeto renderizado.
// Use DepthGetMillimeters() and DepthGetVisibility() to parse the depth image
// for a given pixel, and compare against the depth of the object to render.
float DepthGetMillimeters(in sampler2D depth_texture, in vec2 depth_uv) {
// Depth is packed into the red and green components of its texture.
// The texture is a normalized format, storing millimeters.
vec3 packedDepthAndVisibility = texture2D(depth_texture, depth_uv).xyz;
return dot(packedDepthAndVisibility.xy, vec2(255.0, 256.0 * 255.0));
}
// Return a value representing how visible or occluded a pixel is relative
// to the depth image. The range is 0.0 (not visible) to 1.0 (completely
// visible).
float DepthGetVisibility(in sampler2D depth_texture, in vec2 depth_uv,
in float asset_depth_mm) {
float depth_mm = DepthGetMillimeters(depth_texture, depth_uv);
// Instead of a hard Z-buffer test, allow the asset to fade into the
// background along a 2 * kDepthTolerancePerMm * asset_depth_mm
// range centered on the background depth.
const float kDepthTolerancePerMm = 0.015f;
float visibility_occlusion = clamp(0.5 * (depth_mm - asset_depth_mm) /
(kDepthTolerancePerMm * asset_depth_mm) + 0.5, 0.0, 1.0);
// Use visibility_depth_near to set the minimum depth value. If using
// this value for occlusion, avoid setting it too close to zero. A depth value
// of zero signifies that there is no depth data to be found.
float visibility_depth_near = 1.0 - InverseLerp(
depth_mm, /*min_depth_mm=*/150.0, /*max_depth_mm=*/200.0);
// Use visibility_depth_far to set the maximum depth value. If the depth
// value is too high (outside the range specified by visibility_depth_far),
// the virtual object may get inaccurately occluded at further distances
// due to too much noise.
float visibility_depth_far = InverseLerp(
depth_mm, /*min_depth_mm=*/7500.0, /*max_depth_mm=*/8000.0);
const float kOcclusionAlpha = 0.0f;
float visibility =
max(max(visibility_occlusion, kOcclusionAlpha),
max(visibility_depth_near, visibility_depth_far));
return visibility;
}
Ocluir objetos virtuais
Oculte objetos virtuais no corpo do sombreador de fragmento. Atualize o Canal Alfa do objeto com base na profundidade dele. Isso vai renderizar um objeto obstruído.
// Occlude virtual objects by updating the object’s alpha channel based on its depth.
const float kMetersToMillimeters = 1000.0;
float asset_depth_mm = v_ViewPosition.z * kMetersToMillimeters * -1.;
// Compute the texture coordinates to sample from the depth image.
vec2 depth_uvs = (u_DepthUvTransform * vec3(v_ScreenSpacePosition.xy, 1)).xy;
gl_FragColor.a *= DepthGetVisibility(u_DepthTexture, depth_uvs, asset_depth_mm);
Você pode renderizar a oclusão usando a renderização em duas etapas 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, de passagem direta
A renderização de passagem direta por objeto determina a oclusão de cada pixel do objeto no shader do Material Design. Se os pixels não estiverem visíveis, eles serão cortados, normalmente por meio da mistura Alfa, simulando a oclusão no dispositivo do usuário.
Renderização em duas etapas
Na renderização em duas etapas, a primeira passagem renderiza todo o conteúdo virtual em um buffer intermediário. O segundo passo combina a cena virtual ao plano de fundo 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 shader específico ao objeto e geralmente produz resultados de aparência mais uniforme do que o método de passagem direta.
Conversão de coordenadas entre imagens de câmera e imagens de profundidade
As imagens geradas com ArFrame_acquireCameraImage()
podem ter uma proporção diferente das imagens de profundidade.
Nesse caso, a imagem de profundidade é um corte da imagem da câmera, e nem todos os pixels dela têm uma estimativa de profundidade válida correspondente.
Para obter as coordenadas de profundidade da imagem para coordenadas na imagem da CPU:
const float cpu_image_coordinates[] = {(float)cpu_coordinate_x, (float)cpu_coordinate_y}; float texture_coordinates[2]; ArFrame_transformCoordinates2d( ar_session, ar_frame, AR_COORDINATES_2D_IMAGE_PIXELS, 1, cpu_image_coordinates, AR_COORDINATES_2D_TEXTURE_NORMALIZED, texture_coordinates); if (texture_coordinates[0] < 0 || texture_coordinates[1] < 0) { // There are no valid depth coordinates, because the coordinates in the CPU // image are in the cropped area of the depth image. } else { int depth_image_width = 0; ArImage_getWidth(ar_session, depth_image, &depth_image_width); int depth_image_height = 0; ArImage_getHeight(ar_session, depth_image, &depth_image_height); int depth_coordinate_x = (int)(texture_coordinates[0] * depth_image_width); int depth_coordinate_y = (int)(texture_coordinates[1] * depth_image_height); }
Para obter as coordenadas de imagem da CPU para coordenadas de profundidade da imagem:
int depth_image_width = 0; ArImage_getWidth(ar_session, depth_image, &depth_image_width); int depth_image_height = 0; ArImage_getHeight(ar_session, depth_image, &depth_image_height); float texture_coordinates[] = { (float)depth_coordinate_x / (float)depth_image_width, (float)depth_coordinate_y / (float)depth_image_height}; float cpu_image_coordinates[2]; ArFrame_transformCoordinates2d( ar_session, ar_frame, AR_COORDINATES_2D_TEXTURE_NORMALIZED, 1, texture_coordinates, AR_COORDINATES_2D_IMAGE_PIXELS, cpu_image_coordinates); int cpu_image_coordinate_x = (int)cpu_image_coordinates[0]; int cpu_image_coordinate_y = (int)cpu_image_coordinates[1];
Teste de hit de profundidade
Os testes de hit permitem que os usuários coloquem objetos em um local do mundo real na cena. Antes, os testes de hit só podiam ser realizados em planos detectados, limitando os locais a superfícies grandes e planas, como os resultados mostrados pelos Androids verdes. Os testes de hit de profundidade aproveitam as informações de profundidade suaves e brutas para fornecer resultados de hit mais precisos, mesmo em superfícies não planas e de baixa textura. Isso é mostrado com os Androids vermelhos.
Para usar testes de hit com profundidade ativada, chame ArFrame_hitTest()
e verifique se há AR_TRACKABLE_DEPTH_POINT
s na lista de retorno.
// Create a hit test using the Depth API. ArHitResultList* hit_result_list = NULL; ArHitResultList_create(ar_session, &hit_result_list); ArFrame_hitTest(ar_session, ar_frame, hit_x, hit_y, hit_result_list); int32_t hit_result_list_size = 0; ArHitResultList_getSize(ar_session, hit_result_list, &hit_result_list_size); ArHitResult* ar_hit_result = NULL; for (int32_t i = 0; i < hit_result_list_size; ++i) { ArHitResult* ar_hit = NULL; ArHitResult_create(ar_session, &ar_hit); ArHitResultList_getItem(ar_session, hit_result_list, i, ar_hit); ArTrackable* ar_trackable = NULL; ArHitResult_acquireTrackable(ar_session, ar_hit, &ar_trackable); ArTrackableType ar_trackable_type = AR_TRACKABLE_NOT_VALID; ArTrackable_getType(ar_session, ar_trackable, &ar_trackable_type); // Creates an anchor if a plane or an oriented point was hit. if (AR_TRACKABLE_DEPTH_POINT == ar_trackable_type) { // Do something with the hit result. } ArTrackable_release(ar_trackable); ArHitResult_destroy(ar_hit); } ArHitResultList_destroy(hit_result_list);
O que vem em seguida?
- Ative uma detecção mais precisa com a API Raw Depth.
- Confira o Laboratório de profundidade do ARCore, que demonstra diferentes maneiras de acessar dados de profundidade.