הפיכת ממלכת שר הטבעות לחיים באמצעות WebGL לנייד
בעבר, היה קשה להציע חוויות אינטראקטיביות מבוססות-אינטרנט עם הרבה מולטימדיה בניידים ובטאבלטים. האילוצים העיקריים היו ביצועים, זמינות ה-API, מגבלות באודיו ב-HTML5 במכשירים והיעדר הפעלה חלקה של סרטונים בתוך הטקסט.
מוקדם יותר השנה התחלנו פרויקט עם חברים מ-Google ו-Warner Bros. כדי ליצור חוויית אינטרנט שמתאימה במיוחד לניידים עבור הסרט החדש של 'ההוביט', ההוביט: נקמתו של סמואל. פיתוח ניסוי ב-Chrome לנייד שמתמקד בתוכן מולטימדיה היה משימה מאתגרת ומעוררת השראה.
חוויית השימוש מותאמת במיוחד ל-Chrome ל-Android במכשירי Nexus החדשים, שבהם יש לנו עכשיו גישה ל-WebGL ול-Web Audio. עם זאת, חלק גדול מהחוויה זמין גם במכשירים ובדפדפנים שאינם תומכים ב-WebGL, הודות לעיבוד נתונים משופר בחומרה ואנימציות CSS.
החוויה כולה מבוססת על מפה של ממלכת הארץ התיכונה ועל המיקומים והדמויות מסרטי ההוביט. השימוש ב-WebGL אפשר לנו להציג את העולם העשיר של טרילוגיית 'ההוביט' בצורה דרמטית ולאפשר למשתמשים לשלוט בחוויה.
האתגרים של WebGL במכשירים ניידים
קודם כול, המונח 'מכשירים ניידים' רחב מאוד. המפרטים של המכשירים משתנים מאוד. לכן, כמפתחים, אתם צריכים להחליט אם אתם רוצים לתמוך במכשירים נוספים עם חוויה פחות מורכבת, או, כמו שעשינו במקרה הזה, להגביל את המכשירים הנתמכים למכשירים שיכולים להציג עולם תלת-ממד ריאליסטי יותר. ב'מסע בממלכת שר הטבעות' התמקדנו במכשירי Nexus ובחמישה סמארטפונים פופולריים של Android.
בניסוי השתמשנו ב-three.js, כפי שעשינו בחלק מהפרויקטים הקודמים שלנו ב-WebGL. התחלנו את ההטמעה על ידי פיתוח גרסה ראשונית של המשחק Trollshaw שתעבוד טוב בטאבלט Nexus 10. אחרי כמה בדיקות ראשוניות במכשיר, הגענו לרשימת אופטימיזציות שדומה מאוד לזו שאנחנו משתמשים בה בדרך כלל במחשב נייד עם מפרט נמוך:
- שימוש במודלים עם פוליגונים ספורים
- שימוש בטקסטורות ברזולוציה נמוכה
- צמצום מספר הקריאות ל-drawcall ככל האפשר באמצעות מיזוג גיאומטריה
- פשטות החומרים והתאורה
- הסרת אפקטים שלאחר העיבוד והשבתת החלקת הקצוות
- אופטימיזציה של ביצועי JavaScript
- רינדור של קנבס WebGL בגודל חצי והגדלה באמצעות CSS
אחרי שהחילו את האופטימיזציות האלה על הגרסה הראשונה והלא סופית של המשחק, הצלחנו להגיע לקצב פריימים יציב של 30FPS שגרם לנו שביעות רצון. בשלב הזה המטרה שלנו הייתה לשפר את התצוגה החזותית בלי להשפיע לרעה על קצב הפריימים. ניסינו הרבה טריקים: חלקם השפיעו מאוד על הביצועים, וחלקם לא השפיעו בצורה משמעותית כפי שקיווינו.
שימוש במודלים עם פוליגונים ספורים
נתחיל מהמודלים. שימוש במודלים עם פוליגונים קטנים בהחלט עוזר לקצר את זמן ההורדה, וגם את הזמן שנדרש לאינטליקציה של הסצנה. גילינו שאפשר להגדיל את המורכבות באופן משמעותי בלי להשפיע הרבה על הביצועים. המודלים של הטרולים שבהם אנחנו משתמשים במשחק הזה כוללים כ-5,000 פנים, והסצנה כוללת כ-40,000 פנים, והכול עובד בסדר גמור.

במיקום אחר בחוויה (עדיין לא פורסם) ראינו השפעה רבה יותר על הביצועים כתוצאה מצמצום של פוליגונים. במקרה כזה, נטען אובייקטים עם פחות פוליגונים למכשירים ניידים מאשר אובייקטים שטענו למחשב. יצירת קבוצות שונות של מודלים תלת-ממדיים דורשת עבודה נוספת, והיא לא תמיד נדרשת. הכול תלוי ברמת המורכבות של המודלים שלכם מלכתחילה.
כשעובדים על סצנות גדולות עם הרבה אובייקטים, חשוב לחלק את הגיאומטריה בצורה אסטרטגית. כך הצלחנו להפעיל ולכבות במהירות רשתות רשתות פחות חשובות, כדי למצוא הגדרה שתעבוד בכל המכשירים הניידים. לאחר מכן, נוכל לבחור למזג את הגיאומטריה ב-JavaScript בזמן הריצה לצורך אופטימיזציה דינמית, או למזג אותה בשלב טרום הייצור כדי לחסוך בבקשות.
שימוש בטקסטורות ברזולוציה נמוכה
כדי לקצר את זמן הטעינה במכשירים ניידים, בחרנו לטעון טקסטורות שונות בגודל של חצי מהטקסטורות במחשב. מסתבר שכל המכשירים יכולים לטפל בגדלים של טקסטורות עד 2048x2048px, ורובם יכולים לטפל בגדלים של 4096x4096px. חיפוש טקסטורות בטקסטורות הנפרדות לא נראה כבעיה אחרי שהן מועלות ל-GPU. כדי למנוע העלאה והורדה מתמידות של טקסטורות, הגודל הכולל של הטקסטורות צריך להתאים לזיכרון של ה-GPU. עם זאת, סביר להניח שזו לא בעיה גדולה ברוב חוויות הגלישה באינטרנט. עם זאת, חשוב לשלב טקסטורות בכמה שפחות גיליונות ספרייטים כדי לצמצם את מספר הקריאות לציור (drawcall) – לכך יש השפעה רבה על הביצועים במכשירים ניידים.

(הגודל המקורי הוא 512x512 פיקסלים)
פשטות החומרים והתאורה
גם בחירת החומרים יכולה להשפיע מאוד על הביצועים, ולכן צריך לנהל אותם בחוכמה בנייד. אחת מהשיטות שבהן השתמשנו כדי לבצע אופטימיזציה של הביצועים היא שימוש ב-MeshLambertMaterial
(לחישוב תאורת קודקוד) ב-three.js במקום ב-MeshPhongMaterial
(לחישוב תאורת texel). בעיקרון, ניסינו להשתמש בשיידרים פשוטים ככל האפשר עם כמה שפחות חישובי תאורה.
כדי לראות איך החומרים שבהם אתם משתמשים משפיעים על הביצועים של סצנה, אתם יכולים לשנות את החומרים של הסצנה באמצעות MeshBasicMaterial
. כך תוכלו לבצע השוואה טובה.
scene.overrideMaterial = new THREE.MeshBasicMaterial({color:0x333333, wireframe:true});
אופטימיזציה של ביצועי JavaScript
כשמפתחים משחקים לנייד, ה-GPU לא תמיד הוא המכשול הגדול ביותר. הרבה זמן מוקדש ל-CPU, במיוחד לפיזיקה ולאנימציות של שלדים. טריק אחד שעוזר לפעמים, בהתאם לסימולציה, הוא להריץ את החישובים היקרים האלה רק בכל פריים שני. אפשר גם להשתמש בשיטות אופטימיזציה של JavaScript שזמינות לגבי מאגר אובייקטים, אוסף אשפה ויצירת אובייקטים.
עדכון של אובייקטים שהוקצתה להם זיכרון מראש בלולאות במקום ליצור אובייקטים חדשים הוא צעד חשוב כדי למנוע 'הפרעות' באיסוף האשפה במהלך המשחק.
לדוגמה, הקוד הבא:
var currentPos = new THREE.Vector3();
function gameLoop() {
currentPos = new THREE.Vector3(0+offsetX,100,0);
}
גרסה משופרת של הלולאה הזו מונעת יצירת אובייקטים חדשים שצריך לבצע עליהם איסוף אשפה:
var originPos = new THREE.Vector3(0,100,0);
var currentPos = new THREE.Vector3();
function gameLoop() {
currentPos.copy(originPos).x += offsetX;
//or
currentPos.set(originPos.x+offsetX,originPos.y,originPos.z);
}
ככל האפשר, פונקציות עיבוד אירועים צריכות לעדכן רק מאפיינים, ולהשאיר ל-requestAnimationFrame
render-loop את הטיפול בעדכון של השלב.
טיפ נוסף הוא לבצע אופטימיזציה של פעולות של הקרנת קרניים ו/או לחשב אותן מראש. לדוגמה, אם צריך לצרף אובייקט למערך משולבים במהלך תנועה בנתיב סטטי, אפשר "לתעד" את המיקומים במהלך לולאה אחת ואז לקרוא מהנתונים האלה במקום לבצע הקרנת קרן על המערך המשולב. לחלופין, כמו שאנחנו עושים בחוויית Rivendell, אפשר להשתמש ב-ray-cast כדי לחפש אינטראקציות של העכבר עם רשת בלתי נראית פשוטה יותר עם פוליגונים קטנים. חיפוש התנגשויות במערך פוליגונים בעל רמת פירוט גבוהה הוא תהליך איטי מאוד, וצריך להימנע ממנו באופן כללי בלולאת המשחק.
רינדור של קנבס WebGL בגודל חצי והגדלה באמצעות CSS
גודל לוח הציור של WebGL הוא כנראה הפרמטר היעיל ביותר שאפשר לשנות כדי לבצע אופטימיזציה של הביצועים. ככל שהקנבס שבו משתמשים כדי לצייר את הסצנה התלת-ממדית גדול יותר, כך צריך לצייר יותר פיקסלים בכל פריים. כמובן שהדבר משפיע על הביצועים.ב-Nexus 10, עם המסך ברזולוציה גבוהה של 2560x1600 פיקסלים, צריך להציג פי 4 יותר פיקסלים מאשר בטאבלט עם צפיפות נמוכה. כדי לבצע אופטימיזציה לנייד, אנחנו משתמשים בטריק שבו אנחנו מגדירים את הלוח לחצי מהגודל (50%) ואז מגדילים אותו לגודל הרצוי (100%) באמצעות טרנספורמציות 3D של CSS במהירות חומרה. החיסרון של השיטה הזו הוא יצירת תמונה מפורטת מדי, שבה קווים דקים עלולים להוות בעיה, אבל במסך ברזולוציה גבוהה האפקט לא נורא. כדאי מאוד להשקיע בכך כדי לשפר את הביצועים.

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

ב-Rivendell יש כמה קטעי שטח שאנחנו כל הזמן משנים את המיקום שלהם בעומק Z ככל שהמשתמש מתקדם בתהליך. כשהמשתמש עובר על פני קטעים, הם מועברים למיקום חדש במרחק רב.
בטירת Dol Guldur, רצינו שהמבוך ייוצר מחדש בכל משחק. כדי לעשות זאת, יצרנו סקריפט שיוצר מחדש את המבוך.
מיזוג של כל המבנה לרשת גדולה אחת כבר מההתחלה יוצר סצנה גדולה מאוד וביצועים נמוכים. כדי לטפל בבעיה הזו, החלטנו להסתיר ולהציג את אבני הבניין בהתאם למיקום שלהן בתצוגה. כבר מההתחלה, הייתה לנו רעיון להשתמש בסקריפט של raycaster 2D, אבל בסופו של דבר השתמשנו בחיסכון בזמן עיבוד (culling) מובנה של three.js. השתמשנו שוב בסקריפט של raycaster כדי להגדיל את התצוגה של 'הסכנה' שבה השחקן נתקל.
השלב הבא הוא טיפול באינטראקציה של המשתמשים. במחשב, הקלט הוא בעזרת עכבר ומקלדת. במכשירים ניידים, המשתמשים מבצעים פעולות באמצעות מגע, החלקה, צביטה, שינוי כיוון המכשיר וכו'.
שימוש באינטראקציה במגע בחוויית השימוש באינטרנט בנייד
הוספת תמיכה במגע היא לא משימה קשה. יש מאמרים נהדרים שאפשר לקרוא על הנושא. אבל יש כמה דברים קטנים שיכולים להקשות על התהליך.
אפשר להשתמש גם במגע וגם בעכבר. ב-Chromebook Pixel ובמחשבים ניידים אחרים עם תמיכה במגע יש תמיכה גם בעכבר וגם במגע. אחת הטעויות הנפוצות היא לבדוק אם המכשיר תומך במגע, ואז להוסיף רק מאזינים לאירועי מגע ולא להוסיף מאזינים לאירועי עכבר.
לא מעדכנים את הרינדור ב-event listeners. במקום זאת, שומרים את אירועי המגע במשתנים ומגיבים להם בלולאת הרינדור של requestAnimationFrame. כך אפשר לשפר את הביצועים ולמזג אירועים סותרים. חשוב להשתמש שוב באובייקטים במקום ליצור אובייקטים חדשים ב-event listeners.
חשוב לזכור שמדובר במגע רב-משתמש: event.touches הוא מערך של כל המגעים. במקרים מסוימים, עדיף להסתכל על event.targetTouches או על event.changedTouches ולהגיב רק למגעים שמעניינים אתכם. כדי להבדיל בין הקשות לבין החלקות, אנחנו משתמשים בהשהיה לפני שאנחנו בודקים אם המגע זז (החלקה) או אם הוא עדיין באותו מקום (הקשה). כדי לזהות צביטה, אנחנו מודדים את המרחק בין שתי הנגיעות הראשוניות ואת השינוי במרחק הזה לאורך זמן.
בעולם תלת-ממדי, צריך להחליט איך המצלמה תגיב לפעולות של העכבר לעומת פעולות של החלקה. אחת מהדרכים הנפוצות להוספת תנועה של המצלמה היא לעקוב אחרי תנועת העכבר. אפשר לעשות זאת באמצעות שליטה ישירה באמצעות מיקום העכבר או באמצעות תנועה דלתא (שינוי מיקום). לא תמיד רוצים שהתנהגות הדפדפן בנייד תהיה זהה לזו של הדפדפן במחשב. בדקנו הרבה כדי להחליט מה מתאים לכל גרסה.
כשעובדים עם מסכים קטנים ומסכי מגע, לרוב האצבעות של המשתמש והגרפיקה של האינטראקציה עם ממשק המשתמש מפריעות למה שאתם רוצים להציג. אנחנו רגילים לחשוב על זה כשאנחנו מעצבים אפליקציות מקוריות, אבל לא היינו צריכים לחשוב על זה בעבר לגבי חוויות אינטרנט. זהו אתגר אמיתי למעצבים ולמעצבי UX.
סיכום
החוויה הכוללת שלנו מהפרויקט הזה היא ש-WebGL בניידים פועל מצוין, במיוחד במכשירים חדשים ומתקדמים. כשמדובר בביצועים, נראה שמספר הפוליגונים וגודל הטקסטורה משפיעים בעיקר על זמני ההורדה וההפעלה, ושחומרים, שידרים וגודל הלוח של WebGL הם החלקים החשובים ביותר לביצוע אופטימיזציה לשיפור הביצועים בנייד. עם זאת, הביצועים מושפעים מסך כל החלקים, כך שכל מה שאפשר לעשות כדי לבצע אופטימיזציה חשוב.
טירגוט למכשירים ניידים גם אומר שצריך להתרגל לחשוב על אינטראקציות מגע, ושהעניין לא קשור רק לגודל הפיקסלים – הוא קשור גם לגודל הפיזי של המסך. במקרים מסוימים נאלצנו לקרב את מצלמת ה-3D כדי שנוכל לראות בבירור מה קורה.
הניסוי הופעל, וזו הייתה חוויה נהדרת. אנחנו מקווים שתיהנו!
רוצה לנסות? מתחילים את המסע שלכם בארץ התיכונה.