ה-API של בדיקת פגיעה מאפשר למקם פריטים וירטואליים בתצוגה של העולם האמיתי.
ממשק WebXR Device API הושק בסתיו האחרון ב-Chrome 79. כמו שצוין אז, ההטמעה של ה-API ב-Chrome נמצאת בשלבי פיתוח. אנחנו שמחים להודיע שחלק מהעבודה הסתיים. בגרסה 81 של Chrome נוספו שתי תכונות חדשות:
במאמר הזה מוסבר על WebXR Hit Test API, שמאפשר למקם אובייקטים וירטואליים בתצוגת מצלמה בעולם האמיתי.
במאמר הזה אני מניח שאתם כבר יודעים איך ליצור סשן של מציאות רבודה ואיך להריץ לולאת פריימים. אם אתם לא מכירים את המושגים האלה, כדאי לקרוא את המאמרים הקודמים בסדרה הזו.
- מציאות מדומה מגיעה לאינטרנט
- מציאות מדומה מגיעה לאינטרנט, חלק ב'
- Web AR: יכול להיות שאתם כבר יודעים איך להשתמש בו
דוגמה לסשן AR במצב immersive
הקוד במאמר הזה מבוסס על הקוד שמופיע בדוגמה של בדיקת פגיעה (Hit Test) של קבוצת העבודה Immersive Web, אבל הוא לא זהה לו (הדגמה, מקור). בדוגמה הזו אפשר להציב חמניות וירטואליות על משטחים בעולם האמיתי.
כשפותחים את האפליקציה בפעם הראשונה, מופיע עיגול כחול עם נקודה באמצע. הנקודה היא הצומת בין קו דמיוני מהמכשיר לבין הנקודה בסביבה. הוא זז כשמזיזים את המכשיר. כשהוא מוצא נקודות חיתוך, נראה שהוא נצמד למשטחים כמו רצפות, משטחי שולחנות וקירות. הסיבה לכך היא שבדיקת פגיעה מספקת את המיקום והכיוון של נקודת החיתוך, אבל לא מידע על המשטחים עצמם.
העיגול הזה נקרא כוונת, והוא תמונה זמנית שעוזרת למקם אובייקט במציאות רבודה. אם מקישים על המסך, חמנייה מוצבת על המשטח במיקום של נקודת הכוונת ובכיוון של נקודת הכוונת, לא משנה איפה הקשתם על המסך. הכוונת ממשיכה לנוע עם המכשיר.
יצירת הכוונת
אתם צריכים ליצור את תמונת הכוונת בעצמכם, כי היא לא מסופקת על ידי הדפדפן או ה-API. השיטה לטעינה ולציור של התמונה היא ספציפית למסגרת.
אם אתם לא מציירים אותו ישירות באמצעות WebGL או WebGL2, כדאי לעיין במסמכי התיעוד של המסגרת. לכן לא אפרט איך הכוונת מצוירת בדוגמה. בהמשך מוצגת שורה אחת מהקוד, וזאת רק כדי שבדוגמאות הקוד הבאות תדעו למה אני מתכוון כשאני משתמש במשתנה reticle.
let reticle = new Gltf2Node({url: 'media/gltf/reticle/reticle.gltf'});
בקשה להפעלה
כשמבקשים סשן, צריך לבקש 'hit-test' במערך requiredFeatures כמו שמוצג בהמשך.
navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
// Do something with the session
});
כניסה לסשן
במאמרים הקודמים הצגתי קוד לכניסה לסשן XR. בהמשך מופיעה גרסה של ההודעה הזו עם כמה תוספות. קודם הוספתי את מאזין האירועים select. כשהמשתמש מקיש על המסך, פרח ממוקם בתצוגת המצלמה בהתאם לתנוחה של הכוונת. אני אתאר את פונקציית ה-event listener הזו בהמשך.
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);
});
}
כמה מרחבים להשוואה
שימו לב שהקוד המודגש קורא ל-XRSession.requestReferenceSpace()
פעמיים. בהתחלה זה היה לי קצת מבלבל. שאלתי למה קוד בדיקת ההתאמה לא מבקש מסגרת אנימציה (התחלת לולאת המסגרת) ולמה נראה שלולאת המסגרת לא כוללת בדיקות התאמה. מקור הבלבול היה אי הבנה של מרחבי ייחוס. מרחבי הפניה מבטאים קשרים בין מקור לבין העולם.
כדי להבין מה הקוד הזה עושה, נניח שאתם צופים בדוגמה הזו באמצעות מערכת עצמאית, ויש לכם גם אוזניות וגם בקר. כדי למדוד מרחקים מהבקר, צריך להשתמש במערכת צירים שמרכזה בבקר. אבל כדי לצייר משהו במסך, צריך להשתמש בקואורדינטות שמתמקדות במשתמש.
בדוגמה הזו, הצופה והשולט הם אותו מכשיר. אבל נתקלתי בבעיה. מה שאני מצייר צריך להיות יציב ביחס לסביבה, אבל ה'בקר' שאיתו אני מצייר זז.
לציור תמונות, אני משתמש במרחב ההתייחסות local, שנותן לי יציבות מבחינת הסביבה. אחרי שמקבלים את זה, מתחילים את לולאת הפריימים על ידי קריאה ל-requestAnimationFrame().
לצורך בדיקת פגיעה, אני משתמש במרחב הייחוס viewer, שמבוסס על
המיקום של המכשיר בזמן בדיקת הפגיעה. התווית 'צופה' קצת מבלבלת בהקשר הזה כי אני מדבר על בקר. ההגדרה הזו הגיונית אם חושבים על הבקר כעל צופה אלקטרוני. אחרי שאקבל את זה, אקרא ל-xrSession.requestHitTestSource(), שיצור את המקור של נתוני בדיקת ההיט שאשתמש בהם כשאצייר.
הפעלת לולאת פריימים
הקריאה החוזרת (callback) requestAnimationFrame() מקבלת גם קוד חדש לטיפול בבדיקת פגיעה.
כשמזיזים את המכשיר, הכוונת צריכה לזוז איתו בניסיון למצוא משטחים. כדי ליצור אשליה של תנועה, צריך לצייר מחדש את הכוונת בכל פריים.
אבל לא להציג את הכוונת אם בדיקת הפגיעה נכשלת. לכן, עבור הכוונת שיצרתי קודם, הגדרתי את המאפיין visible שלה כ-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
}
כדי לצייר משהו ב-AR, אני צריכה לדעת איפה הצופה נמצא ומה הוא רואה. לכן אני בודק שhitTestSource וxrViewerPose עדיין תקפים.
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
}
אני מתקשרת עכשיו אל getHitTestResults(). הפונקציה מקבלת את hitTestSource כארגומנט ומחזירה מערך של מופעים של HitTestResult. יכול להיות שבדיקת הפגיעה תמצא כמה משטחים. הראשון במערך הוא זה שהכי קרוב למצלמה.
ברוב המקרים תשתמשו בו, אבל המערך מוחזר לשימושים מתקדמים. לדוגמה, נניח שהמצלמה מכוונת לקופסה על שולחן על רצפה. יכול להיות שבדיקת ההתאמה תחזיר את כל שלושת המשטחים במערך. ברוב המקרים, זה יהיה התיבה שאני רוצה. אם אורך המערך שמוחזר הוא 0, כלומר אם לא מוחזרת בדיקת פגיעה, ממשיכים. כדאי לנסות שוב בפריים הבא.
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
}
לבסוף, צריך לעבד את תוצאות הבדיקה. זה התהליך הבסיסי. מקבלים
תנוחה מתוצאת בדיקת הפגיעה, משנים את המיקום של תמונת הכוונת למיקום של בדיקת הפגיעה, ואז מגדירים את המאפיין visible לערך true. הפוזה מייצגת את הפוזה של נקודה על משטח.
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
}
הצבת אובייקט
אובייקט מוצב ב-AR כשהמשתמש מקיש על המסך. כבר הוספתי גורם מטפל באירועים select לפגישה. (ראו למעלה).
הדבר החשוב בשלב הזה הוא לדעת איפה למקם את התג. מכיוון שהכוונת הנעה מספקת מקור קבוע של בדיקות פגיעה, הדרך הפשוטה ביותר למקם אובייקט היא לצייר אותו במיקום של הכוונת בבדיקת הפגיעה האחרונה.
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);
}
}
סיכום
הדרך הכי טובה להבין את זה היא לעבור על קוד הדוגמה או לנסות את ה-codelab. אני מקווה שסיפקתי לך מספיק מידע כדי להבין את שניהם.
אנחנו עדיין לא סיימנו לפתח ממשקי API לאינטרנט סוחף, ויש לנו עוד הרבה עבודה. ככל שנתקדם, נפרסם כאן מאמרים חדשים.
תמונה מאת Daniel Frank ב-Unsplash