Hit Test API memungkinkan Anda memosisikan item virtual dalam tampilan dunia nyata.
WebXR Device API diluncurkan musim gugur lalu di Chrome 79. Seperti yang dinyatakan saat itu, penerapan API di Chrome masih 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, sebuah cara untuk menempatkan objek virtual dalam tampilan kamera dunia nyata.
Dalam artikel ini, saya mengasumsikan bahwa Anda sudah tahu cara membuat sesi augmented reality dan cara menjalankan loop frame. Jika Anda belum memahami konsep ini, Anda harus membaca artikel sebelumnya dalam seri ini.
- Virtual reality hadir di web
- Virtual reality hadir di web, bagian II
- Web AR: Anda mungkin sudah tahu cara menggunakannya
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 dunia nyata.
Saat pertama kali membuka aplikasi, Anda akan melihat lingkaran biru dengan titik di tengahnya. Titik adalah persimpangan antara garis imajiner dari perangkat Anda ke titik di lingkungan. Peta ini bergerak saat Anda menggerakkan perangkat. Saat menemukan titik persimpangan, titik tersebut akan menempel pada permukaan seperti lantai, meja, dan dinding. Hal ini dilakukan karena pengujian hit memberikan posisi dan orientasi titik persimpangan, tetapi tidak ada informasi tentang permukaan itu sendiri.
Lingkaran ini disebut retikula, 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. Bidik terus bergerak bersama perangkat Anda.
Membuat retikel
Anda harus membuat gambar retikula sendiri karena tidak disediakan oleh
browser atau API. Metode pemuatan dan penggambarannya khusus untuk framework.
Jika Anda tidak menggambarnya secara langsung menggunakan WebGL atau WebGL2, lihat dokumentasi framework Anda. Oleh karena itu, saya tidak akan membahas secara mendetail cara retikula digambar dalam contoh. Di bawah ini, saya hanya menampilkan satu baris kode tersebut karena satu alasan: agar dalam contoh kode berikutnya, Anda akan tahu apa yang saya maksud saat saya 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
});
Bergabung ke sesi
Dalam artikel sebelumnya, saya telah menyajikan kode untuk memasuki sesi XR. Saya telah menunjukkan
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 retikel. Saya 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 disorot memanggil XRSession.requestReferenceSpace() dua kali. Awalnya saya merasa ini membingungkan. Saya bertanya mengapa kode uji hit tidak
meminta frame animasi (memulai loop frame) dan mengapa loop frame
tampaknya tidak melibatkan uji hit. Sumber kebingungan adalah
kesalahpahaman tentang ruang referensi. Ruang referensi menyatakan hubungan antara asal dan dunia.
Untuk memahami fungsi kode ini, bayangkan Anda melihat sampel ini menggunakan rig mandiri, dan Anda memiliki headset dan pengontrol. Untuk mengukur jarak dari pengontrol, Anda akan menggunakan kerangka 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. Namun, saya punya masalah. Apa yang saya gambar harus stabil terkait 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 pengujian hit, saya menggunakan ruang referensi viewer, yang didasarkan pada pose perangkat pada saat pengujian hit. Label 'pelihat' 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 uji hit
yang akan saya gunakan saat menggambar.
Menjalankan loop frame
Callback requestAnimationFrame() juga mendapatkan kode baru untuk menangani pengujian hit.
Saat Anda menggerakkan perangkat, reticle harus bergerak bersamanya saat mencoba menemukan permukaan. Untuk menciptakan ilusi gerakan, gambar ulang retikula di setiap frame.
Namun, jangan tampilkan retikel jika uji hit gagal. Jadi, untuk retikula 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 dalam AR, saya perlu mengetahui lokasi penonton dan arah pandang mereka. 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 menelepon getHitTestResults(). Fungsi ini menggunakan hitTestSource sebagai argumen
dan menampilkan array instance HitTestResult. Hit test dapat menemukan beberapa permukaan. Yang pertama dalam array adalah yang paling dekat dengan kamera.
Sebagian besar waktu Anda akan menggunakannya, tetapi array ditampilkan untuk kasus penggunaan lanjutan. Misalnya, bayangkan kamera Anda diarahkan ke sebuah kotak di atas meja di lantai. Ada kemungkinan bahwa uji hit akan menampilkan ketiga permukaan dalam
array. Dalam kebanyakan kasus, itu akan menjadi kotak yang saya perhatikan. Jika panjang array yang ditampilkan adalah 0, dengan kata lain, jika tidak ada uji kecocokan 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 uji kecocokan. Proses dasarnya adalah sebagai berikut. Dapatkan
pose dari hasil uji kecocokan, ubah (pindahkan) gambar retikula ke posisi uji
kecocokan, lalu tetapkan properti visible ke benar (true). Pose mewakili
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 telah menambahkan pengendali peristiwa
select ke sesi. (Lihat di atas.)
Hal penting dalam langkah ini adalah mengetahui tempat untuk meletakkannya. Karena retikel yang bergerak memberi Anda sumber pengujian hit yang konstan, cara paling sederhana untuk menempatkan objek adalah dengan menggambarnya di lokasi retikel pada pengujian hit 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 memahami hal ini adalah dengan menelusuri kode contoh atau mencoba codelab. Semoga saya telah memberikan cukup banyak informasi latar belakang agar Anda dapat memahami keduanya.
Kami belum selesai membangun API web imersif, masih banyak yang harus dilakukan. Kami akan memublikasikan artikel baru di sini seiring dengan progres kami.
Foto oleh Daniel Frank di Unsplash