ARCore Depth

ARCore adalah platform untuk mem-build aplikasi Augmented Reality (AR) di perangkat seluler. ARCore Depth API Google memberikan akses ke gambar kedalaman yang mencocokkan tiap bingkai yang diberikan oleh Sesi ARCore. Setiap piksel memberikan pengukuran jarak dari kamera ke lingkungan, yang digunakan oleh codelab ini untuk memberikan realisme yang ditingkatkan untuk aplikasi AR.

Depth API hanya didukung pada subkumpulan perangkat yang berkemampuan ARCore. Harap lihat daftar ini untuk mengetahui ponsel mana saja yang mendukung panggilan kedalaman. Depth API hanya tersedia di Android.

Codelab ini memandu Anda menjalani proses mem-build aplikasi sederhana yang berkemampuan AR dan menggunakan gambar kedalaman untuk menjalankan penutupan aset virtual di belakang permukaan dunia nyata serta memvisualisasikan geometri dunia yang terdeteksi ini.

Yang akan Anda build

d9bd5136c54ce47a.gif

Dalam codelab ini, 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 berikut:

  1. Memeriksa dukungan Depth API di ponsel
  2. Mengambil gambar kedalaman untuk setiap bingkai
  3. Beberapa cara untuk memvisualisasi informasi kedalaman (lihat animasi di atas)
  4. Cara menggunakan kedalaman untuk meningkatkan realisme aplikasi dengan penutupan.
  5. Cara menangani dengan baik ponsel yang tidak mendukung Depth API.

Catatan: Jika ada masalah selama menjalani proses, langsung lihat bagian terakhir untuk mengetahui tips pemecahan masalah.

Anda membutuhkan hardware dan software tertentu untuk menyelesaikan codelab ini.

Persyaratan Hardware

Persyaratan Software

Menyiapkan mesin pengembangan

Hubungkan perangkat ARCore ke komputer melalui kabel USB. Pastikan perangkat memungkinkan proses debug USB. 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. Pastikan bahwa Anda hanya melihat satu perangkat sebelum melanjutkan.

Mendownload dan menginstal Kode

Anda dapat meng-clone repositori:

git clone https://github.com/googlecodelabs/arcore-depth

Atau mendownload file ZIP lalu mengekstraknya:

Download ZIP

Buka Android Studio. Klik Open an existing Android Studio project. Lalu, buka direktori tempat Anda mengekstrak file ZIP yang didownload di atas, lalu klik dua kali pada 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.

Klik Run > Run... > ‘part0_work'. Di dialog Select Deployment Target yang ditampilkan, perangkat Anda seharusnya dicantumkan di bawah Connected Devices. Pilih perangkat lalu klik OK. Android Studio akan mem-build aplikasi awal dan menjalankannya di perangkat Anda.

Saat Anda menjalankan aplikasi untuk pertama kalinya, aplikasi akan meminta izin KAMERA. Ketuk Izinkan untuk melanjutkan.

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 sangatlah sederhana dan belum tahu banyak tentang geometri adegan 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.

5e8f5fe9098d316e.png 76f41b692224801b.png 3f320c851d1903d.png

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

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

Tambahkan anggota pribadi ke DepthCodelabActivity yang berfungsi sebagai tanda yang menyimpan apakah perangkat saat ini 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 mengerti apakah dapat menggunakan fitur berbasis kedalaman atau tidak.

Kita harus memberi tahu pengguna apakah kedalaman digunakan untuk sesi ini atau tidak. Kita dapat menambahkan pesan lain ke Snackbar yang ditampilkan 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]";

Dan di dalam onDrawFrame(), kita 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 ini dijalankan di perangkat yang tidak mendukung kedalaman, pesan yang baru kita tambahkan akan muncul di bagian bawah:

feb1c9f42f3cf396.png

Selanjutnya, kita mengupdate aplikasi untuk memanggil Depth API dan mengambil gambar kedalaman di setiap bingkai

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

Codelab ini menggunakan gambar kedalaman tersebut untuk menyediakan rendering dan visualisasi di aplikasi. Langkah pertama adalah mengambil gambar kedalaman untuk setiap bingkai dan mengikat tekstur tersebut untuk digunakan oleh GPU.

Pertama, kita tambahkan class baru ke project. DepthTextureHandler bertanggung jawab dalam 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;
  }
}

Kedua, kita dapat menambahkan instance kelas ini ke DepthCodelabActivity, yang memastikan bahwa kita 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, kita akan mengupdate 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, kita ingin mengisi tekstur ini pada setiap bingkai dengan gambar kedalaman terbaru, yang dapat dilakukan dengan memanggil metode update() yang kita buat di atas pada bingkai terbaru yang diambil dari session. Karena dukungan kedalaman bersifat opsional untuk aplikasi ini, panggilan ini hanya dapat dilakukan jika kita menggunakan kedalaman.

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

Sekarang kita memiliki gambar kedalaman yang diupdate setiap bingkai. Gambar siap digunakan oleh shader. Namun, perilaku aplikasi masih belum berubah. Mari kita gunakan gambar kedalaman untuk meningkatkan aplikasi.

Kita memiliki gambar kedalaman yang dapat dijadikan eksperimen; mari kita lihat seperti apa gambarnya. Di bagian ini, kita 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.

Pertama, tambahkan shader .frag dan .vert yang baru ke direktori src/main/assets/shaders/.

Menambahkan shader .vert yang baru

Di Android Studio:

  1. Klik kanan pada direktori shader
  2. Pilih New -> File
  3. Beri nama shader dengan background_show_depth_map.vert
  4. 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 yang sama di atas untuk membuat shader fragmen di direktori yang sama, yang bernama 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 lain 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 kita sekarang memiliki kemampuan untuk merender peta kedalaman, mari kita gunakan. Melihat DepthCodelabActivity, kita dapat menambahkan tombol yang mengaktifkan/menonaktifkan rendering ini.

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

import android.widget.Button;

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

private boolean showDepthMap = false;

Selanjutnya, tambahkan tombol yang mengendalikan 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 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, kita 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, sedangkan piksel biru mewakili area yang jauh.

Ada banyak cara untuk menyampaikan informasi kedalaman. Dalam subbagian ini, kita mengubah shader untuk "mendenyut" kedalaman 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, kita dapat mengendalikan rentang ini dari waktu ke waktu dalam metode drawDepth(). Tambahkan kode berikut, yang 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 adegan 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.

Oklusi adalah saat objek virtual tidak dirender sepenuhnya karena ada permukaan dunia nyata di antara objek virtual dan kamera.

Kemampuan merender objek virtual secara tepat di lokasi asal, akan meningkatkan realisme dan kredibilitas adegan yang diperkaya. Untuk contoh lain, harap lihat video kami tentang menyatukan realita dengan Depth API.

Di bagian ini, kita mengupdate aplikasi untuk menyertakan objek virtual jika kedalaman tersedia.

Menambahkan shader objek baru

Seperti di bagian sebelumnya, kita akan menambahkan shader baru untuk mendukung informasi kedalaman. Kali ini, kita akan 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.

  1. Salin object.vert ke file tujuan src/main/assets/shaders/occlusion_object.vert
  2. 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 masalah 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);

Karena kita memiliki versi shader objek yang baru, kita dapat mengubah kode perender.

Rendering 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 > "Copy" Pilih folder rendering, Klik Kanan > "Paste"

Ganti nama class menjadi OcclusionObjectRenderer

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

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 yang lain di bagian atas class:

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

Variabel di atas menyesuaikan ketajaman batas oklusi. Mulai dengan membuat variabel anggota berikut 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() untuk menyertakan:

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

Mengendalikan oklusi objek

Karena kita memiliki OcclusionObjectRenderer yang baru, kita dapat menambahkannya ke DepthCodelabActivity, lalu pilih waktu dan cara menerapkan rendering oklusi.

Logika ini diaktifkan 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, kita dapat mengendalikan kapan occludedVirtualObject digunakan berdasarkan apakah perangkat saat ini mendukung Depth API. Tambahkan baris berikut di dalam metode onSurfaceCreated, di bawah adalah tempat virtualObject dikonfigurasikan:

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

Kami 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 kita dapat menjalankan aplikasi dengan oklusi objek virtual! Aplikasi 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

[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 di main():

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

dengan baris:

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.

Mem-build dan Menjalankan aplikasi

Ikuti langkah-langkah berikut untuk 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

di perangkat. Pilih Oke untuk melanjutkan.

Saat pertama kali menjalankan aplikasi di perangkat, Anda akan ditanya apakah aplikasi memiliki izin untuk menggunakan kamera perangkat. Anda harus mengizinkan akses untuk melanjutkan penggunaan fungsi AR.

Menguji Aplikasi

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.

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

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:
  • Buka aplikasi Setelan.
  • Jika perangkat Anda menggunakan Android v8.0 atau lebih tinggi, pilih Sistem. Jika tidak, lanjutkan ke langkah berikutnya.
  • Scroll ke bagian bawah, lalu pilih Tentang ponsel.
  • Scroll ke bagian bawah, lalu ketuk Nomor build tujuh kali.
  • Kembali ke layar sebelumnya, scroll ke bagian bawah, lalu ketuk Opsi developer.
  • 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

Pertanyaan Umum (FAQ)