Memosisikan objek virtual dalam tampilan dunia nyata

Hit Test API memungkinkan Anda memosisikan item virtual dalam tampilan dunia nyata.

Joe Medley
Joe Medley

WebXR Device API diluncurkan musim gugur lalu di Chrome 79. Seperti yang dinyatakan saat itu, penerapan API Chrome sedang dalam proses. Chrome dengan senang hati mengumumkan bahwa beberapa pekerjaan telah selesai. Di Chrome 81, dua fitur baru telah hadir:

Artikel ini membahas WebXR Hit Test API, yaitu cara menempatkan objek virtual dalam tampilan kamera dunia nyata.

Dalam artikel ini, kami mengasumsikan bahwa Anda sudah mengetahui cara membuat sesi augmented reality dan cara menjalankan loop frame. Jika Anda belum memahami konsep ini, sebaiknya baca artikel sebelumnya dalam seri ini.

Contoh sesi AR imersif

Kode dalam artikel ini didasarkan pada, tetapi tidak identik dengan, kode yang ditemukan dalam contoh Hit Test Immersive Web Working Group (demo, sumber). Contoh ini memungkinkan Anda menempatkan bunga matahari virtual di permukaan di dunia nyata.

Saat pertama kali membuka aplikasi, Anda akan melihat lingkaran biru dengan titik di tengah. Titik tersebut adalah persimpangan antara garis imajiner dari perangkat Anda ke titik di lingkungan. Tampilan akan bergerak saat Anda menggerakkan perangkat. Saat menemukan titik persimpangan, titik tersebut akan tampak menempel pada permukaan seperti lantai, permukaan meja, dan dinding. Hal ini dilakukan karena hit testing memberikan posisi dan orientasi titik persimpangan, tetapi tidak memberikan informasi tentang permukaan itu sendiri.

Lingkaran ini disebut reticle, yang merupakan gambar sementara yang membantu menempatkan objek dalam augmented reality. Jika Anda mengetuk layar, bunga matahari akan ditempatkan di permukaan pada lokasi reticle dan orientasi titik reticle, di mana pun Anda mengetuk layar. Retikel akan terus bergerak mengikuti perangkat Anda.

Retikel yang dirender di dinding, Longgar, atau Ketat, bergantung pada konteksnya
Reticle adalah gambar sementara yang membantu menempatkan objek dalam augmented reality.

Membuat reticle

Anda harus membuat gambar reticle sendiri karena tidak disediakan oleh browser atau API. Metode pemuatan dan penggambaran bergantung pada framework. Jika Anda tidak menggambarnya secara langsung menggunakan WebGL atau WebGL2, lihat dokumentasi framework Anda. Oleh karena itu, kami tidak akan membahas secara mendetail cara reticle digambar dalam contoh. Di bawah ini, kami menampilkan satu baris kode hanya karena satu alasan: agar dalam contoh kode berikutnya, Anda akan tahu apa yang kami maksud saat menggunakan variabel reticle.

let reticle = new Gltf2Node({url: 'media/gltf/reticle/reticle.gltf'});

Meminta sesi

Saat meminta sesi, Anda harus meminta 'hit-test' dalam array requiredFeatures seperti yang ditunjukkan di bawah.

navigator.xr.requestSession('immersive-ar', {
  requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
  // Do something with the session
});

Memasuki sesi

Dalam artikel sebelumnya, kami telah menyajikan kode untuk memasuki sesi XR. Kami telah menampilkan versi ini di bawah dengan beberapa tambahan. Pertama, saya telah menambahkan pemroses peristiwa select. Saat pengguna mengetuk layar, bunga akan ditempatkan di tampilan kamera berdasarkan pose reticle. Kami akan menjelaskan pemroses peristiwa tersebut nanti.

function onSessionStarted(xrSession) {
  xrSession.addEventListener('end', onSessionEnded);
  xrSession.addEventListener('select', onSelect);

  let canvas = document.createElement('canvas');
  gl = canvas.getContext('webgl', { xrCompatible: true });

  xrSession.updateRenderState({
    baseLayer: new XRWebGLLayer(session, gl)
  });

  xrSession.requestReferenceSpace('viewer').then((refSpace) => {
    xrViewerSpace = refSpace;
    xrSession.requestHitTestSource({ space: xrViewerSpace })
    .then((hitTestSource) => {
      xrHitTestSource = hitTestSource;
    });
  });

  xrSession.requestReferenceSpace('local').then((refSpace) => {
    xrRefSpace = refSpace;
    xrSession.requestAnimationFrame(onXRFrame);
  });
}

Beberapa ruang referensi

Perhatikan bahwa kode yang ditandai memanggil XRSession.requestReferenceSpace() dua kali. Awalnya, saya merasa hal ini membingungkan. Saya bertanya mengapa kode hit test tidak meminta frame animasi (memulai loop frame) dan mengapa loop frame tampaknya tidak melibatkan hit test. Sumber kebingungan tersebut adalah kesalahpahaman tentang ruang referensi. Ruang referensi mengekspresikan hubungan antara titik asal dan dunia.

Untuk memahami fungsi kode ini, bayangkan Anda melihat contoh ini menggunakan rig mandiri, dan Anda memiliki headset serta pengontrol. Untuk mengukur jarak dari pengontrol, Anda akan menggunakan frame referensi yang berpusat pada pengontrol. Namun, untuk menggambar sesuatu ke layar, Anda akan menggunakan koordinat yang berpusat pada pengguna.

Dalam contoh ini, penampil dan pengontrol adalah perangkat yang sama. Tapi saya punya masalah. Apa yang saya gambar harus stabil sehubungan dengan lingkungan, tetapi 'pengontrol' yang saya gunakan untuk menggambar bergerak.

Untuk menggambar gambar, saya menggunakan ruang referensi local, yang memberi saya stabilitas dalam hal lingkungan. Setelah mendapatkan ini, saya memulai loop frame dengan memanggil requestAnimationFrame().

Untuk hit testing, saya menggunakan ruang referensi viewer, yang didasarkan pada pose perangkat pada saat hit testing. Label 'viewer' agak membingungkan dalam konteks ini karena saya sedang membahas pengontrol. Hal ini masuk akal jika Anda menganggap pengontrol sebagai penampil elektronik. Setelah mendapatkan ini, saya memanggil xrSession.requestHitTestSource(), yang membuat sumber data hit test yang akan saya gunakan saat menggambar.

Menjalankan loop frame

Callback requestAnimationFrame() juga mendapatkan kode baru untuk menangani hit testing.

Saat Anda menggerakkan perangkat, reticle harus bergerak bersamanya saat mencoba menemukan permukaan. Untuk menciptakan ilusi gerakan, gambar ulang reticle di setiap frame. Namun, jangan tampilkan reticle jika hit test gagal. Jadi, untuk reticle yang saya buat sebelumnya, saya menetapkan properti visible-nya ke false.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);

  reticle.visible = false;

  // Reminder: the hitTestSource was acquired during onSessionStart()
  if (xrHitTestSource && xrViewerPose) {
    let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
    if (hitTestResults.length > 0) {
      let pose = hitTestResults[0].getPose(xrRefSpace);
      reticle.visible = true;
      reticle.matrix = pose.transform.matrix;
    }
  }

  // Draw to the screen
}

Untuk menggambar apa pun di AR, saya perlu mengetahui di mana penampil berada dan ke mana mereka melihat. Jadi, saya menguji apakah hitTestSource dan xrViewerPose masih valid.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);

  reticle.visible = false;

  // Reminder: the hitTestSource was acquired during onSessionStart()
  if (xrHitTestSource && xrViewerPose) {
    let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
    if (hitTestResults.length > 0) {
      let pose = hitTestResults[0].getPose(xrRefSpace);
      reticle.visible = true;
      reticle.matrix = pose.transform.matrix;
    }
  }

  // Draw to the screen
}

Sekarang saya panggil getHitTestResults(). Fungsi ini menggunakan hitTestSource sebagai argumen dan menampilkan array instance HitTestResult. Hit test dapat menemukan beberapa permukaan. Objek pertama dalam array adalah objek yang paling dekat dengan kamera. Biasanya Anda akan menggunakannya, tetapi array ditampilkan untuk kasus penggunaan lanjutan. Misalnya, bayangkan kamera Anda diarahkan ke kotak di atas meja di lantai. Ada kemungkinan hit test akan menampilkan ketiga permukaan dalam array. Biasanya, kotak ini adalah kotak yang saya minati. Jika panjang array yang ditampilkan adalah 0, dengan kata lain, jika tidak ada hit test yang ditampilkan, lanjutkan. Coba lagi di frame berikutnya.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);

  reticle.visible = false;

  // Reminder: the hitTestSource was acquired during onSessionStart()
  if (xrHitTestSource && xrViewerPose) {
    let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
    if (hitTestResults.length > 0) {
      let pose = hitTestResults[0].getPose(xrRefSpace);
      reticle.visible = true;
      reticle.matrix = pose.transform.matrix;
    }
  }

  // Draw to the screen
}

Terakhir, saya perlu memproses hasil hit test. Proses dasarnya adalah sebagai berikut. Dapatkan pose dari hasil hit test, ubah (pindahkan) gambar reticle ke posisi hit test, lalu tetapkan properti visible-nya ke true. Pose ini merepresentasikan pose titik di permukaan.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);

  reticle.visible = false;

  // Reminder: the hitTestSource was acquired during onSessionStart()
  if (xrHitTestSource && xrViewerPose) {
    let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
    if (hitTestResults.length > 0) {
      let pose = hitTestResults[0].getPose(xrRefSpace);
      reticle.matrix = pose.transform.matrix;
      reticle.visible = true;

    }
  }

  // Draw to the screen
}

Menempatkan objek

Objek ditempatkan di AR saat pengguna mengetuk layar. Saya sudah menambahkan pengendali peristiwa select ke sesi. (Lihat di atas.)

Hal penting dalam langkah ini adalah mengetahui tempat untuk meletakkannya. Karena reticle yang bergerak memberi Anda sumber hit test yang konstan, cara termudah untuk menempatkan objek adalah dengan menggambarnya di lokasi reticle pada hit test terakhir.

function onSelect(event) {
  if (reticle.visible) {
    // The reticle should already be positioned at the latest hit point,
    // so we can just use its matrix to save an unnecessary call to
    // event.frame.getHitTestResults.
    addARObjectAt(reticle.matrix);
  }
}

Kesimpulan

Cara terbaik untuk memahaminya adalah dengan mempelajari kode contoh atau mencoba codelab. Semoga saya telah memberi Anda cukup latar belakang untuk memahami keduanya.

Kami belum selesai membangun API web imersif, masih jauh dari selesai. Kami akan memublikasikan artikel baru di sini seiring dengan kemajuan yang kami buat.