ใช้ ARCore Depth API เพื่อประสบการณ์ Augmented Reality ที่สมจริง

1. ก่อนเริ่มต้น

ARCore เป็นแพลตฟอร์มสำหรับสร้างแอป Augmented Reality (AR) ในอุปกรณ์เคลื่อนที่ ARCore จะช่วยให้อุปกรณ์ของผู้ใช้สังเกตและรับข้อมูลเกี่ยวกับสภาพแวดล้อมของตน รวมถึงโต้ตอบกับข้อมูลนั้นได้ด้วยการใช้ API ที่แตกต่างกัน

ใน Codelab นี้ คุณจะได้ทำตามขั้นตอนการสร้างแอปง่ายๆ ที่เปิดใช้ AR ซึ่งใช้ ARCore Depth API

ข้อกำหนดเบื้องต้น

Codelab นี้เขียนขึ้นสำหรับนักพัฒนาซอฟต์แวร์ที่มีความรู้เกี่ยวกับแนวคิด AR พื้นฐาน

สิ่งที่คุณจะสร้าง

1a0236e93212210c.gif

คุณจะต้องสร้างแอปที่ใช้ภาพความลึกของแต่ละเฟรมเพื่อแสดงภาพเรขาคณิตของฉากและทำการบังเนื้อหาเสมือนที่วางไว้ คุณจะดำเนินการตามขั้นตอนเฉพาะต่างๆ ต่อไปนี้

  • กำลังตรวจสอบการรองรับ Depth API ในโทรศัพท์
  • กำลังเรียกดูรูปภาพความลึกของแต่ละเฟรม
  • การแสดงภาพข้อมูลความลึกในรูปแบบต่างๆ (ดูภาพเคลื่อนไหวด้านบน)
  • ใช้ความลึกเพื่อเพิ่มความสมจริงของแอปที่มีการบัง
  • ดูวิธีจัดการโทรศัพท์ที่ไม่รองรับ Depth API อย่างราบรื่น

สิ่งที่คุณต้องมี

ข้อกำหนดด้านฮาร์ดแวร์

ข้อกำหนดของซอฟต์แวร์

  • ARCore SDK 1.31.0 ขึ้นไป
  • เครื่องสำหรับการพัฒนาที่มี Android Studio (เวอร์ชัน 3.0 ขึ้นไป)

2. ARCore และ Depth API

Depth API ใช้กล้อง RGB ของอุปกรณ์ที่รองรับเพื่อสร้างแผนที่ความลึก (หรือที่เรียกว่ารูปภาพความลึก) คุณสามารถใช้ข้อมูลที่ได้จากแผนที่ความลึกเพื่อทำให้วัตถุเสมือนปรากฏขึ้นอย่างถูกต้อง ด้านหน้าหรือด้านหลังวัตถุในโลกจริง เพื่อให้ผู้ใช้ได้รับประสบการณ์ที่สมจริงและสมจริง

ARCore Depth API จะให้สิทธิ์เข้าถึงรูปภาพความลึกที่ตรงกับแต่ละเฟรมที่ได้จากเซสชันของ ARCore แต่ละพิกเซลจะมีการวัดระยะทางจากกล้องถึงสิ่งแวดล้อม ซึ่งช่วยเพิ่มความสมจริงให้กับแอป AR ของคุณ

ความสามารถสำคัญเบื้องหลัง Depth API คือการบัง ซึ่งเป็นความสามารถในการให้วัตถุดิจิทัลปรากฏอย่างถูกต้องเมื่อเทียบกับวัตถุในโลกจริง ซึ่งจะทำให้วัตถุรู้สึกราวกับว่าวัตถุนั้นอยู่ในสภาพแวดล้อมจริงกับผู้ใช้

Codelab นี้จะแนะนำขั้นตอนการสร้างแอปที่ใช้งานง่ายซึ่งเปิดใช้ AR ซึ่งใช้รูปภาพความลึกในการบดบังวัตถุเสมือนที่อยู่หลังพื้นผิวโลกจริง และแสดงภาพรูปทรงเรขาคณิตที่ตรวจพบของอวกาศ

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 Studio แล้วคลิกเปิดโปรเจ็กต์ Android Studio ที่มีอยู่
  2. ค้นหาไดเรกทอรีที่คุณแตกไฟล์ ZIP ที่ดาวน์โหลดไว้ด้านบน แล้วเปิดไดเรกทอรี depth_codelab_io2020

นี่คือโปรเจ็กต์ Gradle เดียวที่มีหลายโมดูล หากแผงโครงการที่ด้านซ้ายบนของ Android Studio ยังไม่ปรากฏในแผงโครงการ ให้คลิกโครงการจากเมนูแบบเลื่อนลง

ผลลัพธ์ที่ได้ควรมีลักษณะดังนี้

โครงการนี้มีโมดูลต่อไปนี้:

  • part0_work: แอปเริ่มต้น คุณควรแก้ไขโมดูลนี้เมื่อทํา Codelab นี้
  • part1: รหัสอ้างอิงแสดงลักษณะของการแก้ไขเมื่อคุณทำส่วนที่ 1 เสร็จ
  • part2: รหัสอ้างอิงเมื่อทำส่วนที่ 2 เสร็จ
  • part3: รหัสอ้างอิงเมื่อทำส่วนที่ 3 เสร็จแล้ว
  • part4_completed: แอปเวอร์ชันสุดท้าย รหัสอ้างอิงเมื่อคุณทำส่วนที่ 4 และ Codelab นี้เสร็จสมบูรณ์แล้ว

คุณจะได้ทำงานในโมดูล part0_work นอกจากนี้ยังมีโซลูชันที่สมบูรณ์สำหรับแต่ละส่วนของ Codelab แต่ละโมดูลคือแอปที่บิลด์ได้

4. เรียกใช้แอปเริ่มต้น

  1. คลิกเรียกใช้ > เรียกใช้... "part0_work" ในกล่องโต้ตอบเลือกเป้าหมายการทำให้ใช้งานได้ที่ปรากฏขึ้น อุปกรณ์ของคุณควรอยู่ภายใต้อุปกรณ์ที่เชื่อมต่อ
  2. เลือกอุปกรณ์แล้วคลิกตกลง Android Studio จะสร้างแอปเริ่มต้นและเรียกใช้บนอุปกรณ์ของคุณ
  3. แอปจะขอสิทธิ์ใช้กล้อง แตะอนุญาตเพื่อดำเนินการต่อ

c5ef65f7a1da0d9.png

วิธีใช้แอป

  1. เลื่อนอุปกรณ์ไปรอบๆ เพื่อช่วยให้แอปค้นหาเครื่องบินได้ ข้อความด้านล่างจะระบุว่าต้องย้ายเมื่อใด
  2. แตะที่ใดก็ได้บนเครื่องบินเพื่อวางจุดยึด ระบบจะวาดรูป Android ในตำแหน่งที่วางสมอไว้ แอปนี้อนุญาตให้คุณวาง Anchor ได้ทีละ 1 รายการเท่านั้น
  3. เคลื่อนอุปกรณ์ไปรอบๆ รูปจะปรากฏอยู่ที่ตำแหน่งเดิม แม้ว่าอุปกรณ์จะเคลื่อนไหวไปมา

ปัจจุบันแอปของคุณเรียบง่ายมากและไม่ค่อยมีความรู้เกี่ยวกับเรขาคณิตในฉากในโลกแห่งความเป็นจริงมากนัก

ตัวอย่างเช่น หากคุณวางรูป Android ไว้หลังเก้าอี้ การแสดงภาพจะลอยอยู่ด้านหน้า เนื่องจากแอปพลิเคชันไม่ทราบว่ามีเก้าอี้อยู่และควรซ่อน Android ไว้

6182cf62be13cd97.png beb0d327205f80ee.png e4497751c6fad9a7.png

ในการแก้ปัญหานี้ เราจะใช้ Depth API เพื่อปรับปรุงความสมจริงและความสมจริงในแอปนี้

5. ตรวจสอบว่าระบบรองรับ Depth API หรือไม่ (ส่วนที่ 1)

ARCore Depth API จะทำงานในอุปกรณ์ที่รองรับบางรุ่นเท่านั้น ก่อนที่จะผสานรวมฟังก์ชันเข้ากับแอปโดยใช้รูปภาพความลึกเหล่านี้ คุณต้องตรวจสอบก่อนว่าแอปทำงานอยู่ในอุปกรณ์ที่รองรับ

เพิ่มสมาชิกส่วนตัวคนใหม่ไปยัง DepthCodelabActivity ซึ่งทำหน้าที่เป็นธงซึ่งจัดเก็บไว้ว่าอุปกรณ์ปัจจุบันรองรับความลึกหรือไม่:

private boolean isDepthSupported;

เราสามารถป้อนข้อมูล Flag นี้ได้จากภายในฟังก์ชัน 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);

ตอนนี้เซสชัน AR ได้รับการกำหนดค่าอย่างเหมาะสมแล้ว และแอปก็จะรู้ว่าจะใช้ฟีเจอร์ที่อิงตามความลึกได้หรือไม่

คุณควรแจ้งให้ผู้ใช้ทราบว่าเซสชันนี้ใช้ความลึกหรือไม่

เพิ่มอีกข้อความลงในแถบแสดงข้อความ ซึ่งจะปรากฏที่ด้านล่างของหน้าจอ

// 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 จะบันทึกการสังเกตสภาพแวดล้อมของอุปกรณ์แบบ 3 มิติและแสดงผลรูปภาพความลึกที่มีข้อมูลดังกล่าวไปยังแอปของคุณ ภาพที่มีความลึกแต่ละพิกเซลจะแสดงการวัดระยะทางจากกล้องของอุปกรณ์ไปยังสภาพแวดล้อมจริง

ตอนนี้คุณจะใช้รูปภาพความลึกเหล่านี้เพื่อปรับปรุงการแสดงผลและการแสดงภาพในแอป ขั้นตอนแรกคือการดึงข้อมูลรูปภาพความลึกของแต่ละเฟรมและเชื่อมโยงพื้นผิวที่ GPU จะใช้

ก่อนอื่นให้เพิ่มคลาสใหม่ลงในโปรเจ็กต์
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() เพื่อเริ่มต้นพื้นผิวนี้เพื่อให้ตัวสร้างเฉดสี 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);
}

ตอนนี้คุณมีรูปภาพความลึกที่อัปเดตทุกเฟรมแล้ว พร้อมที่จะใช้งานโดยเครื่องมือให้เฉดสีของคุณแล้ว

อย่างไรก็ตาม ยังไม่มีการเปลี่ยนแปลงลักษณะการทำงานของแอป ต่อไปคุณจะใช้รูปภาพความลึกเพื่อปรับปรุงแอป

7. แสดงภาพความลึก (ส่วนที่ 3)

ตอนนี้คุณมีภาพความลึกไว้เล่นแล้ว ก็อยากรู้ว่าภาพจะเป็นอย่างไร ในส่วนนี้ คุณจะต้องเพิ่มปุ่มลงในแอปเพื่อแสดงผลความลึกของแต่ละเฟรม

เพิ่มตัวปรับแสงเงาใหม่

การดูรูปภาพที่มีความลึกมีหลายวิธี ตัวปรับเฉดสีต่อไปนี้ช่วยให้เห็นภาพการจับคู่สีที่เรียบง่าย

เพิ่มตัวปรับแสงเงา .vert ใหม่

ใน Android Studio ให้ทำดังนี้

  1. ขั้นแรก ให้เพิ่มเฉดสี .vert และ .frag ใหม่ในไดเรกทอรี src/main/assets/shaders/
  2. คลิกขวาที่ไดเรกทอรีตัวปรับแสงเงา
  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;
}

ทำขั้นตอนด้านบนซ้ำเพื่อทำให้ตัวควบคุมเงา Fragment อยู่ในไดเรกทอรีเดียวกันและตั้งชื่อว่า 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 เนื่องจากจะใช้ตัวปรับแสง 2 แบบ ดังนี้

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

ตอนนี้คุณจะจัดการการบล็อกวัตถุในแอปได้

การซ้อนทับหมายถึงสิ่งที่เกิดขึ้นเมื่อแสดงผลวัตถุเสมือนจริงได้ไม่สมบูรณ์เนื่องจากมีวัตถุจริงอยู่ระหว่างวัตถุเสมือนและกล้อง การจัดการการบังเป็นสิ่งสำคัญเพื่อให้ประสบการณ์ AR สมจริง

การแสดงภาพวัตถุเสมือนที่ถูกต้องในแบบเรียลไทม์จะช่วยเพิ่มความสมจริงและความน่าเชื่อถือของฉากที่เพิ่มเข้าไป ดูตัวอย่างเพิ่มเติมได้ที่วิดีโอเกี่ยวกับการผสานความจริงด้วย Depth API

ในส่วนนี้ คุณจะอัปเดตแอปให้รวมออบเจ็กต์เสมือนเฉพาะในกรณีที่มีความลึกเท่านั้น

การเพิ่มตัวปรับแสงเงาวัตถุใหม่

คุณจะเพิ่มเครื่องมือให้เฉดสีใหม่เพื่อรองรับข้อมูลเชิงลึกเช่นเดียวกับในส่วนก่อนหน้านี้ ในตอนนี้คุณสามารถคัดลอกตัวปรับแสงเงาวัตถุที่มีอยู่และเพิ่มฟังก์ชันการบังได้

คุณต้องเก็บตัวปรับแสงเงาวัตถุทั้ง 2 เวอร์ชันไว้ เพื่อให้แอปสามารถตัดสินใจเกี่ยวกับระยะเวลาที่จะรองรับความลึกได้

ทำสำเนาไฟล์ตัวปรับแสงสี 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 แต่ไม่มีการใช้งานในอุปกรณ์ที่ไม่รองรับความลึก ในโทรศัพท์ที่มีความลึก ระบบจะเริ่มต้นทั้ง 2 เวอร์ชัน และตัดสินใจว่าจะใช้โหมดแสดงภาพเวอร์ชันใดเมื่อวาด

ภายในเมธอด 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. Build-Run-Test

สร้างและเรียกใช้แอป

  1. เสียบอุปกรณ์ Android ผ่าน USB
  2. เลือก ไฟล์ > สร้างและเรียกใช้
  3. บันทึกเป็น ARCodeLab.apk
  4. รอให้แอปสร้างและทำให้ใช้งานได้ในอุปกรณ์ของคุณ

ครั้งแรกที่คุณพยายามทำให้แอปใช้งานได้ในอุปกรณ์ของคุณ ให้ทำดังนี้

  • คุณจะต้องอนุญาตให้แก้ไขข้อบกพร่อง USB บนอุปกรณ์ เลือก "ตกลง" เพื่อดำเนินการต่อ
  • ระบบจะถามว่าแอปมีสิทธิ์ใช้กล้องของอุปกรณ์หรือไม่ อนุญาตให้เข้าถึงเพื่อใช้ฟังก์ชัน AR ต่อไป

การทดสอบแอป

เมื่อเรียกใช้แอป คุณทดสอบลักษณะการทำงานเบื้องต้นของแอปได้โดยถืออุปกรณ์ไว้ เคลื่อนไปรอบๆ พื้นที่ของคุณ และสแกนพื้นที่ช้าๆ พยายามรวบรวมข้อมูลอย่างน้อย 10 วินาทีและสแกนพื้นที่จากหลายๆ เส้นทางก่อนไปยังขั้นตอนถัดไป

การแก้ปัญหา

ตั้งค่าอุปกรณ์ Android เพื่อการพัฒนา

  1. เชื่อมต่ออุปกรณ์กับเครื่องพัฒนาซอฟต์แวร์ด้วยสาย USB หากคุณพัฒนาโดยใช้ Windows คุณอาจต้องติดตั้งไดรเวอร์ USB ที่เหมาะสมสำหรับอุปกรณ์ของคุณ
  2. ทำตามขั้นตอนต่อไปนี้เพื่อเปิดใช้การแก้ไขข้อบกพร่อง USB ในหน้าต่างตัวเลือกสำหรับนักพัฒนาแอป
  3. เปิดแอปการตั้งค่า
  4. หากอุปกรณ์ใช้ Android เวอร์ชัน 8.0 ขึ้นไป ให้เลือกระบบ หรือไปยังขั้นตอนถัดไป
  5. เลื่อนไปด้านล่างแล้วเลือกเกี่ยวกับโทรศัพท์
  6. เลื่อนไปด้านล่างและแตะหมายเลขบิลด์ 7 ครั้ง
  7. กลับไปที่หน้าจอก่อนหน้า เลื่อนไปด้านล่าง แล้วแตะตัวเลือกสำหรับนักพัฒนาซอฟต์แวร์
  8. ในหน้าต่างตัวเลือกสำหรับนักพัฒนาซอฟต์แวร์ ให้เลื่อนลงเพื่อหาและเปิดใช้การแก้ไขข้อบกพร่อง USB

คุณสามารถดูข้อมูลโดยละเอียดเพิ่มเติมเกี่ยวกับกระบวนการนี้ได้ที่เว็บไซต์นักพัฒนาซอฟต์แวร์ Android ของ Google

cfa20a722a68f54f.png

หากพบปัญหาเกี่ยวกับใบอนุญาต (ติดตั้งแพ็กเกจ Android SDK ต่อไปนี้ไม่สำเร็จเนื่องจากระบบไม่ยอมรับใบอนุญาตบางรายการ) คุณสามารถใช้คำสั่งต่อไปนี้เพื่อตรวจสอบและยอมรับใบอนุญาตเหล่านี้ได้

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

11. ขอแสดงความยินดี

ขอแสดงความยินดี คุณได้สร้างและเรียกใช้แอป Augmented Reality แบบเจาะลึกแอปแรกโดยใช้ ARCore Depth API ของ Google เรียบร้อยแล้ว

คำถามที่พบบ่อย