Cómo usar Depth en tu app de NDK de Android

La API de Depth ayuda a la cámara de un dispositivo a comprender el tamaño y la forma de los objetos reales de una escena. Usa la cámara para crear imágenes o mapas de profundidad, lo que agrega una capa de realismo de RA a tus apps. Puedes usar la información que proporciona una imagen de profundidad para hacer que los objetos virtuales aparezcan con precisión delante o detrás de objetos del mundo real, lo que permite experiencias del usuario inmersivas y realistas.

La información de profundidad se calcula a partir del movimiento y se puede combinar con la información de un sensor de profundidad de hardware, como un sensor de tiempo de vuelo (ToF), si está disponible. Un dispositivo no necesita un sensor ToF para admitir la API de Depth.

Requisitos previos

Asegúrate de comprender los conceptos fundamentales de RA y cómo configurar una sesión de ARCore antes de continuar.

Cómo restringir el acceso a los dispositivos compatibles con Depth

Si tu app requiere compatibilidad con la API de Depth, ya sea porque una parte La experiencia de RA se basa en la profundidad o porque no hay resguardo de la app que usan profundidad, puedes restringir la distribución de tu de Google Play Store para dispositivos compatibles con la API de Depth agregando la siguiente línea a tu AndroidManifest.xml, además del AndroidManifest.xml cambios descritos en el Guía para habilitar ARCore:

<uses-feature android:name="com.google.ar.core.depth" />

Habilitar profundidad

En una nueva sesión de ARCore, verifica si el dispositivo de un usuario es compatible con Depth. No todos los dispositivos compatibles con ARCore admiten la API de Depth debido a limitaciones de la capacidad de procesamiento. Para ahorrar recursos, la profundidad está inhabilitada de forma predeterminada en ARCore. Habilita el modo de profundidad para que tu app use la API de 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);

Cómo adquirir imágenes de profundidad

Llama a ArFrame_acquireDepthImage16Bits() para obtener la imagen de profundidad del fotograma actual.

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

La imagen que se muestra proporciona el búfer de imagen sin procesar, que se puede pasar a un sombreador de fragmentos para usarlo en la GPU y ocultar cada objeto renderizado. Está orientada en AR_COORDINATES_2D_OPENGL_NORMALIZED_DEVICE_COORDINATES y se puede cambiar a AR_COORDINATES_2D_TEXTURE_NORMALIZED llamando a ArFrame_transformCoordinates2d(). Una vez que se puede acceder a la imagen de profundidad dentro de un sombreador de objetos, se puede acceder directamente a estas mediciones de profundidad para controlar la oclusión.

Cómo interpretar los valores de profundidad

Dado el punto A en la geometría del mundo real observada y un punto 2D a que representan el mismo punto en la imagen de profundidad, el valor que otorga la profundidad La API en a es igual a la longitud de CA proyectada en el eje principal. También se puede denominar la coordenada z de A en relación con la cámara. origen C. Cuando trabajes con la API de Depth, es importante que comprendas lo siguiente: Los valores de profundidad no son la longitud del rayo CA, sino la proyección de sus aspectos más emocionantes.

Cómo usar profundidad en sombreadores

Cómo analizar la información de profundidad del fotograma actual

Usa las funciones auxiliares DepthGetMillimeters() y DepthGetVisibility() en un sombreador de fragmentos para acceder a la información de profundidad de la posición actual de la pantalla. Luego, usa esta información para ocluir de manera selectiva partes del 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;
}

Ocluye objetos virtuales

Ocluye objetos virtuales en el cuerpo del sombreador de fragmentos. Actualiza el canal alfa del objeto en función de su profundidad. Esto renderizará un objeto oculto.

// Occlude virtual objects by updating the objects 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);

Puedes renderizar la oclusión con renderización de dos pasos o por objeto, renderizado de pase hacia delante. La eficiencia de cada enfoque depende de la complejidad de la escena y otras consideraciones específicas de la app.

Renderización de pase hacia delante por objeto

La renderización de pase hacia delante por objeto determina la oclusión de cada píxel del objeto en el sombreador de material. Si los píxeles no son visibles, se recortan, generalmente, mediante una combinación alfa, con lo que se simula la oclusión en el dispositivo del usuario.

Renderización de dos pasos

Con la renderización de dos pasos, el primer pase renderiza todo el contenido virtual en un búfer intermedio. El segundo pase combina la escena virtual con el fondo en función de la diferencia entre la profundidad del mundo real y la profundidad de la escena virtual. Este enfoque no requiere trabajo adicional de sombreador específico del objeto y, por lo general, produce resultados más uniformes que el método de pase hacia delante.

Cómo convertir coordenadas entre imágenes de la cámara y de profundidad

Las imágenes obtenidas con ArFrame_acquireCameraImage() pueden tener una relación de aspecto diferente en comparación con las imágenes de profundidad. En este caso, la imagen de profundidad es un recorte de la imagen de la cámara y no todos los píxeles de la imagen de la cámara tienen una estimación de profundidad válida correspondiente.

Para obtener coordenadas de imágenes de profundidad en la imagen de la CPU, haz lo siguiente:

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 obtener las coordenadas de la imagen de la CPU correspondientes a las coordenadas de la imagen de profundidad, haz lo siguiente:

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];

Prueba de posicionamiento de profundidad

Las pruebas de posicionamiento permiten a los usuarios colocar objetos en una ubicación real dentro de la escena. Anteriormente, las pruebas de posicionamiento solo se podían realizar en planos detectados, lo que limitaba las ubicaciones a superficies grandes y planas, como los resultados que mostraban los Android verdes. Las pruebas de alcance de profundidad aprovechan la información de profundidad tanto suave como sin procesar para proporcionar resultados de hits más precisos, incluso en superficies no planas y con baja textura. Esto se muestra con los Androides rojos.

Para usar pruebas de posicionamiento habilitadas por profundidad, llama a ArFrame_hitTest() y verifica si hay AR_TRACKABLE_DEPTH_POINT en la lista de devoluciones.

// 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);

¿Qué sigue?