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 dostarczanych przez obraz 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. Aby obsługiwać Depth API, urządzenie nie musi mieć czujnika ToF.

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 oszczędzać zasoby, ARCore domyślnie wyłącza głębię. Włącz tryb głębi, aby aplikacja mogła korzystać z 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

Aby uzyskać obraz głębi dla bieżącej ramki, wywołaj funkcję ArFrame_acquireDepthImage16Bits().

// 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 to być też współrzędna Z obiektu A względem punktu początkowego kamery 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 użyj tych informacji, aby selektywnie zasłonić części 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;
}

Zakrywanie wirtualnych obiektów

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

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

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 są niewidoczne, są one przycinane, zwykle przez mieszanie alfa, co symuluje zasłonięcie na urządzeniu użytkownika.

Renderowanie dwuprzebiegowe

W przypadku renderowania dwuprzebiegowego pierwsze przejście renderuje całą zawartość wirtualną w buforze pośrednim. Drugi przejazd łączy wirtualną scenę z tłem na podstawie różnicy między rzeczywistą głębią a głębią wirtualnej sceny. To podejście nie wymaga dodatkowej pracy nad shaderami dla poszczególnych obiektów i zwykle daje bardziej jednolite wyniki niż metoda bezpośredniego renderowania.

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 jest wycięciem obrazu z aparatu, a nie wszystkie piksele w tym obrazie mają odpowiadającą im prawidłową głębię.

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 pozycji wskaźnika w głębi

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.