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. Nhờ sử dụng nhiều API, ARCore giúp thiết bị của người dùng có thể quan sát và nhận thông tin về môi trường xung quanh cũng như tương tác với thông tin đó.
Trong lớp học lập trình này, bạn sẽ tìm hiểu quy trình xây dựng một ứng dụng đơn giản có hỗ trợ AR, sử dụng API ARCore depth.
Điều kiện tiên quyết
Lớp học lập trình này dành cho các nhà phát triển có kiến thức về các khái niệm cơ bản của thực tế tăng cường.
Sản phẩm bạn sẽ tạo ra
Bạn sẽ tạo một ứng dụng sử dụng hình ảnh chiều sâu cho từng khung hình để trực quan hoá hình dạng của cảnh và che khuất các thành phần ảo được đặt. Bạn sẽ tìm hiểu các bước cụ thể:
- Đang kiểm tra tính năng hỗ trợ depth API trên điện thoại
- Truy xuất hình ảnh chiều sâu cho mỗi khung hình
- Trực quan hoá thông tin chiều sâu theo nhiều cách (xem ảnh động ở trên)
- Sử dụng chiều sâu để tăng độ chân thực cho ứng dụng bị che khuất
- Tìm hiểu cách xử lý hiệu quả các điện thoại không hỗ trợ depth API
Bạn cần có
Yêu cầu về phần cứng
- Một thiết bị ARCore được hỗ trợ, kết nối với máy phát triển qua cáp USB. Thiết bị này cũng phải hỗ trợ depth API (API Độ sâu). Vui lòng xem danh sách thiết bị được hỗ trợ này. Depth API (API Chiều sâu) chỉ có trên Android.
- Bật tính năng gỡ lỗi qua USB cho thiết bị này.
Yêu cầu về phần mềm
- SDK ARCore 1.31.0 trở lên.
- Máy phát triển có Android Studio (phiên bản 3.0 trở lên).
2. ARCore và API depth
depth API sử dụng máy ảnh RGB của một thiết bị được hỗ trợ để tạo bản đồ độ sâu (còn được gọi là ảnh độ sâu). Bạn có thể sử dụng thông tin do bản đồ độ sâu cung cấp để làm cho 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, nhờ đó mang đến trải nghiệm sống động và chân thực cho người dùng.
API Độ sâu ARCore cho phép truy cập vào hình ảnh chiều sâu phù hợp với từng khung do Phiên của ARCore cung cấp. Mỗi pixel cung cấp thông tin đo lường khoảng cách từ máy ảnh đến môi trường, giúp tăng cường tính chân thực cho ứng dụng thực tế tăng cường của bạn.
Một khả năng quan trọng của API Độ sâu là che khuất: khả năng các đối tượng kỹ thuật số xuất hiện chính xác so với các vật thể trong thế giới thực. Điều này giúp các đối tượng có cảm giác như chúng thực sự đang ở trong môi trường cùng với người dùng.
Lớp học lập trình này sẽ hướng dẫn bạn về quy trình xây dựng một ứng dụng đơn giản có hỗ trợ AR. Ứng dụng này sử dụng hình ảnh chiều sâu để che khuất các đối tượng ảo phía sau các bề mặt thực và trực quan hoá hình dạng không gian được phát hiện.
3. Bắt đầu thiết lập
Thiết lập máy phát triển
- Kết nối thiết bị ARCore với máy tính của bạn qua cáp USB. Đảm bảo rằng thiết bị của bạn cho phép gỡ lỗi qua USB.
- Mở 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. Hãy đảm bảo bạn nhìn thấy chính xác một thiết bị trước khi tiếp tục.
Tải và cài đặt Code
- 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:
- 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).
- Tìm thư mục mà 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 Project (Dự án) ở trên cùng bên trái của Android Studio chưa hiển thị trong ngăn Project (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:
|
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 này. Mỗi mô-đun là một ứng dụng có thể xây dựng.
4. Chạy ứng dụng Starter
- Nhấp vào Run > (Chạy >) Chạy... > "part0_work". Trong hộp thoại Select Deployment Target (Chọn đối tượng triển khai) hiện ra, thiết bị của bạn sẽ được liệt kê trong phần Thiết bị đã kết nối.
- Chọn thiết bị rồi nhấp 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.
- Ứng dụng sẽ yêu cầu quyền truy cập vào camera. Hãy nhấn vào Cho phép để tiếp tục.
Cách dùng ứng dụng
|
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 nhân vật Android phía sau một chiếc ghế, thì nội dung kết xuất sẽ xuất hiện ở phía trước, vì ứng dụng không biết là chiếc ghế đó ở đó và phải đang ẩn Android đó.
Để khắc phục vấn đề này, chúng ta sẽ dùng depth API để cải thiện độ sống động và chân thực 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 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à cờ giúp lưu trữ liệu thiết bị hiện tại có hỗ trợ chiều 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, Phiên thực tế tăng cường đã được định cấu hình phù hợp và ứng dụng của bạn sẽ biết liệu ứng dụng đó có thể sử dụng các tính năng theo chiều sâu hay không.
Bạn cũng nên cho người dùng biết liệu depth (chiều sâu) có được sử dụng trong 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ội dung 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]";
Bên 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ợ chiều sâu, thì thông báo bạn vừa thêm sẽ xuất hiện ở dưới cùng:
Tiếp theo, bạn sẽ cập nhật ứng dụng để gọi depth API (API Chiều sâu) và truy xuất hình ảnh chiều sâu cho từng khung hình.
6. Truy xuất hình ảnh chiều sâu (Phần 2)
depth API ghi lại các quan sát 3D về môi trường của thiết bị và trả về một hình ảnh chiều sâu cùng với dữ liệu đó cho ứng dụng của bạn. Mỗi pixel trong hình ảnh chiều sâu biểu thị một phép đo khoảng cách từ máy ảnh của thiết bị đến môi trường thực tế.
Giờ bạn sẽ sử dụng những hình ảnh có chiều sâu này để cải thiện khả năng kết xuất và trình bày trực quan 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 mà GPU sẽ 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 chiều sâu cho một khung ARCore nhất định.
Thêm tệp này:
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;
}
}
Bây giờ, bạn sẽ thêm một thực thể của lớp này vào DepthCodelabActivity
để đảm bảo bạn có một bản sao dễ truy cập của hình ảnh chiều 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 dưới dạng biến thành phầ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 tạo hoạ tiết này để chương trình đổ bóng GPU có thể sử dụng chúng:
// 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 bằng hình ảnh chiều 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 cho khung 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ợ dữ liệu độ sâu, nên bạn chỉ nên dùng lệnh gọi này nếu đang dùng chế độ depth.
// Add this just after "frame" is created inside onDrawFrame().
if (isDepthSupported) {
depthTexture.update(frame);
}
Giờ đây, bạn đã có hình ảnh chiều sâu được cập nhật ở mỗi khung hình. Chương trình này đã sẵn sàng cho 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. Bây giờ, bạn sẽ 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 chiều sâu (Phần 3)
Giờ đây, khi đã có hình ảnh chiều sâu, 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 để kết xuất chiều 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 chiều sâu. Các chương trình đổ bóng sau đây cung cấp hình ảnh trực quan hoá bản đồ màu đơn giản.
Thêm chương trình đổ bóng .vert mớiTrong Android Studio:
|
Trong tệp mới, thêm 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 chương trình đổ bóng mảnh nằm trong cùng thư mục rồi đặt tên là background_show_depth_map.frag
.
Thêm 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 các biến thành phần khác 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 phương thức mới để điền các trường sau:
// 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 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
Bây giờ, bạn đã có khả năng kết xuất bản đồ độ sâu, hãy sử dụng tính năng này! Thêm nút có thể bật và tắt kết xuất này.
Ở đầu tệp DepthCodelabActivity
, hãy thêm một nội dung nhập cho nút này:
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 theo chiều sâu có được bật/tắt hay không: (tính năng này bị tắt theo mặc định):
private boolean showDepthMap = false;
Tiếp theo, hãy thêm nút kiểm soát 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 sau 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"/>
Nút này hiện kiểm soát giá trị của boolean showDepthMap
. Sử dụng cờ này để kiểm soát xem bản đồ độ sâu có được hiển thị 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 hoạ tiết chiều sâu vào backgroundRenderer
bằng cách thêm dòng sau trong 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 nút ở góc trên bên phải màn hình.
Chạy mà không hỗ trợ depth API | Chạy có hỗ trợ depth API |
[Không bắt buộc] Ảnh động có độ sâu yêu thích
Ứng dụng hiện hiển thị trực tiếp bản đồ độ sâu. Điểm ảnh màu đỏ thể hiện các khu vực gần nhau. Các pixel màu xanh dương biểu thị các khu vực ở xa.
Có nhiều cách để truyền đạt thông tin chuyên sâu. Trong phần này, bạn sẽ định kỳ điều chỉnh chương trình đổ bóng thành xung độ sâu bằng cách sửa đổi chương trình đổ bóng để chỉ hiển thị độ sâu trong phạm vi các dải mà liên tục di chuyển ra xa máy ảnh.
Hãy bắt đầu bằng cách thêm các biến sau 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 điểm ảnh cần che phủ bằng giá trị chiều 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ố trong 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 đoạn mã 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 để tăng phạm vi này mỗi khi vẽ một khung:
// 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, chiều sâu được hiển thị dưới dạng một xung động động chạy qua cảnh của bạn.
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á các cách hoàn toàn mới để thay đổi chương trình đổ bóng để hiển thị thông tin về độ sâu!
8. Dùng depth API để che khuất (Phần 4)
Bây giờ, bạn sẽ xử lý trường hợp che khuất đối tượng trong ứng dụng.
Che khuất là những gì xảy ra khi đối tượng ảo không thể kết xuất hoàn toàn vì có vật thể thực giữa vật thể ảo và máy ảnh. Để trải nghiệm thực tế tăng cường trở nên sống động, bạn cần phải quản lý cảnh bị che khuất.
Việc kết xuất các đối tượng ảo đúng cách theo thời gian thực giúp nâng cao độ chân thực và độ tin cậy của cảnh được tăng cường. Để xem thêm ví dụ, vui lòng xem video của chúng tôi về cách kết hợp thực tế với depth API.
Trong phần này, bạn sẽ cập nhật ứng dụng để chỉ thêm các đối tượng ảo khi có chiều 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 chương trình đổ bóng mới để hỗ trợ thông tin chuyên sâu. Lần này, bạn có thể sao chép chương trình đổ bóng đối tượng hiện có và thêm chức năng che khuất.
Quan trọng là bạn phải 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 xem 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.vert
và object.frag
trong thư mục src/main/assets/shaders
.
|
Bên trong occlusion_object.vert
, hãy thêm biến sau lên 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 sau vào 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 sau đây ở phía trên
main()
trong chương trình đổ bóng để xử lý thông tin chuyên sâu dễ dàng hơn:
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;
}
Bây giờ, hãy cập nhật main()
trong occlusion_object.frag
để nhận biết chiều sâu và áp dụng tính nă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);
Giờ đây, khi đã 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ã kết xuất đồ hoạ.
Hiển thị đối tượng che khuất
Tạo bản sao của lớp ObjectRenderer
tiếp theo, có trong src/main/java/com/google/ar/core/codelab/common/rendering/ObjectRenderer.java
.
- Chọn lớp
ObjectRenderer
- Nhấp chuột phải > Nội dung
- Chọn thư mục render (hiển thị)
- Nhấp chuột phải > Dán
- Đổi tên lớp thành
OcclusionObjectRenderer
Lúc này, lớp mới, đã được đổi tên sẽ xuất hiện trong cùng một thư mục:
Mở OcclusionObjectRenderer.java
mới tạo và thay đổi đường dẫn 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 phần có liên quan đến chiều sâu này cùng với các biến khác ở đầu lớp. Các biến sẽ điều chỉnh độ sắc nét của đường viền bị 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 phần sau đâ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 động 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");
- Hãy đảm bảo các giá trị này được cập nhật mỗi khi giá trị đượ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 vào draw()
để bật chế độ kết hợp trong quá trình kết xuất để có thể áp dụng độ trong suốt cho đối tượng ảo khi các đối tượ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 để phương thức gọi của
OcclusionObjectRenderer
có thể cung cấp thông tin chiều 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 việc che khuất đối tượng
Giờ đây, khi đã có OcclusionObjectRenderer
mới, bạn có thể thêm thành phần này vào DepthCodelabActivity
rồi chọn thời điểm cũng như cách thức 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ể của OcclusionObjectRenderer
vào hoạt động để cả ObjectRenderer
và OcclusionObjectRenderer
đề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ể điều khiển thời điểm
occludedVirtualObject
này được sử dụng, tuỳ thuộc vào việc thiết bị hiện tại có hỗ trợ depth API hay không. Thêm các dòng sau vào phương thứconSurfaceCreated
, dưới đây là vị trí định cấu hìnhvirtualObject
:
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 các thiết bị không hỗ trợ chiều sâu, thực thể occludedVirtualObject
sẽ được tạo nhưng không được sử dụng. Trên điện thoại có chiều sâu, cả hai phiên bản đều được khởi chạy và quyết định trong thời gian chạy được đưa ra phiên bản trình kết xuất nào sẽ sử dụng khi vẽ.
Bên 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:
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 vào kết xuất đầu ra. Vì hình ảnh chiều 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ạ độ hoạ tiết giữa chính hình ảnh đó và hình ảnh máy ảnh có thể khác.
- 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 mà khi được áp dụng, giúp các UV không gian trên màn hình khớp chính xác với toạ độ 4 chiều hoạ tiết dùng để kết xuất nguồn cấp dữ liệu máy ảnh. Hướng màn hình cũng tính đến hướng 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 nhập sau ở đầu tệp:
import com.google.ar.core.Coordinates2d;
Bạn muốn tính toán sự biến đổi giữa các toạ độ hoạ tiết này mỗi khi hoạ tiết màn hình thay đổi (chẳng hạn như khi màn hình xoay). Đây là chức năng bị kiểm soát.
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 phép biến đổi đã lưu trữ có cần được tính toán lại sau khi tạo khung 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);
}
Với 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!
Lúc này, ứng dụng của bạn sẽ chạy dễ dàng trên tất cả các điện thoại, đồng thời tự động sử dụng tính năng theo chiều sâu để che khuất nếu được hỗ trợ.
Ứng dụng đang chạy có hỗ trợ depth API (API Độ sâu) | Chạy ứng dụng mà không hỗ trợ API Độ sâu |
9. [Không bắt buộc] Cải thiện chất lượng che khuất
Phương pháp che khuất theo chiều sâu, được triển khai ở trên, cung cấp sự che khuất có ranh giới sắc nét. Khi máy ảnh di chuyển ra xa vật thể, kết quả đo chiều sâu có thể kém chính xác hơn và có thể dẫn đến hiện tượng hình ảnh giả.
Chúng ta có thể giảm thiểu vấn đề này bằng cách thêm hiệu ứng làm mờ bổ sung vào kiểm thử che khuất, mang lại cạnh mượt mà hơn cho các đối tượng ảo bị ẩ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 để áp dụng hiệu ứng làm mờ hạt nhân cho mẫu che khuất:
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);
Hãy cập nhật trình kết xuất để tận dụng chức năng đổ bóng mới này.
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;
Hãy thêm nội dung sau đây vào phương thức createOnGlThread
:
// Add alongside the other calls to GLES20.glGetUniformLocation.
occlusionBlurUniform = GLES20.glGetUniformLocation(program, "u_OcclusionBlurAmount");
Hãy thêm nội dung sau đây vào phương thức draw
:
// Add alongside the other calls to GLES20.glUniform1f.
GLES20.glUniform1f(occlusionBlurUniform, occlusionsBlur);
So sánh trực quanNhững thay đổi này nay giúp ranh giới che khuất trở nên mượt mà hơn. |
10. Build-Run-Test
Tạo và chạy ứng dụng
- Cắm thiết bị Android qua USB.
- Chọn Tệp > Xây dựng và chạy.
- Lưu dưới dạng: ARCodeLab.apk.
- Chờ ứng dụng tạo và triển khai cho 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ị của mình:
- Bạn cần 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 máy ảnh của thiết bị hay không. Hãy 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 giữ thiết bị, di chuyển xung quanh và từ từ quét một khu vực. Cố gắng thu thập ít nhất 10 giây dữ liệu 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
- 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 phải cài đặt trình điều khiển USB thích hợp cho thiết bị của mình.
- Thực hiện các bước sau để bật tính năng Gỡ lỗi qua USB trong cửa sổ Tuỳ chọn cho nhà phát triển:
- Mở ứng dụng Cài đặt.
- 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.
- Cuộn xuống dưới cùng rồi chọn Giới thiệu về điện thoại.
- Di chuyển xuống dưới cùng rồi nhấn 7 lần vào Số bản dựng.
- Quay lại màn hình trước, cuộn xuống dưới cùng rồi nhấn vào Tuỳ chọn cho nhà phát triển.
- Trong cửa sổ Tuỳ 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.
Lỗi bản dựng liên quan đến giấy phép
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 vì một số giấy phép chưa được chấp nhận), bạn có thể sử 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 chiều sâu đầu tiên bằng API ARCore depth của Google!