Utiliser la fonctionnalité Depth dans votre application NDK Android

L'API Depth aide l'appareil photo d'un appareil à comprendre la taille et la forme des objets réels d'une scène. Elle utilise l'appareil photo pour créer des images de profondeur, ou cartes de profondeur, et ajoute ainsi un niveau de réalisme RA à vos applications. Vous pouvez utiliser les informations fournies par une représentation de profondeur pour faire apparaître des objets virtuels avec précision devant ou derrière des objets du monde réel, afin de proposer des expériences utilisateur immersives et réalistes.

Les informations de profondeur sont calculées à partir des mouvements et peuvent être combinées avec celles d'un capteur de profondeur matériel, tel qu'un capteur de temps de vol, le cas échéant. Un appareil n'a pas besoin de capteur ToF pour être compatible avec l'API Depth.

Conditions préalables

Assurez-vous de bien comprendre les concepts fondamentaux de la RA et de configurer une session ARCore avant de continuer.

Limiter l'accès aux appareils compatibles avec Depth

Si votre application nécessite la prise en charge de l'API Depth, soit parce qu'une partie essentielle de l'expérience de RA repose sur la profondeur, soit parce qu'il n'y a pas de solution de secours pour les parties de l'application qui utilisent la profondeur, vous pouvez choisir de limiter la distribution de votre application sur le Google Play Store aux appareils compatibles avec l'API Depth en ajoutant la ligne suivante à votre AndroidManifest.xml, en plus des modifications de AndroidManifest.xml décrites dans le guide de base pour AndroidManifest.xml :

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

Activer la profondeur

Dans une nouvelle session ARCore, vérifiez si l'appareil d'un utilisateur est compatible avec Depth. Certains appareils compatibles avec ARCore ne sont pas compatibles avec l'API Depth en raison de contraintes de puissance de traitement. Pour économiser les ressources, la profondeur est désactivée par défaut sur ARCore. Activez le mode Profondeur pour que votre application utilise l'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);

Obtenir des représentations de profondeur

Appelez ArFrame_acquireDepthImage16Bits() pour obtenir la profondeur de champ pour l'image actuelle.

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

L'image renvoyée fournit le tampon d'image brut, qui peut être transmis à un nuanceur de fragments pour une utilisation sur le GPU pour chaque objet rendu à masquer. Il est orienté dans AR_COORDINATES_2D_OPENGL_NORMALIZED_DEVICE_COORDINATES et peut être remplacé par AR_COORDINATES_2D_TEXTURE_NORMALIZED en appelant ArFrame_transformCoordinates2d(). Une fois que la représentation de profondeur est accessible dans un nuanceur d'objets, vous pouvez accéder directement à ces mesures de profondeur pour traiter l'occlusion.

Comprendre les valeurs de profondeur

En se basant sur le point A sur la géométrie réelle observée et sur un point 2D a représentant le même point sur la représentation de profondeur, la valeur donnée par l'API Depth à a est égale à la longueur de CA projetée sur l'axe principal. Elle peut également être désignée par l'expression "coordonnée Z" de A par rapport à l'origine de la caméra (C). Lorsque vous utilisez l'API Depth, il est important de comprendre que les valeurs de profondeur ne correspondent pas à la longueur du rayon CA lui-même, mais à sa projection.

Utiliser la profondeur dans les nuanceurs

Analyser les informations de profondeur pour l'image actuelle

Utilisez les fonctions d'assistance DepthGetMillimeters() et DepthGetVisibility() dans un nuanceur de fragments pour accéder aux informations de profondeur pour la position actuelle de l'écran. Utilisez ensuite ces informations pour masquer de manière sélective des parties de l'objet affiché.

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

Occlure les objets virtuels

Occluez les objets virtuels dans le corps du nuanceur de fragments. Mettez à jour le canal alpha de l'objet en fonction de sa profondeur. Cela entraînera le rendu d'un objet masqué.

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

Vous pouvez effectuer le rendu de l'occlusion à l'aide du rendu en deux passes ou par objet, avant l'affichage. L'efficacité de chaque approche dépend de la complexité de la scène et d'autres considérations propres à l'application.

Rendu par objet, transfert vers l'avant

Le rendu direct de l'objet détermine l'occlusion de chaque pixel de l'objet dans son nuanceur Material. Si les pixels ne sont pas visibles, ils sont rognés, généralement par le biais de la combinaison alpha, ce qui simule une occlusion sur l'appareil de l'utilisateur.

Rendu en deux passes

Avec le rendu en deux passages, la première passe affiche tout le contenu virtuel dans un tampon intermédiaire. La seconde passe mélange la scène virtuelle à l'arrière-plan en fonction de la différence entre la profondeur du monde réel et celle de la scène virtuelle. Cette approche ne nécessite aucun travail supplémentaire de nuanceur spécifique à l'objet et produit généralement des résultats plus uniformes que la méthode de transmission vers l'avant.

Convertir des coordonnées entre des images de profondeur et des images de profondeur

Les images obtenues avec ArFrame_acquireCameraImage() peuvent avoir un format différent de celui des images de profondeur. Dans ce cas, la profondeur de champ est un recadrage de l'image de l'appareil photo, et tous les pixels de cette image n'ont pas d'estimation de profondeur valide correspondante.

Pour obtenir les coordonnées de la représentation de profondeur pour les coordonnées de l'image de processeur:

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

Pour obtenir les coordonnées de la représentation de profondeur à l'aide de son processeur:

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

Test de positionnement de profondeur

Les tests de positionnement permettent aux utilisateurs de placer des objets à un endroit réel de la scène. Auparavant, les tests de positionnement ne pouvaient être effectués que sur des avions détectés, en limitant les emplacements aux grandes surfaces planes, comme les résultats affichés par les Android verts. Les tests de profondeur de niveau utilisent à la fois des informations lisses et brutes sur la profondeur pour fournir des résultats de positionnement plus précis, même sur des surfaces non planes et à faible texture. Elle est représentée par des personnages Android rouges.

Pour utiliser des tests de positionnement avec détection de la profondeur, appelez ArFrame_hitTest() et recherchez les AR_TRACKABLE_DEPTH_POINT dans la liste renvoyée.

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

Et ensuite ?

  • Améliorez la précision de la détection avec l'API Raw Depth.
  • Rendez-vous sur l'atelier ARCore Depth Lab, qui présente différentes manières d'accéder aux données de profondeur.