אופטימיזציה של אטלסי טקסטורות: היכן נופל הפריים
הסצנה של משחק מובייל גדלה באיטרציות: תחילה עשרה אובייקטים בשלב, אחר כך מאה, ותקציב זמן הפריים מפסיק להסתדר בלי שמים לב.
נפילות פריימים במכשירים חלשים וטעינה ארוכה של שלב כמעט אף פעם לא קשורות לתסריטים או ללוגיקה — בפרויקטים שבדקנו, הסיבה לרוב היא באופן שבו נבנה אטלס הטקסטורות של משחק המובייל.
טעות אופיינית — דף גדול אחד לכל קטגוריית המשאבים: ממשק, סביבה ואפקטים מונחים יחד, והמנוע מעלה לזיכרון את כל הדף בגלל שניים-שלושה אלמנטים נחוצים. במקביל גדל נפח הבילד: חלק מהדף אינו נדחס, חלק מהערוצים נשאר ללא אובדן, ופענוח של תמונה גדולה תופס חלק ניכר מזמן ההתחלה של השלב.
בנפרד פוגע פורמט לא מדויק: אלמנטים ללא שוליים בקצוות, ערוצי שקיפות שונים באותו דף, ערבוב משאבים עם רמת פירוט נדרשת שונה. במכשירים חלשים זה יוצר קצוות קרועים ופריקה-טעינה חוזרת של הדף במקום שבו היה מספיק לטעון פרגמנט אחד.
אופטימיזציית אטלסי טקסטורות מתחילה לא מהאריזה, אלא מהפרופיל: מה ומתי באמת מגיע למסך. אנחנו מצלמים את הפרופיל הזה במכשירי היעד, אחר כך חותכים את הדפים לפי סצנות וקבוצות נראות, בוחרים פורמט דחיסה לפי תוכן הדף ומוסיפים רמות מיפ היכן שהאובייקט נכנס לעומק הפריים.
את הבילד בונים באותו סדר כמו אצלכם — אורז כמו TexturePacker מתחבר כשלב אוטומציה, כדי שעריכה ידנית של דפים לא תחזור לפרויקט. מפרטים כיצד זה נסגר.
מה נותנת אריזה מחדש של אטלסים של סצנת מובייל
אטלס שנבנה נכון מקטין קודם כל את נפח משאבי הבילד: אלמנטים חוזרים ותמונות קטנות נארזים לדפים משותפים, כפילויות ושוליים מיותרים בקצוות נעלמים. מהמשלוח נעלם חלק ניכר מנתוני המקור, ואיתו גם הזמן לפרוק שלהם ולבדיקת שלמות בהפעלה הראשונה.
זמן הטעינה של הסצנה מתקצר בזכות שני אפקטים: פחות קבצים שצריך לפתוח ולקרוא, פחות נתונים שמפוענחים במעברים בין מסכים. במכשיר ממוצע ההבדל מורגש למשתמש — מסך המוכנות לא מחזיק אותו שניות מיותרות, והטעינה ברקע של הסצנות הבאות לא מתחרה ברינדור של הנוכחית.
צריכת הזיכרון במכשיר יורדת באופן צפוי: דף גדול אחד במקום עשרות קטנים מפחית את תקורת האחסון וההחלפה, ופורמט אחסון אחיד מסיר שחרורים תכופים וטעינות חוזרות. זה אותו מרווח שנשאר לגרפיקה בדגמים זולים וקובע אם הסצנה תגיע לסיומה בלי פריקה כפויה.
יציבות הפריימים עולה ישירות: ככל שיש פחות החלפות חומרים לפריים, כך זמן הרינדור אחיד יותר והנפילות בשיאים נדירות יותר. כשלים בגלל חוסר בזיכרון, שבדרך כלל צפים בסשנים ארוכים, נעלמים יחד עם פיצול המשאבים להמון קבצים קטנים.
את התוצאה בודקים לפי ארבעה סימנים מדידים: משקל הבילד קטן, הסצנה נטענת מהר יותר, הזיכרון נצרך בחסכנות, והפריימים זורמים באופן אחיד בלי צניחות. אם אפילו סעיף אחד לא זז, כדאי להרכיב מחדש את אריזת האטלסים — לפי מדידות, לא לפי תחושות.
מצבי אריזה ודחיסה: השוואת גישות
המשאבים של משחק מובייל נתקלים בשתי מגבלות: נפח זיכרון הווידאו ומספר החלפות החומרים לפריים. את מצב האריזה והדחיסה בוחרים לא «לפי טעם», אלא לפי תוכן האטלס — גרדיאנטים, אייקונים קטנים של ממשק ורקעים במסך מלא מתנהגים אחרת.
דחיסה ללא אובדן שומרת על כל פיקסל, אבל בזיכרון טקסטורה כזו בכל זאת נפרסת במלואה — הרווח נשאר רק בגודל הבילד. דחיסה בלוקית עם אובדן (ASTC) מחזיקה את הנתונים דחוסים ישירות בזיכרון המאיץ: הנפח ורוחב הפס יורדים, אבל מופיעים ארטיפקטים על קווים דקים וגרדיאנטים.
| אפשרות | מתי מתאימה | מגבלות |
|---|---|---|
| ללא אובדן | ממשק, גופנים, מסכות, תצוגה מקדימה | נפרסת במלואה בזיכרון, לא חוסכת בזיכרון הווידאו |
| בלוקית עם אובדן | רקעים גדולים, מפות חומרים | ארטיפקטים בגרדיאנטים, דרישות לכפילות הבלוק |
| חלוקה לעמודים | האטלס לא נכנס במגבלת המכשיר | יותר החלפות חומרים לפריים |
| אריזה צפופה עם רוטציה | ספרייטים מגוונים, רשת ברורה | הרוטציה שוברת אלמנטים בעלי תשעה חלקים ושיידרים |
צפיפות האריזה מתנגשת עם החלוקה לעמודים. ככל שהאטלס צפוף יותר, כך יש פחות עמודים ופחות החלפות, אבל רוטציה של ספרייטים שוברת אלמנטים בעלי תשעה חלקים ושיידרים שתלויים בקואורדינטות. החלוקה, להיפך, מקלה על תחזוקת הארטים, אבל מוסיפה החלפות לפריים.
לכן אנחנו לא קובעים מצב אחד: ממשק וגופנים הולכים ללא אובדן, רקעים גדולים — בדחיסה בלוקית, אטלסים עמוסים מדי מתחלקים לפי תדירות השימוש המשותף. הפרופיל מקובע בסקריפט הבנייה, וארט חדש לא שובר את הפריסה.
סדר העבודות: מהמדידה ועד מסירת הפרויקט
את האטלס אורזים מחדש לא «בעין», אלא לפי תוצאות המדידה: תחילה מצלמים פרופיל במכשירי היעד ומוצאים מה בדיוק נתקע — מילוי בלוק הטקסטורה, נפח זיכרון הווידאו התפוס, מספר קריאות הרינדור או זמן טעינת הסצנה. בהמשך באה רצף של צעדים, שבו כל החלטה נשענת על הנתונים של הקודם, ולא על הרגל של המפתח.
- מדידת צווארי בקבוק. עורכים פרופיילינג לסצנות ולממשקים הבעייתיים על מדגם מייצג של מכשירים — מחלשים ועד מובילים. מתעדים מדדי בסיס: זמן פריים, צריכת זיכרון, משך הטעינה, מספר החלפות החומר.
- סכמת חלוקת האטלס. מקבצים ספרייטים לפי תדירות ההופעה המשותפת בפריים ולפי החומר המשותף, מחשבים תקציב שטח ומרווח טכנולוגי בין אלמנטים, כדי לא לתפוס «דליפה» של קצוות ברמות המיפ.
- אריזה מחדש ובנייה מחדש. אורזים אטלסים לפי הסכמה המאושרת, מביאים גדלים לערכים נוחים לדחיסה, מעדכנים את ההפניות במשאבים ובודקים שהבילד עובר בלי עריכות ידניות.
- בדיקה במכשירי היעד. מריצים שוב את אותן סצנות ומשווים מדדים לבסיס: בודקים אם צוואר הבקבוק נעלם ואם לא הופיע חדש — למשל, גידול בזמן הטעינה בגלל פיצול קבצים.
- ייצוב ורגרסיה. מתעדים מדידות ייחוס ומוסיפים בדיקה אוטומטית של סכמת האריזה, כדי שמנה הבאה של גרפיקה לא תשבור את התוצאה שהושגה.
- מסירת הפרויקט לצוות. מוסרים את סכמת החלוקה, סקריפטי האריזה מחדש, תקנון להוספת משאבים חדשים והוראה קצרה לאמנים — מכאן צוות הלקוח מנהל את האריזה בעצמו, בלעדינו.
כל שלב מסתיים בארטיפקט: מדידה, סכמה, אטלסים מעודכנים, דוח לפי מכשירים, תקנון. כך התוצאה ניתנת לשחזור, ולא נשענת על מהנדס אחד.
ארטיפקטים: סכמות חלוקה, פרופילי זיכרון, תקנונים
אחרי העבודה נשאר לצוות הלקוח לא דוח, אלא ערכה עובדת: לפיה מהנדס חדש משחזר את בניית האטלסים ומקבל את אותה תוצאה בלי לפנות אלינו. כל מסמך קשור לשלב מסוים בצינור המשאבים, ולכן הפער בין «מה שסיכמו» ל«מה שנבנה» נראה מיד — בשלב הבדיקה, ולא במכשיר של השחקן.
- סכמת חלוקת המשאבים — אילו קבוצות תמונות מתאחדות לאטלס אחד, לפי איזה סימן (תדירות תצוגה בו-זמנית, חומר משותף, מסך), ואילו נשארות כקבצים נפרדים. עם ציון הגודל המרבי של הדף וכללים למילוי שטח פנוי.
- פרופיל הזיכרון של האפליקציה — מדידות צריכה במכשירי היעד לפני ואחרי התיקונים, מחולקות לפי סצנות: טעינה, קרב, ממשק, סשן ארוך. משמש כייחוס להשוואה בגרסאות הבאות.
- כללי שמות למשאבים — תבנית אחידה לתמונות, אטלסים וקבצים נגזרים, כדי שהבונה ימצא תלויות באופן אוטומטי, והחיפוש אחרי הדף הנחוץ לא יהפוך למעבר על תיקיות.
- תקנון רוויזיות לגרפיקה — מי ומתי בוחן מחדש את הרכב האטלסים, מה נחשב עילה לבנייה מחדש (סט תוכן חדש, גידול בצריכה, תלונות על טעינות), וכיצד מתעדים את התוצאה.
- תיעוד לבניית המשאבים — סדר הצעדים, הרכב הכלים ונקודות בקרה עם בדיקות: אזורים לא מלאים, כפילויות, משאבים מחוץ לסכמה. אדם חדש עובר את הדרך ממקורות לסט מוכן בלי הסברים בעל פה.
- מפת בעלות — אילו קבצים והגדרות משויכים לתפקידים מסוימים, מה משתנה רק דרך רוויזיה, ומה אפשר לתקן מקומית.
את הערכה מוסרים בצורה שמתאימה לעריכות: סכמות וכללים מונחים לצד הפרויקט ומנוהלים בגרסאות יחד איתו, ולא בהתכתבות. אחרי כמה גרסאות הצוות חוזר למסמכים, ולא אלינו — זהו הקריטריון שהעבודה נעשתה.
מקרה בוחן: הסרנו נפילות במכשירים חלשים
מה היה לפני העבודה
אלינו הגיע פרויקט שבו הגרפיקה גדלה מהר יותר מהיכולת לסדר אותה: כל מסך חדש משך סט טקסטורות משלו, חלק מהתמונות הוכפל בכמה אטלסים בבת אחת, ונכסים נדירים שכבו במאגר משותף ונעינו תמיד.
בקצה התחתון של המכשירים הנתמכים זה יצר נפילות בקרב ובמעברים בין סצנות — הזיכרון נתקע בתקרה, והטעינה קרתה בדיוק ברגע הפעולה של השחקן.
מה שינו
הרכבנו מחדש את האטלסים לפי הופעה משותפת בפועל: משאבים של מסך אחד — לסט אחד, נדירים הוצאו בנפרד עם טעינה לפי דרישה. כפילויות הוחלפו בהפניות למקור משותף, מספר הפריימים באנימציות הוגבל, ופורמטי הדחיסה נבחרו לפי מה שהמכשיר מפענח בחומרה.
- הסרנו חפיפות של אטלסים בין מסכים;
- אלמנטים לשימוש חוזר הוצאו למאגר משותף במקום עותקים;
- סצנות נדירות הועברו לטעינה אסינכרונית עם טעינה מוקדמת בהפסקות;
- המטמון חומם בהתחלה, כדי שהכניסה הראשונה לקרב לא תאסוף משאבים.
כיצד מדדנו את התוצאה
מדדנו לפי עובדות, לא בעין: פרויקט על Unity מפיק פרופיל פריים-אחר-פריים, ונשענו עליו. בדקנו לא על דגמים מובילים, אלא בקצה התחתון של רשימת התמיכה — אותם מכשירים שבאמת מביאים התקנות.
אותו סט סצנות לפני ואחרי: נפח הבילד קטן בכשליש בערך, טעינת השלב נעשתה מהירה פי שניים, הנפילות במכשירים חלשים נעלמו, והזיכרון הפסיק להיתקע בתקרה.
מה לעשות אם האפליקציה כבר יצאה ומתעדכנת?
אפליקציה שיצאה מתעדכנת בקצב שלה, ואף אחד לא יעצור גרסאות בגלל אריזה מחדש של גרפיקה. אופטימיזציית אטלסי טקסטורות משתלבת במחזור הנוכחי: גל אחד — מסך אחד או לוקיישן אחד. כל צעד חייב להיות בר-החזרה, אחרת הסיכון בעדכון גובר על הרווח.
התנאי המרכזי — תאימות לאחור של המשאבים. בילדים ישנים במכשירים ממשיכים לקרוא את הדפים הקודמים, חדשים — כבר ארוזים מחדש: שמות הסלוטים ומפתחות הגישה לפריימים נשמרים, ורק הפריסה משתנה. הסט הקודם נשאר בהפצה עד הרגע שבו חלק השחקנים בגרסאות מיושנות יורד לזניח.
מעבר חלק נבנה על מדידות, ולא על אמונה ברווח. בכל גל משווים את זיכרון הווידאו התפוס, זמן טעינת השלב ותדירות הטעינות במהלך המשחק. אם האפקט לא מאושר על מטריצת המכשירים, הגל לא נכנס לגרסה — התיקונים ממתינים בענף.
הסיכונים ידועים ונסגרים מראש: ארטיפקטים בקצוות הדפים בעת סינון ומעבר בין רמות פירוט, הפניות שאבדו אחרי מחיקת פריימים ישנים, גידול בגודל הבילד בתקופת המעבר. החזרה לסכמה הקודמת נעשית בדגל קונפיגורציה, בלי עדכון חירום.
כך האופטימיזציה מתקדמת במקביל להוצאת גרסאות, ולא במקומה: כל גל נותן חתך מדיד בזיכרון ובטעינה, והמראה וההתנהגות העקביים של המשחק עבור השחקן נשארים ללא שינוי.
סינון, שקיפות, זיכרון: שאלות נפוצות
שאלות כאלה בדרך כלל צפות כבר בשיחה, כשהאטלס הגולמי נבנה ורואים היכן הוא נופל. להלן תשובות קצרות למה שלרוב מעורר ספקות אצל צוות הלקוח.
העיקרון הכללי אחד: החלטה על אטלס מתקבלת לפי מדידות — הזיכרון התפוס, זמן הכנת הסצנה, מספר החלפות הטקסטורות לפריים. בלי המספרים האלה, הוויכוח על שוליים או איכות דחיסה נשאר עניין של טעם.
בקצוות האלמנטים מופיעים שוליים — אפשר לרפא את זה?
כן, זה הארטיפקט הנפוץ ביותר, והסיבה טכנית: הסינון תופס פיקסלים של האלמנט השכן. זה נסגר במרווח של 2–4 פיקסלים בקצה, שאליו משכפלים את הפיקסלים הקיצוניים, ובעותקים מוקטנים שנבנו נכון של הדף — אחרת השוליים יופיעו ברמות הפירוט הרחוקות.
אפשר להחזיק אלמנטים חצי-שקופים באטלס משותף?
אפשר, אבל בהסתייגויות: סדר המיזוג ואופן אחסון השקיפות הם קריטיים. אם אלמנט חצי-שקוף שכב באותו דף עם אטומים, הוא מתמזג עם פיקסלים זרים ונותן הילה כהה — אלמנטים כאלה מוציאים לדף נפרד או לשכבת רינדור נפרדת.
מדוע אחרי חלוקת האטלס הזיכרון גדל?
כי כל דף הוא טקסטורה נפרדת עם גודל מינימלי משלו, מרווח בקצוות ועותקים מוקטנים משלו. מפצלים לא «לפי תיקיות», אלא לפי פורמט דחיסה ותדירות השימוש באלמנטים: אלמנטים נדירים עוברים לדף נפרד שנטען לפי דרישה.
איך מוודאים שהדחיסה לא הרגה את האיכות?
משווים דף דחוס לייחוס ללא דחיסה, בודקים סטייה לפי ערוצים, ובעין — בגרדיאנטים, קווים דקים וטקסט קטן. אלמנטים רגישים לאובדן פרטים מחזיקים בדף נפרד עם מצב עדין יותר.
ממה להתחיל אם האפליקציה פשוט «מאטה»?
ממדידה. מצלמים את נפח הטקסטורות הנטענות, מספרן וזמן הכנת הסצנה, אחר כך בודקים מה משתנה במעבר בין מסכים. לרוב צוואר הבקבוק הוא לא גיאומטריה, אלא נפח הטקסטורות והחלפות החומרים המיותרות בפריים.
נדון במשימה: מדידת סצנה, תוכנית עבודה, הערכה
אופטימיזציית אטלסי טקסטורות מתחילה לא מתיקונים, אלא ממדידת הסצנה: בלי מספרים כל שינוי הופך לניחוש. כדי לחזור עם הערכת לוחות זמנים ותוכנית עבודה, ולא עם המלצות כלליות, אנחנו צריכים נתוני פתיחה של הפרויקט.
- הבילד הנוכחי של המשחק או גישה לריפו — תמונת מצב אמיתית, לא שחזור בעל פה;
- רשימת סצנות שבהן נראות נפילות: טעינת שלב, קרב, מסך החנות, מלאי;
- סטטיסטיקה של זיכרון וזמן רינדור, אם הצוות כבר אוסף אותה;
- רשימת אטלסים עם גדלים וכללי אריזה: אילו סטים של ספרייטים שייכים לאיזה מסך;
- מגבלות על מכשירי היעד וגרסת המערכת המינימלית הנתמכת;
- נתוני אנליטיקת קריסות וביקורות — היכן מתלוננים יותר על האטות וחימום המארז.
לפי הנתונים האלה אנחנו בונים תוכנית עבודה לאופטימיזציה: סדר העדיפויות של האטלסים, מה אורזים מחדש, מה מפצלים לפי מסכים, מה פורקים מהזיכרון בין סצנות. לתוכנית מצרפים הערכת לוחות זמנים לפי שלבים — כך אתם רואים באיזה שלב מופיעה תוצאה מדידה.
סדר תחילת העבודה: דנים במשימה, מסכימים על היקף השלב הראשון ונקודות הבקרה, ואחר כך מקבעים את התוכנית ומתחילים. תארו את המשימה ואשלחו את מה שכבר יש — נחזור עם תוכנית עבודה והערכה.







