Menggunakan ARCore Depth API untuk pengalaman augmented reality yang imersif

1. Sebelum memulai

ARCore adalah platform untuk mem-build aplikasi Augmented Reality (AR) di perangkat seluler. Dengan menggunakan API yang berbeda, ARCore memungkinkan perangkat pengguna mengamati dan menerima informasi tentang lingkungannya, serta berinteraksi dengan informasi tersebut.

Dalam codelab ini, Anda akan menjalani proses build aplikasi sederhana berkemampuan AR yang menggunakan ARCore Depth API.

Prasyarat

Codelab ini ditulis bagi developer yang memiliki pengetahuan tentang konsep AR mendasar.

Yang akan Anda build

d9bd5136c54ce47a.gif

Anda akan mem-build aplikasi yang menggunakan gambar kedalaman untuk setiap bingkai guna memvisualisasi geometri adegan dan menjalankan penutupan di aset virtual yang diletakkan. Anda akan menjalani langkah-langkah spesifik berikut:

  • Memeriksa dukungan Depth API di ponsel
  • Mengambil gambar kedalaman untuk setiap bingkai
  • Memvisualisasikan informasi kedalaman dengan beberapa cara (lihat animasi di atas)
  • Menggunakan kedalaman untuk meningkatkan realisme aplikasi dengan oklusi
  • Mempelajari cara menangani dengan baik ponsel yang tidak mendukung Depth API.

Yang akan Anda butuhkan

Persyaratan Hardware

Persyaratan Software

2. ARCore dan Depth API

Depth API menggunakan kamera RGB pada perangkat yang didukung untuk membuat peta kedalaman (juga disebut gambar kedalaman). Anda dapat menggunakan informasi yang diberikan oleh peta kedalaman untuk menampilkan objek virtual secara akurat, di depan atau di belakang, objek dunia nyata, sehingga memungkinkan pengalaman pengguna yang imersif dan realistis.

ARCore Depth API memberikan akses ke gambar kedalaman yang akan mencocokkan setiap bingkai yang diberikan oleh Sesi ARCore. Setiap piksel memberikan pengukuran jarak dari kamera ke lingkungan sekitarnya, yang akan memberikan peningkatan realisme untuk aplikasi AR Anda.

Kemampuan utama di balik Depth API adalah oklusi: kemampuan objek digital untuk muncul akurat yang relatif terhadap objek dunia nyata. Hal ini membuat objek terasa seperti berada di lingkungan bersama pengguna.

Codelab ini akan memandu Anda menjalani proses build aplikasi sederhana yang berkemampuan AR dan menggunakan gambar kedalaman untuk menjalankan oklusi objek virtual di belakang permukaan dunia nyata serta memvisualisasikan geometri ruang yang terdeteksi.

3. Memulai persiapan

Menyiapkan mesin pengembangan

  1. Hubungkan perangkat ARCore ke komputer melalui kabel USB. Pastikan perangkat memungkinkan proses debug USB.
  2. Buka terminal dan jalankan adb devices, seperti yang ditampilkan di bawah:
adb devices

List of devices attached
<DEVICE_SERIAL_NUMBER>    device

<DEVICE_SERIAL_NUMBER> akan berupa string yang unik untuk perangkat Anda. Pastikan bahwa Anda hanya melihat satu perangkat sebelum melanjutkan.

Mendownload dan menginstal Kode

  1. Anda dapat meng-clone repositori:
git clone https://github.com/googlecodelabs/arcore-depth

Atau mendownload file ZIP lalu mengekstraknya:

  1. Luncurkan Android Studio, dan klik Open an existing Android Studio project.
  2. Temukan direktori tempat Anda mengekstrak file ZIP yang didownload di atas, lalu buka direktori depth_codelab_io2020.

Ini adalah satu project Gradle dengan beberapa modul. Jika panel Project di kiri atas Android Studio belum menampilkan panel Project, klik Projects dari menu drop-down.

Hasilnya akan terlihat seperti ini:

Project ini berisi modul berikut:

  • part0_work: Aplikasi awal. Anda harus mengedit modul ini saat menjalani codelab ini.
  • part1: Kode referensi mengenai bagaimana seharusnya hasil editan Anda saat menyelesaikan Bagian 1.
  • part2: Kode referensi saat Anda menyelesaikan Bagian 2.
  • part3: Kode referensi saat Anda menyelesaikan Bagian 3.
  • part4_completed: Versi final aplikasi. Kode referensi saat Anda menyelesaikan Bagian 4 dan codelab ini.

Anda akan mengerjakan modul part0_work. Selain itu, ada solusi lengkap untuk setiap bagian codelab. Setiap modul adalah aplikasi yang dapat di-build.

4. Menjalankan Aplikasi Awal

  1. Klik Run > Run... > ‘part0_work'. Di dialog Select Deployment Target yang ditampilkan, perangkat Anda seharusnya dicantumkan di bawah Connected Devices.
  2. Pilih perangkat lalu klik OK. Android Studio akan mem-build aplikasi awal dan menjalankannya di perangkat Anda.
  3. Aplikasi akan meminta izin kamera. Ketuk Izinkan untuk melanjutkan.

c5ef65f7a1da0d9.png

Cara menggunakan aplikasi

  1. Gerakkan perangkat untuk membantu aplikasi menemukan permukaan. Pesan di bawah menunjukkan kapan Anda harus terus menggerakkan perangkat.
  2. Ketuk permukaan di bagian mana pun untuk meletakkan anchor. Gambar Android akan dibuat di tempat anchor diletakkan. Aplikasi ini hanya memungkinkan Anda meletakkan satu anchor dalam satu waktu.
  3. Gerakkan perangkat. Gambar akan muncul agar berada di tempat yang sama, meskipun perangkat digerakkan.

Saat ini, aplikasi Anda sangatlah sederhana dan belum tahu banyak mengenai geometri scene dunia nyata.

Misalnya, Anda menempatkan gambar Android di belakang kursi. Rendering akan mengarahkan gambar ke depan kursi karena aplikasi tidak tahu bahwa kursi ada di sana dan harus menyembunyikan Android, bukan sebaliknya.

6182cf62be13cd97.png beb0d327205f80ee.png e4497751c6fad9a7.png

Untuk memperbaiki masalah ini, kami akan menggunakan Depth API guna meningkatkan sifat imersif dan realisme dalam aplikasi ini.

5. Memeriksa apakah Depth API didukung (Bagian 1)

ARCore Depth API hanya dapat dijalankan pada subkumpulan perangkat yang didukung. Sebelum mengintegrasikan fungsi ke dalam aplikasi menggunakan gambar kedalaman tersebut, Anda harus memastikan bahwa aplikasi dijalankan di perangkat yang didukung.

Tambahkan anggota pribadi baru ke DepthCodelabActivity yang berfungsi sebagai tanda yang menyimpan apakah perangkat yang sedang digunakan mendukung kedalaman:

private boolean isDepthSupported;

Kita dapat mengisi tanda ini dari dalam fungsi onResume(), tempat Sesi baru dibuat.

Temukan kode yang sudah ada:

// Creates the ARCore session.
session = new Session(/* context= */ this);

Update kode menjadi:

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

Sesi AR sudah dikonfigurasi dengan benar. Kini, aplikasi telah mengerti apakah dapat menggunakan fitur berbasis kedalaman atau tidak.

Anda harus memberi tahu pengguna apakah kedalaman digunakan untuk sesi ini atau tidak.

Tambahkan pesan lain ke Snackbar. Pesan akan muncul di bagian bawah layar:

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

Di dalam onDrawFrame(), Anda dapat menampilkan pesan ini jika diperlukan:

// Add this if-statement above messageSnackbarHelper.showMessage(this, messageToShow).
if (!isDepthSupported) {
  messageToShow += "\n" + DEPTH_NOT_AVAILABLE_MESSAGE;
}

Jika aplikasi Anda dijalankan di perangkat yang tidak mendukung kedalaman, pesan yang baru Anda tambahkan akan muncul di bagian bawah:

5c878a7c27833cb2.png

Selanjutnya, Anda akan mengupdate aplikasi tersebut untuk memanggil Depth API dan mengambil gambar kedalaman untuk setiap bingkai.

6. Mengambil gambar kedalaman (Bagian 2)

Depth API menangkap pengamatan 3D lingkungan perangkat dan menampilkan gambar kedalaman dengan data tersebut ke aplikasi Anda. Setiap piksel dalam gambar kedalaman mewakili pengukuran jarak dari kamera perangkat ke lingkungan dunia nyata.

Sekarang, Anda akan menggunakan gambar kedalaman ini untuk meningkatkan rendering dan visualisasi di aplikasi tersebut. Langkah pertama adalah mengambil gambar kedalaman untuk setiap bingkai dan mengikat tekstur tersebut yang akan digunakan oleh GPU.

Pertama, tambahkan class baru ke project Anda.
DepthTextureHandler bertanggung jawab untuk mengambil gambar kedalaman untuk bingkai ARCore tertentu.
Tambahkan file ini:

41c3889f2bbc8345.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 acquireDepthImage().
   * This method needs to be called on a thread with an EGL context attached.
   */
  public void update(final Frame frame) {
    try {
      Image depthImage = frame.acquireDepthImage();
      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;
  }
}

Sekarang, Anda akan menambahkan instance class ini ke DepthCodelabActivity, yang memastikan Anda akan memiliki salinan gambar kedalaman yang mudah diakses untuk setiap bingkai.

Di DepthCodelabActivity.java, tambahkan instance dari class baru kita sebagai variabel anggota pribadi:

private final DepthTextureHandler depthTexture = new DepthTextureHandler();

Selanjutnya, update metode onSurfaceCreated() untuk melakukan inisialisasi pada tekstur ini sehingga dapat digunakan oleh shader GPU:

// Put this at the top of the "try" block in onSurfaceCreated().
depthTexture.createOnGlThread();

Terakhir, Anda ingin mengisi tekstur ini di setiap bingkai dengan gambar kedalaman terbaru, yang dapat dilakukan dengan memanggil metode update() yang Anda buat di atas pada frame terbaru yang diambil dari session.
Karena dukungan kedalaman bersifat opsional untuk aplikasi ini, gunakan panggilan ini hanya jika Anda menggunakan kedalaman.

// Add this just after "frame" is created inside onDrawFrame().
if (isDepthSupported) {
  depthTexture.update(frame);
}

Sekarang, Anda memiliki gambar kedalaman yang diupdate ke setiap bingkai. Gambar siap digunakan oleh shader Anda.

Namun, perilaku aplikasi masih belum berubah. Sekarang, Anda akan menggunakan gambar kedalaman untuk meningkatkan aplikasi.

7. Merender gambar kedalaman (Bagian 3)

Kini Anda memiliki gambar kedalaman yang dapat dijadikan eksperimen, Anda pasti ingin melihat tampilannya. Di bagian ini, Anda akan menambahkan tombol ke aplikasi untuk merender kedalaman setiap bingkai.

Menambahkan shader baru

Ada banyak cara untuk melihat gambar kedalaman. Shader berikut memberikan visualisasi pemetaan warna yang sederhana.

Menambahkan shader .vert baru

Di Android Studio:

  1. Pertama, tambahkan shader .frag dan .vert yang baru ke direktori src/main/assets/shaders/.
  2. Klik kanan pada direktori shader
  3. Pilih New -> File
  4. Beri nama shader dengan background_show_depth_map.vert
  5. Tetapkan shader sebagai file teks.

Di file baru, tambahkan kode berikut:

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

Ulangi langkah-langkah di atas untuk membuat shader fragmen di direktori yang sama, dan beri nama background_show_depth_map.frag.

Lalu tambahkan kode berikut ke file baru ini:

src/main/assets/shaders/background_show_depth_map.frag

precision mediump float;
uniform sampler2D u_Depth;
varying vec2 v_TexCoord;
const highp float kMaxDepth = 8000.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;
}

Selanjutnya, update class BackgroundRenderer untuk menggunakan shader baru tersebut, yang terletak di src/main/java/com/google/ar/core/codelab/common/rendering/BackgroundRenderer.java.

Tambahkan jalur file ke shader di bagian atas class:

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

Tambahkan variabel anggota lainnya ke class BackgroundRenderer karena class akan menjalankan dua shader.

// Add to the top of file with the rest of the member variables.
private int depthProgram;
private int depthTextureParam;
private int depthTextureId = -1;
private int depthQuadPositionParam;
private int depthQuadTexCoordParam;

Tambahkan metode baru untuk mengisi kolom berikut:

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

Tambahkan metode ini, yang digunakan untuk membuat dengan shader berikut di setiap bingkai:

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

Menambahkan tombol

Karena Anda sekarang memiliki kemampuan untuk merender peta kedalaman, gunakan peta tersebut. Tambahkan tombol yang akan mengaktifkan dan menonaktifkan rendering ini.

Di bagian atas file DepthCodelabActivity, tambahkan impor agar dapat digunakan tombol:

import android.widget.Button;

Update class untuk menambahkan anggota (logika) boolean yang menunjukkan apakah rendering kedalaman dialihkan: (nonaktif secara default):

private boolean showDepthMap = false;

Selanjutnya, tambahkan tombol yang mengontrol (logika) boolean showDepthMap ke bagian akhir metode 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);
          }
        });

Tambahkan string berikut ke 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>

Tambahkan tombol ini ke bagian bawah layout aplikasi di 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"/>

Tombol kini mengendalikan nilai boolean showDepthMap. Gunakan tanda ini untuk mengendalikan apakah peta kedalaman dirender atau tidak.

Kembali di metode onDrawFrame() di DepthCodelabActivity, tambahkan:

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

Teruskan tekstur kedalaman ke backgroundRenderer dengan menambahkan baris berikut di onSurfaceCreated():

// Add to onSurfaceCreated() after backgroundRenderer.createonGlThread(/*context=*/ this);
backgroundRenderer.createDepthShaders(/*context=*/ this, depthTexture.getDepthTexture());

Sekarang, Anda dapat melihat gambar kedalaman dari setiap bingkai dengan menekan tombol di kanan atas layar.

Menjalankan tanpa dukungan Depth API

Menjalankan dengan dukungan Depth API

[Opsional] Animasi kedalaman yang keren

Aplikasi saat ini menampilkan peta kedalaman secara langsung. Piksel merah mewakili area yang dekat. Piksel biru mewakili area yang jauh.

Ada banyak cara untuk menyampaikan informasi kedalaman. Di bagian ini, Anda akan mengubah shader ke kedalaman denyutan secara berkala, dengan mengubah shader agar hanya menampilkan kedalaman dalam band yang berulang kali menjauh dari kamera.

Mulai dengan menambahkan variabel berikut ke bagian atas background_show_depth_map.frag:

uniform float u_DepthRangeToRenderMm;
const float kDepthWidthToRenderMm = 350.0;
  • Lalu, gunakan nilai berikut untuk memfilter piksel mana yang harus ditutupi dengan nilai kedalaman dalam fungsi 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);

Selanjutnya, update BackgroundRenderer.java untuk mempertahankan parameter shader tersebut. Tambahkan kolom berikut di bagian atas class:

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

Di dalam metode createDepthShaders(), tambahkan yang berikut ini untuk mencocokkan parameter tersebut dengan program shader:

depthRangeToRenderMmParam = GLES20.glGetUniformLocation(depthProgram, "u_DepthRangeToRenderMm");
  • Terakhir, Anda dapat mengontrol rentang ini dari waktu ke waktu dalam metode drawDepth(). Tambahkan kode berikut, yang akan menambahkan rentang ini setiap kali bingkai dibuat.
// 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);

Kini, kedalamannya divisualisasikan sebagai denyutan bergerak yang mengalir melalui scene Anda.

37e2a86b833150f8.gif

Jangan ragu untuk mengubah nilai yang diberikan di sini agar denyutan menjadi lebih lambat, lebih cepat, lebih luas, lebih sempit, dll. Anda juga dapat mencoba berbagai cara baru untuk mengubah shader demi menunjukkan informasi kedalaman.

8. Menggunakan Depth API untuk oklusi (Bagian 4)

Sekarang Anda akan menangani oklusi objek di aplikasi Anda.

Oklusi mengacu pada apa yang terjadi ketika objek virtual tidak dapat dirender sepenuhnya, karena ada objek nyata antara objek virtual dan kamera. Mengelola oklusi sangat penting agar pengalaman AR menjadi imersif.

Merender objek virtual dengan benar secara real time akan meningkatkan realisme dan kredibilitas scene yang diaugmentasi. Untuk contoh lain, harap lihat video kami tentang menggabungkan realita dengan Depth API.

Di bagian ini, Anda akan mengupdate aplikasi untuk menyertakan objek virtual hanya jika kedalaman tersedia.

Menambahkan shader objek baru

Seperti di bagian sebelumnya, Anda akan menambahkan shader baru untuk mendukung informasi kedalaman. Kali ini, Anda dapat menyalin shader object yang sudah ada dan menambahkan fungsi oklusi.

Penting untuk mempertahankan kedua versi shader objek sehingga aplikasi dapat mengambil keputusan runtime terkait apakah akan mendukung kedalaman atau tidak.

Buat salinan file shader object.frag dan object.vert di direktori src/main/assets/shaders.

  • Salin object.vert ke file tujuan src/main/assets/shaders/occlusion_object.vert
  • Salin object.frag ke file tujuan src/main/assets/shaders/occlusion_object.frag

Di dalam occlusion_object.vert, tambahkan variabel berikut di atas main():

varying vec3 v_ScreenSpacePosition;

Tetapkan variabel ini di bagian bawah main():

v_ScreenSpacePosition = gl_Position.xyz / gl_Position.w;

Update occlusion_object.frag dengan menambahkan variabel berikut di atas main() pada bagian atas file:

varying vec3 v_ScreenSpacePosition;

uniform sampler2D u_Depth;
uniform mat3 u_UvTransform;
uniform float u_DepthTolerancePerMm;
uniform float u_OcclusionAlpha;
uniform float u_DepthAspectRatio;
  • Tambahkan fungsi bantuan berikut di atas main() di shader agar informasi kedalaman dapat ditangani dengan lebih mudah:
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=*/7500.0, /*max_depth_mm=*/8000.0);

  float visibility =
    max(max(visibility_occlusion, u_OcclusionAlpha),
      max(visibility_depth_near, visibility_depth_far));

  return visibility;
}

Sekarang, update main() di occlusion_object.frag agar mengenali kedalaman dan terapkan oklusi. Tambahkan baris berikut di bagian bawah file:

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

Setelah memiliki versi shader objek yang baru, Anda dapat mengubah kode perender.

Merender oklusi objek

Selanjutnya, buat salinan class ObjectRenderer, yang ditemukan di src/main/java/com/google/ar/core/codelab/common/rendering/ObjectRenderer.java.

  • Pilih class ObjectRenderer
  • Klik kanan > Salin
  • Pilih folder rendering
  • Klik kanan > Tempel

6c87dcb87da558c1.png

  • Ganti nama class menjadi OcclusionObjectRenderer

f2ffe488c81ad404.png

Kini, class baru yang diganti namanya akan muncul di folder yang sama:

e5bf1c158e26c322.png

Buka OcclusionObjectRenderer.java yang baru dibuat, lalu ubah jalur shader di bagian atas file:

private static final String VERTEX_SHADER_NAME = "shaders/occlusion_object.vert";
private static final String FRAGMENT_SHADER_NAME = "shaders/occlusion_object.frag";
  • Tambahkan variabel anggota terkait kedalaman ini dengan variabel lain di bagian atas class. Variabel di atas menyesuaikan ketajaman batas oklusi.
// 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;

Buat variabel anggota ini dengan nilai default di bagian atas class:

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

Lakukan inisialisasi pada parameter seragam untuk shader di metode 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");
  • Pastikan nilai berikut diupdate setiap kali nilai dibuat dengan mengupdate metode 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);

Tambahkan baris berikut dalam draw() untuk memungkinkan mode campuran dalam rendering sehingga transparansi dapat diterapkan ke objek virtual saat objek virtual dioklusi:

// 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);
  • Tambahkan metode berikut sehingga pemanggil OcclusionObjectRenderer dapat menyediakan informasi kedalaman:
// 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;
}

Mengontrol oklusi objek

Setelah memiliki OcclusionObjectRenderer baru, Anda dapat menambahkannya ke DepthCodelabActivity dan memilih waktu serta cara menerapkan rendering oklusi.

Aktifkan logika ini dengan menambahkan instance OcclusionObjectRenderer ke aktivitas sehingga ObjectRenderer dan OcclusionObjectRenderer menjadi anggota 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();
  • Selanjutnya, Anda dapat mengontrol kapan occludedVirtualObject digunakan berdasarkan apakah perangkat yang digunakan mendukung Depth API. Tambahkan baris ini di dalam metode onSurfaceCreated, di bawah, tempat virtualObject dikonfigurasi:
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);
}

Pada perangkat yang tidak mendukung kedalaman, instance occludedVirtualObject akan dibuat tetapi tidak digunakan. Pada ponsel yang memiliki kedalaman, inisialisasi dilakukan pada kedua versi, dan keputusan run-time diambil untuk memutuskan versi perender mana yang digunakan saat membuat.

Di dalam metode onDrawFrame(), temukan kode yang sudah ada:

virtualObject.updateModelMatrix(anchorMatrix, scaleFactor);
virtualObject.draw(viewmtx, projmtx, colorCorrectionRgba, OBJECT_COLOR);

Ganti kode ini dengan kode berikut:

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

Terakhir, pastikan bahwa gambar kedalaman dipetakan dengan benar ke rendering output. Karena gambar kedalaman memiliki resolusi yang berbeda, dan kemungkinan memiliki rasio lebar tinggi yang berbeda dengan layar, koordinasi tekstur mungkin berbeda di antara koordinat tekstur dan gambar kamera.

  • Tambahkan metode helper getTextureTransformMatrix() ke bagian bawah file. Metode ini menampilkan matriks transformasi yang, saat diterapkan, membuat UV ruang layar cocok dengan koordinat tekstur segi empat yang digunakan untuk merender feed kamera. Metode tersebut juga mempertimbangkan orientasi perangkat.
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() memerlukan impor berikut di bagian atas file:

import com.google.ar.core.Coordinates2d;

Anda ingin menghitung perubahan di antara koordinat tekstur tersebut setiap kali tekstur layar berubah (seperti jika layar berputar). Fungsi ini dibatasi.

Tambahkan tanda berikut di bagian atas file:

// Add this member at the top of the file.
private boolean calculateUVTransform = true;
  • Di dalam onDrawFrame(), periksa apakah perubahan yang disimpan harus dihitung kembali setelah bingkai dan kamera dibuat:
// Add these lines inside onDrawFrame() after frame.getCamera().
if (frame.hasDisplayGeometryChanged() || calculateUVTransform) {
  calculateUVTransform = false;
  float[] transform = getTextureTransformMatrix(frame);
  occludedVirtualObject.setUvTransformMatrix(transform);
}

Dengan perubahan yang diterapkan tersebut, sekarang Anda dapat menjalankan aplikasi dengan oklusi objek virtual.

Aplikasi Anda kini akan berjalan dengan lancar di semua ponsel, dan otomatis menggunakan kedalaman untuk oklusi jika perangkat mendukung.

Menjalankan aplikasi dengan dukungan Depth API

Menjalankan aplikasi tanpa dukungan Depth API

9. [Opsional] Meningkatkan kualitas oklusi

Metode untuk oklusi berbasis kedalaman, yang diterapkan di atas, memberikan oklusi dengan batasan yang detail. Saat kamera bergerak menjauh dari objek, pengukuran kedalaman kemungkinan menjadi kurang akurat, yang dapat menyebabkan artefak visual.

Kita dapat mengurangi masalah dengan menambahkan blur tambahan ke pengujian oklusi, yang menghasilkan tepi yang lebih mulus ke objek virtual yang tersembunyi.

occlusion_object.frag

Tambahkan variabel seragam berikut di bagian atas occlusion_object.frag:

uniform float u_OcclusionBlurAmount;

Tambahkan fungsi helper ini tepat di atas main() pada shader, yang menerapkan blur kernel ke sampel oklusi:

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

Ganti baris yang sudah ada ini di main():

gl_FragColor.a *= GetVisibility(depth_uvs, asset_depth_mm);

dengan baris ini:

gl_FragColor.a *= GetBlurredVisibilityAroundUV(depth_uvs, asset_depth_mm);

Update perender untuk memanfaatkan fungsi shader baru ini.

OcclusionObjectRenderer.java

Tambahkan variabel anggota di bagian atas class:

private int occlusionBlurUniform;
private final float occlusionsBlur = 0.01f;

Tambahkan yang berikut di dalam metode createOnGlThread:

// Add alongside the other calls to GLES20.glGetUniformLocation.
occlusionBlurUniform = GLES20.glGetUniformLocation(program, "u_OcclusionBlurAmount");

Tambahkan yang berikut di dalam metode draw:

// Add alongside the other calls to GLES20.glUniform1f.
GLES20.glUniform1f(occlusionBlurUniform, occlusionsBlur);

Perbandingan Visual

Batas oklusi kini akan lebih mulus dengan perubahan tersebut.

10. Build-Jalankan-Uji

Mem-build dan Menjalankan aplikasi

  1. Colokkan perangkat Android melalui USB.
  2. Pilih File > Build dan Jalankan.
  3. Simpan Sebagai: ARCodeLab.apk.
  4. Tunggu aplikasi mem-build dan men-deploy ke perangkat Anda.

Saat pertama kali Anda mencoba men-deploy aplikasi ke perangkat:

  • Anda harus Mengizinkan proses debug USB di perangkat. Pilih OK untuk melanjutkan.
  • Anda akan ditanya apakah aplikasi memiliki izin untuk menggunakan kamera perangkat. Izinkan akses untuk melanjutkan penggunaan fungsi AR.

Menguji Aplikasi Anda

Saat menjalankan aplikasi, Anda dapat menguji perilaku dasarnya dengan memegang perangkat, menggerakkan perangkat, dan memindai area secara perlahan. Cobalah mengumpulkan data setidaknya selama 10 detik dan pindai area dari beberapa arah sebelum melanjutkan ke langkah berikutnya.

Pemecahan masalah

Menyiapkan perangkat Android untuk pengembangan

  1. Hubungkan perangkat Anda ke mesin pengembangan dengan kabel USB. Jika mengembangkan menggunakan Windows, Anda mungkin harus menginstal driver USB yang sesuai dengan perangkat.
  2. Jalankan langkah-langkah berikut untuk mengaktifkan Proses debug USB di jendela Opsi developer:
  3. Buka aplikasi Setelan.
  4. Jika perangkat Anda menggunakan Android v8.0 atau lebih tinggi, pilih Sistem. Jika tidak, lanjutkan ke langkah berikutnya.
  5. Scroll ke bagian bawah, lalu pilih Tentang ponsel.
  6. Scroll ke bagian bawah, lalu tap Nomor build sebanyak tujuh kali.
  7. Kembali ke layar sebelumnya, scroll ke bagian bawah, lalu ketuk Opsi developer.
  8. Di jendela Opsi developer, scroll ke bawah untuk menemukan dan mengaktifkan Proses debug USB.

Anda dapat menemukan informasi yang lebih mendetail tentang proses ini di situs developer Android Google.

1480e83e227b94f1.png

Jika mengalami kegagalan build terkait dengan lisensi (Gagal menginstal paket Android SDK berikut karena beberapa lisensi belum disetujui), Anda dapat menggunakan perintah berikut untuk meninjau dan menyetujui lisensi berikut:

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

11. Selamat

Selamat, Anda berhasil mem-build dan menjalankan aplikasi Augmented Reality berbasis kedalaman pertama Anda menggunakan ARCore Depth API Google.

Pertanyaan Umum (FAQ)