Używaj głębi w aplikacji NDK na Androida

Depth API pomaga aparatowi urządzenia rozpoznać rozmiar i kształt rzeczywistych obiektów w scenie. Używa aparatu do tworzenia zdjęć głębi, czyli map głębi, dzięki czemu Twoje aplikacje zyskują dodatkową warstwę realizmu AR. Na podstawie informacji uzyskanych dzięki obrazowi głębi możesz precyzyjnie wyświetlać wirtualne obiekty przed lub za rzeczywistymi obiektami, co zapewnia użytkownikom realistyczne i ciekawe wrażenia.

Informacje o głębi są obliczane na podstawie ruchu i mogą być łączone z informacjami ze sprzętowego czujnika głębokości, np. z czujnika czasu lotu (ToF), jeśli jest dostępny. Urządzenie nie potrzebuje czujnika ToF do obsługi interfejsu Depth API.

Wymagania wstępne

Upewnij się, że znasz podstawowe pojęcia związane z AR. i dowiedz się, jak skonfigurować sesję ARCore, zanim przejdziesz dalej.

Ogranicz dostęp do urządzeń z obsługą głębi

Jeśli Twoja aplikacja wymaga obsługi interfejsu Depth API, ponieważ kluczowa część Jakość AR zależy od głębi lub dlatego, które wymagają głębszej reakcji, możesz ograniczyć rozpowszechnianie aplikację w Sklepie Google Play, aby urządzeniach obsługujących Depth API, dodając następujący wiersz do: AndroidManifest.xml, oprócz Zmiany (AndroidManifest.xml) opisane w Włączanie przewodnika po ARCore:

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

Włącz głębię

W nowej sesji ARCore sprawdź, czy urządzenie użytkownika obsługuje Depth. Nie wszystkie urządzenia zgodne z ARCore obsługują Depth API ze względu na ograniczenia mocy obliczeniowej. Aby można było oszczędzać zasoby, głębokość jest domyślnie wyłączona w ARCore. Włącz tryb głębi, aby aplikacja używała interfejsu Depth API.

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

Uzyskiwanie zdjęć głębi

Wywołaj ArFrame_acquireDepthImage16Bits(), aby uzyskać obraz głębi bieżącej klatki.

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

Zwrócony obraz zawiera bufor nieprzetworzonego obrazu, który można przekazać do cieniowania fragmentów, aby użyć go w GPU w celu pominięcia każdego renderowanego obiektu. Ma ona język AR_COORDINATES_2D_OPENGL_NORMALIZED_DEVICE_COORDINATES i można ją zmienić na AR_COORDINATES_2D_TEXTURE_NORMALIZED, wywołując metodę ArFrame_transformCoordinates2d(). Po uzyskaniu dostępu do obrazu głębi w cieniowaniu obiektów można uzyskać dostęp do pomiarów głębokości i wyeliminować przesłonięcie.

Poznawanie wartości głębokości

Podany punkt A na zaobserwowanej geometrii rzeczywistej i punkt 2D: a reprezentujący ten sam punkt na zdjęciu głębi. Wartość podana jako Głębia Interfejs API w miejscu a ma długość CA przewidywanej na oś podmiotu zabezpieczeń. Może też być nazywana współrzędną Z zmiennej A względem kamery. miejsce wylotu: C. Podczas pracy z Depth API należy pamiętać, że wartości głębi to nie długość promienia CA, ale odwzorowanie .

Używaj głębi w cieniowaniu

Przeanalizuj informacje o głębi w bieżącej ramce

Aby uzyskać dostęp do informacji o głębi w bieżącej pozycji na ekranie, użyj funkcji pomocniczych DepthGetMillimeters() i DepthGetVisibility() w cieniowaniu fragmentów. Następnie na podstawie tych informacji możesz selektywnie ukrywać fragmenty renderowanego obiektu.

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

Blokuj wirtualne obiekty

Blokuj obiekty wirtualne w treści modułu cieniowania fragmentów. Zaktualizuj kanał alfa obiektu na podstawie jego głębokości. Spowoduje to wyrenderowanie wykluczonego obiektu.

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

Możesz wyrenderować przesłanianie za pomocą renderowania dwuetapowego lub renderowania z przebiegiem do przodu dla poszczególnych obiektów. Skuteczność każdego podejścia zależy od złożoności sceny i innych czynników związanych z aplikacją.

Na obiekt, renderowanie do przodu

Renderowanie z przebiegiem do przodu określa przesłonięcie każdego piksela obiektu w trybie cieniowania materiału. Jeśli piksele nie są widoczne, są przycinane, zwykle przez mieszanie alfa, w celu symulowania przesłaniania na urządzeniu użytkownika.

Renderowanie dwuprzebiegowe

W przypadku renderowania dwuprzebiegowego pierwsze przejście renderuje całą zawartość wirtualną w buforze pośrednim. Drugi etap polega na połączeniu sceny wirtualnej z tłem na podstawie różnicy między głębią świata a głębią sceny wirtualnej. Ta metoda nie wymaga dodatkowej pracy z programem cieniowania dla konkretnego obiektu i zwykle daje bardziej jednolite wyniki niż metoda oparta na przebiegu do przodu.

Konwersja współrzędnych między zdjęciami aparatu a zdjęciami głębi

Obrazy uzyskane za pomocą ArFrame_acquireCameraImage() mogą mieć inny format obrazu niż obrazy głębi. W tym przypadku obraz głębi to przycięcie obrazu z aparatu i nie wszystkie piksele na zdjęciu mają odpowiednią przybliżoną głębię obrazu.

Aby uzyskać współrzędne głębi obrazu dla współrzędnych obrazu procesora:

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

Aby uzyskać współrzędne obrazu CPU dla współrzędnych obrazu głębi:

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 głębokości

Dzięki testom trafień użytkownicy mogą umieszczać obiekty w rzeczywistych lokalizacjach sceny. Wcześniej testy trafień można było przeprowadzać tylko na wykrytych samolotach, ograniczając lokalizacje do dużych, płaskich powierzchni, jak na przykład wyniki wyświetlane przez zielone Androida. Wykorzystują one zarówno płynne, jak i nieprzetworzone informacje o głębi, by zapewnić dokładniejsze wyniki trafień, nawet na powierzchniach niepłaskich i o niskiej tekstur. Ta informacja jest widoczna przy czerwonych urządzeniach z Androidem.

Aby użyć testów trafień z uwzględnieniem głębokości, wywołaj metodę ArFrame_hitTest() i sprawdź, czy na liście zwrotów znajdują się AR_TRACKABLE_DEPTH_POINT.

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

Co dalej?

  • Włącz dokładniejsze wykrywanie za pomocą interfejsu Raw Depth API.
  • Wypróbuj ARCore Depth Lab, aby dowiedzieć się, jak uzyskać dostęp do szczegółowych danych.