Разработка игр
Производительность: GPU и CPU
Cyberpunk 2077 при релизе на PS4 работал в 20fps - и это после 3 лет оптимизации. Мобильные игры должны держать 60fps при 2W потребления. Разница между 'работает' и 'работает на всех платформах' - это профилирование, batching, LOD и culling. Каждый миллисекунд CPU/GPU frame time - это профессия.
- **Fortnite** держит 60fps на iPhone 11 через агрессивный LOD: острова вдали - один draw call через HLOD, вблизи - полная детализация
- **Call of Duty Mobile** использует комбинацию GPU Instancing для повторяющихся объектов и Portal culling для indoor сцен - 300+ draw calls удалось сократить до 80
- **Minecraft RTX** на RTX 3080 рендерит ray-traced освещение: DLSS компенсирует стоимость трассировки лучей, rendering pipeline переработан под Vulkan Ray Tracing
- **God of War** (PS5) загружает мир без экрана загрузки: потоковая загрузка геометрии и текстур на основе позиции камеры, occlusion culling убирает невидимые чанки
Draw Call Batching
**Draw call** - команда CPU к GPU: «нарисуй этот mesh с этим материалом». Проблема не в самом рендеринге, а в overhead на CPU: каждый draw call требует изменения состояния GPU, flush pipeline, синхронизации. На мобильных устройствах даже 100 draw calls могут занять 3-4ms CPU time. Цель: 1 draw call вместо 1000.
Dynamic Batching в Unity автоматически объединяет маленькие меши (< 300 вершин) с одинаковым материалом. Но: работает только на CPU, добавляет CPU overhead на объединение. На быстрых GPU часто выгоднее отключить и использовать GPU Instancing - профилировать каждый случай отдельно.
Сцена с 500 одинаковыми деревьями даёт 500 draw calls и 8ms CPU time. GPU загружен на 40%. Что делать?
Level of Detail (LOD)
**LOD** - фундаментальная техника: дальние объекты рендерятся упрощёнными версиями меша. Персонаж вблизи: 50000 полигонов. В 100 метрах: 5000. В 300 метрах: 500. В 500 метрах: заменяется billboard (один quad). GPU не тратит время на треугольники меньше пикселя. Unreal Engine Nanite автоматизирует LOD, но ручные LOD группы всё ещё быстрее на мобильных.
HLOD (Hierarchical LOD) в Unreal Engine объединяет несколько статичных мешей в один меш при удалении камеры - например, целый квартал города становится одним low-poly мешем. Fortnite использует HLOD для островов: вдали виден упрощённый остров как один объект, вблизи - полная детализация. Это снижает draw calls для открытых миров.
LOD переключения видны игроку как 'popping' - резкая смена детализации. Как смягчить?
Culling: что не рендерить
**Culling** - не рендерить то, что не видно. Три уровня: Frustum Culling (за пределами frustum камеры), Occlusion Culling (за стенами и препятствиями), Backface Culling (обратные грани треугольников). Frustum culling - бесплатно, встроен везде. Occlusion culling - самый эффективный, но требует bake или dynamic occlusion.
Portal-based culling в коридорных играх (Doom, Quake, CS:GO движки): мир делится на комнаты, соединённые порталами. Рендеритель проверяет через какие порталы видна каждая комната - не видимые через portal комнаты не рендерятся вообще. O(depth) вместо O(N) объектов. Call of Duty использует portal culling для indoor уровней.
Occlusion Culling 'bake' занял 5 минут и сохранил данные в asset файл. Персонаж игрока перемещается за стену - объекты за стеной рендерятся. В чём проблема?
GPU и CPU профилирование
**Профилирование - первый шаг оптимизации.** Нельзя оптимизировать то, что не измерено. Типичная ошибка: оптимизировать C# код, когда bottleneck на GPU. Или оптимизировать шейдеры, когда игра CPU-bound из-за физики. Инструменты: Unity Profiler + Frame Debugger, Unreal Insights + RenderDoc, PIX (Xbox/PC), Xcode GPU Frame Debugger (iOS).
GPU Timestamp Queries - встроить измерения прямо в GPU pipeline. Unreal Stat GPU command показывает breakdown по GPU: BasePass, Translucency, PostProcess, Shadows. Если ShadowDepths занимает 40% GPU frame time - это сигнал уменьшить Dynamic Shadow Distance или перейти на статичные тени для дальних объектов.
Больше объектов в сцене = медленнее игра
Медленнее игра от draw calls (CPU overhead) и GPU load (пиксели, шейдеры). 10000 заbatch'енных деревьев быстрее 100 объектов с уникальными материалами
GPU рендерит геометрию параллельно - больше треугольников не линейно увеличивает GPU time. Bottleneck часто в CPU-GPU communication (draw calls) или в fillrate (пиксельные шейдеры), а не в количестве объектов
Unity Profiler показывает: CPU Main Thread 22ms, GPU 9ms. Разработчик начинает оптимизировать шейдеры. Правильное ли это решение?
Ключевые идеи
- **Batching:** GPU Instancing для одинаковых объектов - 500 деревьев в 1 draw call; Static Batching для неподвижных; Dynamic Batching для маленьких мешей
- **LOD:** меньше полигонов вдали; 4 уровня (high/med/low/billboard); Cross-fade сглаживает popping; HLOD объединяет несколько объектов
- **Culling:** Frustum (бесплатно), Occlusion (baked или dynamic), Portal (коридорные сцены); не рендерить невидимое
- **Профилирование первым:** CPU-bound vs GPU-bound - разные решения; RenderDoc и Unity Profiler показывают где именно тратится время
Связанные темы
GPU оптимизация тесно связана со смежными темами:
- Memory Management в играх — Batching и LOD влияют на память: Static Batching увеличивает mesh память, LOD требует хранить несколько версий меша
- Архитектура AAA движка — Rendering pipeline AAA движков (Unreal, id Tech) строится вокруг этих техник: clustered shading, virtual geometry (Nanite) - эволюция batching и LOD
- Мобильные игры — На мобильных устройствах все эти техники критичны вдвойне: 30fps при 2W, жёсткие ограничения draw calls на мобильных GPU
Вопросы для размышления
- GPU Instancing требует одинаковый Material для всех инстансов. Нужно 500 деревьев с разным цветом листьев. Как реализовать через MaterialPropertyBlock без отдельного Material на каждое дерево?
- Dynamic Occlusion Culling (Unreal Hardware Occlusion Queries) имеет latency в 1-2 кадра. Как это проявляется визуально и почему в быстрых играх иногда лучше precomputed static occlusion?
- Nanite (Unreal Engine 5) утверждает, что LOD больше не нужен - виртуальная геометрия автоматически адаптирует детализацию. Почему тогда ручные LOD группы всё ещё используются в мобильных играх на UE5?