استخدام واجهة برمجة التطبيقات ARCore Depth API للحصول على تجارب الواقع المعزّز الغامرة

1. قبل البدء

ARCore هي منصة لإنشاء تطبيقات الواقع المعزّز (AR) على الأجهزة الجوّالة. باستخدام واجهات برمجة تطبيقات مختلفة، تتيح ARCore لجهاز المستخدم مراقبة البيئة المحيطة به وتلقّي معلومات عنها والتفاعل معها.

في هذا الدرس التطبيقي حول الترميز، ستتعرّف على عملية إنشاء تطبيق بسيط متوافق مع الواقع المعزّز ويستخدم Depth API في ARCore.

المتطلبات الأساسية

تمت كتابة هذا الدرس التطبيقي حول الترميز للمطوّرين الذين لديهم معرفة بمفاهيم الواقع المعزّز الأساسية.

ما ستنشئه

1a0236e93212210c.gif

ستنشئ تطبيقًا يستخدم صورة العمق لكل إطار لعرض شكل المشهد الهندسي وتنفيذ عملية الحجب على مواد العرض الافتراضية التي تم وضعها. ستمر بالخطوات المحدّدة التالية:

  • التحقّق من توفّر Depth API على الهاتف
  • استرداد صورة العمق لكل إطار
  • عرض معلومات العمق بطرق متعدّدة (راجِع الصورة المتحركة أعلاه)
  • استخدام العمق لزيادة واقعية التطبيقات التي تتضمّن حجبًا
  • التعرّف على كيفية التعامل مع الهواتف التي لا تتوافق مع Depth API

المتطلبات

متطلبات الأجهزة

  • جهاز متوافق مع ARCore، تم توصيله بجهاز التطوير باستخدام كابل USB يجب أن يكون هذا الجهاز متوافقًا أيضًا مع Depth API. يُرجى الاطّلاع على قائمة الأجهزة المتوافقة. تتوفّر Depth API على Android فقط.
  • فعِّل ميزة "تصحيح أخطاء الجهاز عبر USB" لهذا الجهاز.

متطلبات البرامج

2. ‫ARCore وDepth API

تستخدِم Depth API كاميرا RGB على جهاز متوافق لإنشاء خرائط العمق (تُعرف أيضًا باسم صور العمق). يمكنك استخدام المعلومات المقدَّمة من خلال خريطة العمق لجعل العناصر الافتراضية تظهر بدقة، سواء أمام عناصر العالم الحقيقي أو خلفها، ما يتيح تجارب مستخدم غامرة وواقعية.

تتيح واجهة برمجة التطبيقات ARCore Depth API إمكانية الوصول إلى صور العمق التي تتطابق مع كل إطار تقدّمه "جلسة" ARCore. يوفّر كل بكسل قياسًا للمسافة من الكاميرا إلى البيئة المحيطة، ما يوفّر واقعية محسّنة لتطبيق مزوَّد بميزة الواقع المعزَّز.

إحدى الإمكانات الرئيسية التي توفّرها Depth API هي الحجب، أي قدرة العناصر الرقمية على الظهور بدقة بالنسبة إلى العناصر في العالم الحقيقي. ويجعل هذا العناصر تبدو وكأنّها موجودة فعلاً في البيئة مع المستخدم.

سيرشدك هذا الدرس التطبيقي حول الترميز خلال عملية إنشاء تطبيق بسيط متوافق مع الواقع المعزّز يستخدم صور العمق لإخفاء الكائنات الافتراضية خلف الأسطح الواقعية وعرض الأشكال الهندسية التي تم رصدها في المساحة.

3- طريقة الإعداد

إعداد جهاز التطوير

  1. وصِّل جهاز ARCore بالكمبيوتر باستخدام كابل USB. تأكَّد من أنّ جهازك يسمح بتصحيح أخطاء الجهاز عبر USB.
  2. افتح وحدة طرفية ونفِّذ الأمر adb devices، كما هو موضّح أدناه:
adb devices

List of devices attached
<DEVICE_SERIAL_NUMBER>    device

سيكون <DEVICE_SERIAL_NUMBER> عبارة عن سلسلة فريدة لجهازك. تأكَّد من ظهور جهاز واحد فقط قبل المتابعة.

تنزيل وتثبيت Code

  1. يمكنك إما استنساخ المستودع:
git clone https://github.com/googlecodelabs/arcore-depth

أو نزِّل ملف ZIP واستخرِج ملفاته:

  1. شغِّل "استوديو Android" وانقر على فتح مشروع حالي في "استوديو Android".
  2. ابحث عن الدليل الذي استخرجت منه ملف ZIP الذي تم تنزيله أعلاه، وافتح الدليل depth_codelab_io2020.

هذا مشروع Gradle واحد يتضمّن وحدات متعددة. إذا لم تكن لوحة "المشروع" في أعلى يمين "استوديو Android" معروضة في لوحة "المشروع"، انقر على المشاريع من القائمة المنسدلة.

يجب أن تبدو النتيجة على النحو التالي:

يحتوي هذا المشروع على الوحدات التالية:

  • part0_work: هو التطبيق الأوّلي. عليك إجراء تعديلات على هذه الوحدة عند تنفيذ هذا الدرس التطبيقي حول الترميز.
  • part1: رمز مرجعي للشكل الذي يجب أن تبدو عليه تعديلاتك عند إكمال الجزء 1.
  • part2: رمز مرجعي عند إكمال الجزء 2.
  • part3: رمز مرجعي عند إكمال الجزء 3.
  • part4_completed: الإصدار النهائي من التطبيق. يُرجى الرجوع إلى الرمز عند إكمال الجزء 4 من هذا الدرس التطبيقي حول الترميز.

ستعمل في الوحدة part0_work. تتوفّر أيضًا حلول كاملة لكل جزء من الدرس العملي. كل وحدة هي تطبيق قابل للإنشاء.

4. تشغيل التطبيق النموذجي

  1. انقر على تشغيل > تشغيل... > ‘part0_work'. في مربّع الحوار اختيار هدف النشر الذي يظهر، يجب أن يكون جهازك مُدرَجًا ضمن الأجهزة المتصلة.
  2. اختَر جهازك وانقر على حسنًا. سينشئ استوديو Android التطبيق الأوّلي ويشغّله على جهازك.
  3. سيطلب التطبيق الحصول على أذونات لاستخدام الكاميرا. انقر على سماح للمتابعة.

c5ef65f7a1da0d9.png

كيفية استخدام التطبيق

  1. حرِّك الجهاز لمساعدة التطبيق في العثور على طائرة. تشير الرسالة في أسفل الشاشة إلى الوقت الذي يجب فيه مواصلة الحركة.
  2. انقر على مكان ما في الطائرة لوضع نقطة ارتساء. سيتم رسم شكل Android في المكان الذي تم وضع نقطة الارتكاز فيه. يتيح لك هذا التطبيق وضع نقطة ارتساء واحدة فقط في كل مرة.
  3. حرِّك الجهاز. يجب أن يظهر الشكل وكأنّه ثابت في المكان نفسه، حتى إذا كان الجهاز يتحرّك.

في الوقت الحالي، تطبيقك بسيط جدًا ولا يعرف الكثير عن هندسة المشهد في العالم الحقيقي.

إذا وضعت صورة Android خلف كرسي مثلاً، سيظهر العرض وكأنّه معلّق في الأمام، لأنّ التطبيق لا يعرف أنّ الكرسي موجود ويجب أن يخفي صورة Android.

6182cf62be13cd97.png beb0d327205f80ee.png e4497751c6fad9a7.png

لحلّ هذه المشكلة، سنستخدم Depth API لتحسين مستوى الانغماس والواقعية في هذا التطبيق.

5- التحقّق من توفّر Depth API (الجزء 1)

لا تعمل واجهة برمجة التطبيقات Depth API في ARCore إلا على مجموعة فرعية من الأجهزة المتوافقة. قبل دمج وظائف في تطبيق باستخدام صور العمق هذه، عليك أولاً التأكّد من أنّ التطبيق يعمل على جهاز متوافق.

أضِف عضوًا خاصًا جديدًا إلى DepthCodelabActivity يعمل كعلامة تخزِّن ما إذا كان الجهاز الحالي يتيح استخدام العمق:

private boolean isDepthSupported;

يمكننا ملء هذا العلامة من داخل الدالة onResume()، حيث يتم إنشاء جلسة جديدة.

ابحث عن الرمز الحالي:

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

تم الآن ضبط إعدادات جلسة الواقع المعزّز بشكل مناسب، ويعرف تطبيقك ما إذا كان بإمكانه استخدام الميزات المستندة إلى العمق أم لا.

عليك أيضًا إعلام المستخدم بما إذا كان يتم استخدام بيانات العمق في هذه الجلسة أم لا.

أضِف رسالة أخرى إلى شريط إعلام منبثق. سيظهر في أسفل الشاشة:

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

إذا تم تشغيل تطبيقك على جهاز لا يتوافق مع بيانات العمق، ستظهر الرسالة التي أضفتها للتو في أسفل الشاشة:

5c878a7c27833cb2.png

بعد ذلك، عليك تعديل التطبيق لاستدعاء Depth API واسترداد صور العمق لكل إطار.

6. استرداد صور العمق (الجزء 2)

تسجّل Depth API ملاحظات ثلاثية الأبعاد عن بيئة الجهاز وتعرض صورة عمق تتضمّن هذه البيانات في تطبيقك. ويمثّل كل بكسل في صورة العمق قياسًا للمسافة من كاميرا الجهاز إلى البيئة المحيطة به في العالم الحقيقي.

ستستخدم الآن صور العمق هذه لتحسين العرض والتصوّر في التطبيق. تتمثّل الخطوة الأولى في استرداد صورة العمق لكل إطار وربط هذا النسيج ليتم استخدامه بواسطة وحدة معالجة الرسومات.

أولاً، أضِف فئة جديدة إلى مشروعك.
DepthTextureHandler هي المسؤولة عن استرداد صورة العمق لإطار ARCore معيّن.
أضِف هذا الملف:

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

الآن، ستضيف مثيلاً من هذه الفئة إلى DepthCodelabActivity، ما يضمن توفّر نسخة يسهل الوصول إليها من صورة العمق لكل إطار.

في DepthCodelabActivity.java، أضِف مثيلاً لفئتنا الجديدة كمتغير عضو خاص:

private final DepthTextureHandler depthTexture = new DepthTextureHandler();

بعد ذلك، عدِّل طريقة onSurfaceCreated() لتهيئة هذه الصورة، حتى تتمكّن برامج تظليل وحدة معالجة الرسومات من استخدامها:

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

أصبح لديك الآن صورة عمق يتم تعديلها مع كل إطار. أصبحت جاهزة للاستخدام من خلال برامج التظليل.

ومع ذلك، لم يتغيّر أي شيء في سلوك التطبيق حتى الآن. الآن، عليك استخدام صورة العمق لتحسين تطبيقك.

7. عرض صورة العمق (الجزء 3)

بعد الحصول على صورة عمق يمكنك التعديل عليها، سيهمّك معرفة شكلها. في هذا القسم، ستضيف زرًا إلى التطبيق لعرض العمق لكل إطار.

إضافة مظلّلات جديدة

تتوفّر طرق عديدة لعرض صورة عمق. توفّر برامج التظليل التالية عرضًا بسيطًا لربط الألوان بالبيانات.

إضافة أداة تظليل .vert جديدة

في "استوديو Android"، اتّبِع الخطوات التالية:

  1. أولاً، أضِف مظلّلات .vert و.frag جديدة إلى الدليل src/main/assets/shaders/.
  2. انقر بزر الماوس الأيمن على دليل shaders
  3. انقر على "جديد" -> "ملف"
  4. تسمية background_show_depth_map.vert
  5. اضبطه كملف نصي.

في الملف الجديد، أضِف الرمز التالي:

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.

أضِف مسارات الملفات إلى برامج التظليل في أعلى الفئة:

// 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، لأنّها ستشغّل برنامجَي تظليل:

// 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"/>

يتحكّم الزر الآن في قيمة القيمة المنطقية showDepthMap. استخدِم هذه العلامة للتحكّم في ما إذا كان سيتم عرض خريطة العمق.

في طريقة onDrawFrame() في DepthCodelabActivity، أضِف ما يلي:

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

مرِّر نسيج العمق إلى backgroundRenderer من خلال إضافة السطر التالي في onSurfaceCreated():

// 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 للحفاظ على مَعلمات التظليل هذه. أضِف الحقول التالية إلى أعلى الفئة:

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

داخل طريقة createDepthShaders()، أضِف ما يلي لمطابقة هذه المَعلمات مع برنامج أداة التظليل:

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

يتم الآن عرض العمق على شكل نبض متحرك يتدفق عبر المشهد.

b846e4365d7b69b1.gif

يمكنك تغيير القيم المقدَّمة هنا لجعل النبض أبطأ أو أسرع أو أوسع أو أضيق أو غير ذلك. يمكنك أيضًا تجربة طرق جديدة تمامًا لتغيير أداة التظليل لعرض معلومات العمق.

8. استخدام Depth API لإخفاء الأجزاء (الجزء 4)

ستتعامل الآن مع حجب الكائنات في تطبيقك.

يشير الانسداد إلى ما يحدث عندما يتعذّر عرض العنصر الافتراضي بالكامل، لأنّ هناك عناصر حقيقية بين العنصر الافتراضي والكاميرا. تُعدّ إدارة الانسداد ضرورية لكي تكون تجارب الواقع المعزّز غامرة.

يؤدي عرض الكائنات الافتراضية بشكل سليم في الوقت الفعلي إلى تعزيز واقعية المشهد المعزّز وإمكانية تصديقه. لمزيد من الأمثلة، يُرجى الاطّلاع على الفيديو الخاص بنا حول دمج الواقعين باستخدام Depth API.

في هذا القسم، ستعدّل تطبيقك ليتضمّن كائنات افتراضية فقط في حال توفّر بيانات العمق.

إضافة مظلّلات عناصر جديدة

كما هو الحال في الأقسام السابقة، ستضيف برامج تظليل جديدة لتوفير معلومات العمق. في هذه المرة، يمكنك نسخ مظلّلات العناصر الحالية وإضافة وظيفة الانسداد.

من المهم الاحتفاظ بكلتا نسختَي مظلّلات العناصر، حتى يتمكّن تطبيقك من اتّخاذ قرار في وقت التشغيل بشأن ما إذا كان سيتيح استخدام العمق.

أنشئ نُسخًا من ملفات التظليل object.vert وobject.frag في الدليل src/main/assets/shaders.

  • نسخ object.vert إلى ملف الوجهة src/main/assets/shaders/occlusion_object.vert
  • نسخ object.frag إلى ملف الوجهة src/main/assets/shaders/occlusion_object.frag

داخل occlusion_object.vert، أضِف المتغيّر التالي فوق main():

varying vec3 v_ScreenSpacePosition;

اضبط هذا المتغيّر في أسفل main():

v_ScreenSpacePosition = gl_Position.xyz / gl_Position.w;

عدِّل occlusion_object.frag بإضافة هذه المتغيّرات فوق main() في أعلى الملف:

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
  • انقر بزر الماوس الأيمن > نسخ
  • اختَر مجلد العرض
  • انقر بزر الماوس الأيمن > لصق

7487ece853690c31.png

  • أعِد تسمية الصف إلى OcclusionObjectRenderer

760a4c80429170c2.png

يجب أن يظهر الصف الجديد الذي تمت إعادة تسميته الآن في المجلد نفسه:

9335c373dc60cd17.png

افتح الملف OcclusionObjectRenderer.java الذي تم إنشاؤه حديثًا، وغيِّر مسارات أداة التظليل في أعلى الملف:

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;

تهيئة المَعلمات الموحّدة لبرنامج التظليل في الطريقة 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() لتفعيل وضع المزج في العرض حتى يمكن تطبيق الشفافية على الكائنات الافتراضية عند حجبها:

// 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();
  • يمكنك بعد ذلك التحكّم في وقت استخدام occludedVirtualObject هذا استنادًا إلى ما إذا كان الجهاز الحالي يتيح استخدام Depth API. أضِف الأسطر التالية داخل طريقة 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);
}

على الأجهزة التي لا تتوافق مع ميزة "عمق المجال"، يتم إنشاء مثيل occludedVirtualObject ولكن لا يتم استخدامه. على الهواتف التي تتضمّن ميزة قياس العمق، يتم تهيئة كلا الإصدارين، ويتم اتّخاذ قرار في وقت التشغيل بشأن إصدار أداة العرض الذي سيتم استخدامه عند الرسم.

داخل طريقة 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 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 API

تشغيل التطبيق بدون توفُّر Depth API

9- [اختياري] تحسين جودة الانسداد

توفّر طريقة الحجب المستند إلى العمق، والموضّحة أعلاه، حجبًا بحدود حادة. عندما تبتعد الكاميرا عن العنصر، قد تصبح قياسات العمق أقل دقة، ما قد يؤدي إلى ظهور تشوّهات مرئية.

يمكننا التخفيف من هذه المشكلة عن طريق إضافة تمويه إضافي إلى اختبار الانسداد، ما يؤدي إلى الحصول على حافة أكثر سلاسة للكائنات الافتراضية المخفية.

occlusion_object.frag

أضِف المتغيّر الموحّد التالي في أعلى ملف occlusion_object.frag:

uniform float u_OcclusionBlurAmount;

أضِف دالة المساعد هذه فوق main() مباشرةً في أداة تظليل، ما يؤدي إلى تطبيق تمويه النواة على أخذ عينات الانسداد:

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

يجب تعديل أداة العرض للاستفادة من وظيفة التظليل الجديدة هذه.

OcclusionObjectRenderer.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);

المقارنة المرئية

من المفترض أن يكون حد الانسداد أكثر سلاسة الآن بعد إجراء هذه التغييرات.

10. إنشاء-تشغيل-اختبار

إنشاء تطبيقك وتشغيله

  1. وصِّل جهاز Android باستخدام كابل USB.
  2. اختَر ملف > إنشاء وتشغيل.
  3. الحفظ باسم: ARCodeLab.apk
  4. انتظِر إلى أن يتم إنشاء التطبيق ونشره على جهازك.

في المرة الأولى التي تحاول فيها نشر التطبيق على جهازك:

  • يجب السماح بتصحيح أخطاء الجهاز عبر USB على الجهاز. انقر على "حسنًا" للمتابعة.
  • سيُطلب منك تحديد ما إذا كان التطبيق لديه إذن باستخدام كاميرا الجهاز. يجب السماح بالوصول لمواصلة استخدام وظائف الواقع المعزّز.

اختبار تطبيقك

عند تشغيل تطبيقك، يمكنك اختبار سلوكه الأساسي من خلال حمل جهازك والتنقّل في محيطك ومسح منطقة ضوئيًا ببطء. حاوِل جمع بيانات لمدة 10 ثوانٍ على الأقل ومسح المنطقة من عدّة اتجاهات قبل الانتقال إلى الخطوة التالية.

تحديد المشاكل وحلّها

إعداد جهاز Android للتطوير

  1. وصِّل جهازك بجهاز التطوير باستخدام كابل USB. إذا كنت تستخدم نظام التشغيل Windows في عملية التطوير، قد تحتاج إلى تثبيت برنامج تشغيل USB المناسب لجهازك.
  2. اتّبِع الخطوات التالية لتفعيل تصحيح أخطاء الجهاز عبر USB في نافذة خيارات المطوّرين:
  3. افتح تطبيق الإعدادات.
  4. إذا كان جهازك يعمل بالإصدار 8.0 من نظام التشغيل Android أو إصدار أحدث، اختَر النظام. بخلاف ذلك، انتقِل إلى الخطوة التالية.
  5. انتقِل إلى أسفل الصفحة وانقر على لمحة عن الهاتف.
  6. انتقِل إلى أسفل الصفحة وانقر على رقم الإصدار 7 مرّات.
  7. ارجع إلى الشاشة السابقة، وانتقِل إلى أسفلها، ثم انقر على خيارات المطوّرين.
  8. في نافذة خيارات المطوّرين، انتقِل للأسفل للعثور على خيار تصحيح أخطاء الجهاز عبر USB وفعِّله.

يمكنك العثور على مزيد من المعلومات التفصيلية حول هذه العملية على موقع "مطوّرو تطبيقات Android" الإلكتروني من Google.

cfa20a722a68f54f.png

إذا واجهت خطأ في الإنشاء مرتبطًا بالتراخيص (تعذّر تثبيت حِزم Android SDK التالية لأنّه لم يتم قبول بعض التراخيص)، يمكنك استخدام الأوامر التالية لمراجعة هذه التراخيص وقبولها:

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

11. تهانينا

تهانينا، لقد أنشأت وشغّلت بنجاح أول تطبيق واقع معزّز يستند إلى العمق باستخدام Depth API في ARCore من Google.

الأسئلة الشائعة