כל מה שצריך לדעת על לולאת הפריימים
לאחרונה פרסמתי את המאמר מציאות מדומה מגיעה לרשת, שבו הצגתי מושגים בסיסיים שקשורים ל-WebXR Device API. I also provided instructions for requesting, entering, and ending an XR session.
במאמר הזה מתואר לולאת הפריימים, שהיא לולאה אינסופית בשליטת סוכן המשתמש, שבה התוכן מצויר שוב ושוב על המסך. התוכן מוצג בבלוקים נפרדים שנקראים פריימים. רצף הפריימים יוצר אשליה של תנועה.
מה לא מופיע במאמר הזה
WebGL ו-WebGL2 הן האפשרויות היחידות לעיבוד תוכן במהלך לולאת פריימים באפליקציית WebXR. למרבה המזל, הרבה מסגרות מספקות שכבת הפשטה מעל WebGL ו-WebGL2. מסגרות כאלה כוללות את three.js, babylonjs ו-PlayCanvas, בעוד ש-A-Frame ו-React 360 תוכננו לאינטראקציה עם WebXR.
במאמר הזה מוסבר על לולאת פריימים באמצעות הדוגמה Immersive VR Session של קבוצת העבודה Immersive Web (הדגמה, מקור). אם אתם רוצים להתעמק ב-WebGL או באחד מהמסגרות, יש באינטרנט רשימה הולכת וגדלה של מקורות מידע.
השחקנים והמשחק
כשניסיתי להבין את לולאת הפריימים, כל הזמן הלכתי לאיבוד בפרטים. יש הרבה אובייקטים במשחק, וחלק מהם נקראים רק על ידי מאפייני הפניה באובייקטים אחרים. כדי לעזור לך להבין, אתאר את האובייקטים, שאקרא להם 'שחקנים'. אחר כך אתאר את האינטראקציה ביניהם, שאקרא לה 'המשחק'.
השחקנים
XRViewerPose
תנוחה היא המיקום והכיוון של משהו במרחב תלת-ממדי. גם לצופים וגם למכשירי קלט יש תנוחה, אבל כאן אנחנו מתמקדים בתנוחה של הצופים. למיקום של הצופה ולמיקום של מכשיר הקלט יש מאפיין transform שמתאר את המיקום שלו כווקטור ואת הכיוון שלו כקוורטניון ביחס לנקודת המוצא. המקור מצוין על סמך סוג מרחב הייחוס המבוקש כשמבצעים קריאה ל-XRSession.requestReferenceSpace().
קשה להסביר את המרחבים המשותפים האלה. אני מסביר עליהן לעומק במאמר בנושא מציאות רבודה. הדוגמה שבה השתמשתי כבסיס למאמר הזה משתמשת ב'local' מרחב ייחוס, כלומר המקור הוא המיקום של הצופה בזמן יצירת הסשן, בלי רצפה מוגדרת היטב, והמיקום המדויק שלו עשוי להשתנות בהתאם לפלטפורמה.
XRView
תצוגה מתאימה למצלמה שצופה בסצנה הווירטואלית. לתצוגה יש גם מאפיין transform שמתאר את המיקום שלה כווקטור ואת הכיוון שלה.
הנתונים האלה מסופקים גם כזוג וקטורים/קוורטניונים וגם כמטריצה מקבילה. אתם יכולים להשתמש בכל אחד מהייצוגים, בהתאם למה שמתאים לקוד שלכם. כל צפייה מתאימה לתצוגה או לחלק מהתצוגה שמשמשים מכשיר להצגת תמונות לצופה. אובייקטים של XRView מוחזרים במערך מהאובייקט XRViewerPose. מספר הצפיות במערך משתנה. במכשירים ניידים, סצנת AR כוללת תצוגה אחת, שיכולה לכסות את המסך של המכשיר או לא.
בדרך כלל יש למשקפי VR שתי תצוגות, אחת לכל עין.
XRWebGLLayer
השכבות מספקות מקור לתמונות bitmap ותיאורים של אופן העיבוד של התמונות במכשיר. התיאור הזה לא משקף במדויק את הפעולות של הנגן הזה. אני חושב על זה כעל מתווך בין מכשיר לבין WebGLRenderingContext. ב-MDN יש דעה דומה, וכתוב שם שהתכונה 'מספקת קישור' בין השניים. לכן, הוא מאפשר גישה לשחקנים האחרים.
באופן כללי, אובייקטים של WebGL מאחסנים מידע על מצב העיבוד של גרפיקה דו-ממדית ותלת-ממדית.
WebGLFramebuffer
מאגר מסגרות מספק נתוני תמונה ל-WebGLRenderingContext. אחרי שמקבלים אותו מ-XRWebGLLayer, מעבירים אותו ל-WebGLRenderingContext הנוכחי. לא תהיה לכם גישה ישירה לאובייקט הזה, אלא רק דרך התקשרות למספר bindFramebuffer() (פרטים נוספים בהמשך). פשוט מעבירים אותו מ-XRWebGLLayer אל WebGLRenderingContext.
XRViewport
אזור התצוגה מספק את הקואורדינטות והממדים של אזור מלבני ב-WebGLFramebuffer.
WebGLRenderingContext
הקשר רינדור הוא נקודת גישה תוכנתית לקנבס (המרחב שבו אנחנו מציירים). לשם כך, היא צריכה גם WebGLFramebuffer וגם XRViewport.
שימו לב לקשר בין XRWebGLLayer לבין WebGLRenderingContext. אחד מהם מתאים למכשיר של הצופה והשני מתאים לדף האינטרנט.
WebGLFramebuffer ו-XRViewport מועברים מהראשון לאחרון.
XRWebGLLayer לבין WebGLRenderingContext
המשחק
עכשיו, אחרי שהבנו מי השחקנים, נבדוק את המשחק שהם משחקים. זה משחק שמתחיל מחדש בכל פריים. חשוב לזכור שפריימים הם חלק מלולאת פריימים שמתרחשת בקצב שתלוי בחומרה הבסיסית. באפליקציות VR, קצב הפריימים לשנייה יכול להיות בין 60 ל-144. AR ל-Android פועל במהירות של 30 פריימים לשנייה. הקוד לא צריך להניח קצב פריימים מסוים.
התהליך הבסיסי של לולאת הפריימים נראה כך:
- התקשרו אל
XRSession.requestAnimationFrame(). בתגובה, סוכן המשתמש מפעיל אתXRFrameRequestCallback, שמוגדר על ידכם. - בתוך פונקציית הקריאה החוזרת:
- צריך להתקשר שוב אל
XRSession.requestAnimationFrame(). - קבלת תנוחת הצופה.
- העברת ('קישור')
WebGLFramebufferמ-XRWebGLLayerאלWebGLRenderingContext. - מבצעים איטרציה על כל אובייקט
XRView, מאחזרים אתXRViewportמ-XRWebGLLayerומעבירים אותו אלWebGLRenderingContext. - מציירים משהו ב-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.
סטייה קצרה מהמסלול
בשלב הבא נדרשים אובייקטים שנוצרו במהלך הגדרת הסשן.
תזכורת: יצרתי בד ציור והוריתי לו ליצור הקשר של Web GL rendering שתואם ל-XR, שקיבלתי על ידי קריאה ל-canvas.getContext(). כל הציורים מתבצעים באמצעות WebGL API, WebGL2 API או framework מבוסס-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 מספק מאגר מסגרות (framebuffer) ל-WebGLRenderingContext, שנועד במיוחד לשימוש עם WebXR ולהחלפת מאגר המסגרות שמוגדר כברירת מחדל בהקשרים של עיבוד. הפעולה הזו נקראת 'קישור' בשפה של 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 מאפשר לשמות של שיטות להיראות כמו המקבילות שלהם ב-OpenGL ES 2.0 API, שמשמש ליצירת VR בשפות שעברו קומפילציה. העובדה הזו ברורה אם כתבתם אפליקציות VR באמצעות OpenGL, אבל היא עלולה להיות מבלבלת אם אתם חדשים לגמרי בטכנולוגיה הזו.
ציור משהו ב-framebuffer
אם אתם מרגישים שאתם רוצים לנסות משהו יותר מתקדם, אתם יכולים להשתמש ישירות ב-WebGL, אבל אני לא ממליץ על כך. הרבה יותר פשוט להשתמש באחת מהמסגרות שמופיעות למעלה.
סיכום
המאמר הזה הוא לא האחרון בסדרת המאמרים על עדכוני WebXR. ב-MDN אפשר למצוא הפניה לכל הממשקים והחברים של WebXR. כדי להתעדכן בשיפורים הקרובים בממשקים עצמם, אפשר לעקוב אחרי תכונות ספציפיות ב-Chrome Status.