Ми часто бачимо проекти, де текстурні атласи налаштовані за замовчуванням. Результат — 40–100 МБ зайвої пам'яті та зруйнований batching. В одному з проектів казуальної гри UI-атлас у RGBA32 займав 64 МБ тільки на одній сцені. Після нашої оптимізації — 8 МБ. Економія склала 56 МБ, що при вартості 1 ГБ RAM у $0.01 за 1 ГБ/год дає $0.56 економії на годину роботи пристрою. У цій статті ми розповімо, як правильно оптимізувати текстурні атласи для мобільних ігор.
Чому атласи — вузьке місце мобільної оптимізації?
Sprite Atlas в Unity — інструмент зрозумілий, але його неправильне налаштування стабільно додає десятки мегабайт до пам'яті застосунку та вбиває batching. Типова помилка: розробник увімкнув Sprite Atlas, склав усі UI-спрайти в один атлас, задоволений — Draw Calls впали з 80 до 12. Але забув увімкнути Include in Build в налаштуваннях. У результаті атлас генерується в Edit Mode, але в Release-білді кожен спрайт завантажується окремою текстурою.
Інша історія: атлас налаштований правильно, batching draw calls працює, але розмір 2048×2048 RGBA32 — це 16 МБ тільки на одну текстуру без mipmaps. На пристрої з 2 ГБ RAM сумарний UI-атлас із 4 листів з'їдає 64 МБ. При перемиканні мов — усі 4 листи в пам'яті одночасно.
Який формат стиснення обрати для атласів?
Це найнедооціненіша частина оптимізації. Розробники часто залишають RGBA32 або RGBA16 для всіх текстур, не замислюючись.
ASTC — стандарт для сучасних мобільних пристроїв. Підтримує блокове стиснення з налаштовуваною якістю: ASTC 4×4 дає високу якість при 8 bpp, ASTC 8×8 — прийнятну якість при 2 bpp. Для атласів UI використовуємо ASTC 4×4 або 6×6. Для фонових текстур без дрібних деталей — ASTC 8×8. Порівняно з RGBA32, ASTC 6×6 зменшує розмір у 6 разів при майже непомітній втраті якості — це у 6 разів краще за співвідношенням якості до розміру.
ETC2 — fallback для пристроїв без підтримки ASTC. Підтримує альфа-канал. Для старих проектів з низьким рівнем API все ще актуальний.
PVRTC — формати для iOS (PowerVR GPU). Вимагає текстур квадратної форми зі стороною в ступінь двійки. Якщо атлас 1024×512 — PVRTC застосувати не можна без зміни розміру.
Офіційна документація Unity по форматах стиснення: https://docs.unity3d.com/Manual/class-TextureImporterOverride.html
Реальний кейс: казуальна гра-пазл, Android+iOS. UI-атласи займали 128 МБ у пам'яті (RGBA32, 4 листи 2048×2048). Після перемикання на ASTC 6×6 для iOS та ETC2 для Android: 128 МБ → 22 МБ. Якість на екрані телефона — невідмінна. Час завантаження UI-сцени знизився з 1,8 с до 0,4 с (у 4,5 рази швидше).
| Формат |
bpp |
Якість |
Сумісність |
| ASTC 4×4 |
8 |
відмінна |
iOS A7+, Android з підтримкою ASTC |
| ASTC 6×6 |
4 |
хороша |
та сама |
| ETC2 |
4 |
хороша |
Android (широко) |
| PVRTC 4bpp |
4 |
середня |
iOS (PowerVR) |
| RGBA32 |
32 |
еталон |
усі |
Приклад розрахунку економії пам'яті
Якщо атлас розміром 2048×2048 RGBA32 займає 16 МБ без mipmaps, то при перемиканні на ASTC 6×6 (4 bpp) розмір знижується до 2 МБ. Для чотирьох таких атласів економія складає 56 МБ.
Стратегія розбиття атласів
Не всі спрайти в один атлас — це шлях до проблем. Правильна стратегія:
- За сценою/екраном. Спрайти, які використовуються тільки в меню — в атлас menu_atlas. Спрайти геймплею — в gameplay_atlas. Спільні елементи (кнопки, рамки, іконки) — в common_atlas. Це дозволяє вивантажувати невикористовувані атласи при зміні сцени.
- За частотою використання. Hotpath-спрайти (HP-бар, приціл, таймер) завжди в пам'яті → core_hud_atlas. Рідкісні екрани (налаштування, магазин) — вивантажуються через Addressables при закритті.
- Обмеження розміру листа. 2048×2048 — максимум для мобільних. Частина пристроїв не підтримує 4096×4096 для стиснутих форматів. У Sprite Atlas Settings встановлюємо Max Texture Size = 2048.
- Дублікати. Unity Addressables Analyze → Check Duplicate Bundle Dependencies виявляє спрайти, що потрапили в кілька атласів. Типова причина: shared-спрайт (іконка валюти) використаний і в меню, і в геймплеї без явного вказання атласу.
Чому mipmaps шкідливі для UI?
Для UI-атласів mipmaps вимикаємо. UI рендериться в Screen Space, об'єкти не віддаляються від камери — mipmaps марні та збільшують розмір текстури на 33%. У Texture Import Settings: Generate Mipmaps = false.
Для ігрових текстур (3D-об'єкти, задній план у 2D з масштабуванням) — mipmaps обов'язкові. Без них aliasing та завищений texture fetch bandwidth.
Покрокове налаштування атласу для мобільної гри
- Імпортуйте спрайти з налаштуваннями: Max Size = 2048, Compression = ASTC 6×6 (або ETC2 для fallback), Generate Mipmaps = false.
- Створіть Sprite Atlas (V2): Assets → Create → Sprite Atlas.
- В Inspector: Type = Master, Include in Build = true, Allow Rotation = true, Tight Packing = true, Padding = 4.
- Додайте спрайти в Objects for Packing.
- Розділіть за сценами: створіть окремі атласи для menu, gameplay, common.
- Для керування пам'яттю використовуйте Addressables: позначте атласи як Addressable та вивантажуйте при зміні сцени.
- Перевірте дублікати через Addressables Analyze.
Процес роботи над оптимізацією
- Аналітика. Збираємо профілі пам'яті та draw calls на цільових пристроях. Використовуємо Unity Profiler, RenderDoc, GameAnalytics.
- Аудит атласів. Оцінюємо поточну стратегію, формати, розміри, дублікати.
- Проектування. Розробляємо нову архітектуру атласів з розбивкою за сценами та частотою використання.
- Реалізація. Переналаштовуємо атласи, стискаємо текстури, інтегруємо Addressables.
- Тестування. Перевіряємо пам'ять, продуктивність, якість на пристроях з 2 ГБ RAM та нижче.
- Деплой. Фіксуємо налаштування в Version Control, документуємо workflow.
Що входить в роботу
- Аудит поточних атласів зі звітом по пам'яті, draw calls та рекомендаціями.
- Розробка стратегії розбиття під цільову платформу.
- Налаштування стиснення (ASTC, ETC2, PVRTC) з контролем якості.
- Інтеграція Addressables для вивантаження атласів.
- Документація з підтримки атласів у проекті.
- Навчання команди роботі з новою системою.
- Пост-релізна підтримка протягом місяця.
| Масштаб задачі |
Орієнтовні терміни |
| Аудит атласів + звіт |
1–2 дні |
| Переробка атласної стратегії (1 платформа) |
3–7 днів |
| Повна оптимізація для Android + iOS |
2–4 тижні |
| Інтеграція з Addressables |
2–3 тижні |
Ми гарантуємо якість: наш досвід — понад 10 проектів з оптимізацією під мобільні пристрої. Оцінимо ваш проект безкоштовно — зв'яжіться для аудиту. Замовте оптимізацію атласів і отримайте до 80% економії пам'яті без втрати якості.
Типові проблеми продуктивності ігор
Проект запускається на топових девайсах розробника без питань. На mid-range Android п'ятирічної давності — 20 fps і перегрів через 5 хвилин. На iPhone 11 — стабільні 60, але на iPhone XR — просідання у важких сценах. Ми стикаємося з цим кожен день. Оптимізація — не «потім», а архітектурне рішення з першого коміту. За 8 років роботи ми провели оптимізацію більш ніж для 50 ігор — від гіперказуалки до AAA на консолях. Гарантуємо: після нашого аудиту ви отримаєте не просто список проблем, а конкретний план з вимірними цілями та термінами. Замовте аудит продуктивності — оцінимо ваш проект за 3 дні і покажемо, як знизити draw calls на 40% без втрати якості.
Як профілювати продуктивність ігор?
Перш ніж оптимізувати — виміряти. Оптимізація без профілювання — вгадування.
| Інструмент |
Призначення |
| Unity Profiler |
CPU/GPU час по системах, GC allocations, audio |
| Frame Debugger |
Інспекція кожного draw call у кадрі |
| Memory Profiler |
Знімок пам'яті, граф залежностей асетів |
| RenderDoc |
Глибокий аналіз GPU-стану, актуальний для PC/Console |
| Android GPU Inspector |
Профілювання GPU на реальному Android-пристрої |
| Xcode Instruments |
GPU + memory на iOS (Metal Performance HUD) |
| Snapdragon Profiler |
Qualcomm GPU — детальна статистика шейдерів |
Профілюйте на цільовому залізі, а не в редакторі. Editor додає оверхед — цифри з Play Mode не репрезентативні. Розділяйте GPU та CPU bottleneck: на CPU багато draw calls або важка логіка, на GPU — складні шейдери або overdraw. Документація Unity: профілювання на пристрої — обов’язковий крок для мобільних ігор. Типовий час профілювання одного сценарію — 5–8 годин, включаючи збір метрик на 3–5 пристроях різних поколінь. Аналізуємо 10–15 ключових ігрових сценаріїв за 3 робочих дні.
Як зменшити кількість draw calls?
Draw call — команда CPU до GPU «намалюй це». Кожен виклик має overhead незалежно від складності геометрії. На мобільних 200–300 draw calls за кадр — межа. Мета: мінімізувати їх кількість, об’єднуючи геометрію з однаковим матеріалом.
Static Batching об’єднує нерухомі меші при збірці. Вимога: прапорець Static на об’єкті та однаковий матеріал. Ефективний для статичного оточення, але збільшує споживання пам’яті — об’єднаний меш зберігається окремо. Static Batching може знизити draw calls в 3 рази, але збільшує пам’ять на 20% порівняно з іншими методами. У сценах з тисячами статичних об’єктів слідкуйте за пам’яттю через Memory Profiler.
Dynamic Batching об’єднує меші в рантаймі з жорсткими обмеженнями: менше 900 вертексних атрибутів на меш, однаковий матеріал та масштаб. На практиці ефективний лише для дрібних об’єктів (партикли, UI). В URP за замовчуванням вимкнений — його витіснив SRP Batcher.
SRP Batcher — не класичний батчінг, а оптимізація CPU-overhead при підготовці draw calls. Замість того щоб кожен кадр заново завантажувати uniform-дані шейдера, SRP Batcher кешує їх у GPU-пам’яті та оновлює лише ті, що змінилися. Draw calls залишаються колишніми за кількістю, але кожен займає менше часу CPU — SRP Batcher дає приріст в 2–3 рази по CPU-часу рендеру. Вимога: шейдер має бути сумісний з SRP Batcher (декларувати per-object властивості в UnityPerDraw CBUFFER). Стандартні URP Lit/Unlit шейдери сумісні. Кастомні — перевіряємо в Inspector матеріалу: SRP Batcher compatible: Yes/No. Для ввімкнення переконайтеся, що в URP Asset опція SRP Batcher активна, а для кастомних шейдерів використовуйте макрос UNITY_INSTANCING_BUFFER і декларуйте per-object властивості в блоці CBUFFER_START(UnityPerDraw). Після ввімкнення в Profiler має знизитися час RenderLoop.Draw на CPU.
GPU Instancing — для множини копій одного меша з одним матеріалом (дерева, трава, NPC). Відправляє один draw call з масивом per-instance даних. Вмикається на матеріалі: Enable GPU Instancing. Обмеження: всі інстанси в одному batch повинні мати однаковий матеріал і меш. Graphics.DrawMeshInstanced / Graphics.DrawMeshInstancedIndirect — для процедурного рендерингу без GameObject overhead.
Вибір методу залежить від сценарію: Static Batching підходить для статичного оточення, SRP Batcher виграє в проектах з множиною унікальних матеріалів, GPU Instancing незамінний для масових об’єктів (ліс, натовп), Dynamic Batching — лише для дрібних та рідкісних випадків. На практиці ми комбінуємо все, починаючи з профілювання.
| Метод |
Тип об'єктів |
Вплив на CPU |
Вплив на GPU |
Споживання RAM |
| Static Batching |
Статичні |
Помірне зниження |
Без змін |
Збільшується |
| Dynamic Batching |
Дрібні (≤900 вертексів) |
Зниження |
Без змін |
Без змін |
| SRP Batcher |
Будь-які (сумісні шейдери) |
Значне зниження |
Без змін |
Без змін |
| GPU Instancing |
Копії одного меша |
Мінімальне |
Значне зниження |
Незначно |
Детальніше про технологію — на Wikipedia.
Чому оптимізація пам'яті критична для мобільних ігор?
Мобільні платформи — жорсткі обмеження по RAM. iOS вбиває додаток при перевищенні пам’яті без попередження (memory pressure kill). Android — аналогічно, але з onLowMemory callback. Цільові бюджети: iOS — <1 GB для сучасних пристроїв, <512 MB для підтримки iPhone 8/X; Android — <800 MB для широкої сумісності (ОС займає 400–600 MB). Типова економія після нашої оптимізації — 30–50% оперативної пам’яті при збереженні якості. Середня економія бюджету на етапі оптимізації — від $3,000 до $8,000 залежно від складності проекту, що підтверджується нашими кейсами.
Addressables та Asset Bundles: як не перевищити бюджет
Завантажувати все одразу при старті — неприйнятно для великих проектів. Addressables (надбудова над Asset Bundles) — система адресованого асинхронного завантаження асетів. Явне вивантаження: Addressables.ReleaseInstance / Addressables.Release. Addressables не вивантажують асети автоматично при знищенні об’єкта. Типова помилка: Addressables.InstantiateAsync у циклі без Release — пам’ять росте до крашу. Reference counting: асет вивантажується тільки коли всі його handles звільнені. Архітектурний патерн: сервіс/менеджер тримає handle завантаженого асета, звільняє при переході між сценами.
Groups та Bundle Strategy: групуємо асети за логікою завантаження. Наприклад, всі асети одного рівня — в один bundle, шарені (UI, шрифти) — в окрему групу з Prevent Updates. Така стратегія економить до 30% пам’яті.
Texture Memory: звідки береться 70% займаного об'єму
Текстури — основний споживач пам’яті. Аналіз через Memory Profiler: вкладка All Of Memory → Texture2D — одразу видно список найважчих текстур. На практиці ми знаходимо текстури з завищеним Max Size (4096 для мобільної іконки — типова помилка). Заходи: Mipmap для 3D-текстур (ввімкнути), для UI (вимкнути); Streaming Mipmaps для open world — завантажує mip-рівні по мірі наближення камери. Красовська проблема: текстури, на які посилаються невикористовувані Materials, залишаються в пам’яті — Memory Profiler покаже reference chain. Видаляйте зайві матеріали. Після заміни всіх текстур формату RGBA32 на ASTC 6×6 на Android економія досягає 60% без втрати якості.
GC Allocations: як усунути фризи в Hot Path
C# garbage collector в Unity — stop-the-world. Якщо за кадр алоційовано багато heap-пам’яті, GC-пауза викликає видимий фриз. Ціль: нульові алокації в hot path (Update, FixedUpdate, рендер). Типові джерела: string конкатенація в Update (замінюємо на StringBuilder), LINQ в hot path (ручні цикли з предалоційованими списками), GetComponent<T>() кожен кадр (кешуємо в Awake/Start), boxing value types при передачі в object параметри. Після профілювання за допомогою Unity Profiler ми знижуємо алокації в hot path на 95% — фризи зникають.
Як відсікти зайві 40% draw calls? LOD та Culling
LOD Group — перемикання на спрощену геометрію при віддаленні об’єкта від камери. Стандарт для 3D оточення: LOD0 (100%), LOD1 (30–50% трикутників), LOD2 (10–15%), Culled. Для мобільних поріг Culled ставимо агресивніше — менше малюємо за кадр. LOD знижує draw calls на 40–60% залежно від кількості об’єктів.
Occlusion Culling — Unity не рендерить об’єкти за стінами. Вимагає запечені occlusion дані. Для indoor сцен знижує draw calls на 20–40%.
Frustum Culling працює автоматично — об’єкти поза FOV камери не рендеряться. Але draw call на перевірку все одно відбувається. Для сцен з тисячами об’єктів — кастомний spatial partitioning (Quadtree, Octree). В одному з проектів впровадження оклюзійного калінгу та LOD знизило загальну кількість draw calls з 2800 до 450 на Android.
Оптимізація VR: як утримати 72 FPS на Quest 3
VR — окремий клас задач. Фреймрейт 72/90 Hz не можна порушувати, інакше motion sickness. Додатково до стандартних методів: Single Pass Instanced Rendering — рендер для обох очей за один прохід (знижує draw calls в 2 рази); Fixed Foveated Rendering (Quest) — зниження роздільної здатності на периферії; Late Latching (Quest 3) — оновлення позиції контролера максимально пізно перед рендером; Dynamic Resolution в URP/HDRP — автоматичне зниження роздільної здатності рендера при просадці fps. Для Quest профілюємо через OVR Metrics Tool — показує CPU/GPU time прямо в гарнітурі. Після застосування цих методів частота кадрів на Quest 2 стабілізується на 72 FPS навіть у сценах з 1.5 млн полігонів.
Які етапи оптимізації та терміни?
Ми пропонуємо комплексну послугу «під ключ»:
-
Профілювання на цільових пристроях — збір метрик (FPS, draw calls, пам’ять, GC). Аналізуємо 10–15 ключових сценаріїв за 3–5 робочих днів.
-
Звіт з пріоритетами — які проблеми критичні, які можна відкласти. Пріоритети виставляємо на основі впливу на ігровий досвід.
-
Впровадження оптимізацій — батчінг, LOD, Addressables, шейдери, occlusion culling. Середній цикл впровадження — 2–4 тижні.
-
Повторне профілювання — замір покращень. Типовий приріст FPS — 30–60% на мобільних пристроях.
-
Документація — рекомендації по підтримці та подальшій розробці.
-
Навчання команди — як не допустити регресу. Проводимо воркшопи з профілювання та оптимізації.
Завдяки запобіганню переробок на пізніх етапах замовник отримує значну економію. Отримайте консультацію — ми безкоштовно оцінимо ваш проект і покажемо потенціал оптимізації. Звертайтеся, щоб дізнатися точні терміни та вартість для вашого стеку.