Hit Test API memungkinkan Anda memosisikan item virtual dalam tampilan dunia nyata.
WebXR Device API dikirimkan pada musim gugur lalu di Chrome 79. Seperti yang dinyatakan saat itu, penerapan API oleh 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, yaitu cara menempatkan objek virtual dalam tampilan kamera dunia nyata.
Dalam artikel ini, saya mengasumsikan bahwa Anda sudah tahu cara membuat sesi augmented reality dan tahu cara menjalankan loop frame. Jika tidak memahami konsep ini, Anda harus membaca artikel sebelumnya dalam seri ini.
- Virtual reality hadir di web
- Virtual reality hadir di web, bagian II
- AR Web: 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 dari 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 tengahnya. Titik adalah persimpangan antara garis imajiner dari perangkat Anda ke titik di lingkungan. Tampilan akan bergerak saat Anda menggerakkan perangkat. Saat menemukan titik persimpangan, alat ini akan menempel ke permukaan seperti lantai, permukaan meja, dan dinding. Hal ini dilakukan karena hit testing memberikan posisi dan orientasi titik persimpangan, tetapi tidak ada 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 ditempatkan di permukaan di lokasi reticle dan orientasi titik reticle, di mana pun Anda mengetuk layar. Reticle akan terus bergerak dengan perangkat Anda.
Membuat reticle
Anda harus membuat gambar reticle sendiri karena tidak disediakan oleh browser atau API. Metode pemuatan dan gambar adalah metode khusus framework.
Jika Anda tidak menggambarnya langsung menggunakan WebGL atau WebGL2, baca dokumentasi framework Anda. Karena alasan ini, saya tidak akan membahas detail tentang bagaimana
reticle digambar dalam sampel. Di bawah ini, saya menampilkan satu baris hanya karena satu alasan: agar dalam
contoh kode berikutnya, 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 ditunjukkan di bawah ini.
navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
// Do something with the session
});
Memasuki sesi
Dalam artikel sebelumnya, saya telah menyajikan kode untuk memasuki sesi XR. Saya 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. 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 ditandai memanggil XRSession.requestReferenceSpace()
dua kali. Awalnya saya merasa 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 adalah
kesalahpahaman tentang ruang referensi. Ruang referensi mengekspresikan hubungan
antara tempat asal dan dunia.
Untuk memahami apa yang dilakukan kode ini, anggaplah Anda melihat contoh ini menggunakan rig mandiri, dan Anda memiliki headset dan 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. Namun, saya memiliki masalah. Gambar yang saya buat 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 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 'pelihat' agak
membingungkan dalam konteks ini karena saya berbicara tentang pengontrol. Hal ini wajar
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 pengujian hit.
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 dalam AR, saya perlu mengetahui posisi pelihat dan tempat mereka
melihat. 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 menggunakan hitTestSource
sebagai argumen
dan menampilkan array instance HitTestResult
. Hit test dapat menemukan
beberapa platform. Yang pertama dalam array adalah yang terdekat 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 platform 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 dan seterusnya. 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 ini. Dapatkan
pose dari hasil hit test, ubah (pindahkan) image reticle ke posisi
pengujian hit, lalu tetapkan properti visible
-nya ke true. Pose mewakili
pose 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
Objek ditempatkan di AR saat pengguna mengetuk layar. Saya sudah menambahkan
penangan peristiwa select
ke sesi. (Lihat di atas.)
Hal yang penting dalam langkah ini adalah mengetahui tempat untuk meletakkannya. Karena reticle 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 mendapatkan handle terkait hal ini adalah dengan mengikuti kode contoh atau mencoba codelab. Saya harap kami telah memberikan latar belakang yang cukup untuk memahami keduanya.
Kami belum selesai membuat API web imersif, jauh dari kata selesai. Kami akan memublikasikan artikel baru di sini seiring dengan progres kami.
Foto oleh Daniel Frank di Unsplash