Utilizzare la profondità nell'app NDK per Android

L'API Depth aiuta la fotocamera di un dispositivo a comprendere le dimensioni e la forma degli oggetti reali in una scena. Utilizza la fotocamera per creare immagini di profondità o mappe di profondità, aggiungendo così un livello di realismo AR alle tue app. Puoi utilizzare le informazioni fornite da un'immagine di profondità per fare in modo che gli oggetti virtuali appaiano accuratamente davanti o dietro oggetti del mondo reale, consentendo un'esperienza utente immersiva e realistica.

Le informazioni sulla profondità vengono calcolate in base al movimento e possono essere combinate con quelle provenienti da un sensore di profondità hardware, ad esempio un sensore del tempo di volo (ToF), se disponibile. Un dispositivo non ha bisogno di un sensore ToF per supportare l'API Depth.

Prerequisiti

Assicurati di comprendere i concetti fondamentali della realtà aumentata e su come configurare una sessione ARCore prima di procedere.

Limitare l'accesso ai dispositivi che supportano la profondità

Se la tua app richiede il supporto dell'API Depth, perché una parte fondamentale L'esperienza AR si basa sulla profondità o perché non esiste un alternativo parti dell'app che utilizzano la profondità, puoi scegliere di limitare la distribuzione dei tuoi nel Google Play Store per che supportano l'API Depth aggiungendo la seguente riga al tuo AndroidManifest.xml, oltre al AndroidManifest.xml modifiche descritte in Guida Abilita ARCore:

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

Attiva profondità

In una nuova sessione ARCore, controlla se il dispositivo di un utente supporta la profondità. Non tutti i dispositivi compatibili con ARCore supportano l'API Depth a causa di limitazioni della potenza di elaborazione. Per risparmiare risorse, la profondità è disattivata per impostazione predefinita su ARCore. Attiva la modalità depth per fare in modo che la tua app usi 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);

Acquisisci immagini di profondità

Chiama ArFrame_acquireDepthImage16Bits() per ottenere l'immagine di profondità per il frame corrente.

// 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'immagine restituita fornisce il buffer di immagini non elaborate, che può essere passato a uno shaker per frammenti per l'utilizzo sulla GPU per ogni oggetto sottoposto a rendering da occultare. È orientato in AR_COORDINATES_2D_OPENGL_NORMALIZED_DEVICE_COORDINATES e può essere modificato in AR_COORDINATES_2D_TEXTURE_NORMALIZED chiamando ArFrame_transformCoordinates2d(). Una volta che l'immagine di profondità è accessibile all'interno di uno shaker di oggetti, è possibile accedere direttamente a queste misurazioni della profondità per la gestione dell'occlusione.

Informazioni sui valori di profondità

Dato il punto A sulla geometria reale osservata e un punto 2D a che rappresenta lo stesso punto nell'immagine di profondità, il valore specificato dal valore L'API in a è uguale alla lunghezza di CA prevista sull'asse principale. È anche indicata come la coordinata z di A rispetto alla fotocamera origine C. Quando lavori con l'API Depth, è importante capire che i valori di profondità non sono la lunghezza del raggio CA stesso, ma la proiezione da questa parte.

Usa la profondità negli Shaper

Analizza le informazioni di profondità per il frame corrente

Usa le funzioni helper DepthGetMillimeters() e DepthGetVisibility() in uno shaker di frammenti per accedere alle informazioni sulla profondità per la posizione corrente della schermata. Dopodiché utilizza queste informazioni per nascondere selettivamente le parti dell'oggetto visualizzato.

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

Occlude oggetti virtuali

Occlude oggetti virtuali nel corpo dello shaker dei frammenti. Aggiorna il canale alfa dell'oggetto in base alla sua profondità. Verrà visualizzato un oggetto occulto.

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

Puoi visualizzare la copertura utilizzando il rendering a due passaggi o il rendering in avanti per oggetto. L'efficienza di ciascun approccio dipende dalla complessità della scena e da altre considerazioni specifiche dell'app.

Rendering con progresso in avanti per oggetto

Il rendering in avanti per singolo oggetto determina l'occlusione di ogni pixel dell'oggetto nel suo tonalità di materiale. Se i pixel non sono visibili, vengono ritagliati, in genere tramite una combinazione alfa, simulando così l'occlusione sul dispositivo dell'utente.

Rendering a due passaggi

Con il rendering a due passaggi, il primo passaggio esegue il rendering di tutti i contenuti virtuali in un buffer intermedio. Il secondo passaggio unisce la scena virtuale con lo sfondo in base alla differenza tra la profondità del mondo reale e quella della scena virtuale. Questo approccio non richiede alcun lavoro aggiuntivo sullo screenr specifico per gli oggetti e generalmente produce risultati più uniformi rispetto al metodo del progresso in avanti.

Conversione delle coordinate tra immagini della fotocamera e immagini di profondità

Le immagini ottenute utilizzando ArFrame_acquireCameraImage() potrebbero avere proporzioni diverse rispetto alle immagini in profondità. In questo caso, l'immagine di profondità è un ritaglio dell'immagine della fotocamera e non tutti i pixel nell'immagine della fotocamera hanno una stima della profondità valida corrispondente.

Per ottenere le coordinate dell'immagine di profondità per le coordinate sull'immagine 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);
}

Per ottenere le coordinate dell'immagine CPU per le coordinate dell'immagine di profondità:

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

Hit test di profondità

Gli hit test consentono agli utenti di posizionare oggetti in un punto reale della scena. In precedenza, gli hit test potevano essere condotti solo sugli aerei rilevati, limitando la posizione a superfici grandi e piane, come ad esempio i risultati mostrati dagli androidi verdi. I test degli hit di profondità sfruttano informazioni di profondità sia uniformi che non elaborate per fornire risultati più accurati, anche su superfici non piane e con trama bassa. Ciò viene mostrato con gli Android rossi.

Per utilizzare i test degli hit con profondità, chiama ArFrame_hitTest() e cerca AR_TRACKABLE_DEPTH_POINT nell'elenco restituito.

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

Passaggi successivi

  • Abilita un rilevamento più preciso con l'API Raw Depth.
  • Dai un'occhiata al ARCore Depth Lab, che illustra diversi modi per accedere a dati di profondità.