Sử dụng API ARCore depth để có được trải nghiệm thực tế tăng cường sống động

1. Trước khi bắt đầu

ARCore là một nền tảng để tạo các ứng dụng Thực tế tăng cường (AR) trên thiết bị di động. Bằng cách sử dụng nhiều API, ARCore cho phép thiết bị của người dùng quan sát và nhận thông tin về môi trường xung quanh, đồng thời tương tác với thông tin đó.

Trong lớp học lập trình này, bạn sẽ trải qua quy trình tạo một ứng dụng đơn giản có hỗ trợ thực tế tăng cường (AR) bằng ARCore Depth API.

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

Lớp học lập trình này được viết cho những nhà phát triển có kiến thức về các khái niệm cơ bản về thực tế tăng cường.

Sản phẩm bạn sẽ tạo ra

1a0236e93212210c.gif

Bạn sẽ tạo một ứng dụng sử dụng hình ảnh độ sâu cho mỗi khung hình để trực quan hoá hình học của cảnh và thực hiện việc che khuất trên các tài sản ảo được đặt. Bạn sẽ thực hiện các bước cụ thể sau:

  • Kiểm tra xem điện thoại có hỗ trợ Depth API hay không
  • Truy xuất hình ảnh độ sâu cho từng khung hình
  • Trực quan hoá thông tin về độ sâu theo nhiều cách (xem ảnh động ở trên)
  • Sử dụng độ sâu để tăng tính chân thực của các ứng dụng có tính năng che khuất
  • Tìm hiểu cách xử lý một cách thích hợp những điện thoại không hỗ trợ Depth API

Bạn cần có

Yêu cầu về phần cứng

Yêu cầu về phần mềm

2. ARCore và Depth API

Depth API sử dụng camera RGB của một thiết bị được hỗ trợ để tạo bản đồ độ sâu (còn gọi là hình ảnh độ sâu). Bạn có thể sử dụng thông tin do bản đồ độ sâu cung cấp để các đối tượng ảo xuất hiện chính xác, ở phía trước hoặc phía sau các đối tượng trong thế giới thực, mang đến trải nghiệm chân thực và sống động cho người dùng.

ARCore Depth API cung cấp quyền truy cập vào hình ảnh độ sâu khớp với từng khung hình do Phiên của ARCore cung cấp. Mỗi pixel cung cấp thông tin đo khoảng cách từ camera đến môi trường, giúp tăng tính chân thực cho ứng dụng thực tế tăng cường của bạn.

Một chức năng chính của Depth API là tắc nghẽn: khả năng hiển thị chính xác các đối tượng kỹ thuật số so với các đối tượng trong thế giới thực. Điều này khiến người dùng cảm thấy như thể các đối tượng thực sự ở trong môi trường xung quanh họ.

Lớp học lập trình này sẽ hướng dẫn bạn quy trình tạo một ứng dụng đơn giản có hỗ trợ thực tế tăng cường, sử dụng hình ảnh độ sâu để thực hiện việc che khuất các đối tượng ảo phía sau các bề mặt trong thế giới thực và trực quan hoá hình học đã phát hiện của không gian.

3. Bắt đầu thiết lập

Thiết lập máy phát triển

  1. Kết nối thiết bị ARCore với máy tính bằng cáp USB. Đảm bảo rằng thiết bị của bạn cho phép gỡ lỗi qua USB.
  2. Mở một cửa sổ dòng lệnh rồi chạy adb devices, như minh hoạ dưới đây:
adb devices

List of devices attached
<DEVICE_SERIAL_NUMBER>    device

<DEVICE_SERIAL_NUMBER> sẽ là một chuỗi dành riêng cho thiết bị của bạn. Đảm bảo rằng bạn thấy chỉ một thiết bị trước khi tiếp tục.

Tải xuống và cài đặt Code

  1. Bạn có thể sao chép kho lưu trữ:
git clone https://github.com/googlecodelabs/arcore-depth

Hoặc tải tệp ZIP xuống rồi giải nén:

  1. Khởi chạy Android Studio rồi nhấp vào Open an existing Android Studio project (Mở một dự án hiện có trong Android Studio).
  2. Tìm thư mục nơi bạn đã giải nén tệp ZIP đã tải xuống ở trên rồi mở thư mục depth_codelab_io2020.

Đây là một dự án Gradle duy nhất có nhiều mô-đun. Nếu ngăn Dự án ở trên cùng bên trái của Android Studio chưa xuất hiện trong ngăn Dự án, hãy nhấp vào Projects (Dự án) trong trình đơn thả xuống.

Kết quả sẽ có dạng như sau:

Dự án này chứa các mô-đun sau:

  • part0_work: Ứng dụng khởi đầu. Bạn nên chỉnh sửa mô-đun này khi thực hiện lớp học lập trình này.
  • part1: Mã tham chiếu cho biết nội dung chỉnh sửa của bạn sẽ trông như thế nào khi bạn hoàn thành Phần 1.
  • part2: Mã tham chiếu khi bạn hoàn tất Phần 2.
  • part3: Mã tham chiếu khi bạn hoàn thành Phần 3.
  • part4_completed: Phiên bản cuối cùng của ứng dụng. Mã tham chiếu khi bạn hoàn thành Phần 4 và lớp học lập trình này.

Bạn sẽ làm việc trong mô-đun part0_work. Ngoài ra, còn có các giải pháp hoàn chỉnh cho từng phần của lớp học lập trình. Mỗi mô-đun là một ứng dụng có thể tạo.

4. Chạy ứng dụng cơ bản

  1. Nhấp vào Run > Run... > "part0_work". Trong hộp thoại Select Deployment Target (Chọn mục tiêu triển khai) xuất hiện, thiết bị của bạn sẽ có trong phần Connected Devices (Thiết bị đã kết nối).
  2. Chọn thiết bị của bạn rồi nhấp vào OK. Android Studio sẽ tạo ứng dụng ban đầu và chạy ứng dụng đó trên thiết bị của bạn.
  3. Ứng dụng sẽ yêu cầu quyền sử dụng camera. Nhấn vào Cho phép để tiếp tục.

c5ef65f7a1da0d9.png

Cách sử dụng ứng dụng

  1. Di chuyển thiết bị để giúp ứng dụng tìm thấy một mặt phẳng. Thông báo ở dưới cùng cho biết thời điểm bạn cần tiếp tục di chuyển.
  2. Nhấn vào một vị trí trên mặt phẳng để đặt điểm neo. Một biểu tượng Android sẽ được vẽ tại vị trí đặt điểm neo. Ứng dụng này chỉ cho phép bạn đặt một điểm neo tại một thời điểm.
  3. Di chuyển thiết bị. Hình ảnh sẽ xuất hiện ở cùng một vị trí, ngay cả khi thiết bị di chuyển.

Hiện tại, ứng dụng của bạn rất đơn giản và không biết nhiều về hình học cảnh trong thế giới thực.

Ví dụ: nếu bạn đặt một hình người Android phía sau ghế, thì hình ảnh kết xuất sẽ xuất hiện lơ lửng phía trước, vì ứng dụng không biết rằng có chiếc ghế ở đó và chiếc ghế sẽ che khuất hình người Android.

6182cf62be13cd97.png beb0d327205f80ee.png e4497751c6fad9a7.png

Để khắc phục vấn đề này, chúng tôi sẽ sử dụng Depth API để cải thiện tính chân thực và sống động trong ứng dụng này.

5. Kiểm tra xem Depth API có được hỗ trợ hay không (Phần 1)

ARCore Depth API chỉ chạy trên một số thiết bị được hỗ trợ. Trước khi tích hợp chức năng vào một ứng dụng bằng cách sử dụng những hình ảnh chiều sâu này, trước tiên, bạn phải đảm bảo rằng ứng dụng đang chạy trên một thiết bị được hỗ trợ.

Thêm một thành phần riêng tư mới vào DepthCodelabActivity. Thành phần này đóng vai trò là một cờ lưu trữ thông tin về việc thiết bị hiện tại có hỗ trợ độ sâu hay không:

private boolean isDepthSupported;

Chúng ta có thể điền cờ này từ bên trong hàm onResume(), nơi một Phiên mới được tạo.

Tìm mã hiện có:

// Creates the ARCore session.
session = new Session(/* context= */ this);

Cập nhật mã thành:

// Creates the ARCore session.
session = new Session(/* context= */ this);
Config config = session.getConfig();
isDepthSupported = session.isDepthModeSupported(Config.DepthMode.AUTOMATIC);
if (isDepthSupported) {
  config.setDepthMode(Config.DepthMode.AUTOMATIC);
} else {
  config.setDepthMode(Config.DepthMode.DISABLED);
}
session.configure(config);

Giờ đây, AR Session đã được định cấu hình một cách thích hợp và ứng dụng của bạn biết liệu có thể sử dụng các tính năng dựa trên độ sâu hay không.

Bạn cũng nên cho người dùng biết liệu độ sâu có được dùng cho phiên này hay không.

Thêm một thông báo khác vào Thanh thông báo nhanh. Nút này sẽ xuất hiện ở cuối màn hình:

// Add this line at the top of the file, with the other messages.
private static final String DEPTH_NOT_AVAILABLE_MESSAGE = "[Depth not supported on this device]";

Trong onDrawFrame(), bạn có thể trình bày thông báo này khi cần:

// Add this if-statement above messageSnackbarHelper.showMessage(this, messageToShow).
if (!isDepthSupported) {
  messageToShow += "\n" + DEPTH_NOT_AVAILABLE_MESSAGE;
}

Nếu ứng dụng của bạn chạy trên một thiết bị không hỗ trợ độ sâu, thì thông báo bạn vừa thêm sẽ xuất hiện ở dưới cùng:

5c878a7c27833cb2.png

Tiếp theo, bạn sẽ cập nhật ứng dụng để gọi Depth API và truy xuất hình ảnh độ sâu cho mỗi khung hình.

6. Truy xuất hình ảnh chiều sâu (Phần 2)

Depth API ghi lại thông tin quan sát 3D về môi trường của thiết bị và trả về một hình ảnh độ sâu có dữ liệu đó cho ứng dụng của bạn. Mỗi pixel trong hình ảnh độ sâu đại diện cho một phép đo khoảng cách từ camera của thiết bị đến môi trường thực tế.

Giờ đây, bạn sẽ sử dụng những hình ảnh chiều sâu này để cải thiện quá trình kết xuất và trực quan hoá trong ứng dụng. Bước đầu tiên là truy xuất hình ảnh chiều sâu cho từng khung hình và liên kết hoạ tiết đó để GPU sử dụng.

Trước tiên, hãy thêm một lớp mới vào dự án của bạn.
DepthTextureHandler chịu trách nhiệm truy xuất hình ảnh độ sâu cho một khung ARCore nhất định.
Thêm tệp này:

be8d14dfe9656551.png

src/main/java/com/google/ar/core/codelab/depth/DepthTextureHandler.java

package com.google.ar.core.codelab.depth;

import static android.opengl.GLES20.GL_CLAMP_TO_EDGE;
import static android.opengl.GLES20.GL_TEXTURE_2D;
import static android.opengl.GLES20.GL_TEXTURE_MAG_FILTER;
import static android.opengl.GLES20.GL_TEXTURE_MIN_FILTER;
import static android.opengl.GLES20.GL_TEXTURE_WRAP_S;
import static android.opengl.GLES20.GL_TEXTURE_WRAP_T;
import static android.opengl.GLES20.GL_UNSIGNED_BYTE;
import static android.opengl.GLES20.glBindTexture;
import static android.opengl.GLES20.glGenTextures;
import static android.opengl.GLES20.glTexImage2D;
import static android.opengl.GLES20.glTexParameteri;
import static android.opengl.GLES30.GL_LINEAR;
import static android.opengl.GLES30.GL_RG;
import static android.opengl.GLES30.GL_RG8;

import android.media.Image;
import com.google.ar.core.Frame;
import com.google.ar.core.exceptions.NotYetAvailableException;

/** Handle RG8 GPU texture containing a DEPTH16 depth image. */
public final class DepthTextureHandler {

  private int depthTextureId = -1;
  private int depthTextureWidth = -1;
  private int depthTextureHeight = -1;

  /**
   * Creates and initializes the depth texture. This method needs to be called on a
   * thread with a EGL context attached.
   */
  public void createOnGlThread() {
    int[] textureId = new int[1];
    glGenTextures(1, textureId, 0);
    depthTextureId = textureId[0];
    glBindTexture(GL_TEXTURE_2D, depthTextureId);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  }

  /**
   * Updates the depth texture with the content from acquireDepthImage16Bits().
   * This method needs to be called on a thread with an EGL context attached.
   */
  public void update(final Frame frame) {
    try {
      Image depthImage = frame.acquireDepthImage16Bits();
      depthTextureWidth = depthImage.getWidth();
      depthTextureHeight = depthImage.getHeight();
      glBindTexture(GL_TEXTURE_2D, depthTextureId);
      glTexImage2D(
          GL_TEXTURE_2D,
          0,
          GL_RG8,
          depthTextureWidth,
          depthTextureHeight,
          0,
          GL_RG,
          GL_UNSIGNED_BYTE,
          depthImage.getPlanes()[0].getBuffer());
      depthImage.close();
    } catch (NotYetAvailableException e) {
      // This normally means that depth data is not available yet.
    }
  }

  public int getDepthTexture() {
    return depthTextureId;
  }

  public int getDepthWidth() {
    return depthTextureWidth;
  }

  public int getDepthHeight() {
    return depthTextureHeight;
  }
}

Giờ đây, bạn sẽ thêm một thực thể của lớp này vào DepthCodelabActivity, đảm bảo bạn sẽ có một bản sao dễ truy cập của hình ảnh độ sâu cho mọi khung hình.

Trong DepthCodelabActivity.java, hãy thêm một thực thể của lớp mới làm biến thành viên riêng tư:

private final DepthTextureHandler depthTexture = new DepthTextureHandler();

Tiếp theo, hãy cập nhật phương thức onSurfaceCreated() để khởi động hoạ tiết này, nhờ đó, các chương trình đổ bóng GPU của chúng ta có thể sử dụng hoạ tiết này:

// Put this at the top of the "try" block in onSurfaceCreated().
depthTexture.createOnGlThread();

Cuối cùng, bạn muốn điền hoạ tiết này vào mọi khung hình bằng hình ảnh độ sâu mới nhất. Bạn có thể thực hiện việc này bằng cách gọi phương thức update() mà bạn đã tạo ở trên trên khung hình mới nhất được truy xuất từ session.
Vì ứng dụng này không bắt buộc phải hỗ trợ độ sâu, nên bạn chỉ sử dụng lệnh gọi này nếu đang dùng độ sâu.

// Add this just after "frame" is created inside onDrawFrame().
if (isDepthSupported) {
  depthTexture.update(frame);
}

Giờ đây, bạn đã có một hình ảnh chiều sâu được cập nhật theo từng khung hình. Nó đã sẵn sàng để được các chương trình đổ bóng của bạn sử dụng.

Tuy nhiên, chưa có gì thay đổi về hành vi của ứng dụng. Giờ đây, bạn sẽ dùng hình ảnh chiều sâu để cải thiện ứng dụng.

7. Kết xuất hình ảnh độ sâu (Phần 3)

Giờ đây, bạn đã có một hình ảnh chiều sâu để thử nghiệm, bạn sẽ muốn xem hình ảnh đó trông như thế nào. Trong phần này, bạn sẽ thêm một nút vào ứng dụng để hiển thị độ sâu cho từng khung hình.

Thêm chương trình đổ bóng mới

Có nhiều cách để xem hình ảnh có thông tin về độ sâu. Các chương trình đổ bóng sau đây cung cấp một hình ảnh trực quan đơn giản về việc ánh xạ màu.

Thêm một chương trình đổ bóng .vert mới

Trong Android Studio:

  1. Trước tiên, hãy thêm các chương trình đổ bóng .vert.frag mới vào thư mục src/main/assets/shaders/.
  2. Nhấp chuột phải vào thư mục chương trình đổ bóng
  3. Chọn New (Mới) -> File (Tệp)
  4. Đặt tên cho tệp là background_show_depth_map.vert
  5. Đặt tệp này làm tệp văn bản.

Trong tệp mới, hãy thêm đoạn mã sau:

src/main/assets/shaders/background_show_depth_map.vert

attribute vec4 a_Position;
attribute vec2 a_TexCoord;

varying vec2 v_TexCoord;

void main() {
   v_TexCoord = a_TexCoord;
   gl_Position = a_Position;
}

Lặp lại các bước trên để tạo chương trình đổ bóng mảnh trong cùng một thư mục và đặt tên là background_show_depth_map.frag.

Thêm đoạn mã sau vào tệp mới này:

src/main/assets/shaders/background_show_depth_map.frag

precision mediump float;
uniform sampler2D u_Depth;
varying vec2 v_TexCoord;
const highp float kMaxDepth = 20000.0; // In millimeters.

float GetDepthMillimeters(vec4 depth_pixel_value) {
  return 255.0 * (depth_pixel_value.r + depth_pixel_value.g * 256.0);
}

// Returns an interpolated color in a 6 degree polynomial interpolation.
vec3 GetPolynomialColor(in float x,
  in vec4 kRedVec4, in vec4 kGreenVec4, in vec4 kBlueVec4,
  in vec2 kRedVec2, in vec2 kGreenVec2, in vec2 kBlueVec2) {
  // Moves the color space a little bit to avoid pure red.
  // Removes this line for more contrast.
  x = clamp(x * 0.9 + 0.03, 0.0, 1.0);
  vec4 v4 = vec4(1.0, x, x * x, x * x * x);
  vec2 v2 = v4.zw * v4.z;
  return vec3(
    dot(v4, kRedVec4) + dot(v2, kRedVec2),
    dot(v4, kGreenVec4) + dot(v2, kGreenVec2),
    dot(v4, kBlueVec4) + dot(v2, kBlueVec2)
  );
}

// Returns a smooth Percept colormap based upon the Turbo colormap.
vec3 PerceptColormap(in float x) {
  const vec4 kRedVec4 = vec4(0.55305649, 3.00913185, -5.46192616, -11.11819092);
  const vec4 kGreenVec4 = vec4(0.16207513, 0.17712472, 15.24091500, -36.50657960);
  const vec4 kBlueVec4 = vec4(-0.05195877, 5.18000081, -30.94853351, 81.96403246);
  const vec2 kRedVec2 = vec2(27.81927491, -14.87899417);
  const vec2 kGreenVec2 = vec2(25.95549545, -5.02738237);
  const vec2 kBlueVec2 = vec2(-86.53476570, 30.23299484);
  const float kInvalidDepthThreshold = 0.01;
  return step(kInvalidDepthThreshold, x) *
         GetPolynomialColor(x, kRedVec4, kGreenVec4, kBlueVec4,
                            kRedVec2, kGreenVec2, kBlueVec2);
}

void main() {
  vec4 packed_depth = texture2D(u_Depth, v_TexCoord.xy);
  highp float depth_mm = GetDepthMillimeters(packed_depth);
  highp float normalized_depth = depth_mm / kMaxDepth;
  vec4 depth_color = vec4(PerceptColormap(normalized_depth), 1.0);
  gl_FragColor = depth_color;
}

Tiếp theo, hãy cập nhật lớp BackgroundRenderer để sử dụng các chương trình đổ bóng mới này, nằm trong src/main/java/com/google/ar/core/codelab/common/rendering/BackgroundRenderer.java.

Thêm đường dẫn tệp vào chương trình đổ bóng ở đầu lớp:

// Add these under the other shader names at the top of the class.
private static final String DEPTH_VERTEX_SHADER_NAME = "shaders/background_show_depth_map.vert";
private static final String DEPTH_FRAGMENT_SHADER_NAME = "shaders/background_show_depth_map.frag";

Thêm nhiều biến thành viên hơn vào lớp BackgroundRenderer, vì lớp này sẽ chạy 2 chương trình đổ bóng:

// Add to the top of file with the rest of the member variables.
private int depthProgram;
private int depthTextureParam;
private int depthTextureId = -1;
private int depthQuadPositionParam;
private int depthQuadTexCoordParam;

Thêm một phương thức mới để điền sẵn các trường này:

// Add this method below createOnGlThread().
public void createDepthShaders(Context context, int depthTextureId) throws IOException {
  int vertexShader =
      ShaderUtil.loadGLShader(
          TAG, context, GLES20.GL_VERTEX_SHADER, DEPTH_VERTEX_SHADER_NAME);
  int fragmentShader =
      ShaderUtil.loadGLShader(
          TAG, context, GLES20.GL_FRAGMENT_SHADER, DEPTH_FRAGMENT_SHADER_NAME);

  depthProgram = GLES20.glCreateProgram();
  GLES20.glAttachShader(depthProgram, vertexShader);
  GLES20.glAttachShader(depthProgram, fragmentShader);
  GLES20.glLinkProgram(depthProgram);
  GLES20.glUseProgram(depthProgram);
  ShaderUtil.checkGLError(TAG, "Program creation");

  depthTextureParam = GLES20.glGetUniformLocation(depthProgram, "u_Depth");
  ShaderUtil.checkGLError(TAG, "Program parameters");

  depthQuadPositionParam = GLES20.glGetAttribLocation(depthProgram, "a_Position");
  depthQuadTexCoordParam = GLES20.glGetAttribLocation(depthProgram, "a_TexCoord");

  this.depthTextureId = depthTextureId;
}

Thêm phương thức này (được dùng để vẽ bằng các chương trình đổ bóng này trên mỗi khung hình):

// Put this at the bottom of the file.
public void drawDepth(@NonNull Frame frame) {
  if (frame.hasDisplayGeometryChanged()) {
    frame.transformCoordinates2d(
        Coordinates2d.OPENGL_NORMALIZED_DEVICE_COORDINATES,
        quadCoords,
        Coordinates2d.TEXTURE_NORMALIZED,
        quadTexCoords);
  }

  if (frame.getTimestamp() == 0 || depthTextureId == -1) {
    return;
  }

  // Ensure position is rewound before use.
  quadTexCoords.position(0);

  // No need to test or write depth, the screen quad has arbitrary depth, and is expected
  // to be drawn first.
  GLES20.glDisable(GLES20.GL_DEPTH_TEST);
  GLES20.glDepthMask(false);

  GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
  GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, depthTextureId);
  GLES20.glUseProgram(depthProgram);
  GLES20.glUniform1i(depthTextureParam, 0);

  // Set the vertex positions and texture coordinates.
  GLES20.glVertexAttribPointer(
        depthQuadPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, quadCoords);
  GLES20.glVertexAttribPointer(
        depthQuadTexCoordParam, TEXCOORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, quadTexCoords);

  // Draws the quad.
  GLES20.glEnableVertexAttribArray(depthQuadPositionParam);
  GLES20.glEnableVertexAttribArray(depthQuadTexCoordParam);
  GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
  GLES20.glDisableVertexAttribArray(depthQuadPositionParam);
  GLES20.glDisableVertexAttribArray(depthQuadTexCoordParam);

  // Restore the depth state for further drawing.
  GLES20.glDepthMask(true);
  GLES20.glEnable(GLES20.GL_DEPTH_TEST);

  ShaderUtil.checkGLError(TAG, "BackgroundRendererDraw");
}

Thêm nút bật/tắt

Giờ đây, bạn có thể kết xuất bản đồ độ sâu, hãy sử dụng bản đồ này! Thêm một nút bật/tắt chế độ kết xuất này.

Ở đầu tệp DepthCodelabActivity, hãy thêm một lệnh nhập để sử dụng nút:

import android.widget.Button;

Cập nhật lớp để thêm một thành phần boolean cho biết liệu tính năng kết xuất độ sâu có được bật/tắt hay không (theo mặc định, tính năng này sẽ tắt):

private boolean showDepthMap = false;

Tiếp theo, hãy thêm nút điều khiển giá trị boolean showDepthMap vào cuối phương thức onCreate():

final Button toggleDepthButton = (Button) findViewById(R.id.toggle_depth_button);
    toggleDepthButton.setOnClickListener(
        view -> {
          if (isDepthSupported) {
            showDepthMap = !showDepthMap;
            toggleDepthButton.setText(showDepthMap ? R.string.hide_depth : R.string.show_depth);
          } else {
            showDepthMap = false;
            toggleDepthButton.setText(R.string.depth_not_available);
          }
        });

Thêm các chuỗi này vào res/values/strings.xml:

<string translatable="false" name="show_depth">Show Depth</string>
<string translatable="false" name="hide_depth">Hide Depth</string>
<string translatable="false" name="depth_not_available">Depth Not Available</string>

Thêm nút này vào cuối bố cục ứng dụng trong res/layout/activity_main.xml:

<Button
    android:id="@+id/toggle_depth_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="20dp"
    android:gravity="center"
    android:text="@string/show_depth"
    android:layout_alignParentRight="true"
    android:layout_alignParentTop="true"/>

Giờ đây, nút này sẽ kiểm soát giá trị của boolean showDepthMap. Sử dụng cờ này để kiểm soát việc bản đồ độ sâu có được kết xuất hay không.

Quay lại phương thức onDrawFrame() trong DepthCodelabActivity, hãy thêm:

// Add this snippet just under backgroundRenderer.draw(frame);
if (showDepthMap) {
  backgroundRenderer.drawDepth(frame);
}

Truyền kết cấu độ sâu đến backgroundRenderer bằng cách thêm dòng sau vào onSurfaceCreated():

// Add to onSurfaceCreated() after backgroundRenderer.createonGlThread(/*context=*/ this);
backgroundRenderer.createDepthShaders(/*context=*/ this, depthTexture.getDepthTexture());

Giờ đây, bạn có thể xem hình ảnh chiều sâu của từng khung hình bằng cách nhấn vào nút ở phía trên bên phải màn hình.

Chạy mà không có sự hỗ trợ của Depth API

Chạy với sự hỗ trợ của Depth API

[Không bắt buộc] Ảnh động chiều sâu sống động

Ứng dụng hiện hiển thị trực tiếp bản đồ độ sâu. Các pixel màu đỏ biểu thị những khu vực gần. Các pixel màu xanh dương biểu thị những khu vực ở xa.

Có nhiều cách để truyền tải thông tin về độ sâu. Trong phần này, bạn sẽ sửa đổi chương trình đổ bóng để phát ra ánh sáng độ sâu định kỳ, bằng cách sửa đổi chương trình đổ bóng để chỉ hiển thị độ sâu trong các dải liên tục di chuyển ra xa camera.

Bắt đầu bằng cách thêm các biến này vào đầu background_show_depth_map.frag:

uniform float u_DepthRangeToRenderMm;
const float kDepthWidthToRenderMm = 350.0;
  • Sau đó, hãy dùng các giá trị này để lọc những pixel cần có giá trị độ sâu trong hàm main() của chương trình đổ bóng:
// Add this line at the end of main().
gl_FragColor.a = clamp(1.0 - abs((depth_mm - u_DepthRangeToRenderMm) / kDepthWidthToRenderMm), 0.0, 1.0);

Tiếp theo, hãy cập nhật BackgroundRenderer.java để duy trì các tham số chương trình đổ bóng này. Thêm các trường sau vào đầu lớp:

private static final float MAX_DEPTH_RANGE_TO_RENDER_MM = 20000.0f;
private float depthRangeToRenderMm = 0.0f;
private int depthRangeToRenderMmParam;

Bên trong phương thức createDepthShaders(), hãy thêm nội dung sau để so khớp các tham số này với chương trình đổ bóng:

depthRangeToRenderMmParam = GLES20.glGetUniformLocation(depthProgram, "u_DepthRangeToRenderMm");
  • Cuối cùng, bạn có thể kiểm soát phạm vi này theo thời gian trong phương thức drawDepth(). Thêm mã sau đây, mã này sẽ tăng phạm vi này mỗi khi một khung hình được vẽ:
// Enables alpha blending.
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);

// Updates range each time draw() is called.
depthRangeToRenderMm += 50.0f;
if (depthRangeToRenderMm > MAX_DEPTH_RANGE_TO_RENDER_MM) {
  depthRangeToRenderMm = 0.0f;
}

// Passes latest value to the shader.
GLES20.glUniform1f(depthRangeToRenderMmParam, depthRangeToRenderMm);

Giờ đây, độ sâu được trực quan hoá dưới dạng một xung động chảy qua cảnh của bạn.

b846e4365d7b69b1.gif

Bạn có thể thay đổi các giá trị được cung cấp ở đây để làm cho xung chậm hơn, nhanh hơn, rộng hơn, hẹp hơn, v.v. Bạn cũng có thể thử khám phá những cách hoàn toàn mới để thay đổi chương trình đổ bóng nhằm hiển thị thông tin về độ sâu!

8. Sử dụng Depth API để che khuất (Phần 4)

Bây giờ, bạn sẽ xử lý hiện tượng che khuất đối tượng trong ứng dụng.

Tắc nghẽn là hiện tượng xảy ra khi đối tượng ảo không thể hiển thị đầy đủ, vì có các đối tượng thực giữa đối tượng ảo và camera. Việc quản lý hiện tượng che khuất là điều cần thiết để trải nghiệm AR trở nên sống động.

Việc kết xuất đúng cách các đối tượng ảo theo thời gian thực sẽ tăng cường tính chân thực và độ tin cậy của cảnh tăng cường. Để xem thêm ví dụ, vui lòng xem video của chúng tôi về việc kết hợp thực tế bằng Depth API.

Trong phần này, bạn sẽ cập nhật ứng dụng để chỉ bao gồm các đối tượng ảo nếu có thông tin về độ sâu.

Thêm chương trình đổ bóng đối tượng mới

Giống như trong các phần trước, bạn sẽ thêm các chương trình đổ bóng mới để hỗ trợ thông tin về độ sâu. Lần này, bạn có thể sao chép các chương trình đổ bóng đối tượng hiện có và thêm chức năng tắc nghẽn.

Bạn cần giữ cả hai phiên bản của chương trình đổ bóng đối tượng để ứng dụng có thể đưa ra quyết định trong thời gian chạy về việc có hỗ trợ độ sâu hay không.

Tạo bản sao của các tệp chương trình đổ bóng object.vertobject.frag trong thư mục src/main/assets/shaders.

  • Sao chép object.vert vào tệp đích src/main/assets/shaders/occlusion_object.vert
  • Sao chép object.frag vào tệp đích src/main/assets/shaders/occlusion_object.frag

Trong occlusion_object.vert, hãy thêm biến sau đây phía trên main():

varying vec3 v_ScreenSpacePosition;

Đặt biến này ở cuối main():

v_ScreenSpacePosition = gl_Position.xyz / gl_Position.w;

Cập nhật occlusion_object.frag bằng cách thêm các biến này phía trên main() ở đầu tệp:

varying vec3 v_ScreenSpacePosition;

uniform sampler2D u_Depth;
uniform mat3 u_UvTransform;
uniform float u_DepthTolerancePerMm;
uniform float u_OcclusionAlpha;
uniform float u_DepthAspectRatio;
  • Thêm các hàm trợ giúp này phía trên main() trong chương trình đổ bóng để dễ dàng xử lý thông tin về độ sâu:
float GetDepthMillimeters(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(u_Depth, depth_uv).xyz;
  return dot(packedDepthAndVisibility.xy, vec2(255.0, 256.0 * 255.0));
}

// Returns linear interpolation position of value between min and max bounds.
// E.g., InverseLerp(1100, 1000, 2000) returns 0.1.
float InverseLerp(in float value, in float min_bound, in float max_bound) {
  return clamp((value - min_bound) / (max_bound - min_bound), 0.0, 1.0);
}

// Returns a value between 0.0 (not visible) and 1.0 (completely visible)
// Which represents how visible or occluded is the pixel in relation to the
// depth map.
float GetVisibility(in vec2 depth_uv, in float asset_depth_mm) {
  float depth_mm = GetDepthMillimeters(depth_uv);

  // Instead of a hard z-buffer test, allow the asset to fade into the
  // background along a 2 * u_DepthTolerancePerMm * asset_depth_mm
  // range centered on the background depth.
  float visibility_occlusion = clamp(0.5 * (depth_mm - asset_depth_mm) /
    (u_DepthTolerancePerMm * asset_depth_mm) + 0.5, 0.0, 1.0);

  // Depth close to zero is most likely invalid, do not use it for occlusions.
  float visibility_depth_near = 1.0 - InverseLerp(
      depth_mm, /*min_depth_mm=*/150.0, /*max_depth_mm=*/200.0);

  // Same for very high depth values.
  float visibility_depth_far = InverseLerp(
      depth_mm, /*min_depth_mm=*/17500.0, /*max_depth_mm=*/20000.0);

  float visibility =
    max(max(visibility_occlusion, u_OcclusionAlpha),
      max(visibility_depth_near, visibility_depth_far));

  return visibility;
}

Giờ đây, hãy cập nhật main() trong occlusion_object.frag để nhận biết độ sâu và áp dụng hiệu ứng che khuất. Thêm các dòng sau vào cuối tệp:

const float kMToMm = 1000.0;
float asset_depth_mm = v_ViewPosition.z * kMToMm * -1.;
vec2 depth_uvs = (u_UvTransform * vec3(v_ScreenSpacePosition.xy, 1)).xy;
gl_FragColor.a *= GetVisibility(depth_uvs, asset_depth_mm);

Bây giờ, bạn đã có phiên bản mới của chương trình đổ bóng đối tượng, bạn có thể sửa đổi mã trình kết xuất.

Kết xuất vật thể bị che khuất

Tiếp theo, hãy sao chép lớp ObjectRenderer trong src/main/java/com/google/ar/core/codelab/common/rendering/ObjectRenderer.java.

  • Chọn lớp ObjectRenderer
  • Nhấp chuột phải > Sao chép
  • Chọn thư mục rendering
  • Nhấp chuột phải > Dán

7487ece853690c31.png

  • Đổi tên lớp thành OcclusionObjectRenderer

760a4c80429170c2.png

Lớp mới được đổi tên hiện sẽ xuất hiện trong cùng một thư mục:

9335c373dc60cd17.png

Mở OcclusionObjectRenderer.java vừa tạo và thay đổi đường dẫn của chương trình đổ bóng ở đầu tệp:

private static final String VERTEX_SHADER_NAME = "shaders/occlusion_object.vert";
private static final String FRAGMENT_SHADER_NAME = "shaders/occlusion_object.frag";
  • Thêm các biến thành viên liên quan đến độ sâu này cùng với các biến khác ở đầu lớp. Các biến này sẽ điều chỉnh độ sắc nét của đường viền che khuất.
// Shader location: depth texture
private int depthTextureUniform;

// Shader location: transform to depth uvs
private int depthUvTransformUniform;

// Shader location: depth tolerance property
private int depthToleranceUniform;

// Shader location: maximum transparency for the occluded part.
private int occlusionAlphaUniform;

private int depthAspectRatioUniform;

private float[] uvTransform = null;
private int depthTextureId;

Tạo các biến thành viên này với giá trị mặc định ở đầu lớp:

// These values will be changed each frame based on the distance to the object.
private float depthAspectRatio = 0.0f;
private final float depthTolerancePerMm = 0.015f;
private final float occlusionsAlpha = 0.0f;

Khởi chạy các tham số đồng nhất cho chương trình đổ bóng trong phương thức createOnGlThread():

// Occlusions Uniforms.  Add these lines before the first call to ShaderUtil.checkGLError
// inside the createOnGlThread() method.
depthTextureUniform = GLES20.glGetUniformLocation(program, "u_Depth");
depthUvTransformUniform = GLES20.glGetUniformLocation(program, "u_UvTransform");
depthToleranceUniform = GLES20.glGetUniformLocation(program, "u_DepthTolerancePerMm");
occlusionAlphaUniform = GLES20.glGetUniformLocation(program, "u_OcclusionAlpha");
depthAspectRatioUniform = GLES20.glGetUniformLocation(program, "u_DepthAspectRatio");
  • Đảm bảo các giá trị này được cập nhật mỗi khi chúng được vẽ bằng cách cập nhật phương thức draw():
// Add after other GLES20.glUniform calls inside draw().
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, depthTextureId);
GLES20.glUniform1i(depthTextureUniform, 1);
GLES20.glUniformMatrix3fv(depthUvTransformUniform, 1, false, uvTransform, 0);
GLES20.glUniform1f(depthToleranceUniform, depthTolerancePerMm);
GLES20.glUniform1f(occlusionAlphaUniform, occlusionsAlpha);
GLES20.glUniform1f(depthAspectRatioUniform, depthAspectRatio);

Thêm các dòng sau đây trong draw() để bật chế độ hoà trộn trong quá trình kết xuất để có thể áp dụng độ trong suốt cho các đối tượng ảo khi chúng bị che khuất:

// Add these lines just below the code-block labeled "Enable vertex arrays"
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
// Add these lines just above the code-block labeled "Disable vertex arrays"
GLES20.glDisable(GLES20.GL_BLEND);
GLES20.glDepthMask(true);
  • Thêm các phương thức sau để người gọi OcclusionObjectRenderer có thể cung cấp thông tin về độ sâu:
// Add these methods at the bottom of the OcclusionObjectRenderer class.
public void setUvTransformMatrix(float[] transform) {
  uvTransform = transform;
}

public void setDepthTexture(int textureId, int width, int height) {
  depthTextureId = textureId;
  depthAspectRatio = (float) width / (float) height;
}

Kiểm soát hiện tượng che khuất đối tượng

Giờ đây, khi đã có một OcclusionObjectRenderer mới, bạn có thể thêm OcclusionObjectRenderer đó vào DepthCodelabActivity và chọn thời điểm cũng như cách sử dụng tính năng kết xuất che khuất.

Bật logic này bằng cách thêm một thực thể OcclusionObjectRenderer vào hoạt động, để cả ObjectRendererOcclusionObjectRenderer đều là thành phần của DepthCodelabActivity:

// Add this include at the top of the file.
import com.google.ar.core.codelab.common.rendering.OcclusionObjectRenderer;
// Add this member just below the existing "virtualObject", so both are present.
private final OcclusionObjectRenderer occludedVirtualObject = new OcclusionObjectRenderer();
  • Tiếp theo, bạn có thể kiểm soát thời điểm sử dụng occludedVirtualObject này dựa trên việc thiết bị hiện tại có hỗ trợ Depth API hay không. Thêm các dòng này vào bên trong phương thức onSurfaceCreated, bên dưới vị trí mà virtualObject được định cấu hình:
if (isDepthSupported) {
  occludedVirtualObject.createOnGlThread(/*context=*/ this, "models/andy.obj", "models/andy.png");
  occludedVirtualObject.setDepthTexture(
     depthTexture.getDepthTexture(),
     depthTexture.getDepthWidth(),
     depthTexture.getDepthHeight());
  occludedVirtualObject.setMaterialProperties(0.0f, 2.0f, 0.5f, 6.0f);
}

Trên những thiết bị không hỗ trợ độ sâu, phiên bản occludedVirtualObject sẽ được tạo nhưng không được dùng. Trên điện thoại có độ sâu, cả hai phiên bản đều được khởi tạo và quyết định thời gian chạy sẽ được đưa ra để chọn phiên bản trình kết xuất cần dùng khi vẽ.

Trong phương thức onDrawFrame(), hãy tìm mã hiện có:

virtualObject.updateModelMatrix(anchorMatrix, scaleFactor);
virtualObject.draw(viewmtx, projmtx, colorCorrectionRgba, OBJECT_COLOR);

Thay thế mã này bằng mã sau:

if (isDepthSupported) {
  occludedVirtualObject.updateModelMatrix(anchorMatrix, scaleFactor);
  occludedVirtualObject.draw(viewmtx, projmtx, colorCorrectionRgba, OBJECT_COLOR);
} else {
  virtualObject.updateModelMatrix(anchorMatrix, scaleFactor);
  virtualObject.draw(viewmtx, projmtx, colorCorrectionRgba, OBJECT_COLOR);
}

Cuối cùng, hãy đảm bảo rằng hình ảnh chiều sâu được ánh xạ chính xác lên hình ảnh kết xuất đầu ra. Vì hình ảnh độ sâu có độ phân giải khác và có thể có tỷ lệ khung hình khác với màn hình của bạn, nên toạ độ kết cấu có thể khác giữa hình ảnh độ sâu và hình ảnh camera.

  • Thêm phương thức trợ giúp getTextureTransformMatrix() vào cuối tệp. Phương thức này trả về một ma trận biến đổi. Khi được áp dụng, ma trận này sẽ giúp các UV trong không gian màn hình khớp chính xác với các toạ độ kết cấu tứ giác dùng để kết xuất nguồn cấp dữ liệu từ camera. Thuật toán này cũng tính đến hướng của thiết bị.
private static float[] getTextureTransformMatrix(Frame frame) {
  float[] frameTransform = new float[6];
  float[] uvTransform = new float[9];
  // XY pairs of coordinates in NDC space that constitute the origin and points along the two
  // principal axes.
  float[] ndcBasis = {0, 0, 1, 0, 0, 1};

  // Temporarily store the transformed points into outputTransform.
  frame.transformCoordinates2d(
      Coordinates2d.OPENGL_NORMALIZED_DEVICE_COORDINATES,
      ndcBasis,
      Coordinates2d.TEXTURE_NORMALIZED,
      frameTransform);

  // Convert the transformed points into an affine transform and transpose it.
  float ndcOriginX = frameTransform[0];
  float ndcOriginY = frameTransform[1];
  uvTransform[0] = frameTransform[2] - ndcOriginX;
  uvTransform[1] = frameTransform[3] - ndcOriginY;
  uvTransform[2] = 0;
  uvTransform[3] = frameTransform[4] - ndcOriginX;
  uvTransform[4] = frameTransform[5] - ndcOriginY;
  uvTransform[5] = 0;
  uvTransform[6] = ndcOriginX;
  uvTransform[7] = ndcOriginY;
  uvTransform[8] = 1;

  return uvTransform;
}

getTextureTransformMatrix() yêu cầu nội dung nhập sau ở đầu tệp:

import com.google.ar.core.Coordinates2d;

Bạn muốn tính toán phép biến đổi giữa các toạ độ kết cấu này bất cứ khi nào kết cấu màn hình thay đổi (chẳng hạn như nếu màn hình xoay). Chức năng này bị hạn chế.

Thêm cờ sau vào đầu tệp:

// Add this member at the top of the file.
private boolean calculateUVTransform = true;
  • Bên trong onDrawFrame(), hãy kiểm tra xem có cần tính toán lại phép biến đổi đã lưu trữ sau khi tạo khung hình và camera hay không:
// Add these lines inside onDrawFrame() after frame.getCamera().
if (frame.hasDisplayGeometryChanged() || calculateUVTransform) {
  calculateUVTransform = false;
  float[] transform = getTextureTransformMatrix(frame);
  occludedVirtualObject.setUvTransformMatrix(transform);
}

Sau khi thực hiện những thay đổi này, giờ đây, bạn có thể chạy ứng dụng với tính năng che khuất đối tượng ảo!

Giờ đây, ứng dụng của bạn sẽ chạy trơn tru trên mọi điện thoại và tự động sử dụng độ sâu để che khuất khi được hỗ trợ.

Ứng dụng đang chạy có hỗ trợ Depth API

Ứng dụng đang chạy không hỗ trợ Depth API

9. [Không bắt buộc] Cải thiện chất lượng che khuất

Phương thức che khuất dựa trên độ sâu (được triển khai ở trên) cung cấp khả năng che khuất với ranh giới rõ ràng. Khi camera di chuyển ra xa vật thể, các phép đo độ sâu có thể trở nên kém chính xác hơn, điều này có thể dẫn đến các hiện tượng giả tạo về hình ảnh.

Chúng ta có thể giảm thiểu vấn đề này bằng cách thêm độ mờ vào kiểm thử che khuất, giúp các đối tượng ảo bị che khuất có cạnh mượt mà hơn.

occlusion_object.frag

Thêm biến đồng nhất sau đây vào đầu occlusion_object.frag:

uniform float u_OcclusionBlurAmount;

Thêm hàm trợ giúp này ngay phía trên main() trong chương trình đổ bóng. Hàm này sẽ áp dụng hiệu ứng làm mờ hạt nhân cho quá trình lấy mẫu tắc nghẽn:

float GetBlurredVisibilityAroundUV(in vec2 uv, in float asset_depth_mm) {
  // Kernel used:
  // 0   4   7   4   0
  // 4   16  26  16  4
  // 7   26  41  26  7
  // 4   16  26  16  4
  // 0   4   7   4   0
  const float kKernelTotalWeights = 269.0;
  float sum = 0.0;

  vec2 blurriness = vec2(u_OcclusionBlurAmount,
                         u_OcclusionBlurAmount * u_DepthAspectRatio);

  float current = 0.0;

  current += GetVisibility(uv + vec2(-1.0, -2.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(+1.0, -2.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(-1.0, +2.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(+1.0, +2.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(-2.0, +1.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(+2.0, +1.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(-2.0, -1.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(+2.0, -1.0) * blurriness, asset_depth_mm);
  sum += current * 4.0;

  current = 0.0;
  current += GetVisibility(uv + vec2(-2.0, -0.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(+2.0, +0.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(+0.0, +2.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(-0.0, -2.0) * blurriness, asset_depth_mm);
  sum += current * 7.0;

  current = 0.0;
  current += GetVisibility(uv + vec2(-1.0, -1.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(+1.0, -1.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(-1.0, +1.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(+1.0, +1.0) * blurriness, asset_depth_mm);
  sum += current * 16.0;

  current = 0.0;
  current += GetVisibility(uv + vec2(+0.0, +1.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(-0.0, -1.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(-1.0, -0.0) * blurriness, asset_depth_mm);
  current += GetVisibility(uv + vec2(+1.0, +0.0) * blurriness, asset_depth_mm);
  sum += current * 26.0;

  sum += GetVisibility(uv , asset_depth_mm) * 41.0;

  return sum / kKernelTotalWeights;
}

Thay thế dòng hiện có này trong main():

gl_FragColor.a *= GetVisibility(depth_uvs, asset_depth_mm);

bằng dòng này:

gl_FragColor.a *= GetBlurredVisibilityAroundUV(depth_uvs, asset_depth_mm);

Cập nhật trình kết xuất để tận dụng chức năng mới này của chương trình đổ bóng.

OcclusionObjectRenderer.java

Thêm các biến thành phần sau vào đầu lớp:

private int occlusionBlurUniform;
private final float occlusionsBlur = 0.01f;

Thêm nội dung sau vào phương thức createOnGlThread:

// Add alongside the other calls to GLES20.glGetUniformLocation.
occlusionBlurUniform = GLES20.glGetUniformLocation(program, "u_OcclusionBlurAmount");

Thêm nội dung sau vào phương thức draw:

// Add alongside the other calls to GLES20.glUniform1f.
GLES20.glUniform1f(occlusionBlurUniform, occlusionsBlur);

So sánh bằng hình ảnh

Giờ đây, ranh giới che khuất sẽ mượt mà hơn nhờ những thay đổi này.

10. Xây dựng – Chạy – Kiểm thử

Tạo và chạy ứng dụng

  1. Cắm một thiết bị Android qua USB.
  2. Chọn File > Build and Run (Tệp > Xây dựng và chạy).
  3. Lưu dưới dạng: ARCodeLab.apk.
  4. Chờ ứng dụng tạo bản dựng và triển khai trên thiết bị của bạn.

Lần đầu tiên bạn cố gắng triển khai ứng dụng cho thiết bị:

  • Bạn cần phải Cho phép gỡ lỗi qua USB trên thiết bị. Chọn OK để tiếp tục.
  • Bạn sẽ được hỏi xem ứng dụng có quyền sử dụng camera của thiết bị hay không. Cho phép truy cập để tiếp tục sử dụng chức năng thực tế tăng cường.

Kiểm thử ứng dụng

Khi chạy ứng dụng, bạn có thể kiểm thử hành vi cơ bản của ứng dụng bằng cách cầm thiết bị, di chuyển xung quanh không gian và quét một khu vực từ từ. Cố gắng thu thập dữ liệu trong ít nhất 10 giây và quét khu vực từ nhiều hướng trước khi chuyển sang bước tiếp theo.

Khắc phục sự cố

Thiết lập thiết bị Android cho hoạt động phát triển

  1. Kết nối thiết bị với máy phát triển bằng cáp USB. Nếu phát triển bằng Windows, bạn có thể cần cài đặt trình điều khiển USB thích hợp cho thiết bị của mình.
  2. Thực hiện các bước sau để bật tính năng Gỡ lỗi qua USB trong cửa sổ Tùy chọn cho nhà phát triển:
  3. Mở ứng dụng Cài đặt.
  4. Nếu thiết bị của bạn sử dụng Android phiên bản 8.0 trở lên, hãy chọn Hệ thống. Nếu không, hãy chuyển sang bước tiếp theo.
  5. Cuộn xuống dưới cùng rồi chọn Giới thiệu về điện thoại.
  6. Di chuyển xuống dưới cùng rồi nhấn 7 lần vào Số bản dựng.
  7. Quay lại màn hình trước, cuộn xuống dưới cùng rồi nhấn vào Tùy chọn cho nhà phát triển.
  8. Trong cửa sổ Tùy chọn cho nhà phát triển, hãy cuộn xuống để tìm và bật tính năng Gỡ lỗi qua USB.

Bạn có thể tìm thêm thông tin chi tiết về quy trình này trên trang web dành cho nhà phát triển Android của Google.

cfa20a722a68f54f.png

Nếu gặp lỗi bản dựng liên quan đến giấy phép (Không cài đặt được các gói SDK Android sau đây vì một số giấy phép chưa được chấp nhận), bạn có thể dùng các lệnh sau để xem xét và chấp nhận các giấy phép này:

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

11. Xin chúc mừng

Xin chúc mừng! Bạn đã tạo và chạy thành công ứng dụng Thực tế tăng cường dựa trên độ sâu đầu tiên bằng cách sử dụng ARCore Depth API của Google!

Câu hỏi thường gặp