Menggunakan Depth di aplikasi Android NDK Anda

Depth API membantu kamera perangkat memahami ukuran dan bentuk objek nyata dalam adegan. API ini menggunakan kamera untuk membuat gambar kedalaman, atau peta kedalaman, sehingga menambahkan lapisan realisme AR ke aplikasi Anda. Anda dapat menggunakan informasi yang diberikan oleh gambar kedalaman untuk membuat objek virtual muncul secara akurat di depan atau di belakang objek dunia nyata, sehingga memungkinkan pengalaman pengguna yang imersif dan realistis.

Informasi kedalaman dihitung dari gerakan dan dapat digabungkan dengan informasi dari sensor kedalaman hardware, seperti sensor waktu terbang (ToF), jika tersedia. Perangkat tidak memerlukan sensor ToF untuk mendukung Depth API.

Prasyarat

Pastikan Anda memahami konsep AR dasar dan cara mengonfigurasi sesi ARCore sebelum melanjutkan.

Batasi akses ke perangkat yang didukung Depth

Jika aplikasi Anda memerlukan dukungan Depth API, baik karena bagian inti dari Pengalaman AR mengandalkan kedalaman, atau karena tidak ada penggantian yang halus untuk bagian aplikasi yang menggunakan kedalaman, Anda dapat memilih untuk membatasi distribusi di Google Play Store untuk perangkat yang mendukung Depth API dengan menambahkan baris berikut ke AndroidManifest.xml Anda, selain baris AndroidManifest.xml perubahan dijelaskan dalam Panduan Enable ARCore:

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

Aktifkan Kedalaman

Dalam sesi ARCore baru, periksa apakah perangkat pengguna mendukung Kedalaman. Tidak semua perangkat yang kompatibel dengan ARCore mendukung Depth API karena kendala daya pemrosesan. Untuk menghemat resource, kedalaman dinonaktifkan secara default di ARCore. Aktifkan mode kedalaman agar aplikasi Anda menggunakan 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);

Mendapatkan gambar kedalaman

Panggil ArFrame_acquireDepthImage16Bits() guna mendapatkan gambar kedalaman untuk frame saat ini.

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

Gambar yang ditampilkan menyediakan buffer gambar mentah, yang dapat diteruskan ke shader fragmen untuk digunakan di GPU agar setiap objek yang dirender dapat dioklusi. Class ini berorientasi pada AR_COORDINATES_2D_OPENGL_NORMALIZED_DEVICE_COORDINATES dan dapat diubah menjadi AR_COORDINATES_2D_TEXTURE_NORMALIZED dengan memanggil ArFrame_transformCoordinates2d(). Setelah gambar kedalaman dapat diakses dalam shader objek, pengukuran kedalaman ini dapat diakses langsung untuk penanganan oklusi.

Memahami nilai kedalaman

Titik yang diberikan A pada geometri dunia nyata yang diamati dan titik 2D a merepresentasikan titik yang sama pada gambar depth, nilai yang diberikan oleh Depth API pada a sama dengan panjang CA yang diproyeksikan pada sumbu utama. Ini juga dapat disebut sebagai koordinat z dari A relatif terhadap kamera C asal. Saat menggunakan Depth API, penting untuk memahami bahwa nilai kedalaman bukanlah panjang sinar CA itu sendiri, melainkan proyeksi data tersebut.

Menggunakan kedalaman dalam shader

Mengurai informasi kedalaman untuk frame saat ini

Gunakan fungsi bantuan DepthGetMillimeters() dan DepthGetVisibility() dalam shader fragmen untuk mengakses informasi kedalaman posisi layar saat ini. Kemudian gunakan informasi ini untuk menutupi bagian objek yang dirender secara selektif.

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

Menghalangi objek virtual

Menghalangi objek virtual dalam isi shader fragmen. Update saluran alfa objek berdasarkan kedalamannya. Tindakan ini akan merender objek yang terhalang.

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

Anda bisa merender oklusi menggunakan rendering dua-pass atau rendering maju-pass per-objek. Efisiensi setiap pendekatan bergantung pada kompleksitas adegan dan pertimbangan khusus aplikasi lainnya.

Rendering per-objek, forward-pass

Per-objek, rendering pass-pass menentukan oklusi setiap piksel objek dalam shader materialnya. Jika piksel tidak terlihat, piksel tersebut akan dipotong, biasanya melalui pencampuran alfa, sehingga menyimulasikan oklusi di perangkat pengguna.

Rendering dua tahap

Dengan rendering dua tahap, penerusan pertama merender semua konten virtual ke dalam buffer perantara. Pass kedua menggabungkan adegan virtual ke latar belakang berdasarkan perbedaan antara kedalaman dunia nyata dengan kedalaman adegan virtual. Pendekatan ini tidak memerlukan pekerjaan shader spesifik per objek tambahan dan umumnya memberikan hasil yang terlihat lebih seragam daripada metode {i>forward-pass<i}.

Mengonversi koordinat antara gambar kamera dan gambar kedalaman

Gambar yang diperoleh menggunakan ArFrame_acquireCameraImage() mungkin memiliki rasio aspek yang berbeda dibandingkan dengan gambar kedalaman. Dalam hal ini, gambar kedalaman adalah pemangkasan gambar kamera, dan tidak semua piksel dalam gambar kamera memiliki perkiraan kedalaman yang valid.

Untuk memperoleh koordinat kedalaman gambar pada koordinat gambar 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);
}

Untuk memperoleh koordinat gambar CPU untuk koordinat kedalaman gambar:

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 kedalaman

Hit test memungkinkan pengguna menempatkan objek di lokasi dunia nyata pada scene. Sebelumnya, hit test hanya dapat dilakukan pada bidang yang terdeteksi, membatasi lokasi ke permukaan yang besar dan datar, seperti hasil yang ditunjukkan oleh Android hijau. Depth hit test memanfaatkan informasi kedalaman yang halus dan mentah untuk memberikan hasil hit yang lebih akurat, bahkan pada permukaan non-planar dan bertekstur rendah. Hal ini ditampilkan dengan Android berwarna merah.

Untuk menggunakan hit test yang mengaktifkan kedalaman, panggil ArFrame_hitTest() dan periksa AR_TRACKABLE_DEPTH_POINT dalam daftar yang ditampilkan.

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

Langkah selanjutnya

  • Aktifkan deteksi yang lebih akurat dengan Raw Depth API.
  • Lihat ARCore Depth Lab, yang menunjukkan berbagai cara untuk mengakses data kedalaman.