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

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

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

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

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

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

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

1a0236e93212210c.gif

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

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

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

ข้อกำหนดเกี่ยวกับฮาร์ดแวร์

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

  • ARCore SDK 1.31.0 ขึ้นไป
  • เครื่องพัฒนาซอฟต์แวร์ที่มี Android Studio (v3.0 ขึ้นไป)

2. ARCore และ Depth API

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

Depth API ของ ARCore ช่วยให้เข้าถึงรูปภาพเชิงลึกที่ตรงกับแต่ละเฟรมที่ Session ของ 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> จะเป็นสตริงที่ไม่ซ้ำกันสำหรับอุปกรณ์ของคุณ โปรดตรวจสอบว่าคุณเห็นอุปกรณ์ 1 เครื่องเท่านั้นก่อนดำเนินการต่อ

ดาวน์โหลดและติดตั้ง 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: Reference code of what your edits should look like when you complete Part 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 ในตำแหน่งที่วางจุดยึด แอปนี้อนุญาตให้คุณวางสมอได้ครั้งละ 1 รายการเท่านั้น
  3. ขยับอุปกรณ์ไปมา โดยตัวบุคคลควรปรากฏในตำแหน่งเดิม แม้ว่าอุปกรณ์จะเคลื่อนที่ไปมาก็ตาม

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

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

6182cf62be13cd97.png beb0d327205f80ee.png e4497751c6fad9a7.png

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

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

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

เพิ่มสมาชิกส่วนตัวใหม่ไปยัง 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 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;
}

หากแอปทำงานบนอุปกรณ์ที่ไม่รองรับความลึก ข้อความที่คุณเพิ่งเพิ่มจะปรากฏที่ด้านล่าง

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)

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

เพิ่ม Shader ใหม่

คุณดูรูปภาพระยะชัดได้หลายวิธี Shader ต่อไปนี้แสดงภาพการแมปสีอย่างง่าย

เพิ่มเชเดอร์ .vert ใหม่

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

  1. ก่อนอื่น ให้เพิ่ม Shader ใหม่ .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 Shader ในไดเรกทอรีเดียวกัน และตั้งชื่อเป็น 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 เพื่อใช้ Shader ใหม่เหล่านี้ ซึ่งอยู่ใน 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 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 ใช้ Flag นี้เพื่อควบคุมว่าจะแสดงผลแผนที่ความลึกหรือไม่

กลับไปที่เมธอด 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

[ไม่บังคับ] ภาพเคลื่อนไหวแสดงความลึกที่สวยงาม

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

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

เริ่มต้นด้วยการเพิ่มตัวแปรต่อไปนี้ที่ด้านบนของ background_show_depth_map.frag

uniform float u_DepthRangeToRenderMm;
const float kDepthWidthToRenderMm = 350.0;
  • จากนั้นใช้ค่าเหล่านี้เพื่อกรองพิกเซลที่จะครอบคลุมด้วยค่าความลึกในฟังก์ชัน main() ของ Shader
// 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);

ตอนนี้ระบบจะแสดงภาพความลึกเป็นชีพจรเคลื่อนไหวที่ไหลผ่านฉาก

b846e4365d7b69b1.gif

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

8. ใช้ Depth API สำหรับการบดบัง (ตอนที่ 4)

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

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

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

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

การเพิ่ม Shader ของออบเจ็กต์ใหม่

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

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

ทำสำเนาไฟล์ Shader 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()ใน Shader เพื่อให้จัดการข้อมูลความลึกได้ง่ายขึ้น
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 ที่สร้างขึ้นใหม่ แล้วเปลี่ยนเส้นทางของ 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;

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

// 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() ใน Shader ซึ่งใช้การเบลอเคอร์เนลกับการสุ่มตัวอย่างการบดบัง

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

อัปเดตโปรแกรมแสดงผลเพื่อใช้ประโยชน์จากฟังก์ชันการทำงานของ Shader ใหม่นี้

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

การทดสอบแอป

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

การแก้ปัญหา

การตั้งค่าอุปกรณ์ Android สำหรับการพัฒนา

  1. เชื่อมต่ออุปกรณ์กับคอมพิวเตอร์สำหรับการพัฒนาซอฟต์แวร์ด้วยสาย USB หากพัฒนาโดยใช้ Windows คุณอาจต้องติดตั้งไดรเวอร์ USB ที่เหมาะสมสำหรับอุปกรณ์
  2. ทำตามขั้นตอนต่อไปนี้เพื่อเปิดใช้การแก้ไขข้อบกพร่อง USB ในหน้าต่างตัวเลือกสำหรับนักพัฒนาแอป
  3. เปิดแอปการตั้งค่า
  4. หากอุปกรณ์ใช้ Android v8.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 แอปแรกที่อิงตามความลึกโดยใช้ Depth API ของ ARCore จาก Google ได้สำเร็จแล้ว

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