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 de la RA à vos applications. Vous pouvez utiliser les informations fournies par une représentation de profondeur pour faire apparaître avec précision des objets virtuels devant ou derrière des objets du monde réel, offrant ainsi des expériences utilisateur immersives et réalistes.

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

Prérequis

Assurez-vous de bien maîtriser les concepts fondamentaux de la RA. et comment configurer une session ARCore avant de continuer.

Limiter l'accès aux appareils compatibles avec la fonctionnalité de profondeur

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, ou parce qu'il n'existe pas de solution de remplacement élégante pour les parties de l'application qui utilisent la profondeur, vous pouvez choisir de limiter la distribution de votre sur le Google Play Store pour appareils compatibles avec l'API Depth en ajoutant la ligne suivante à votre AndroidManifest.xml, en plus de AndroidManifest.xml modifications décrites dans le Guide Activer ARCore:

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

Activer la profondeur

Dans une nouvelle session ARCore, vérifiez si l'appareil de l'utilisateur est compatible avec la fonctionnalité de profondeur. Les appareils compatibles ARCore ne sont pas tous compatibles avec l'API Depth en raison de contraintes de puissance de traitement. Pour économiser des 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 représentation de profondeur 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 afin qu'il soit utilisé sur le GPU pour masquer chaque objet affiché. 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 aux mesures de profondeur pour gérer l'occlusion.

Comprendre les valeurs de profondeur

Point A donné sur la géométrie réelle observée et un point 2D a. représentant le même point dans la représentation de profondeur, la valeur donnée par le paramètre L'API à a est égale à la longueur de CA projetée sur l'axe principal. Également appelée coordonnée Z de A par rapport à la caméra origine 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 à la projection du projet.

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 correspondant à la position actuelle de l'écran. Utilisez ensuite ces informations pour masquer de manière sélective certaines parties de l'objet rendu.

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

Masquer les objets virtuels

Les objets virtuels sont masqués dans le corps du nuanceur de fragments. Mettez à jour le canal alpha de l'objet en fonction de sa profondeur. Un objet masqué est alors affiché.

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

Vous pouvez effectuer le rendu d'une occlusion à l'aide d'un rendu en deux passes ou d'un rendu par objet en effectuant une passe avant. 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, passage avant

Le rendu avant transmission 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 via le mélange alpha, ce qui simule une occlusion sur l'appareil de l'utilisateur.

Rendu en deux passes

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

Convertir des coordonnées entre des images d'appareil photo et des représentations de profondeur

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

Pour obtenir les coordonnées 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 l'image de processeur pour la profondeur de l'image:

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 emplacement 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 plates, comme les résultats des Android verts. Les tests de positionnement de profondeur exploitent les informations de profondeur à la fois fluides et brutes pour fournir des résultats de positionnement plus précis, même sur les surfaces non planes et à faible texture. C'est ce qu'illustrent les personnages Android rouges.

<ph type="x-smartling-placeholder">

Pour utiliser des tests de positionnement avec profondeur, appelez ArFrame_hitTest() et recherchez des 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);

Étapes suivantes

  • Améliorez la précision de la détection avec l'API Raw Depth.
  • Suivez l'atelier ARCore Depth qui présente différentes façons d'accéder aux données de profondeur.