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

Interfejs Depth API ułatwia aparatowi urządzenia określenie rozmiaru i kształtu rzeczywistych obiektów na ekranie. Aparat wykorzystuje aparat do tworzenia zdjęć głębi (map głębi), dzięki czemu aplikacja nabiera warstwy realizmu AR. Za pomocą informacji dostarczanych przez zdjęcia w trybie głębokim możesz tworzyć wirtualne obiekty dokładnie przed lub za rzeczywistymi obiektami, co sprawia, że wrażenia użytkownika są wciągające i realistyczne.

Informacje o głębokości są obliczane na podstawie ruchu i mogą być łączone z informacjami pochodzącymi ze sprzętowego czujnika głębokości, takiego jak czujnik czasu lotu (ToF), jeśli jest dostępny. Urządzenie nie potrzebuje czujnika ToF, aby obsługiwać interfejs Depth API.

Wymagania wstępne

Zanim przejdziesz dalej, upewnij się, że znasz podstawowe pojęcia związane z AR i wiesz, jak skonfigurować sesję ARCore.

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

Jeśli Twoja aplikacja wymaga obsługi interfejsu Depth API ze względu na fakt, że jej główna część zależy od głębi lub z powodu braku efektów zastępczych dla tych części aplikacji, które korzystają z głębokości, możesz ograniczyć jej rozpowszechnianie w Sklepie Google Play tylko do urządzeń obsługujących Depth API. W tym celu oprócz instrukcji dotyczących AndroidManifest.xml możesz dodać do interfejsu AndroidManifest.xml ten wiersz.

<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. Z powodu ograniczeń mocy obliczeniowej nie wszystkie urządzenia zgodne z ARCore obsługują interfejs Depth API. Aby oszczędzać zasoby, głębia 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);

Pobierz zdjęcia głębi

Wywołaj ArFrame_acquireDepthImage16Bits(), aby uzyskać zdjęcie głębi w bieżącej klatce.

// 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 zapewnia bufor nieprzetworzonego obrazu, który można przekazać do cieniowania fragmentów, aby wykorzystać go przez GPU, aby zasłaniać każdy wyrenderowany obiekt. Język jest ukierunkowany na AR_COORDINATES_2D_OPENGL_NORMALIZED_DEVICE_COORDINATES i można go zmienić na AR_COORDINATES_2D_TEXTURE_NORMALIZED, wywołując ArFrame_transformCoordinates2d(). Gdy obraz głębi będzie dostępny w programie cieniowania obiektów, pomiary głębi można wykonywać bezpośrednio w celu obsługi przesłonięcia.

Interpretowanie wartości głębokości

Biorąc pod uwagę punkt A na obserwowanej geometrii w świecie rzeczywistym i punkt 2D a reprezentujący ten sam punkt na obrazie głębi, wartość podana przez interfejs Depth API w miejscu a jest równa długości obiektu CA umieszczonego na głównej osi. Nazywa się to też współrzędną z obiektu A względem źródła kamery C. Podczas pracy z interfejsem Depth API należy pamiętać, że wartości głębi to nie długość promienia CA, ale jego odwzorowanie.

Używaj głębi w cieniowaniu

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

Użyj funkcji pomocniczych DepthGetMillimeters() i DepthGetVisibility() w cieniowaniu fragmentów, aby uzyskać szczegółowe informacje o bieżącym położeniu ekranu. Następnie użyj tych informacji do selektywnego zasłonięcia 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;
}

Wycisz obiekty wirtualne

Ukrywanie obiektów wirtualnych w treści cienia fragmentów. Zaktualizuj kanał alfa obiektu na podstawie jego głębokości. Spowoduje to wyrenderowanie 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);

Stosując renderowanie dwuprzebiegowe lub renderowanie z przebiegiem w przód na obiekt, możesz zastosować przesłonięcie. Efektywność każdego podejścia zależy od złożoności sceny i innych czynników związanych z aplikacją.

Renderowanie według obiektu, przekazywanie do przodu

Renderowanie z przebiegiem w przód na poziomie obiektu decyduje o przesłonięciu każdego piksela obiektu w jego cieniowaniu. Jeśli piksele nie są widoczne, są przycinane, zwykle w ramach mieszania alfa, co powoduje symulowanie przesłonięcia na urządzeniu użytkownika.

Renderowanie dwuprzebiegowe

W przypadku renderowania dwuprzebiegowego pierwszy przebieg renderuje wszystkie wirtualne treści w buforze pośrednim. Druga opcja polega na połączeniu sceny wirtualnej z tłem w oparciu o różnicę między głębią świata rzeczywistego a wirtualną głębią sceny. To podejście nie wymaga dodatkowego cieniowania specyficznego dla obiektów i zwykle daje bardziej jednolite wyniki niż metoda przekazywania do przodu.

Konwertowanie współrzędnych między zdjęciami z aparatu i obrazami głębi

Obrazy uzyskane za pomocą ArFrame_acquireCameraImage() mogą mieć inny format obrazu niż zdjęcia głębi. W tym przypadku obraz głębi to przycięcie obrazu z aparatu, a nie wszystkie piksele na zdjęciu z aparatu mają odpowiadające im oszacowanie głębi.

Aby uzyskać współrzędne obrazu głębi na obrazie 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 procesora 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 zgodności z głębokością

Testy trafień pozwalają użytkownikom umieszczać obiekty w rzeczywistej lokalizacji na scenie. Wcześniej testy trafień można było przeprowadzać tylko na wykrytych samolotach, a lokalizacja była ograniczona do dużych, płaskich powierzchni, takich jak wyniki wyświetlane przez zielone Androida. Wykorzystują one zarówno płynne, jak i nieprzetworzone dane, aby dostarczać dokładniejsze wyniki trafień, nawet na powierzchniach niepłaskich i o niskiej tekstury. Jest to oznaczone czerwonymi Androidem.

Aby użyć szczegółowych testów trafień, wywołaj ArFrame_hitTest() i sprawdź, czy na liście zwróconych jest 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 dzięki Raw Depth API.
  • Sprawdź ARCore Depth Lab, w którym przedstawiamy różne sposoby uzyskiwania dostępu do szczegółowych danych.