Phạm vi độ sâu tối đa có sẵn trong ARCore đã được nâng cấp trong bản phát hành 1.31 vào tháng 5 năm 2022. Bạn nên cập nhật tên phương thức bị ảnh hưởng để có chức năng ứng dụng tốt hơn.

Sử dụng Chiều sâu trong ứng dụng Android của bạn

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

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

Đ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ề thực tế tăng cường và cách định cấu hình 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ị hỗ trợ chiều sâu

Nếu ứng dụng của bạn yêu cầu hỗ trợ về Depth API, do một phần cốt lõi trong trải nghiệm AR dựa trên chiều sâu hoặc do không có giao diện dự phòng linh hoạt cho các phần của ứng dụng sử dụng chiều sâu, bạn có thể chọn hạn chế 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 việc thay đổi hướng dẫn AR được mô tả:

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

Bật chế độ xem độ sâu

Trong 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 tất cả các thiết bị tương thích với ARCore đều hỗ trợ API Chiều sâu do hạn chế về xử lý nguồn. Để tiết kiệm tài nguyên, bạn sẽ tắt tính năng độ sâu theo mặc định trên ARCore. Bật chế độ độ sâu để ứng dụng sử dụng API Độ sâu.

Java

Config config = session.getConfig();

// Check whether the user's device supports the Depth API.
boolean isDepthSupported = session.isDepthModeSupported(Config.DepthMode.AUTOMATIC);
if (isDepthSupported) {
  config.setDepthMode(Config.DepthMode.AUTOMATIC);
}
session.configure(config);

Kotlin

val config = session.config

// Check whether the user's device supports the Depth API.
val isDepthSupported = session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)
if (isDepthSupported) {
  config.depthMode = Config.DepthMode.AUTOMATIC
}
session.configure(config)

Thu thập hình ảnh chuyên sâu

Gọi Frame.acquireDepthImage16Bits() để lấy hình ảnh chiều sâu cho khung hiện tại.

Java

// Retrieve the depth image for the current frame, if available.
Image depthImage = null;
try {
  depthImage = frame.acquireDepthImage16Bits();
  // Use the depth image here.
} catch (NotYetAvailableException e) {
  // This 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.
} finally {
  if (depthImage != null) {
    depthImage.close();
  }
}

Kotlin

// Retrieve the depth image for the current frame, if available.
try {
  frame.acquireDepthImage16Bits().use { depthImage ->
    // Use the depth image here.
  }
} catch (e: NotYetAvailableException) {
  // This 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.
}

Hình ảnh trả về cung cấp vùng đệm hình ảnh thô, có thể được truyền đến chương trình đổ bóng phân mảnh để sử dụng trên GPU cho mỗi đối tượng được kết xuất bị che khuất. Biểu tượng này được định hướng trong OPENGL_NORMALIZED_DEVICE_COORDINATES và có thể thay đổi thành TEXTURE_NORMALIZED bằng cách gọi Frame.transformCoordinates2d(). Sau khi có thể truy cập hình ảnh độ 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 số đo độ sâu này để xử lý đổ bóng.

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

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

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

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

Sử dụng các hàm trợ giúp DepthGetMillimeters()DepthGetVisibility() trong chương trình đổ bóng phân mảnh để truy cập thông tin về chiều sâu của vị trí màn hình hiện tại. Sau đó, hãy sử dụng thông tin này để che khuất một số 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;
}

Ẩn các đối tượng ảo

Ẩn các đối tượng ảo trong phần nội dung của chương trình đổ bóng theo mảnh. Cập nhật kênh alpha của đối tượng dựa trên chiều 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 ẩn bằng cách sử dụng tính năng kết xuất hai chuyển hoặc kết xuất hai đối tượng, chuyển tiếp. Hiệu quả của từng phương pháp phụ thuộc vào độ phức tạp của cảnh và các điểm cần cân nhắc khác.

Kết xuất chuyển đối tượng, chuyển tiếp

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

Kết xuất hai luồng

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

Trích xuất khoảng cách từ một hình ảnh độ sâu

Để dùng API Độ sâu cho các mục đích khác ngoài việc che khuất các đối tượng ảo hoặc thể hiện dữ liệu về chiều sâu, hãy trích xuất thông tin từ hình ảnh của chiều sâu.

Java

/** Obtain the depth in millimeters for depthImage at coordinates (x, y). */
public int getMillimetersDepth(Image depthImage, int x, int y) {
  // The depth image has a single plane, which stores depth for each
  // pixel as 16-bit unsigned integers.
  Image.Plane plane = depthImage.getPlanes()[0];
  int byteIndex = x * plane.getPixelStride() + y * plane.getRowStride();
  ByteBuffer buffer = plane.getBuffer().order(ByteOrder.nativeOrder());
  return buffer.getShort(byteIndex);
}

Kotlin

/** Obtain the depth in millimeters for [depthImage] at coordinates ([x], [y]). */
fun getMillimetersDepth(depthImage: Image, x: Int, y: Int): Int {
  // The depth image has a single plane, which stores depth for each
  // pixel as 16-bit unsigned integers.
  val plane = depthImage.planes[0]
  val byteIndex = x * plane.pixelStride + y * plane.rowStride
  val buffer = plane.buffer.order(ByteOrder.nativeOrder())
  val depthSample = buffer.getShort(byteIndex)
  return depthSample.toInt()
}

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

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

Để có được toạ độ hình ảnh theo chiều sâu cho các toạ độ trên hình ảnh CPU:

Java

float[] cpuCoordinates = new float[] {cpuCoordinateX, cpuCoordinateY};
float[] textureCoordinates = new float[2];
frame.transformCoordinates2d(
    Coordinates2d.IMAGE_PIXELS,
    cpuCoordinates,
    Coordinates2d.TEXTURE_NORMALIZED,
    textureCoordinates);
if (textureCoordinates[0] < 0 || textureCoordinates[1] < 0) {
  // There are no valid depth coordinates, because the coordinates in the CPU image are in the
  // cropped area of the depth image.
  return null;
}
return new Pair<>(
    (int) (textureCoordinates[0] * depthImage.getWidth()),
    (int) (textureCoordinates[1] * depthImage.getHeight()));

Kotlin

val cpuCoordinates = floatArrayOf(cpuCoordinateX.toFloat(), cpuCoordinateY.toFloat())
val textureCoordinates = FloatArray(2)
frame.transformCoordinates2d(
  Coordinates2d.IMAGE_PIXELS,
  cpuCoordinates,
  Coordinates2d.TEXTURE_NORMALIZED,
  textureCoordinates
)
if (textureCoordinates[0] < 0 || textureCoordinates[1] < 0) {
  // There are no valid depth coordinates, because the coordinates in the CPU image are in the
  // cropped area of the depth image.
  return null
}
return (textureCoordinates[0] * depthImage.width).toInt() to
  (textureCoordinates[1] * depthImage.height).toInt()

Để có các tọa độ hình ảnh CPU cho các tọa độ hình ảnh chuyên sâu:

Java

float[] textureCoordinates =
    new float[] {
      (float) depthCoordinateX / (float) depthImage.getWidth(),
      (float) depthCoordinateY / (float) depthImage.getHeight()
    };
float[] cpuCoordinates = new float[2];
frame.transformCoordinates2d(
    Coordinates2d.TEXTURE_NORMALIZED,
    textureCoordinates,
    Coordinates2d.IMAGE_PIXELS,
    cpuCoordinates);
return new Pair<>((int) cpuCoordinates[0], (int) cpuCoordinates[1]);

Kotlin

val textureCoordinates =
  floatArrayOf(
    depthCoordinatesX.toFloat() / depthImage.width.toFloat(),
    depthCoordinatesY.toFloat() / depthImage.height.toFloat()
  )
val cpuCoordinates = FloatArray(2)
frame.transformCoordinates2d(
  Coordinates2d.TEXTURE_NORMALIZED,
  textureCoordinates,
  Coordinates2d.IMAGE_PIXELS,
  cpuCoordinates
)
return cpuCoordinates[0].toInt() to cpuCoordinates[1].toInt()

Kiểm tra lượt truy cập chuyên sâu

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

Để sử dụng tính năng kiểm tra lượt truy cập hỗ trợ chiều sâu, hãy gọi hitTest() và kiểm tra DepthPoints trong danh sách trả về.

Java

// Create a hit test using the Depth API.
List<HitResult> hitResultList = frame.hitTest(tap);
for (HitResult hit : hitResultList) {
  Trackable trackable = hit.getTrackable();
  if (trackable instanceof Plane
      || trackable instanceof Point
      || trackable instanceof DepthPoint) {
    useHitResult(hit);
    break;
  }
}

Kotlin

// Create a hit test using the Depth API.
val hitResult =
  frame
    .hitTest(tap)
    .filter {
      val trackable = it.trackable
      trackable is Plane || trackable is Point || trackable is DepthPoint
    }
    .firstOrNull()
useHitResult(hitResult)

Bước tiếp theo