Sử dụng Chiều sâu trong ứng dụng Android NDK

API Độ sâu giúp máy ảnh của thiết bị hiểu được kích thước và hình dạng của đối tượng thực trong cảnh. Tính năng này sử dụng máy ảnh để tạo hình ảnh có chiều sâu hoặc bản đồ độ sâu, từ đó thêm một lớp hiện thực AR vào ứng dụng của bạn. Bạn có thể sử dụng thông tin mà hình ảnh chiều sâu cung cấp để khiến các vật thể ảo xuất hiện chính xác phía trước hoặc phía sau vật thể trong thế giới thực, mang lại trải nghiệm sống động và chân thực cho người dùng.

Thông tin về độ sâu được tính toán từ chuyển động và có thể kết hợp với thông tin từ cảm biến độ sâu phần cứng, chẳng hạn như cảm biến thời gian bay (ToF), nếu có. Thiết bị không cần có cảm biến ToF để hỗ trợ Depth API.

Điều kiện tiên quyết

Hãy đảm bảo bạn hiểu rõ các khái niệm cơ bản về AR và cách định cấu hình một phiên ARCore trước khi tiếp tục.

Hạn chế quyền truy cập vào các thiết bị được hỗ trợ độ sâu

Nếu ứng dụng của bạn yêu cầu hỗ trợ API Độ sâu, vì phần cốt lõi của trải nghiệm thực tế tăng cường phụ thuộc vào chiều sâu, hoặc vì không có phương án dự phòng phù hợp nào cho các phần của ứng dụng sử dụng chiều sâu, thì bạn có thể chọn giới hạn việc phân phối ứng dụng trong Cửa hàng Google Play ở các thiết bị hỗ trợ API Độ sâu bằng cách thêm dòng sau vào AndroidManifest.xml, ngoài các thay đổi về AndroidManifest.xml được mô tả trong hướng dẫn Bật thực tế tăng cường:

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

Bật độ sâu

Trong một phiên ARCore mới, hãy kiểm tra xem thiết bị của người dùng có hỗ trợ Chiều sâu hay không. Không phải thiết bị nào tương thích với ARCore đều hỗ trợ Depth API do các hạn chế về nguồn điện khi xử lý. Để tiết kiệm tài nguyên, theo mặc định, độ sâu sẽ bị tắt trên ARCore. Bật chế độ độ sâu để ứng dụng của bạn sử dụng API Độ sâu.

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

Thu thập hình ảnh có chiều sâu

Gọi ArFrame_acquireDepthImage16Bits() để lấy hình ảnh chiều sâu của khung hình hiện tại.

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

Hình ảnh được trả về cung cấp vùng đệm hình ảnh thô mà có thể được truyền đến chương trình đổ bóng mảnh để sử dụng trên GPU cho từng đối tượng được kết xuất cần che khuất. Thuộc tính này được định hướng trong AR_COORDINATES_2D_OPENGL_NORMALIZED_DEVICE_COORDINATES và có thể thay đổi thành AR_COORDINATES_2D_TEXTURE_NORMALIZED bằng cách gọi ArFrame_transformCoordinates2d(). Sau khi truy cập được hình ảnh chiều sâu trong chương trình đổ bóng đối tượng, bạn có thể truy cập trực tiếp vào các phép đo độ sâu này để xử lý che kín.

Tìm hiểu giá trị độ sâu

Cho trước điểm A trên hình học thực tế đã quan sát và một điểm 2D a đại diện cho cùng một điểm trong hình ảnh chiều sâu, giá trị do API Độ sâu cung cấp tại a bằng với chiều dài của CA chiếu trên trục chính. Đây cũng có thể được gọi là toạ độ z của A so với C gốc của máy ảnh. Khi làm việc với API Độ sâu, bạn cần hiểu rằng giá trị độ sâu không phải là độ dài của tia CA, mà là phép chiếu của tia đó.

Sử dụng độ sâu trong chương trình đổ bóng

Phân tích cú pháp thông tin về chiều sâu của khung hiện tại

Sử dụng các hàm trợ giúp DepthGetMillimeters()DepthGetVisibility() trong chương trình đổ bóng mảnh để truy cập vào thông tin chiều sâu của vị trí màn hình hiện tại. Sau đó, sử dụng thông tin này để che khuất một cách có chọn lọc các phần của đối tượng được kết xuất.

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

Bao gồm các vật thể ảo

Đưa các đối tượng ảo vào phần nội dung của chương trình đổ bóng mảnh. Cập nhật kênh alpha của đối tượng dựa trên độ sâu của đối tượng. Thao tác này sẽ hiển thị một đối tượng bị che khuất.

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

Bạn có thể kết xuất che khuất bằng cách kết xuất hai lượt hoặc cho mỗi đối tượng, kết xuất chuyển tiếp. Hiệu quả của mỗi phương pháp phụ thuộc vào độ phức tạp của cảnh cũng như những yếu tố khác dành riêng cho ứng dụng.

Kết xuất cho mỗi đối tượng, truyền chuyển tiếp

Tính năng kết xuất theo luồng chuyển tiếp trên mỗi đối tượng xác định việc che khuất từng pixel của đối tượng trong chương trình đổ bóng Material của đối tượng đó. Nếu các pixel không hiển thị, chúng sẽ bị cắt bớt, thường là thông qua pha trộn alpha, do đó mô phỏng sự che khuất trên thiết bị của người dùng.

Kết xuất hai lượt

Với kết xuất hai lượt, lượt đầu tiên kết xuất tất cả nội dung ảo vào một vùng đệm trung gian. Vé thứ hai kết hợp cảnh ảo vào nền dựa trên sự khác biệt giữa chiều sâu trong thế giới thực và chiều sâu của cảnh ảo. Phương pháp này không yêu cầu bạn phải thực hiện thêm thao tác đổ bóng dành riêng cho từng đối tượng và thường tạo ra kết quả đồng nhất hơn so với phương thức chuyển tiếp.

Chuyển đổi toạ độ giữa hình ảnh máy ảnh và hình ảnh có độ sâu

Hình ảnh thu được bằng ArFrame_acquireCameraImage() có thể có tỷ lệ khung hình khác với hình ảnh có chiều sâu. Trong trường hợp này, hình ảnh có chiều sâu là phần cắt của hình ảnh máy ảnh và không phải tất cả các pixel trong hình ảnh máy ảnh đều có ước tính chiều sâu hợp lệ tương ứng.

Cách lấy toạ độ hình ảnh chiều sâu cho các toạ độ trên hình ảnh 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);
}

Cách lấy toạ độ hình ảnh CPU cho toạ độ hình ảnh theo chiều sâu:

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

Kiểm tra chiều sâu

Thử nghiệm nhấn cho phép người dùng đặt vật thể tại một vị trí thực tế trong cảnh. Trước đây, chỉ có thể tiến hành kiểm tra va chạm trên các máy bay được phát hiện, giới hạn các vị trí ở các bề mặt rộng và phẳng, chẳng hạn như kết quả do Android màu xanh lục hiển thị. Kiểm tra độ sâu tấn công tận dụng cả thông tin về độ sâu thô và mượt mà để cung cấp kết quả đo chính xác hơn, ngay cả trên các bề mặt không phẳng và có kết cấu thấp. Điều này được hiển thị với các Android màu đỏ.

Để sử dụng kiểm tra lần truy cập được kích hoạt chiều sâu, hãy gọi ArFrame_hitTest() và kiểm tra các AR_TRACKABLE_DEPTH_POINT trong danh sách trả về.

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

Bước tiếp theo