ריקוד טוניט ב-WebVR

התרגשתי מאוד כאשר הצוות של Google Data Arts ניגש ל-Monicer ולעצמי ולבקש מאיתנו לעבוד יחד כדי לבחון את האפשרויות שהציג WebVR. צפיתי בעבודות של הצוות שלהם לאורך השנים, והפרויקטים שלהם תמיד התחברו אליי. שיתוף הפעולה גרם ל-Dance Tonite, חוויית ריקוד ב-VR שלא מפסיקה להשתנות עם LCD Soundsystem ועם המעריצים שלהם. כך עשינו את זה.

הרעיון הבסיסי

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

לקחנו את זה לתשומת ליבנו. מה שחשבנו צריך להתאים לכל סוגי ה-VR, מאוזניות ה-VR שפועלות עם טלפונים ניידים, כמו Daydream View של Google , Cardboard ו-Gear VR של Samsung, ועד למערכות המתאימות לחדרים, כמו HTC VIVE ו-Oculus Rift, שמשקפות את התנועות הפיזיות בסביבה הווירטואלית. יותר חשוב מכך, חשבנו שברוח האינטרנט, אנחנו יכולים ליצור משהו שיתאים גם לכל מי שאין לו מכשיר VR.

1. צילום תנועה בשיטת 'עשה זאת בעצמך'

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

מישהו מקליט את עצמו ב-Dance Tonite. במסך שמאחוריהם מוצג מה שהם רואים באוזניות

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

באמצעות WebVR, למפתח יש גישה למיקום הראש ולכיוון של המשתמש דרך האובייקט VRPose. הערך הזה מתעדכן כל פריים על ידי חומרת ה-VR, כך שהקוד שלכם יכול לעבד פריימים חדשים מנקודת המבט הנכונה. באמצעות GamePad API עם WebVR, אנחנו גם יכולים לגשת למיקום או לכיוון של פקדי המשתמשים דרך האובייקט GamepadPose. אנחנו פשוט מאחסנים את כל ערכי המיקום והכיוון בכל פריים, וכך יוצרים "הקלטה" של תנועות המשתמש.

2. מינימליזם ותלבושות

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

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

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

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

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

3. דוושת לולאה לתנועה

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

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

4. חדרים שמחוברים ביניהם

חדרים שמחוברים ביניהם

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

אופטימיזציות של ביצועים: ללא ירידה בפריימים

זה לא פשוט ליצור חוויית VR בכמה פלטפורמות שפועלת על בסיס קוד יחיד עם ביצועים אופטימליים לכל מכשיר או פלטפורמה.

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

1. גיאומטריה של מאגר נתונים זמני

מכיוון שבפרויקט כולו נעשה שימוש רק בקומץ של אובייקטים תלת ממדיים, הצלחנו לשפר מאוד את הביצועים באמצעות שימוש בגיאומטריה של מאגרי מכונה. בעיקרון, הוא מאפשר להעלות את האובייקט ל-GPU פעם אחת, ולצייר "מכונות" רבות של האובייקט באמצעות קריאה אחת לשרטוט. בדאנס טוניט יש רק 3 אובייקטים שונים (חרוט, גליל וחדר עם חור), אבל אולי אפילו מאות עותקים של האובייקטים האלה. גיאומטריה של מאגרי מכונות היא חלק מ-ThreeJS, אבל השתמשנו במזלג הניסיוני של Dusan Bosnjak, שמטמיע את THREE.InstanceMesh, והופך את העבודה עם Instanced Buffer Geometry לפשוטה יותר.

2. אין פינוי אשפה

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

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

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

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

const SERIALIZE_POSITION = new THREE.Vector3();
const SERIALIZE_ROTATION = new THREE.Quaternion();
const SERIALIZE_SCALE = new THREE.Vector3();
export const serializeMatrix = (matrix) => {
    matrix.decompose(SERIALIZE_POSITION, SERIALIZE_ROTATION, SERIALIZE_SCALE);
    return SERIALIZE_POSITION.toArray()
    .concat(SERIALIZE_ROTATION.toArray())
    .map(compressNumber);
};

3. יצירת סדרה של תנועה והפעלה פרוגרסיבית

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

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

לכן רצינו לוודא שהפרויקט שלנו יוכל להתחיל לפעול בהקדם האפשרי. בהתחלה השתמשנו ב-JSON כפורמט לטעינת נתוני התנועה שלנו. הבעיה היא שאנחנו צריכים לטעון את קובץ ה-JSON השלם כדי שנוכל לנתח אותו. לא מאוד מתקדם.

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

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

כך נראה קטע באחת מההקלטות שלנו:

{"fps":15,"count":1,"loopIndex":"1","hideHead":false}
[-464,17111,-6568,-235,-315,-44,9992,-3509,7823,-7074, ... ]
[-583,17146,-6574,-215,-361,-38,9991,-3743,7821,-7092, ... ]
[-693,17158,-6580,-117,-341,64,9993,-3977,7874,-7171, ... ]
[-772,17134,-6591,-93,-273,205,9994,-4125,7889,-7319, ... ]
[-814,17135,-6620,-123,-248,408,9988,-4196,7882,-7376, ... ]
[-840,17125,-6644,-173,-227,530,9982,-4174,7815,-7356, ... ]
[-868,17120,-6670,-148,-183,564,9981,-4069,7732,-7366, ... ]
...

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

4. תנועה אינטרפולציה

קיווינו שיוכלו להציג 30 עד 60 ביצועים בו-זמנית, ולכן היה עלינו להוריד את קצב הנתונים שלנו עוד יותר ממה שהיה לנו. הצוות של 'אומנויות הנתונים' התמודד עם אותה בעיה בפרויקט סשנים של אומנות וירטואלית, שבו הוא משמיע הקלטות של אומנים שמציירים ב-VR באמצעות Tilt Brush. כדי לפתור את הבעיה, הם יצרו גרסאות ביניים של נתוני משתמשים עם קצב פריימים נמוך יותר, וביצעו אינטרפולציה בין הפריימים בזמן ההפעלה. הופתענו לגלות שקשה מאוד לזהות את ההבדל בין הקלטה עם אינטרפולציה שפועלת במהירות 15 FPS, לעומת ההקלטה המקורית בקצב של 90 FPS.

רוצים לראות את הנתונים? אפשר לאלץ את ריקוד Tonite להפעיל את הנתונים בקצבים שונים באמצעות מחרוזת השאילתה ?dataRate=. אפשר להשתמש בו כדי להשוות בין התנועה המוקלטת בקצב של 90 פריימים לשנייה, 45 פריימים לשנייה או 15 פריימים לשנייה.

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

const { x: x1, y: y1, z: z1 } = getPosition(previous, performanceIndex, limbIndex);
const { x: x2, y: y2, z: z2 } = getPosition(next, performanceIndex, limbIndex);
interpolatedPosition = new THREE.Vector3();
interpolatedPosition.set(
    x1 + (x2 - x1) * ratio,
    y1 + (y2 - y1) * ratio,
    z1 + (z2 - z1) * ratio
    );

כדי לקבוע את הכיוון, אנחנו מבצעים אינטרפולציה כדורית לינארית (slerp) בין תמונות מפתח. הכיוון נשמר כקואטרניות.

const quaternion = getQuaternion(previous, performanceIndex, limbIndex);
quaternion.slerp(
    getQuaternion(next, performanceIndex, limbIndex),
    ratio
    );

5. סנכרון תנועות עם מוזיקה

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

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

6. איסוף וערפל

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

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

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

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

// this is called every frame
// the FPS calculation is based on stats.js by @mrdoob
tick: (interval = 3000) => {
    frames++;
    const time = (performance || Date).now();
    if (prevTime == null) prevTime = time;
    if (time > prevTime + interval) {
    fps = Math.round((frames * 1000) / (time - prevTime));
    frames = 0;
    prevTime = time;
    const lastCullDistance = settings.cullDistance;

    // if the fps is lower than 52 reduce the cull distance
    if (fps <= 52) {
        settings.cullDistance = Math.max(
        settings.minCullDistance,
        settings.cullDistance - settings.roomDepth
        );
    }
    // if the FPS is higher than 56, increase the cull distance
    else if (fps > 56) {
        settings.cullDistance = Math.min(
        settings.maxCullDistance,
        settings.cullDistance + settings.roomDepth
        );
    }
    }

    // gradually increase the cull distance to the new setting
    cullDistance = cullDistance * 0.95 + settings.cullDistance * 0.05;

    // mask the edge of the cull distance with fog
    viewer.fog.near = cullDistance - settings.roomDepth;
    viewer.fog.far = cullDistance;
}

משהו לכולם: פיתוח מציאות מדומה לאינטרנט

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

1. הגלגל הצהוב

במילים אחרות, מי שיצר את חוויית ה-VR בכל החדרים שלנו יהיה הופעות, אבל איך חווים את הפרויקט הזה למשתמשים במכשירי VR ניידים (כמו Cardboard, Daydream View או Samsung Gear)? לשם כך, הוספנו לסביבה שלנו רכיב חדש: הכדור הצהוב.

הגלגל הצהוב
הגלגל הצהוב

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

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

2. נקודת מבט נוספת

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

3. צלליות: לזייף את זה עד שתעשה זאת

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

אזורים כהים

מסתבר שקשה ליצור צלליות בתלת ממד. במיוחד למכשירים עם הגבלות כמו טלפונים ניידים. בהתחלה היינו צריכים לקבל את ההחלטה הקשה להוציא את הילדים מהמשוואה, אבל אחרי שביקשנו עצה מהמחבר של Three.js וההאקר לדוגמה Mr doob, הוא חשב על הרעיון החדש, של זיוף שלהם.

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

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

function createShadow() {
    const texture = new THREE.TextureLoader().load(shadowTextureUrl);
    const material = new THREE.MeshLambertMaterial({
        map: texture,
        transparent: true,
        side: THREE.BackSide,
        depthWrite: false,
        blending: THREE.SubtractiveBlending,
    });
    const geometry = new THREE.PlaneBufferGeometry(0.5, 0.5, 1, 1);
    const plane = new THREE.Mesh(geometry, material);
    return plane;
    }

4. נוכחות

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

5. שיתוף ההקלטות

אנחנו יודעים כמה אפשר להתגאות בצילום של הקלטה מורכבת עם כוריאוגרפיה מורכבת של 20 שכבות של אומנים שמגיבים זו לזו. ידענו שהמשתמשים שלנו ירצו להראות אותה לחברים שלהם. אבל תמונת סטילס של ההישג הזה לא מייצגת מספיק את זה. במקום זאת, רצינו לאפשר למשתמשים שלנו לשתף סרטונים של הביצועים שלהם. בעצם, למה לא GIF? האנימציות שלנו מוצלות בצורה שטוחה, מושלמות עבור לוחות הצבעים המוגבלים של הפורמט.

שיתוף ההקלטות

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

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

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

6. קרקע יציבה: Google Cloud ו-Firebase

הקצה העורפי של אתר 'תוכן שנוצר על ידי משתמשים' עשוי להיות לעיתים קרובות מסובך ושביר, אבל יצרנו מערכת פשוטה ויציבה הודות ל-Google Cloud ול-Firebase. כשאומן מעלה ריקוד חדש למערכת, הוא עובר אימות אנונימי באמצעות האימות ב-Firebase. הם מקבלים הרשאה להעלות את ההקלטה שלהם למרחב זמני באמצעות Cloud Storage for Firebase. כשההעלאה מסתיימת, מחשב הלקוח קורא לטריגר HTTP של Cloud Functions for Firebase באמצעות אסימון Firebase. כך מופעל תהליך בשרת שמאמת את השליחה, יוצר רשומה של מסד נתונים ומעביר את ההקלטה לספרייה ציבורית ב-Google Cloud Storage.

קרקע יציבה

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

השתמשנו במסד נתונים בזמן אמת ב-Firebase ובנקודות קצה של פונקציות ב-Cloud Functions כדי לפתח כלי פשוט לניהול או איסוף, שמאפשר לנו לצפות בכל סרטון חדש שהוגש ב-VR ולפרסם פלייליסטים חדשים מכל מכשיר.

7. קובצי שירות (service worker)

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

בזכות פלאגין שימושי ל-Webpack, תוכלו להוסיף ל-Service Workers בקלות את רוב העבודה הקשה. בהגדרה הבאה, אנחנו יוצרים קובץ שירות (service worker) שישמור באופן אוטומטי את כל הקבצים הסטטיים שלנו. הוא יחלוף מהרשת את קובץ הפלייליסט האחרון, אם הוא זמין, כי הפלייליסט יתעדכן כל הזמן. כל קובצי ה-JSON של ההקלטה צריכים לשלוף מהמטמון אם אפשר, כי הם לא ישתנו אף פעם.

const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
config.plugins.push(
    new SWPrecacheWebpackPlugin({
    dontCacheBustUrlsMatching: /\.\w{8}\./,
    filename: 'service-worker.js',
    minify: true,
    navigateFallback: 'index.html',
    staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
    runtimeCaching: [{
        urlPattern: /playlist\.json$/,
        handler: 'networkFirst',
    }, {
        urlPattern: /\/recordings\//,
        handler: 'cacheFirst',
        options: {
        cache: {
            maxEntries: 120,
            name: 'recordings',
        },
        },
    }],
    })
);

בשלב זה, הפלאגין לא מטפל בנכסי מדיה שנטענים בהדרגה, כמו קובצי המוזיקה שלנו. לכן טיפלנו בנושא הזה בכך שהגדרנו את הכותרת של Cloud Storage Cache-Control בקבצים האלה לערך public, max-age=31536000, כדי שהדפדפן ישמור את הקובץ במטמון למשך שנה לכל היותר.

סיכום

אנחנו מצפים לראות איך שחקנים יוסיפו לחוויה הזו וישתמשו בה ככלי להבעה יצירתית תוך שימוש בתנועה. השקנו את כל הקוד הפתוח בקוד, שנמצא בכתובת https://github.com/puckey/dance-tonite. בימים הראשונים של ה-VR, ובמיוחד WebVR, אנחנו מצפים לראות אילו כיוונים יצירתיים חדשים, וכיוונים לא צפויים של שימוש במדיום החדש הזה. Dance on (ריקוד מופעל)