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

כל מה שצריך לדעת על לולאת הפריימים

Joe Medley
Joe Medley

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

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

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

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

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

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

השחקנים

XRViewerPose

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

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

XRView

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

XRWebGLLayer

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

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

WebGLFramebuffer

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

XRViewport

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

WebGLRenderingContext

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

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

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

המשחק

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

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

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

מאחר ששלבים 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.

סטייה קצרה

בשלב הבא נדרשים אובייקטים שנוצרו במהלך הגדרת הסשן. נזכרים שיצרתי לוח ציור והנחיתי אותו ליצור הקשר עיבוד (render) של Web GL תואם-XR, שקיבלתי באמצעות קריאה ל-canvas.getContext(). כל הציור מתבצע באמצעות WebGL API,‏ 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)
 
});

העברה ('קישור') של WebGLFramebuffer

XRWebGLLayer מספק framebuffer ל-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

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

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

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 מאפשר לשמות השיטות להיראות כמו עמיתיהם ב-OpenGL ES 2.0 API, שמשמש ליצירת VR בשפות הידור. העובדה הזו ברורה אם כבר כתבתם אפליקציות VR באמצעות OpenGL, אבל היא מבלבלת אם אתם חדשים לגמרי בטכנולוגיה הזו.

איך מציירים משהו ב-framebuffer

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

סיכום

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

תמונה של JESHOOTS.COM ב-Unsplash