Ми стикаємося з LOD-проблемами на кожному другому проєкті. Неправильно налаштований LOD Group в Unity дає зворотний ефект: LOD0 перемикається на LOD1 занадто рано, гравець бачить різкий pop, і це сприймається як баг. Або переходи налаштовані за Screen Relative Height без урахування реальної відстані — на ортографічній камері LOD взагалі не працює. Наше завдання — зробити LOD непомітним, але ефективним.
Суть у тому, що LOD — це система управління складністю сцени залежно від видимості об'єкта. Вона працює правильно лише коли враховані: тип камери, швидкість руху гравця, освітлення (відкидні тіні у LOD1 часто гірші, ніж у LOD0), і те, як рушій рахує відстань.
Як виникає LOD pop і що з ним робити?
LOD Pop — головна візуальна проблема. Відбувається, коли геометрія та/або текстури між рівнями відрізняються надто сильно. Класичний випадок: художник зробив LOD1 з на 60% меншою кількістю полігонів, але UV-розгортка поїхала, і нормал-карта не компенсує втрату форми. Перехід з 10 метрів — помітний неозброєним оком. Вирішується через правильний LOD-генератор (ми використовуємо Simplygon або Unity LOD Generator) зі збереженням UV-seam'ів і перевіркою normal projection.
Тіні не слідують за LOD-переходами. В Unity Shadow Caster Culling працює незалежно від LOD Group. Якщо у вас LOD2 — плоский billboard з 2 полігонами, а тінь все ще рахується від LOD0 mesh (тому що Force Shadow Casting = On), ви платите за рендер тіней від повної геометрії об'єкта, якого візуально немає. Це легко пропустити — Frame Debugger показує Shadow Pass з повним Draw Call-бюджетом. Ми гарантуємо усунення таких прихованих витрат.
HLOD (Hierarchical LOD) не налаштований для великих сцен. В open-world проєктах стандартний LOD Group не працює на відстані понад 500 метрів — об'єкти просто culled, але Scene не вивантажується. Unity HLOD (через HLOD Creator пакет) об'єднує далекі об'єкти в один mesh автоматично. Без цього у вас може бути 3000 Draw Calls від дерев на горизонті. Оцінимо ваш проєкт за 1–2 дні.
Чому LOD-політика повинна відрізнятися для різних типів об'єктів?
Під кожну категорію об'єктів (персонажі, будівлі, пропси, рослинність) встановлюємо окрему LOD-політику. Приклад з практики — мобільна RPG, відкритий світ 2×2 км. Дерева: LOD0 (500 полігонів) до 15 метрів, LOD1 (80 полігонів) до 50 метрів, LOD2 (billboard cross) до 150 метрів, Culled за 150 метрів. Будівлі: LOD0 до 30 метрів, LOD1 до 80 метрів, LOD2 до 200 метрів. Таке налаштування дало зниження Draw Calls з 680 до 210 в центрі міста.
Для Unreal Engine працюємо з Nanite там, де це застосовно — на статичних мешах з високим poly count. Але Nanite не замінює LOD для рухомих об'єктів і не працює з напівпрозорими матеріалами. HLOD в UE5 налаштовуємо через World Partition HLOD Layer. Згідно з документацією Unreal Engine, Nanite best practice вимагає вимикати LOD для Nanite-мешів.
Cross-фейдинг замість hard pop. Unity LOD Group підтримує Cross Fade mode — дітерінговий перехід між рівнями. Працює через Dither Fade шейдерне ключове слово. На мобільних платформах дітерінг дешевший, ніж здається — Adreno добре його паралелить. Вмикаємо для великих об'єктів на передньому плані, залишаємо instant-перехід для дрібного пропса вдалині.
Рослинність — окрема історія. SpeedTree LOD інтегрується в Unity через окремий pipeline. Головна пастка — billboard LOD SpeedTree рендериться через окремий BatchRendererGroup, і його потрібно профілювати окремо від основного Draw Call лічильника. Бачили проєкти, де 40% GPU time йшло на 2000 billboard-дерев, які здавалися «вже оптимізованими». Ми навчаємо команду таким нюансам в рамках підтримки.
Як налаштувати LOD за 5 кроків?
- Профілювання GPU: відкрийте Profiler → GPU Usage і Frame Debugger з фільтром по Draw Calls. Визначте, скільки об'єктів рендериться з надлишковою деталізацією.
- Аналіз Overdraw: в Scene View увімкніть Overdraw mode — знайдіть об'єкти з високим overdraw без LOD.
- Класифікація: розділіть об'єкти на категорії та для кожної встановіть кількість рівнів і порогові дистанції за таблицею нижче.
- Генерація LOD: використовуйте Simplygon або вбудований генератор. Для критичних мешів — ручне доопрацювання.
- Тестування на цільовій платформі: перевірте відсутність pop і приріст FPS.
| Категорія об'єктів |
Рівні |
Пороги (м) |
Тіні |
| Персонажі |
LOD0, LOD1, LOD2 |
0–10, 10–25, 25–50 |
On до LOD1 |
| Будівлі |
LOD0, LOD1, LOD2 |
0–30, 30–80, 80–200 |
On до LOD0 |
| Пропси (дрібні) |
LOD0, LOD1 + Culled |
0–5, 5–15 |
Off на LOD1 |
| Рослинність (дерева) |
LOD0, LOD1, Billboard, Culled |
0–15, 15–50, 50–150 |
Off на LOD2 |
Процес роботи над LOD
Аудит починається з Profiler → GPU Usage і Frame Debugger. Дивимося, скільки об'єктів рендериться за межами видимої деталізації. Після аудиту готуємо LOD-специфікацію: таблиця з категоріями об'єктів, кількістю рівнів, пороговими дистанціями, політикою тіней для кожного рівня. Це артефакт, який погоджуємо з командою художників.
Реалізація: або налаштування існуючих LOD Group, або створення LOD-асетів (якщо їх немає), або автоматична генерація з подальшою ручною правкою критичних об'єктів.
—
Після аудиту ми виявили 450 об'єктів без LOD. Створили LOD-асети з 2–3 рівнями, застосували cross-fade для будівель і instant для пропса. Результат: FPS зріс з 28 до 55 на Samsung Galaxy S10. Економія Draw Calls склала 40%.
Що входить в роботу
| Deliverable |
Опис |
| LOD-специфікація |
Документ з порогами, рівнями та налаштуваннями тіней для кожної категорії |
| Налаштовані LOD Group |
Всі асети з правильною конфігурацією |
| Оптимізовані меші |
Згенеровані або вручну доопрацьовані LOD-копії |
| Рекомендації щодо баджену |
Інструкції для художників з підготовки вихідників |
| Підтримка при впровадженні |
Консультації на етапі інтеграції та тестування (1 місяць) |
| Масштаб задачі |
Орієнтовні терміни |
| Аудит LOD-налаштувань + звіт |
1–3 дні |
| Налаштування LOD для однієї сцени (до 200 типів об'єктів) |
1–2 тижні |
| Розробка LOD-політики + реалізація для open-world |
3–6 тижнів |
| Інтеграція HLOD/Nanite в існуючий проєкт |
2–4 тижні |
Зв'яжіться з нами для консультації — ми підготуємо пропозицію під ваш проєкт. Наші інженери мають досвід налаштування LOD для AAA-проєктів та мобільних ігор. Пишіть, оцінимо задачу за 1–2 дні без зобов'язань.
Типові проблеми продуктивності ігор
Проект запускається на топових девайсах розробника без питань. На 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% на мобільних пристроях.
-
Документація — рекомендації по підтримці та подальшій розробці.
-
Навчання команди — як не допустити регресу. Проводимо воркшопи з профілювання та оптимізації.
Завдяки запобіганню переробок на пізніх етапах замовник отримує значну економію. Отримайте консультацію — ми безкоштовно оцінимо ваш проект і покажемо потенціал оптимізації. Звертайтеся, щоб дізнатися точні терміни та вартість для вашого стеку.