Memosisikan objek virtual dalam tampilan dunia nyata

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

Joe Medley
Joe Medley

WebXR Device API dikirimkan pada musim gugur yang lalu di Chrome 79. Seperti yang telah dinyatakan, implementasi API Chrome sedang dalam proses. Chrome dengan senang hati mengumumkan bahwa beberapa pekerjaan telah selesai. Dua fitur baru telah hadir di Chrome 81:

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

Dalam artikel ini, saya berasumsi bahwa Anda sudah mengetahui cara membuat sesi augmented reality dan mengetahui cara menjalankan loop frame. Jika Anda tidak familier dengan konsep ini, Anda harus membaca artikel sebelumnya.

Contoh sesi AR imersif

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

Saat pertama kali membuka aplikasi, Anda akan melihat lingkaran biru dengan titik di tengahnya. Titik adalah titik potong antara garis imajiner dari perangkat Anda ke titik di lingkungan. Bergerak saat Anda menggerakkan perangkat. Saat menemukan titik titik potong, akan tampak pas dengan permukaan seperti lantai, permukaan meja, dan dinding. Hal ini dilakukan karena hit test memberikan posisi dan orientasi titik persimpangan, tetapi tidak dengan 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. Reticle akan terus bergerak dengan perangkat Anda.

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

Membuat reticle

Anda harus membuat image reticle sendiri karena tidak disediakan oleh browser atau API. Metode memuat dan menggambarnya adalah khusus framework. Jika Anda tidak menggambarnya secara langsung menggunakan WebGL atau WebGL2, baca dokumentasi framework Anda. Karena alasan ini, saya tidak akan menjelaskan cara menggambar reticle dalam sampel secara detail. Di bawah ini saya menampilkan satu barisnya hanya untuk satu alasan: sehingga dalam contoh kode selanjutnya, Anda akan tahu apa yang saya 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 ini.

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

Memasuki sesi

Di artikel sebelumnya, saya telah menyajikan kode untuk memasuki sesi XR. Saya telah menunjukkan versi ini di bawah ini dengan beberapa tambahan. Pertama, saya telah menambahkan pemroses peristiwa select. Saat pengguna mengetuk layar, bunga akan ditempatkan dalam tampilan kamera berdasarkan pose reticle. Saya akan menjelaskan pemroses peristiwa itu 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 pikir 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 dari kebingungannya adalah kesalahpahaman tentang ruang referensi. Ruang referensi mengekspresikan hubungan antara tempat asal dan dunia.

Untuk memahami fungsi kode ini, anggaplah Anda melihat contoh ini menggunakan rig mandiri, dan Anda memiliki headset dan pengontrol. Untuk mengukur jarak dari pengontrol, Anda harus menggunakan frame referensi yang berpusat pada pengontrol. Tetapi 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 memiliki masalah. Apa yang saya gambar harus stabil dalam kaitannya dengan lingkungan, tetapi 'pengontrol' yang saya gunakan untuk menggambar dapat bergerak.

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

Untuk hit test, saya menggunakan ruang referensi viewer, yang didasarkan pada pose perangkat pada saat hit test. Label 'viewer' agak membingungkan dalam konteks ini karena saya berbicara tentang {i>controller<i}. Masuk akal jika Anda menganggap pengontrol sebagai penampil elektronik. Setelah mendapatkannya, 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 test.

Saat Anda memindahkan perangkat, reticle harus bergerak bersamanya saat mencoba menemukan platform. 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 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 tahu di mana penonton berada dan di mana mereka melihatnya. Jadi, saya menguji bahwa 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 memanggil getHitTestResults(). Fungsi ini mengambil hitTestSource sebagai argumen dan menampilkan array instance HitTestResult. Hit test dapat menemukan beberapa platform. Yang pertama dalam array adalah yang paling dekat dengan kamera. Biasanya, Anda akan menggunakannya, tetapi array ditampilkan untuk kasus penggunaan lanjutan. Misalnya, bayangkan kamera Anda diarahkan ke sebuah kotak di atas meja di atas lantai. Ada kemungkinan bahwa hit test akan menampilkan ketiga platform dalam array. Dalam kebanyakan kasus, ini akan menjadi kotak yang penting bagi saya. Jika panjang array yang ditampilkan adalah 0, dengan kata lain, jika tidak ada hit test yang ditampilkan, lanjutkan ke depan. 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 {i>hit test<i}. Proses dasarnya adalah ini. Dapatkan pose dari hasil hit test, ubah (pindahkan) gambar reticle ke posisi hit test, lalu tetapkan properti visible ke true. Pose mewakili pose dari suatu titik pada 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

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

Hal yang penting dalam langkah ini adalah mengetahui di mana harus meletakkannya. Karena reticle yang bergerak memberi Anda sumber hit test yang konstan, cara paling sederhana 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 mendapatkan handle dalam hal ini adalah dengan mengikuti kode contoh atau mencoba codelab. Saya harap saya sudah memberi Anda latar belakang yang cukup untuk memahami keduanya.

Kita belum selesai membangun API web yang imersif, bukan dengan usaha yang panjang. Kami akan menerbitkan artikel baru di sini seiring dengan kemajuan yang kami buat.

Foto oleh Daniel Frank di Unsplash