המציאות הווירטואלית מגיעה לאינטרנט, חלק 2

כל מה שצריך לדעת על לולאת המסגרת

Joe Medley
Joe Medley

לא מזמן פרסמתי את המאמר Virtual Real Reality מגיע לאינטרנט, במאמר שהציג מושגים בסיסיים ב-WebXR Device API. כמו כן, סיפקתי הוראות להגשת בקשה, לכניסה ולסיום של הפעלת XR.

במאמר הזה מתוארת לולאת הפריימים, שהיא לולאה אינסופית שנשלטת על ידי סוכן משתמש, שבה התוכן משורטט שוב ושוב למסך. התוכן משורטט בבלוקים נפרדים שנקראים 'מסגרות'. רצף הפריימים יוצר אשליה של תנועה.

מה לא המאמר הזה

WebGL ו-WebGL2 הן האמצעים היחידים לעיבוד תוכן במהלך לולאת מסגרות באפליקציית WebXR. למרבה המזל, מסגרות רבות מספקות שכבה של הפשטה מעל WebGL ו-WebGL2. מסגרות כאלה כוללות את three.js, babylonjs ו-PlayCanvas, בעוד ש-A-Frame ו-React 360 תוכננו לאינטראקציה עם WebXR.

המאמר הזה הוא לא מדריך של WebGL או של מסגרת. נסביר את העקרונות הבסיסיים של לולאת מסגרות באמצעות דוגמת Immersive VR Session (הדגמה, מקור) של Immersive Web עבודה Group. אם אתם רוצים להתעמק ב-WebGL או באחת מה-frameworks, האינטרנט מספק רשימה הולכת וגדלה של מאמרים.

השחקנים והמשחק

ניסיתי להבין את לולאת המסגרת, והכל הזמן הלכתי לאיבוד בפרטים. יש הרבה חפצים במשחק, ולחלקם יש שמות רק על סמך מאפייני הפניה שנמצאים על אובייקטים אחרים. כדי לעזור לכם ישר, אני אתאר את האובייקטים, והם נקראים 'שחקנים'. לאחר מכן אתאר את האינטראקציה ביניהם. אני קוראת להם 'המשחק'.

השחקנים

XRViewerPose

תנוחה היא המיקום והכיוון של משהו במרחב תלת-ממדי. גם לצופים וגם למכשירי הקלט יש תנוחה, אבל תנוחת הצופה מדאיגה אותנו כאן. גם לתנוחות של הצופה וגם של מכשיר הקלט יש מאפיין transform שמתאר את המיקום שלו כווקטור ואת הכיוון שלו כקווטרניון ביחס למקור. המקור מצוין בהתאם לסוג של מרחב ההפניה המבוקש כאשר מפעילים את XRSession.requestReferenceSpace().

צריך קצת הסבר על המרחבים המשותפים לצורכי הפניה. אני מסבירה עליהן בהרחבה במציאות רבודה. הדוגמה שמשמשת אותי כבסיס למאמר הזה משתמשת במרחב הפניה 'local', כלומר המקור נמצא במיקום הצופה בזמן יצירת הסשן ללא סף מוגדר היטב. המיקום המדויק עשוי להשתנות בהתאם לפלטפורמה.

XRView

התצוגה מקבילה למצלמה שצופה בסצנה הווירטואלית. בתצוגה יש גם מאפיין transform שמתאר את המיקום שלה כווקטור ואת הכיוון שלו. הן ניתנות גם כצמד וקטור/קווטרניון וגם כמטריצה מקבילה, ואפשר להשתמש בכל אחת מהייצוגים בהתאם לקוד המתאים ביותר. כל תצוגה מייצגת תצוגה או חלק מהמסך שמשמש את המכשיר כדי להציג תמונות לצופה. האובייקטים XRView מוחזרים במערך מהאובייקט XRViewerPose. מספר הצפיות במערך משתנה. במכשירים ניידים, סצינת AR כוללת תצוגה אחת, שאינה מכסה את מסך המכשיר. לאוזניות יש בדרך כלל שתי תצוגות – אחת לכל עין.

XRWebGLLayer

שכבות מספקות מקור לתמונות של מפת סיביות (bitmap) ותיאורים של אופן עיבוד התמונות במכשיר. התיאור הזה לא בדיוק משקף את מה שהשחקן הזה עושה. אני חשבתי על זה כמתווך בין מכשיר ל-WebGLRenderingContext. MDN מתייחסת כמעט לאותה תצוגה, ומציין שהיא 'מספקת קישור' בין השניים. כך, הוא מספק גישה לשחקנים האחרים.

באופן כללי, אובייקטים של WebGL מאחסנים פרטי מצב של עיבוד גרפיקה דו-ממדית ותלת-ממדית.

WebGLFramebuffer

ה-framebuffer מספק נתוני תמונה ל-WebGLRenderingContext. אחרי שמאחזרים אותו מה-XRWebGLLayer, פשוט מעבירים אותו אל WebGLRenderingContext הנוכחי. מלבד קריאה ל-bindFramebuffer() (פרטים נוספים על כך בהמשך) אף פעם לא תהיה לכם גישה ישירה לאובייקט הזה. צריך רק להעביר אותו מה-XRWebGLLayer אל WebGLRenderingContext.

XRViewport

אזור תצוגה מספק את הקואורדינטות והמידות של אזור מלבני ב-WebGLFramebuffer.

WebGLRenderingContext

הקשר לרינדור הוא נקודת גישה פרוגרמטית לקנבס (המרחב שאנחנו משרטטים עליו). כדי לעשות זאת, יש צורך גם ב-WebGLFramebuffer וגם ב-XRViewport.

חשוב לשים לב לקשר בין XRWebGLLayer לבין WebGLRenderingContext. תמונה אחת תואמת למכשיר של הצופה והשניה לדף האינטרנט. הערכים WebGLFramebuffer ו-XRViewport מועברים מהראשון לאחרון.

הקשר בין XRWebGLLayer לבין WebGLRenderingContext
הקשר בין XRWebGLLayer לבין WebGLRenderingContext

המשחק

עכשיו, כשאנחנו יודעים מיהם השחקנים, נבחן את המשחק שבו הם משחקים. זה משחק שמתחיל מחדש בכל פריים. חשוב לזכור שמסגרות הן חלק מלולאת מסגרות שמתרחשת בקצב שתלוי בחומרה הבסיסית. באפליקציות VR, קצב הפריימים לשנייה יכול להיות בין 60 ל-144. AR ל-Android פועל בקצב של 30FPS. הקוד לא אמור להניח קצב פריימים מסוים.

התהליך הבסיסי של לולאת המסגרת נראה כך:

  1. התקשרו אל XRSession.requestAnimationFrame(). בתגובה, סוכן המשתמש מפעיל את XRFrameRequestCallback, שמוגדר על ידך.
  2. בתוך פונקציית הקריאה החוזרת:
    1. להתקשר שוב אל XRSession.requestAnimationFrame().
    2. נותנים לצופה את התנוחה.
    3. מעבירים ('קישור') WebGLFramebuffer מה-XRWebGLLayer אל WebGLRenderingContext.
    4. חוזרים על כל אובייקט XRView, מאחזרים את ה-XRViewport שלו מה-XRWebGLLayer ומעבירים אותו ל-WebGLRenderingContext.
    5. צייר משהו למאגר המסגרות.

מאחר שהשלבים 1 ו-2א כבר מפורטים במאמר הקודם, אתחיל בשלב 2ב.

נותנים לצופה את התנוחה

זה מובן מאליו. כדי לצייר משהו ב-AR או ב-VR, אני צריך לדעת איפה הצופה נמצא ואיפה הוא מחפש. המיקום והכיוון של הצופה נקבעים על ידי אובייקט XRViewerPose. אפשר לראות את תנוחת הצופה על ידי קריאה ל-XRFrame.getViewerPose() בפריים האנימציה הנוכחי. אני מעבירה לו את שטח ההפניה שרכשתי כשהגדרתי את ההפעלה. הערכים שהאובייקט הזה מחזיר הם תמיד ביחס לשטח ההפניה שביקשתי כשנכנסתי להפעלה הנוכחית. כפי שאולי אתם זוכרים, אני צריך להעביר את מקום ההפניה הנוכחי בבקשת התנוחה.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    // Render based on the pose.
  }
}

יש תנוחה אחת של הצופה שמייצגת את המיקום הכולל של המשתמש. כלומר, ראש הצופה או מצלמת הטלפון במקרה של סמארטפון. התנוחה מאפשרת לאפליקציה לדעת איפה הצופה נמצא. ברינדור התמונה בפועל נעשה שימוש באובייקטים מסוג XRView, ונתחיל להגיע אליהם עוד מעט.

לפני שממשיכים, אני רוצה לבדוק אם תנוחת הצופה הוחזרה במקרה שהמערכת תאבד את המעקב או תחסום את התנוחה מטעמי פרטיות. מעקב הוא היכולת של מכשיר ה-XR לדעת איפה הוא ו/או המכשירים לקליטת נתונים הוא ביחס לסביבה. יש כמה דרכים לאבד את המעקב, והוא משתנה בהתאם לשיטה שמשמשת אתכם למעקב. לדוגמה, אם מצלמות באוזניות או בטלפון משמשות למעקב אחר המכשיר, יכול להיות שלא תהיה לו אפשרות לקבוע איפה הוא נמצא במצבים שבהם אור חלש או ללא אור, או אם המצלמות מכוסות.

דוגמה לחסימת התנוחה מטעמי פרטיות: אם באוזניות מופיעה תיבת דו-שיח של אבטחה, כמו בקשה להרשאה, הדפדפן עלול להפסיק להציג לאפליקציה תנוחות בזמן הזה. אבל כבר קראתי ל-XRSession.requestAnimationFrame() כדי שאם המערכת תוכל להתאושש, הלולאה של המסגרת תמשיך. אם לא, סוכן המשתמש יסיים את הסשן ויתקשר לגורם המטפל באירועים של end.

דרך עוקפת קצרה

לשלב הבא נדרשים אובייקטים שנוצרים במהלך הגדרת הסשן. אני רוצה לזכור שיצרתי קנבס וקיבלתי הוראה ליצור הקשר להצגת Web GL שתואם ל-XR. את ההקשר קיבלתי אחרי שהתקשרתי ל-canvas.getContext(). כל השרטוט מתבצע באמצעות ממשק ה-API של WebGL, WebGL2 API, או מסגרת מבוססת WebGL כמו Three.js. ההקשר הועבר לאובייקט הסשן באמצעות updateRenderState(), יחד עם מופע חדש של XRWebGLLayer.

let canvas = document.createElement('canvas');
// The rendering context must be based on WebGL or WebGL2
let webGLRenContext = canvas.getContext('webgl', { xrCompatible: true });
xrSession.updateRenderState({
    baseLayer: new XRWebGLLayer(xrSession, webGLRenContext)
  });

העברה ('bind') של WebGLFramebuffer

השדה XRWebGLLayer מספק מסגרת ל-WebGLRenderingContext שמיועד ספציפית לשימוש ב-WebXR ומחליף את הקשרי הרינדור (framebuffer) שמוגדר כברירת מחדל. הפעולה הזו נקראת 'קישור' בשפה של WebGL.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    // Iterate over the views
  }
}

חזרה על כל אובייקט XRView

אחרי שמקבלים את התנוחה ומקשרים את המאגר למאגר המסגרות, זה הזמן להשיג את אזורי התצוגה. השדה XRViewerPose מכיל מערך של ממשקי XRView, שכל אחד מהם מייצג תצוגה או חלק מהמסך. הם מכילים מידע שנחוץ כדי לעבד תוכן שממוקם בצורה נכונה למכשיר ולצופים, כמו שדה הראייה, קיזוז העיניים ומאפיינים אופטיים אחרים. מכיוון שאני מושכת שתי עיניים, יש לי שתי תצוגות, ואני מרחיבה אותן בלולאה ומציירת תמונה נפרדת לכל אחת מהן.

במהלך ההטמעה עבור מציאות רבודה מבוססת-טלפון, הייתה לי רק תצוגה אחת אבל עדיין אשתמש בלולאה. למרות שיכול להיות שלפעמים אי אפשר לחזור על אותה תצוגה מפורטת, זה יאפשר לכם לקבל נתיב רינדור אחד למגוון רחב של חוויות סוחפות. זה הבדל חשוב בין WebXR למערכות עשירות אחרות.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    for (let xrView of xrViewerPose.views) {
      // Pass viewports to the context
    }
  }
}

העברת האובייקט XRViewport אל WebGLRenderingContext

אובייקט XRView מתייחס למה שניתן לראות במסך. אבל כדי לצייר לתצוגה הזו, אני צריך קואורדינטות ומימדים שהם ספציפיים למכשיר שלי. כמו במקרה של framebuffer, מבקשים אותם מה-XRWebGLLayer ומעבירים אותם ל-WebGLRenderingContext.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    for (let xrView of xrViewerPose.views) {
      let viewport = glLayer.getViewport(xrView);
      webGLRenContext.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
      // Draw something to the framebuffer
    }
  }
}

WebGLRenContext

בכתבתי את המאמר הזה ניהלתי דיון עם כמה עמיתים על השם של האובייקט webGLRenContext. הסקריפטים לדוגמה ורוב קוד ה-WebXR קוראים למשתנה הזה gl. כשהצלחתי להבין את הדוגמאות, שכחתי למה התייחסתי ב-gl. קראתי לזה webGLRenContext כדי להזכיר לכם בזמן שלמדתם לנו שזה מקרה של WebGLRenderingContext.

הסיבה לכך היא שהשימוש ב-gl מאפשר לשמות שיטות להיראות כמו מקבילים בממשק ה-API של OpenGL ES 2.0, שמשמש ליצירת VR בשפות שעברו הידור. העובדה הזו מובנת מאליו אם כתבתם אפליקציות VR באמצעות OpenGL, אבל זה לא ברור אם אתם מתחילים להשתמש בטכנולוגיה הזו.

צייר משהו למאגר המסגרות

אם אתם מרגישים שאפתניים מאוד, תוכלו להשתמש ישירות ב-WebGL, אבל זה לא מומלץ. הרבה יותר פשוט להשתמש באחת מהמסגרות שמפורטות למעלה.

סיכום

זה לא הסוף של עדכונים או מאמרים ל-WebXR. ב-MDN אפשר למצוא הפניה לכל הממשקים והחברים של WebXR. לשיפורים עתידיים בממשקים עצמם, עקבו אחר תכונות נפרדות בסטטוס Chrome.

תמונה מאת JESHOOTS.COM ב-UnFlood