۱. قبل از شروع
ARCore پلتفرمی برای ساخت برنامههای واقعیت افزوده (AR) روی دستگاههای تلفن همراه است. ARCore با استفاده از APIهای مختلف، این امکان را برای دستگاه کاربر فراهم میکند تا اطلاعات مربوط به محیط خود را مشاهده و دریافت کند و با آن اطلاعات تعامل داشته باشد.
در این آزمایشگاه کد، شما فرآیند ساخت یک برنامه ساده با قابلیت AR را که از API عمق ARCore استفاده میکند، طی خواهید کرد.
پیشنیازها
این آزمایشگاه کد برای توسعهدهندگانی نوشته شده است که با مفاهیم اساسی واقعیت افزوده آشنایی دارند.
آنچه خواهید ساخت

شما برنامهای خواهید ساخت که از تصویر عمق برای هر فریم برای تجسم هندسه صحنه و انجام انسداد روی داراییهای مجازی قرار داده شده استفاده میکند. شما مراحل خاص زیر را طی خواهید کرد:
- بررسی پشتیبانی از Depth API در گوشی
- بازیابی تصویر عمق برای هر فریم
- تجسم اطلاعات عمق به روشهای مختلف (به انیمیشن بالا مراجعه کنید)
- استفاده از عمق برای افزایش واقعگرایی برنامهها با انسداد
- یادگیری نحوه مدیریت صحیح گوشیهایی که از Depth API پشتیبانی نمیکنند
آنچه نیاز دارید
الزامات سختافزاری
- یک دستگاه ARCore پشتیبانیشده که از طریق کابل USB به دستگاه توسعه شما متصل شده باشد. این دستگاه همچنین باید از Depth API پشتیبانی کند. لطفاً به این لیست از دستگاههای پشتیبانیشده مراجعه کنید. Depth API فقط در اندروید موجود است.
- اشکالزدایی USB را برای این دستگاه فعال کنید.
نیازمندیهای نرمافزاری
- ARCore SDK نسخه ۱.۳۱.۰ یا بالاتر.
- یک دستگاه توسعه با اندروید استودیو (نسخه ۳.۰ یا بالاتر).
۲. ARCore و رابط برنامهنویسی کاربردی عمقی (Depth API)
رابط برنامهنویسی کاربردی عمق (Depth API) از دوربین RGB دستگاه پشتیبانیشده برای ایجاد نقشههای عمق (که تصاویر عمق نیز نامیده میشوند) استفاده میکند. میتوانید از اطلاعات ارائهشده توسط یک نقشه عمق برای نمایش دقیق اشیاء مجازی، چه در جلو و چه در پشت اشیاء دنیای واقعی، استفاده کنید و تجربیات کاربری فراگیر و واقعگرایانهای را فراهم کنید.
API عمق ARCore دسترسی به تصاویر عمقی منطبق با هر فریم ارائه شده توسط Session ARCore را فراهم میکند. هر پیکسل، اندازهگیری فاصله از دوربین تا محیط را ارائه میدهد که واقعگرایی بیشتری را برای برنامه AR شما فراهم میکند.
یکی از قابلیتهای کلیدی Depth API، انسداد است: توانایی نمایش دقیق اشیاء دیجیتال نسبت به اشیاء دنیای واقعی. این باعث میشود اشیاء طوری به نظر برسند که انگار واقعاً در محیط و در کنار کاربر قرار دارند.
این آزمایشگاه کد شما را در فرآیند ساخت یک اپلیکیشن ساده مبتنی بر واقعیت افزوده راهنمایی میکند که از تصاویر عمقی برای انجام انسداد اشیاء مجازی در پشت سطوح دنیای واقعی و تجسم هندسه شناسایی شده فضا استفاده میکند.
۳. آماده شوید
دستگاه توسعه را راهاندازی کنید
- دستگاه ARCore خود را از طریق کابل USB به رایانه متصل کنید. مطمئن شوید که دستگاه شما از اشکالزدایی USB پشتیبانی میکند .
- یک ترمینال باز کنید و
adb devicesاجرا کنید، همانطور که در زیر نشان داده شده است:
adb devices List of devices attached <DEVICE_SERIAL_NUMBER> device
<DEVICE_SERIAL_NUMBER> یک رشته منحصر به فرد برای دستگاه شما خواهد بود. قبل از ادامه، مطمئن شوید که دقیقاً یک دستگاه را مشاهده میکنید.
کد را دانلود و نصب کنید
- میتوانید مخزن را کلون کنید:
git clone https://github.com/googlecodelabs/arcore-depth
یا یک فایل زیپ دانلود کنید و آن را از حالت فشرده خارج کنید:
- اندروید استودیو را اجرا کنید و روی «باز کردن یک پروژه اندروید استودیو موجود» کلیک کنید.
- پوشهای را که فایل زیپ دانلود شده در بالا را در آن استخراج کردهاید، پیدا کنید و پوشه
depth_codelab_io2020را باز کنید.
این یک پروژه Gradle با چندین ماژول است. اگر پنجره Project در سمت چپ بالای Android Studio از قبل در پنجره Project نمایش داده نشده است، از منوی کشویی روی Projects کلیک کنید.
نتیجه باید به این شکل باشد:
| این پروژه شامل ماژولهای زیر است:
|
شما در ماژول part0_work کار خواهید کرد. همچنین برای هر بخش از codelab راهحلهای کاملی وجود دارد. هر ماژول یک برنامه قابل ساخت است.
۴. برنامهی آغازین را اجرا کنید
- روی Run > Run... > 'part0_work' کلیک کنید. در پنجرهی Select Deployment Target که نمایش داده میشود، دستگاه شما باید در زیر Connected Devices فهرست شده باشد.
- دستگاه خود را انتخاب کنید و روی تأیید کلیک کنید. اندروید استودیو برنامه اولیه را میسازد و آن را روی دستگاه شما اجرا میکند.
- برنامه از شما مجوزهای دوربین را درخواست میکند. برای ادامه، روی «مجاز» ضربه بزنید.

| نحوه استفاده از برنامه
|
در حال حاضر، برنامه شما بسیار ساده است و اطلاعات زیادی در مورد هندسه صحنه واقعی ندارد.
برای مثال، اگر یک آدمک اندروید را پشت یک صندلی قرار دهید، رندر به صورت معلق در جلو ظاهر میشود، زیرا برنامه نمیداند که صندلی آنجاست و باید اندروید را پنهان کند.



برای رفع این مشکل، ما از Depth API برای بهبود غوطهوری و واقعگرایی در این برنامه استفاده خواهیم کرد.
۵. بررسی کنید که آیا Depth API پشتیبانی میشود یا خیر (بخش ۱)
API عمق ARCore فقط روی زیرمجموعهای از دستگاههای پشتیبانیشده اجرا میشود. قبل از ادغام عملکرد در یک برنامه با استفاده از این تصاویر عمق، ابتدا باید مطمئن شوید که برنامه روی یک دستگاه پشتیبانیشده اجرا میشود.
یک عضو خصوصی جدید به DepthCodelabActivity اضافه کنید که به عنوان یک پرچم عمل میکند و ذخیره میکند که آیا دستگاه فعلی از depth پشتیبانی میکند یا خیر:
private boolean isDepthSupported;
ما میتوانیم این پرچم را از داخل تابع onResume() ، جایی که یک Session جدید ایجاد میشود، پر کنیم.
کد موجود را پیدا کنید:
// Creates the ARCore session.
session = new Session(/* context= */ this);
کد را به این صورت بهروزرسانی کنید:
// 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);
اکنون جلسه واقعیت افزوده (AR Session) به طور مناسب پیکربندی شده است و برنامه شما میداند که آیا میتواند از ویژگیهای مبتنی بر عمق استفاده کند یا خیر.
همچنین باید به کاربر اطلاع دهید که آیا از عمق برای این جلسه استفاده میشود یا خیر.
پیام دیگری به Snackbar اضافه کنید. این پیام در پایین صفحه نمایش داده خواهد شد:
// 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]";
درون تابع onDrawFrame() ، میتوانید این پیام را در صورت نیاز نمایش دهید:
// Add this if-statement above messageSnackbarHelper.showMessage(this, messageToShow).
if (!isDepthSupported) {
messageToShow += "\n" + DEPTH_NOT_AVAILABLE_MESSAGE;
}
اگر برنامه شما روی دستگاهی اجرا میشود که از عمق پشتیبانی نمیکند، پیامی که اضافه کردهاید در پایین ظاهر میشود:

در مرحله بعد، برنامه را بهروزرسانی خواهید کرد تا Depth API را فراخوانی کرده و تصاویر عمق را برای هر فریم بازیابی کند.
۶. بازیابی تصاویر عمق (بخش ۲)
رابط برنامهنویسی کاربردی عمق (Depth API) مشاهدات سهبعدی از محیط دستگاه را ثبت میکند و یک تصویر عمقی با آن دادهها به برنامه شما برمیگرداند. هر پیکسل در تصویر عمقی، نشاندهندهی اندازهگیری فاصله از دوربین دستگاه تا محیط دنیای واقعی آن است.
حالا از این تصاویر عمق برای بهبود رندرینگ و تجسم در برنامه استفاده خواهید کرد. اولین قدم بازیابی تصویر عمق برای هر فریم و اتصال آن بافت برای استفاده توسط GPU است.
ابتدا یک کلاس جدید به پروژه خود اضافه کنید.
DepthTextureHandler مسئول بازیابی تصویر عمق برای یک فریم ARCore مشخص است.
این فایل را اضافه کنید:

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;
}
}
حالا یک نمونه از این کلاس را به DepthCodelabActivity اضافه میکنید، و مطمئن میشوید که برای هر فریم، یک کپی آسان از تصویر عمق خواهید داشت.
در DepthCodelabActivity.java ، یک نمونه از کلاس جدیدمان را به عنوان یک متغیر عضو خصوصی اضافه کنید:
private final DepthTextureHandler depthTexture = new DepthTextureHandler();
در مرحله بعد، متد onSurfaceCreated() را برای مقداردهی اولیه این بافت بهروزرسانی کنید تا توسط سایهزنهای GPU ما قابل استفاده باشد:
// Put this at the top of the "try" block in onSurfaceCreated().
depthTexture.createOnGlThread();
در نهایت، شما میخواهید این بافت را در هر فریم با آخرین تصویر عمق پر کنید، که این کار را میتوان با فراخوانی متد update() که در بالا ایجاد کردید، روی آخرین فریم بازیابی شده از session انجام داد.
از آنجایی که پشتیبانی از عمق برای این برنامه اختیاری است، فقط در صورتی از این فراخوانی استفاده کنید که از عمق استفاده میکنید.
// Add this just after "frame" is created inside onDrawFrame().
if (isDepthSupported) {
depthTexture.update(frame);
}
حالا شما یک تصویر عمق دارید که با هر فریم بهروزرسانی میشود. این تصویر آماده است تا توسط سایهزنهای شما مورد استفاده قرار گیرد.
با این حال، هنوز هیچ چیز در مورد رفتار برنامه تغییر نکرده است. اکنون از تصویر عمق برای بهبود برنامه خود استفاده خواهید کرد.
۷. رندر تصویر عمق (بخش ۳)
حالا یک تصویر عمق دارید که میتوانید با آن کار کنید، میخواهید ببینید چه شکلی است. در این بخش، دکمهای به برنامه اضافه میکنید تا عمق هر فریم را رندر کند.
اضافه کردن شیدرهای جدید
روشهای زیادی برای مشاهده یک تصویر عمق وجود دارد. سایهزنهای زیر یک تجسم ساده از نگاشت رنگ ارائه میدهند.
| یک شیدر .vert جدید اضافه کنیددر اندروید استودیو:
|
در فایل جدید، کد زیر را اضافه کنید:
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;
}
مراحل بالا را برای ایجاد سایهزن قطعه در همان دایرکتوری تکرار کنید و نام آن را background_show_depth_map.frag بگذارید.
کد زیر را به این فایل جدید اضافه کنید:
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;
}
در مرحله بعد، کلاس BackgroundRenderer بهروزرسانی کنید تا از این سایهزنهای جدید که در src/main/java/com/google/ar/core/codelab/common/rendering/BackgroundRenderer.java قرار دارند، استفاده کند.
مسیرهای فایل را به shader های بالای کلاس اضافه کنید:
// 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";
متغیرهای عضو بیشتری را به کلاس BackgroundRenderer اضافه کنید، زیرا قرار است دو shader را اجرا کند:
// 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;
یک متد جدید برای پر کردن این فیلدها اضافه کنید:
// 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;
}
این متد را اضافه کنید، که برای ترسیم با این سایهزنها روی هر فریم استفاده میشود:
// 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");
}
دکمه تغییر وضعیت را اضافه کنید
حالا که قابلیت رندر نقشه عمق را دارید، از آن استفاده کنید! دکمهای اضافه کنید که این رندر را فعال و غیرفعال کند.
در بالای فایل DepthCodelabActivity ، یک ورودی برای استفاده از دکمه اضافه کنید:
import android.widget.Button;
کلاس را بهروزرسانی کنید تا یک عضو بولی اضافه شود که نشان میدهد آیا رندر عمقی فعال است یا خیر: (به طور پیشفرض غیرفعال است):
private boolean showDepthMap = false;
سپس، دکمهای که مقدار بولی showDepthMap را کنترل میکند، به انتهای متد 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);
}
});
این رشتهها را به 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>
این دکمه را به پایین طرحبندی برنامه در 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"/>
اکنون دکمه مقدار boolean showDepthMap کنترل میکند. از این پرچم برای کنترل رندر شدن یا نشدن نقشه عمق استفاده کنید.
به متد onDrawFrame() در DepthCodelabActivity برگردید و موارد زیر را اضافه کنید:
// Add this snippet just under backgroundRenderer.draw(frame);
if (showDepthMap) {
backgroundRenderer.drawDepth(frame);
}
با اضافه کردن خط زیر در onSurfaceCreated() ، بافت عمق را به backgroundRenderer منتقل کنید:
// Add to onSurfaceCreated() after backgroundRenderer.createonGlThread(/*context=*/ this);
backgroundRenderer.createDepthShaders(/*context=*/ this, depthTexture.getDepthTexture());
حالا میتوانید با فشردن دکمهی سمت راست بالای صفحه، عمق تصویر هر فریم را ببینید.
|
| ||
اجرا بدون پشتیبانی از Depth API | اجرا با پشتیبانی از Depth API | ||
[اختیاری] انیمیشن عمق فانتزی
این برنامه در حال حاضر نقشه عمق را مستقیماً نشان میدهد. پیکسلهای قرمز نشاندهنده مناطقی هستند که نزدیک هستند و پیکسلهای آبی نشاندهنده مناطقی هستند که دور هستند.
|
|
روشهای زیادی برای انتقال اطلاعات عمق وجود دارد. در این بخش، شما سایهزن را طوری تنظیم میکنید که عمق پالس را به صورت دورهای نمایش دهد، به این صورت که سایهزن را طوری تنظیم میکنید که فقط عمق را در باندهایی که به طور مکرر از دوربین دور میشوند، نشان دهد.
با اضافه کردن این متغیرها به بالای background_show_depth_map.frag شروع کنید:
uniform float u_DepthRangeToRenderMm;
const float kDepthWidthToRenderMm = 350.0;
- سپس، از این مقادیر برای فیلتر کردن پیکسلهایی که باید با مقادیر عمق در تابع
main()سایهزن پوشانده شوند، استفاده کنید:
// Add this line at the end of main().
gl_FragColor.a = clamp(1.0 - abs((depth_mm - u_DepthRangeToRenderMm) / kDepthWidthToRenderMm), 0.0, 1.0);
در مرحله بعد، BackgroundRenderer.java را بهروزرسانی کنید تا این پارامترهای shader حفظ شوند. فیلدهای زیر را به بالای کلاس اضافه کنید:
private static final float MAX_DEPTH_RANGE_TO_RENDER_MM = 20000.0f;
private float depthRangeToRenderMm = 0.0f;
private int depthRangeToRenderMmParam;
درون متد createDepthShaders() ، موارد زیر را برای تطبیق این پارامترها با برنامه shader اضافه کنید:
depthRangeToRenderMmParam = GLES20.glGetUniformLocation(depthProgram, "u_DepthRangeToRenderMm");
- در نهایت، میتوانید این محدوده را در طول زمان در داخل متد
drawDepth()کنترل کنید. کد زیر را اضافه کنید که هر بار که یک فریم رسم میشود، این محدوده را افزایش میدهد:
// 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);
حالا عمق به صورت یک پالس متحرک که در صحنه شما جریان دارد، تجسم میشود.

میتوانید مقادیر ارائه شده در اینجا را تغییر دهید تا پالس کندتر، سریعتر، پهنتر، باریکتر و غیره شود. همچنین میتوانید روشهای کاملاً جدیدی را برای تغییر سایهزن (shader) برای نمایش اطلاعات عمق امتحان کنید!
۸. استفاده از Depth API برای انسداد (بخش ۴)
اکنون انسداد شیء را در برنامه خود مدیریت خواهید کرد.
انسداد به اتفاقی اشاره دارد که در آن شیء مجازی به طور کامل رندر نمیشود، زیرا اشیاء واقعی بین شیء مجازی و دوربین وجود دارند. مدیریت انسداد برای فراگیر شدن تجربیات واقعیت افزوده ضروری است.
رندر صحیح اشیاء مجازی در زمان واقعی، واقعگرایی و باورپذیری صحنه افزوده شده را افزایش میدهد. برای مثالهای بیشتر، لطفاً ویدیوی ما در مورد ترکیب واقعیتها با Depth API را ببینید.
در این بخش، برنامه خود را بهروزرسانی میکنید تا فقط در صورت وجود عمق، اشیاء مجازی را شامل شود.
اضافه کردن سایهزنهای جدید برای اشیاء
مانند بخشهای قبلی، شیدرهای جدیدی برای پشتیبانی از اطلاعات عمق اضافه خواهید کرد. این بار میتوانید شیدرهای شیء موجود را کپی کرده و قابلیت انسداد را اضافه کنید.
مهم است که هر دو نسخه از شیدرهای شیء را نگه دارید، تا برنامه شما بتواند در زمان اجرا تصمیم بگیرد که آیا از عمق پشتیبانی کند یا خیر.
از فایلهای shader مربوط به object.vert و object.frag در پوشه src/main/assets/shaders کپی تهیه کنید.
|
|
درون occlusion_object.vert ، متغیر زیر را بالای main() اضافه کنید:
varying vec3 v_ScreenSpacePosition;
این متغیر را در انتهای تابع main() قرار دهید:
v_ScreenSpacePosition = gl_Position.xyz / gl_Position.w;
با اضافه کردن این متغیرها در بالای main() در بالای فایل occlusion_object.frag را بهروزرسانی کنید:
varying vec3 v_ScreenSpacePosition;
uniform sampler2D u_Depth;
uniform mat3 u_UvTransform;
uniform float u_DepthTolerancePerMm;
uniform float u_OcclusionAlpha;
uniform float u_DepthAspectRatio;
- این توابع کمکی را بالای
main()در سایهزن اضافه کنید تا کار با اطلاعات عمق آسانتر شود:
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;
}
حالا main() در occlusion_object.frag را بهروزرسانی کنید تا از عمق آگاه باشد و انسداد را اعمال کند. خطوط زیر را در انتهای فایل اضافه کنید:
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);
حالا که نسخه جدیدی از شیدرهای شیء خود را دارید، میتوانید کد رندر را تغییر دهید.
رندر انسداد شیء
در مرحله بعد، یک کپی از کلاس ObjectRenderer که در src/main/java/com/google/ar/core/codelab/common/rendering/ObjectRenderer.java قرار دارد، تهیه کنید.
- کلاس
ObjectRendererرا انتخاب کنید - کلیک راست > کپی
- پوشه رندر را انتخاب کنید
- کلیک راست > چسباندن

- نام کلاس را به
OcclusionObjectRendererتغییر دهید.

کلاس جدید و تغییر نام داده شده، اکنون باید در همان پوشه ظاهر شود:

فایل OcclusionObjectRenderer.java که به تازگی ایجاد شده را باز کنید و مسیرهای shader را در بالای فایل تغییر دهید:
private static final String VERTEX_SHADER_NAME = "shaders/occlusion_object.vert";
private static final String FRAGMENT_SHADER_NAME = "shaders/occlusion_object.frag";
- این متغیرهای عضو مرتبط با عمق را به همراه سایر متغیرهای بالای کلاس اضافه کنید. این متغیرها وضوح مرز انسداد را تنظیم میکنند.
// 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;
این متغیرهای عضو را با مقادیر پیشفرض در بالای کلاس ایجاد کنید:
// 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;
پارامترهای uniform را برای shader در متد 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");
- با بهروزرسانی متد
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);
خطوط زیر را در تابع draw() اضافه کنید تا حالت ترکیبی (blend-mode) در رندر فعال شود و شفافیت بتواند هنگام مسدود شدن اشیاء مجازی به آنها اعمال شود:
// 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);
- متدهای زیر را اضافه کنید تا فراخوانیکنندگان
OcclusionObjectRendererبتوانند اطلاعات عمق را ارائه دهند:
// 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;
}
کنترل انسداد شیء
حالا که یک OcclusionObjectRenderer جدید دارید، میتوانید آن را به DepthCodelabActivity خود اضافه کنید و انتخاب کنید که چه زمانی و چگونه از رندرینگ انسدادی استفاده کنید.
این منطق را با اضافه کردن یک نمونه از OcclusionObjectRenderer به اکتیویتی فعال کنید، به طوری که هم ObjectRenderer و OcclusionObjectRenderer عضو 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();
- در مرحلهی بعد میتوانید بر اساس اینکه آیا دستگاه فعلی از Depth API پشتیبانی میکند یا خیر، زمان استفاده از این
occludedVirtualObjectرا کنترل کنید. این خطوط را داخل متدonSurfaceCreated، زیر جایی کهvirtualObjectپیکربندی شده است، اضافه کنید:
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);
}
در دستگاههایی که از depth پشتیبانی نمیکنند، نمونه occludedVirtualObject ایجاد میشود اما استفاده نمیشود. در گوشیهایی که depth دارند، هر دو نسخه مقداردهی اولیه میشوند و در زمان اجرا تصمیم گرفته میشود که از کدام نسخه رندرکننده هنگام ترسیم استفاده شود.
درون متد onDrawFrame() ، کد موجود را پیدا کنید:
virtualObject.updateModelMatrix(anchorMatrix, scaleFactor);
virtualObject.draw(viewmtx, projmtx, colorCorrectionRgba, OBJECT_COLOR);
این کد را با کد زیر جایگزین کنید:
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);
}
در نهایت، مطمئن شوید که تصویر عمق به درستی روی رندر خروجی نگاشت شده است. از آنجایی که تصویر عمق وضوح متفاوتی دارد و احتمالاً نسبت ابعاد آن با صفحه نمایش شما متفاوت است، مختصات بافت ممکن است بین خودش و تصویر دوربین متفاوت باشد.
- متد کمکی
getTextureTransformMatrix()را به انتهای فایل اضافه کنید. این متد یک ماتریس تبدیل برمیگرداند که وقتی اعمال میشود، باعث میشود UVهای فضای صفحه به درستی با مختصات بافت چهارگانه که برای رندر فید دوربین استفاده میشوند، مطابقت داشته باشند. همچنین جهتگیری دستگاه را نیز در نظر میگیرد.
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() در ابتدای فایل، دستور import زیر را اجرا میکند:
import com.google.ar.core.Coordinates2d;
شما میخواهید تبدیل بین این مختصات بافت را هر زمان که بافت صفحه نمایش تغییر میکند (مثلاً اگر صفحه نمایش بچرخد) محاسبه کنید. این قابلیت دارای محدودیت است.
پرچم زیر را در بالای فایل اضافه کنید:
// Add this member at the top of the file.
private boolean calculateUVTransform = true;
- درون تابع
onDrawFrame()، بررسی کنید که آیا تبدیل ذخیره شده پس از ایجاد فریم و دوربین نیاز به محاسبه مجدد دارد یا خیر:
// Add these lines inside onDrawFrame() after frame.getCamera().
if (frame.hasDisplayGeometryChanged() || calculateUVTransform) {
calculateUVTransform = false;
float[] transform = getTextureTransformMatrix(frame);
occludedVirtualObject.setUvTransformMatrix(transform);
}
با اعمال این تغییرات، اکنون میتوانید برنامه را با انسداد شیء مجازی اجرا کنید!
اکنون برنامه شما باید به خوبی روی همه تلفنها اجرا شود و در صورت پشتیبانی، به طور خودکار از depth-for-occlusion استفاده کند.
|
| ||
اجرای برنامه با پشتیبانی از Depth API | اجرای برنامه بدون پشتیبانی از Depth API | ||
۹. [اختیاری] بهبود کیفیت انسداد
روش انسداد مبتنی بر عمق که در بالا پیادهسازی شد، انسدادی با مرزهای تیز ارائه میدهد. با دور شدن دوربین از جسم، اندازهگیریهای عمق میتوانند دقت کمتری داشته باشند که ممکن است منجر به مصنوعات بصری شود.
ما میتوانیم با اضافه کردن تاری بیشتر به تست انسداد، که لبههای صافتری را برای اشیاء مجازی پنهان ایجاد میکند، این مشکل را کاهش دهیم.
شیء انسداد.frag
متغیر uniform زیر را در بالای occlusion_object.frag اضافه کنید:
uniform float u_OcclusionBlurAmount;
این تابع کمکی را درست بالای main() در سایهزن اضافه کنید، که یک تاری هسته (kernel blur) را به نمونهبرداری انسداد (occlusion sampling) اعمال میکند:
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;
}
این خط موجود را در main() جایگزین کنید:
gl_FragColor.a *= GetVisibility(depth_uvs, asset_depth_mm);
با این خط:
gl_FragColor.a *= GetBlurredVisibilityAroundUV(depth_uvs, asset_depth_mm);
برای بهرهمندی از این قابلیت جدید سایهزن، رندرکننده را بهروزرسانی کنید.
رندر شیء انسدادی.java
متغیرهای عضو زیر را در بالای کلاس اضافه کنید:
private int occlusionBlurUniform;
private final float occlusionsBlur = 0.01f;
کد زیر را درون متد createOnGlThread اضافه کنید:
// Add alongside the other calls to GLES20.glGetUniformLocation.
occlusionBlurUniform = GLES20.glGetUniformLocation(program, "u_OcclusionBlurAmount");
کد زیر را داخل متد draw اضافه کنید:
// Add alongside the other calls to GLES20.glUniform1f.
GLES20.glUniform1f(occlusionBlurUniform, occlusionsBlur);
| مقایسه بصریاکنون مرز انسداد با این تغییرات باید هموارتر شده باشد. |
۱۰. ساخت-اجرا-تست
برنامه خود را بسازید و اجرا کنید
- یک دستگاه اندروید را از طریق USB وصل کنید.
- فایل > ساخت و اجرا را انتخاب کنید.
- ذخیره به عنوان: ARCodeLab.apk .
- منتظر بمانید تا برنامه ساخته و روی دستگاه شما نصب شود.
اولین باری که سعی میکنید برنامه را روی دستگاه خود نصب کنید:
- شما باید اشکالزدایی USB را روی دستگاه مجاز کنید. برای ادامه، تأیید را انتخاب کنید.
- از شما پرسیده میشود که آیا برنامه اجازه استفاده از دوربین دستگاه را دارد یا خیر. برای ادامه استفاده از قابلیت واقعیت افزوده، اجازه دسترسی را بدهید.
تست برنامه شما
وقتی برنامه خود را اجرا میکنید، میتوانید رفتار اولیه آن را با نگه داشتن دستگاه، حرکت در فضای اطراف و اسکن آهسته یک منطقه آزمایش کنید. سعی کنید حداقل 10 ثانیه داده جمعآوری کنید و قبل از رفتن به مرحله بعدی، منطقه را از چندین جهت اسکن کنید.
عیبیابی
آمادهسازی دستگاه اندروید برای توسعه
- دستگاه خود را با کابل USB به دستگاه توسعه خود وصل کنید. اگر با استفاده از ویندوز توسعه میدهید، ممکن است لازم باشد درایور USB مناسب دستگاه خود را نصب کنید.
- برای فعال کردن اشکالزدایی USB در پنجره گزینههای توسعهدهندگان ، مراحل زیر را انجام دهید:
- برنامه تنظیمات را باز کنید.
- اگر دستگاه شما از اندروید نسخه ۸.۰ یا بالاتر استفاده میکند، گزینه System را انتخاب کنید. در غیر این صورت، به مرحله بعدی بروید.
- به پایین صفحه بروید و درباره تلفن (About phone) را انتخاب کنید.
- به پایین صفحه بروید و روی Build number هفت بار ضربه بزنید.
- به صفحه قبلی برگردید، به پایین بروید و روی گزینههای توسعهدهندگان (Developer options) ضربه بزنید.
- در پنجره گزینههای توسعهدهندگان ، به پایین اسکرول کنید تا گزینه اشکالزدایی USB را پیدا کرده و آن را فعال کنید.
میتوانید اطلاعات دقیقتر در مورد این فرآیند را در وبسایت توسعهدهندگان اندروید گوگل پیدا کنید.
خرابیهای ساخت مربوط به مجوزها

اگر با خطای ساخت مربوط به مجوزها مواجه شدید ( نصب بستههای Android SDK زیر ناموفق بود زیرا برخی از مجوزها پذیرفته نشدهاند )، میتوانید از دستورات زیر برای بررسی و پذیرش این مجوزها استفاده کنید:
cd <path to Android SDK>
tools/bin/sdkmanager --licenses
۱۱. تبریک
تبریک میگوییم، شما با موفقیت اولین برنامه واقعیت افزوده مبتنی بر عمق خود را با استفاده از API عمق ARCore گوگل ساختید و اجرا کردید!












