„Tiefe“ in der Android NDK App verwenden

Die Depth API hilft der Kamera eines Geräts, die Größe und Form der realen Objekte in einer Szene zu erkennen. Dabei wird mithilfe der Kamera Tiefenbilder oder Tiefenkarten erstellt. So erhält deine App AR-realistische Erlebnisse. Mithilfe der Informationen eines Tiefenbildes können Sie virtuelle Objekte vor oder hinter realen Objekten exakt anzeigen lassen. So entstehen immersive und realistische User Experiences.

Tiefeninformationen werden aus Bewegung berechnet und können mit Informationen von einem Hardware-Tiefensensor wie einem Flugzeitsensor (ToF) kombiniert werden, sofern verfügbar. Geräte benötigen zur Unterstützung der Depth API keinen ToF-Sensor.

Vorbereitung

Machen Sie sich mit den grundlegenden AR-Konzepten vertraut. und Konfigurieren einer ARCore-Sitzung beschrieben, bevor du fortfährst.

Zugriff auf Geräte mit Tiefenunterstützung einschränken

Wenn für Ihre App Depth API-Unterstützung erforderlich ist, weil ein zentraler Bestandteil des Das AR-Erlebnis erfordert Tiefe. Teile der App mit Tiefendaten aus, können Sie den Vertrieb Ihrer App im Google Play Store herunter, um Geräte, die die Depth API unterstützen, durch Hinzufügen von folgende Zeile in Ihre AndroidManifest.xml ein, zusätzlich zum AndroidManifest.xml-Änderungen wie Leitfaden zum Aktivieren von ARCore:

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

Tiefe aktivieren

Prüfen Sie in einer neuen ARCore-Sitzung, ob das Gerät des Nutzers „Tiefendaten“ unterstützt. Aufgrund von Einschränkungen bei der Prozessorleistung unterstützen nicht alle ARCore-kompatiblen Geräte die Depth API. Um Ressourcen zu sparen, ist die Tiefe in ARCore standardmäßig deaktiviert. Aktivieren Sie den Tiefenmodus, damit Ihre App die Depth API verwenden kann.

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

Tiefenaufnahmen aufnehmen

Rufen Sie ArFrame_acquireDepthImage16Bits() auf, um das Tiefenbild für den aktuellen Frame abzurufen.

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

Das zurückgegebene Bild stellt den Zwischenspeicher des Rohbilds bereit, der zur Verwendung auf der GPU für jedes zu verdeckende gerenderte Objekt an einen Fragment-Shader übergeben werden kann. Sie ist auf AR_COORDINATES_2D_OPENGL_NORMALIZED_DEVICE_COORDINATES ausgerichtet und kann durch Aufrufen von ArFrame_transformCoordinates2d() in AR_COORDINATES_2D_TEXTURE_NORMALIZED geändert werden. Sobald das Tiefenbild in einem Objekt-Shader zugänglich ist, kann direkt auf diese Tiefenmessungen zugegriffen werden, um Verdeckungen zu verarbeiten.

Tiefenwerte

Angegebener Punkt A in der beobachteten realen Geometrie und ein 2D-Punkt a denselben Punkt im Tiefenbild darstellt, wird der Wert, der durch das Die API in a entspricht der Länge von CA, die auf die Hauptachse projiziert wird. Dies kann auch als Z-Koordinate von A relativ zur Kamera bezeichnet werden. Ursprung C. Wenn Sie mit der Depth API arbeiten, sollten Sie wissen, Die Tiefenwerte entsprechen nicht der Länge des Strahls CA selbst, sondern der Projektion. davon.

Tiefe in Shadern verwenden

Tiefeninformationen für den aktuellen Frame parsen

Verwenden Sie die Hilfsfunktionen DepthGetMillimeters() und DepthGetVisibility() in einem Fragment-Shader, um auf die Tiefeninformationen für die aktuelle Bildschirmposition zuzugreifen. Verwenden Sie diese Informationen dann, um Teile des gerenderten Objekts selektiv zu verdecken.

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

Virtuelle Objekte ausschließen

Schließen Sie virtuelle Objekte in den Textkörper des Fragment-Shaders ein. Aktualisiert den Alphakanal des Objekts anhand seiner Tiefe. Dadurch wird ein verdecktes Objekt gerendert.

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

Sie können Verdeckungen mit 2-Pass-Rendering oder pro Objekt mit Vorwärtsdurchlauf rendern. Die Effizienz der einzelnen Ansätze hängt von der Komplexität der Szene und weiteren anwendungsspezifischen Aspekten ab.

Vorwärtsdurchlauf-Rendering pro Objekt

Beim Vorwärtsdurchlauf-Rendering wird pro Objekt die Verdeckung jedes Pixels des Objekts in seinem Material-Shader bestimmt. Wenn die Pixel nicht sichtbar sind, werden sie abgeschnitten, in der Regel durch Alpha-Überblendung, um eine Verdeckung auf dem Gerät des Nutzers zu simulieren.

Rendering mit zwei Durchgängen

Beim Rendering mit zwei Durchgängen rendert der erste Durchlauf den gesamten virtuellen Inhalt in einem Zwischenpuffer. Beim zweiten Pass wird die virtuelle Szene basierend auf dem Unterschied zwischen der realen und der virtuellen Szenentiefe mit dem Hintergrund verschmelzen lassen. Dieser Ansatz erfordert keine zusätzliche objektspezifische Shader-Arbeit und liefert im Allgemeinen einheitlichere Ergebnisse als die Vorwärtspass-Methode.

Koordinaten zwischen Kamerabildern und Tiefenbildern konvertieren

Mit ArFrame_acquireCameraImage() aufgenommene Bilder haben möglicherweise ein anderes Seitenverhältnis als Tiefenaufnahmen. In diesem Fall ist das Tiefenbild ein Ausschnitt des Kamerabilds und nicht alle Pixel im Kamerabild haben eine entsprechende gültige Tiefenschätzung.

So erhalten Sie Tiefenbildkoordinaten für Koordinaten im CPU-Bild:

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

So erhalten Sie CPU-Bildkoordinaten für Tiefenbildkoordinaten:

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

Tiefstwert-Test

Mithilfe von Treffertests können Nutzer Objekte an einem realen Ort in der Szene platzieren. Früher konnten Treffertests nur in erkannten Flugzeugen durchgeführt werden, wobei die Standorte auf große, flache Oberflächen beschränkt waren, wie die von den grünen Androiden gezeigten Ergebnisse. Bei Tiefen-Treffertests werden sowohl glatte als auch unformatierte Tiefeninformationen genutzt, um genauere Trefferergebnisse zu liefern, selbst auf nicht planaren Oberflächen und Oberflächen mit geringer Textur. Dies wird durch die roten Android-Symbole dargestellt.

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

Wenn Sie tiefenbasierte Treffertests verwenden möchten, rufen Sie ArFrame_hitTest() auf und suchen Sie in der Rückgabeliste nach AR_TRACKABLE_DEPTH_POINTs.

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

Weiteres Vorgehen

  • Ermöglichen Sie eine genauere Erkennung mit der Raw Depth API.
  • Im ARCore Depth Lab werden verschiedene Möglichkeiten für den Zugriff auf detaillierte Daten vorgestellt.