Мы сталкиваемся с 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 устройствах разных поколений.
Как уменьшить количество draw calls: от статического батчинга до SRP Batcher
Draw call — команда CPU к GPU «нарисуй это». Каждый вызов имеет overhead независимо от сложности геометрии. На мобильных 200–300 draw calls за кадр — граница. Цель: минимизировать их количество, объединяя геометрию с одинаковым материалом.
Static Batching объединяет неподвижные меши при сборке. Требование: флаг Static на объекте и одинаковый материал. Эффективен для статичного окружения, но увеличивает потребление памяти — объединённый меш хранится отдельно. В сценах с тысячами статичных объектов следите за памятью через Memory Profiler.
Dynamic Batching объединяет меши в рантайме с жёсткими ограничениями: меньше 900 вертексных атрибутов на меш, одинаковый материал и масштаб. На практике эффективен только для мелких объектов (партиклы, UI). В URP по умолчанию отключён — его вытеснил SRP Batcher.
SRP Batcher — не классический батчинг, а оптимизация CPU-overhead при подготовке draw calls. Вместо того чтобы каждый кадр заново загружать uniform-данные шейдера, SRP Batcher кэширует их в GPU-памяти и обновляет только изменившиеся. Draw calls остаются прежними по количеству, но каждый занимает меньше времени CPU — иногда в 2–3x по 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% оперативной памяти при сохранении качества.
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% — фризы исчезают.
LOD и Culling: как отсечь лишние 40% draw calls
LOD Group — переключение на упрощённую геометрию при удалении объекта от камеры. Стандарт для 3D окружения: LOD0 (100%), LOD1 (30–50% треугольников), LOD2 (10–15%), Culled. Для мобильных порог Culled ставим агрессивнее — меньше рисуем за кадр.
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). Анализируем 5–7 основных сценариев за 3–5 рабочих дней.
-
Отчёт с приоритетами — какие проблемы критичны, какие можно отложить. Приоритеты выставляем на основе влияния на игровой опыт.
-
Внедрение оптимизаций — батчинг, LOD, Addressables, шейдеры, occlusion culling. Средний цикл внедрения — 2–4 недели.
-
Повторное профилирование — замер улучшений. Типичный прирост FPS — 30–60% на мобильных устройствах.
-
Документация — рекомендации по поддержке и дальнейшей разработке.
-
Обучение команды — как не допустить регресса. Проводим воркшопы по профилированию и оптимизации.
Средняя экономия заказчика за счёт предотвращения переделок на поздних этапах — от $15,000 до $40,000 на проекте. Получите консультацию — мы бесплатно оценим ваш проект и покажем потенциал оптимизации. Обращайтесь, чтобы узнать точные сроки и стоимость для вашего стека.