Как оптимизировать игру на unity
Оптимизация производительности графики
Хорошая производительность критична для многих игр. Ниже даны простые советы по увлечению скорости рендеринга в вашей игре.
Какова стоимость графики
Графическая часть вашей игры нагружает в первую очередь две системы компьютера: GPU (графический процессор) и CPU (центральный процессор). Первое правило любой оптимизации: найти, где возникает проблема, так как стратегия оптимизации для GPU и CPU имеет существенные различия (иногда даже возникает ситуация, когда при оптимизации для GPU больше нагрузки ложится на CPU и наоборот).
Типичные узкие места и их проверка:
- GPU часто ограничен филлрейтом (fillrate) или пропускной способностью памяти.
- Lower the display resolution and run the game. If a lower display resolution makes the game run faster, you may be limited by fillrate on the GPU.
- Check “batches” in the Rendering Statistics window. The more batches are being rendered, the higher the cost to the CPU.
- GPU обрабатывает слишком много вершин. Какое количество вершин является нормальным, определяется GPU и набором вертексных шейдеров. Можно посоветовать использовать не более 100 тысяч для мобильных устройств и не более нескольких миллионов для PC.
- The CPU has too many vertices to process. This could be in skinned meshes, cloth simulation, particles, or other game objects and meshes. As above, it is generally good practice to keep this number as low as possible without compromising game quality. See the section on CPU optimization below for guidance on how to do this.
- Рендеринг не создаёт проблем ни для GPU, ни для CPU. Проблема может быть, к примеру, в скриптах или физике. Используйте профайлер для поиска источника проблемы.
Оптимизация для CPU — количество draw call (в дальнейшем, DC)
CPU optimization
To render objects on the screen, the CPU has a lot of processing work to do: working out which lights affect that object, setting up the shader and shader parameters, and sending drawing commands to the graphics driver, which then prepares the commands to be sent off to the graphics card.
All this “per object” CPU usage is resource-intensive, so if you have lots of visible objects, it can add up. For example, if you have a thousand triangles, it is much easier on the CPU if they are all in one mesh, rather than in one mesh per triangle (adding up to 1000 meshes). The cost of both scenarios on the GPU is very similar, but the work done by the CPU to render a thousand objects (instead of one) is significantly higher.
Reduce the visible object count. To reduce the amount of work the CPU needs to do:
- Объединяйте близко расположенные объекты: вручную или используя инструмент draw call batching в Unity.
- Используйте меньше материалов, объединяйте текстуры в большие текстурные атласы.
- Используйте меньше объектов, которые должны визуализироваться несколько раз (отражения, тени, попиксельные источники света и т. п., смотрите ниже).
Объединяйте объекты так, чтобы каждый меш содержал хотя бы несколько сотен треугольников и использоват только один Материал. Важно понимать, что объединение двух объектов, использующих разные материалы, не даст увеличения производительности. Основная причина, по которой два меша используют разные материалы, состоит в том, что они использують разные текстуры. Для оптимизации производительности CPU нужно убедиться, что объекты, которые вы объединяете, используют одну текстуру.
Однако, когда вы используете много пиксельных источников света при Forward rendering path, бывают ситуации, в которых не имеет смысла объединять объекты, это более подробно описано ниже.
CPU optimization using OnDemandRendering
Use OnDemandRendering to improve CPU performance by controlling your application’s rendering speed.
You might want to lower the frame rate in the following scenarios:
- Menus, such as the application entry point or a pause menu. Menus tend to be relatively simple scenes that don’t need to render at full speed. You can render menus at a lower frame rate to reduce power consumption and to prevent device temperature from rising to a point where the CPU frequency may be throttled.
- Turn based games, such as chess. Players spend time waiting for other users to make their move or thinking about their own move. During periods of low activity, you can lower the frame rate to prevent unnecessary power usage and prolong battery life.
- Applications where the content is mostly static, such as Automotive UI.
Adjusting the rendering speed helps you manage power usage and device thermals to maximize battery life and prevent CPU throttling. It works particularly well with the Adaptive Performance package. Even though frames don’t render as often, the application still sends events to scripts at a normal pace (for example, it might receive input during a frame that isn’t rendered). To prevent input lag, you can call OnDemandRendering.renderFrameInterval = 1 for the duration of the input so that movements, buttons, etc. still appear to be responsive.
Situations that are very heavy in areas such as scripting, physics, animation, but not rendering, don’t benefit from using this API. Your application’s visuals might stutter with minimal impact on power usage.
Note: VR applications don’t support On Demand Rendering. Not rendering every frame causes the visuals to be out of sync with head movement and might increase the risk of motion sickness.
GPU: Optimizing Model geometry
There are two basic rules for optimizing the geometry of a Model:
- Don’t use any more triangles than necessary.
- Try to keep the number of UV mapping seams and hard edges (doubled-up vertices) as low as possible.
Следует отметить, что количество вершин, которое обрабатывает видеокарта, обычно не совпадает с количеством, показываемым 3D-приложением. Приложения для моделирования обычно показывают геометрическое количество вершин, то есть, количество угловых точек, составляющих модель. Для видеокарты некоторые геометрические вершины необходимо разбить на несколько логических вершин для корректной визуализации. Вершина может быть разбита на несколько, если она имеет несколько нормалей, UV-координат или вертексных цветов. Следовательно, количество вершин в Unity неизменно выше, чем количество вершин в 3D-приложении.
While the amount of geometry in the Models is mostly relevant for the GPU, some features in Unity also process Models on the CPU (for example, Mesh skinning).
For more tips on improving performance while creating Assets in 3D applications outside of Unity, see Modeling characters for optimal performance.
Lighting performance
Самое быстрое освещение — это то, которое не рассчитывается. Используйте карты освещения для запекания статичного освещения вместо расчёта освещения в каждом кадре. Процесс создания карт освещения требует много времени, чем простое размещения источников света в сцене, но:
- Это намного быстрее работает (в 2–3 раза по сравнению с 2 пиксельными источниками света)
- Это выглядит лучше, так как вы можете запечь глобальное освещение и с более высоким качеством
Во многих случаях можно заменить размещение источников света правильной настройков шейдеров и контента. Для примера, вместо размещения источника света прямо перед камерой для получения эффекта “подсветка краёв модели” (rim lighting), проще добавить расчёт этого эффекта прямо в шейдере.
Освещение в forward rendering
Освещение в forward rendering
Пиксельное динамическое освещение добавит затраты на визуализацию каждого пикселя и может привести к появлению объектов, визуализируемых в несколько проходов. На маломощных устройствах, таких как мобильные устройства или дешёвые PC, следует избегать использования более чем одного пиксельного источника света, освещающего каждый отдельный объект, и стараться использовать карты освещения. Вершинное динамическое освещение может добавить затраты для случаев вершинных трансформаций. Старайтесь избегать ситуаций, когда несколько источников света освещают один объект.
Если вы используете пиксельное освещение, то каждлый меш будет визуализирован столько раз, сколько пиксельных источников света его освещает. Если вы объедините два меша, находящихся далеко друг от друга, то это увеличит габариты меша. Все пиксельные источники света, освещавшие каждую часть объединённого меша, будут освещать теперь этот меш, поэтому количество проходов визуализации, необходимое для этого меша, увеличится. Как правило, число проходов для объединённого меша равно сумме проходов для составивших его частей, в результате чего нет выигрыша от объединения. По этой причине не стоит объединять меши, которые достаточно далеко друг от друга, чтобы быть освещёнными разными пиксельными источниками света.
During rendering, Unity finds all lights surrounding a mesh and calculates which of those lights affect it most. The settings on the Quality window are used to modify how many of the lights end up as pixel lights, and how many as vertex lights. Each light calculates its importance based on how far away it is from the mesh and how intense its illumination is — and some lights are more important than others purely from the game context. For this reason, every light has a Render Mode setting which can be set to Important or Not Important; lights marked as Not Important have a lower rendering overhead.
Для примера рассмотрим игру, где игрок управляет автомобилем, движущимся в темноте со включёнными фарами. Скорее всего, передние фары будут наиболее важным источником света в игре и параметр Render Mode будет установлен для них в значение Important. Задние огни будут менее важны, не оказывая значительного влияния на конечное изображения, так что для них Render Mode можно установить в Not Important, сэкономив тем самым аппаратные ресурсы.
Оптимизация пиксельного освещения сохраняет ресурсы и CPU и GPU: CPU делает меньше draw calls, а GPU обрабатывает меньше вершин и растеризует меньше пикселей для каждого дополнительного объекта.
GPU: сжатие текстур и мипмапы
Use Compressed textures to decrease the size of your textures. This can result in faster load times, a smaller memory footprint, and dramatically increased rendering performance. Compressed textures only use a fraction of the memory bandwidth needed for uncompressed 32-bit RGBA textures.
Использование мипмап для текстур
Как правило, параметр импорта Generate Mip Maps включён для текстур, используемых в 3D-сцене. В этом случае сжатие текстур поможет ограничить количество текстурных данных, транспортируемых в GPU при визуализации. Мимпапы позволяют GPU использовать для маленьких треугольников текстуры пониженного разрешения.
Есть исключение из этого правила: когда один тексель (пиксель текстуры) соответствует одному пикселю экрана, что встречается в элементах пользовательского интерфейса и в 2D-играх.
LOD и послойное задание дистанции для сulling
Culling objects involves making objects invisible. This is an effective way to reduce both the CPU and GPU load.
В некоторых играх целесообразно обрезать мелкие объекты более агрессивно, чем крупные, чтобы снизить разницу между нагрузкой на CPU и GPU. Для примера, маленькие камни и трава могут обрезаться на меньшей дистанции, чем большие здания.
There are a number of ways you can achieve this:
- Use the Level Of Detail system
- Manually set per-layer culling distances on the camera
Это может быть достигнуто использованием системы Level Of Detail или ручной настройкой дистанции обрезки для камеры по слоям. Вы можете поместить мелкие объекты в отдельный слой и задать ему дистанцию обрезки, используя свойство Camera.layerCullDistances.
Тени в реальном времени
Тени в реальном времени хорошо выглядят, но они могут сильно снижать производительность, одновременно добавляя дополнительные draw calls для CPU и дополнительную обработку для GPU. Подробности даны на странице Shadows.
GPU: советы для написания высокопроизводительных шейдеров
Different platforms have vastly different performance capabilities; a high-end PC GPU can handle much more in terms of graphics and shaders than a low-end mobile GPU. The same is true even on a single platform; a fast GPU is dozens of times faster than a slow integrated GPU.
Имейте в виду, что производительность GPU на мобильных устройствах и PC начального уровня скорее всего будет намного ниже, чем на PC, который вы используете для разработки. Как правило, шейдеры нужно вручную оптимизировать, чтобы уменьшить количество расчётов и чтений текстуры для получения высокой производительности. Для примера, некоторые встроенные в Unity шейдеры имеют “мобильные” эквиваленты, которые работают намного быстрее за счёт некоторых ограничений и упрощений.
Ниже приведены рекомендации, которые важны для GPU в мобильных устройствах и PC низкого уровня:
Сложные математические операции
Transcendental mathematical functions (such as pow , exp , log , cos , sin , tan ) are quite resource-intensive, so avoid using them where possible. Consider using lookup textures as an alternative to complex math calculations if applicable.
Avoid writing your own operations (such as normalize , dot , inversesqrt ). Unity’s built-in options ensure that the driver can generate much better code. Remember that the Alpha Test ( discard ) operation often makes your fragment shader slower.
Операции с плавающей точкой
While the precision ( float vs half vs fixed ) of floating point variables is largely ignored on desktop GPUs, it is quite important to get a good performance on mobile GPUs. See the Shader Data Types and Precision page for details.
Подробности о производительности шейдеров можно прочитать на странице Shader Performance.
Список шагов для увеличения производительности вашей игры
- Сохраняйте количество вершин между 200 000 и 3 000 000 в каждом кадре, если целевая платформа — PC
- Если вы используете встроенные шейдеры, проверьте категории шейдеров Mobile и Unlit. Они прекрасно работают и на немобильных платформ, но являются упрощёнными версиями более сложных шейдеров.
- Уменьшите количество различных материалов в сцене — используйте один материал для нескольких объектов, где это возможно.
- Установите свойство Static для неподвижных объектов, чтобы использовать внутреннию оптимизацию static batching.
- Only have a single (preferably directional) pixel light affecting your geometry, rather than multiples.
- Bake lighting rather than using dynamic lighting.
- Используйте сжатие текстур, когда это возможно, а также отдавайте предпочтение 16-битным текстурами перед 32-битными.
- Avoid using fog where possible.
- Узнайте преимущества технологии Occlusion Culling и используйте её для снижения количества видимой геометрии и количества draw calls в случаях со сложными статичными сценами с большим количеством перекрывающих друг друга объектов. Планируйте свои игровые уровни с учётом этой технологии.
- Используйте скайбоксы для имитации далеко расположенной геометрии.
- Используйте пиксельные шейдеры или инструменты для совмещения текстур, чтобы смешивать текстуры вместо многопроходной визуализации.
- Use half precision variables where possible.
- Сводите к минимуму количество сложных математических операций в пиксельных шейдерах: pow, sin, cos и т. п.
- Используйте меньше текстур.
- Unity Profiler Window. Производительность освещения
Оптимизация 2D игр на Unity
На Youtube куча уроков по созданию простейших 2D игр на Unity. Реально, сделать неплохой платформер можно за день, при наличии опыта и готовых ассетов. Но многие начинающие игроделы сделав проект и протестировать его на ПК, с ужасом наблюдают как их творение тормозит на мобильном устройстве.
В мануалах, что встречаются в сети, большинство советов собрано к версии Unity 4.6+, кроме того, они почти все на английском, что для некоторых является преградой. В этом посте, я постарался собрать те моменты, которые помогли мне избавится от лагов на iOS и Android. Но важно понимать — не все можно решить лишь настройками, очень важна и архитектура приложения, и подготовленные текстуры, и знание более оптимальных алгоритмов.
Что нужно предпринять, чтобы повысить производительность, поднять FPS, снизить CPU?
1. Кешируем все!
Все, что будет использоваться больше одного раза лучше закешировать. Операции типа GameObject.Find() или GetComponent() достаточно ресурсозатратны, а если они вызываются где-нибудь в цикле или в Update (), то производительность может упасть.
void Awake () < _cachedTransform = transform; >void Update ()
Не используйте Resources.Load () каждый раз когда нужно в рантайме загрузить что либо, это тоже дорогая операция.Лучше при старте закешировать и работать с кешем. Ну и конечно, объединяйте в префабы.2. Настройки графики
В Unity есть 6 стандартных пресетов настройки качества графики. Но так как мы говорим про оптимизацию для 2D и для мобильных устройств, то все что выше Simple нет смысла ставить. Конечно, если у вас есть какие-то специфические моменты, частицы, и т. д., то с параметры можно поэкспериментировать найдя оптимальный баланс.

3. Используем FrameRate
По-умолчанию FrameRate равен 30. И зачастую этого бывает достаточно. Но например, при создании UI где есть прокручивающие списки и движущие элементы, может появится дрожание или шлейф. Я это заметил при тестировании на iPhone, поэтому вручную повысил FrameRate. А понижая FrameRate на сценах или игровых меню, где ничего не двигается, можно значительно снизить CPU, а следовательно продлить жизнь батарее устройства. Пользователи не любят когда игра сжирает аккумулятор за час.
public int _frameRate = 60; void Start () < #if UNITY_IOS QualitySettings.vSyncCount = 0; #endif >void Update ()
4. Атлас текстур
Определенно, нужно упаковывать все свои спрайты в атласы. Для этого есть как встроенный инструмент Sprite Packer, так и продвинутый TexturePacker. Для работы встроенного упаковщика, достаточно задавать теги в настройках импорта, объединяя текстуры и спрайты по смыслу и месту использования.

Таким образом уменьшается количество вызовов отрисовок ваших спрайтов. Проверить как идет отрисовка можно с помощью встроенного инструмента Frame Debugger.
5. Используем пул объектов
GameObject.Instantiate () — очень дорогая операция! Если есть возможность не использовать ее в процессе игры — не используйте. Для большого количества однотипных объектов надо использовать пул объектов. Один раз инициализировав определенное количество, например пуль, они будут использоваться снова и снова, вместо создания и уничтожения каждый раз. Урок по пулу объектов и готовый шаблон, для начала будет достаточно.
6. Меньше Update — больше событий
Метод Update () вызывается каждый кадр, FixedUpdate () в некоторых случаях еще чаще. Если у вас в этих местах происходит много различных проверок и действий, это можно сказаться на производительности. Я использую событийную модель: вместо проверки условия в Update (), когда происходит какое-либо действие, отправляется событие, а в нужном месте это событие «слушается» и обрабатывается в зависимости от переданных параметров. Для этого можно использовать менеджер событий о котором я писал ранее.
7. Выключаем неиспользуемые компоненты
Деактивируйте объекты которые не попадают в камеру и которые не видно на сцене. Это также повысит производительность. Можно использовать вот такой хак, чтобы автоматически деактивировать объекты которые выходят за определенные границы.
8. Настройки билда
Билд под конкретную платформу, тоже можно оптимизировать. Например, если акселерометр не используется в игре, его лучше вообще отключить. Кроме того, я не использую автовыбор графического API, а задаю его сам убирая все остальные, опять же, если вы не используете какие-то специфические функции из OpenGLES 3.0, а так второй версии вполне хватает, она уже давно протестирована. Включаем статичный и динамический батчинг, а для Android многопоточный рендеринг. Для iOS включаем Script Call Optimization в Fast but no Exceptions, но тут момент — если будет какое-то исключение, то игра крашится.

9. Используем Profiler
Не стоит обделять вниманием профайлер. Обязательно протестируйте свою игру и посмотрите, в какие моменты идет максимальная нагрузка. Эти места нужно оптимизировать в первую очередь. Большинство ответов можно найти на stackoverflow.com или форуме Unity, просто загуглив название метода который тратит больше всего ресурсов.
10. Использование материала для UI Image и SpriteRenderer
Если у вас сложный интерфейс и много компонентов, особенно UI Image, то они существенно будут влиять на FPS. Чтобы повысить производительность, улучшить плавность анимаций, можно применить такой хак: там где вы не используете маски, у картинок нужно задать материал Sprites-Default. Если это сделать вместе с маской, то маска не сработает и получите примерно такой варнинг:
Material Sprites-Default doesn’t have _Stencil property
Чтобы убрать эту ошибку нужно немного изменить стандартный шейдер, сделать новый материал и применить его там где есть маска, тогда все сработает. Ссылка на измененный шейдер.
Цена плавности — повышение CPU 🙁11. Уменьшаем размер текстур
Отличная утилита которая позволяет снизить потребления памяти для текстур до 3х раз. Как это работает и ссылка на Github, в статье на Хабре.
12. Практическое руководство по оптимизации Unity игр
Подойдет как для 2D, так и для 3D. Много полезной информации которую в документации вряд ли найдешь. Тут и про инструменты, и про опыт. Рассказывает специалист по эксплуатации из Unity Technologies — очень интересно. Узнал про memory profiler и то, что Camera.main не закеширована О_О. Обязательно смотреть всем.
13. Используем оптимизированный код
Снова хочется посоветовать набор оптимизированных скриптов от Leopotam. Коллекции, сериализация, векторы и многое другое. Настоятельное рекомендую, к изучению и использованию.
14. Используем одинаковые материалы
Если на объектах стоят разные материалы, они не будут батчится и будет больше вызовов отрисовки. Соответственно, нужно по возможности использовать как можно меньше разных шейдеров и материалов. Для понимания, как работает начальная оптимизация графики 2D, на Lynda.com есть небольшой курс.
15. Размеры атласов и спрайтов
Для применения сжатия спрайтов на мобильных устройствах нужно использовать атласы с размерами кратными степени 2, т. е. 1024х1024, 2048х2048.
16. Используйте UI Profiler
В Unity 2017 появился UI Profiler! Крутая штука, теперь можно выяснить почему в интерфейсе много вызовов отрисовки, увидеть какие материалы на объектах и всё это оптимизировать. Особенно актуально, если у вас сложные интерфейсы со множеством элементов, которые например, прокручиваются в ScrollRect.
17. Уголок оптимизатора
На сайте Unity появился специальный раздел посвященный оптимизации — Optimization corner. Там и про UI, и профайлеры, и основные ошибки, в общем, стоит ознакомиться.
18. Несколько советов по оптимизации UI
Раннее уже упоминали про Camera.main, но в новой статье описаны ещё несколько интересных особенностей оптимизации UI.
19. Сборник советов от FunCorp
Хорошая статья про оптимизацию UI. Многое уже описано тут, но есть и замечания по новым компонентам, например TextMeshPro.
20. Оптимизация UI без кода
Статья от Banzai Games про основные способы не делать неправильно Unity UI. Есть и интересные замечания. Зачем только снова про древний текстовый компоненты говорить. Думаю уже все перебрались на TMP.
21. Оптимизация мобильных 3D-проектов
Хорошая статья про оптимизацию в 3D. Многое подойдет и для 2D. Рассмотрены все аспекты и UI, и текстуры, шейдеры, батчинг и т. д.
22. Топ-10 ошибок в оптимизации Unity
Хороший вебинар от юнитеков, рассмотрены частые ошибки совершаемые при оптимизации, обзор инструментов по оптимизации, примеры с пояснениями.
23. Оптимизация игр на Unity: проверенный в деле план
Перевод отличной статьи по оптимизации игр на Unity. Как подготовиться к оптимизации, что лучше делать, что не делать, как правильно использовать профайле и многое другое.
Пока все. Тут не было про физику, потому что пока я ее не использовал. Возможно, в будущем добавлю. Пишите в комментариях ваши проверенные способы оптимизации для 2D игр под Unity.
Первые шаги в оптимизации и полировке игры на Unity3d

После того как я закончил мой первый проект, пришла мысль о портировании его на мобильные устройства или хотя бы запуске на встроенном GPU. Во всех гайдах по оптимизации, в одном из первых советов вам сообщат, что не стоит переживать о производительности заранее, начинайте оптимизировать, после того как все закончите, и вы постепенно приведете все в порядок. Так и я, выпустив изначально игру на десктоп, решил, что никогда не будет поздно оптимизировать ее под мобильные устройства. К сожалению, мне не удалось в полной мере достичь поставленной цели, потому как, похоже, что мобильные игры следует с самого начала разрабатывать с прицелом на слабое железо. На данный момент, для дальнейшей оптимизации под мобильные платформы я вижу только необходимость серьезно переделать геймплей и дизайн игрового мира. Однако и в текущем варианте получен ценный опыт оптимизации под Unity3d и результирующий прирост производительности более чем в 300% на интегрированном GPU.
Давайте начнем с CPU
Достаточно очевидный список сформировавшийся во время оптимизации:
-
Не используйте Свойства! Поля и методы — ваши лучшие друзья.
Также никогда не поздно упростить некоторую логику в ваших скриптах, или количество обрабатываемых данных в разумных пределах. Однако еще раз повторюсь, что вам нужны веские основания полученные с помощью профилировщика, что конкретный метод слишком медленен. После изменений обязательно проследите, чтобы профилировщик отображал меньшие цифры, чем до начала оптимизаций.
Самым плохим моментом оптимизаций является то, что ваш структурированный и «идеальный» код растекается в местами не очень читабельное нечто. К сожалению, это неизбежно. Главное помнить о том, что это жертва в угоду производительности.
Теперь GPU
С CPU советы были достаточно универсальны и они применимы в любом проекте. Чего не скажешь о GPU-оптимизациях, которые зачастую сильно зависят от конкретной сцены. Однако, если вы не используете сильной магии в своих шейдерах, то явный индикатор — это количество проходов GPU(pass-calls).
Моя игра содержит открытый мир с океаном как основой для передвижений и несколькими островами в качестве декораций. В моем случае проходов было больше 2000, и мне удалось снизить это значение до примерно 300.
Материалы. Уменьшайте количество используемых материалов насколько это возможно. Каждая смена материала это новый проход, также как и каждый текстурный слой внутри материала это тоже новый проход. Конечно, я несколько упрощаю и проходы формируются не так просто, но факт остается — слишком много проходов будут непомерно нагружать слабый GPU. Для мобильных устройств рекомендуют что-то в районе 40-60 проходов. Более продвинутые устройства могут обрабатывать и в районе сотни. Так что вам есть куда стремиться!
Видимые объекты. В моей сцене слишком много объектов, которые постоянно присутствуют на экране. Проблема лишь в том, что они и должны быть видимы! Конечно, издалека нам не нужна такая же детализация как и вблизи, поэтому очевидным решением было использовать LOD-объекты.
Импостеры. Я предпочел заменить мои объекты с помощью импостеров (в целом это очень похоже на биллборды, но это множество текстур полученных пререндером объекта со всех сторон). Во встроенном Asset-Store от Unity3d множество готовых платных решений для LOD и импостеров. Однако я решил воспроизвести базовый алгоритм самостоятельно. Я создал скрипт-расширение редактора, который создавал копию необходимого объекта, менял его слой, затем создавал камеру которая была ограничена только этим специальным слоем, и производил отрисовку объекта в текстуры со всех сторон. Были добавлены основные параметры, как название результирующей папки с текстурами, разрешение получаемых текстур, расстояние до объекта, смещение по высоте, количество сторон и флаг для сохранения или отключения освещения во время создания импостера. После того как все действия завершены, скрипт удалял уже ненужную копию объекта.
Спрайты. Теперь почти все объекты заменяются спрайтами на определенном удалении от камеры. Но количество проходов было все еще огромным. Тогда я обнаружил, что спрайты это далеко не всегда легковесная форма для отображения. Каждый спрайт по умолчанию триангулирует картинку, создавая множество вершин. На каждые 900 или около того(по официальной документации) вершин, создается очередной проход (официально группировка|пакетирование|batching — сохранение данных множества объектов в одну инструкцию для GPU — вообще неприменим к SpriteRenderer объектам). В то же время нельзя заменить все спрайты на полные квадратные регионы с прозрачностью, т. к. все прозрачные пиксели все еще требуют отрисовки, и GPU их не пропускает. Также прозрачность ведет к проблемам во время отрисовки всех спрайтов из-за проверки на глубину отрисовки. GPU все еще будет создавать дополнительный проход для одного или двух спрайтов, между отрисовкой множества уже сгруппированных только потому, что этого требует проверка по глубине. Единственное, что удалось сделать — это изменить тип спрайта на Multiple, что меняет внутренний механизм триангуляции, который создает намного меньше вершин.
Упаковщик спрайтов(SpritePacker). Это последнее о чем вы должны помнить при работе со спрайтами. Чтобы явно указать для спрайта необходимость упаковки в карту атлас, нужно указать его Tag. В момент отрисовки спрайтов из одного атласа GPU не создает дополнительных проходов, даже если порядок отрисовки по глубине не оптимален для неупакованных спрайтов. Размер результирующего атласа также важен. По умолчанию он ограничен значением в 2048х2048. Это максимальный размер атласа, и он динамически подстраивается под оптимальный, в зависимости от заполнения. В моем случае этого было недостаточно для упаковки всех необходимых мне спрайтов на одной странице. Замена алгоритма упаковки на собственный, который основан на базовом, но с измененным значением размера на 4096×2048 значительно улучшило производительность.
Дальнейшее увеличение до 4096×4096 почти не отразилось на количестве проходов, но при этом даже несколько ухудшило производительность. Стоит помнить, что некоторые спрайты не могут быть размещены на одном атласе вместе — для этого они должны иметь одинаковые настройки компрессии, и часть других параметров, иначе они будут автоматически разделены по разным группам. Поэтому старайтесь группировать спрайты по атласам логически и визуально, чтобы в один момент на экране отображалось как можно меньше атласов, ведь каждое переключение между ними, включая неоптимальное расположение по глубине будут стоить вам проходов.
В моем случае я разделил атласы на UI-спрайты, затем все объекты, которые расположены очень далеко — пришлось использовать несколько атласов, но они были разделены на диаметрально противоположные по расположению в мире группы, и одновременно на экране увидеть их достаточно сложно, и все оставшиеся промежуточные объекты.
После всех изменений, производительность улучшилась настолько, что отключение всех объектов импостеров практически не влияет на результирующий FPS.
Вода. В моем случае, мне необходимо было получить более производительную воду. Изначально в сцене использовалась waterProDaytime с включенным преломлением, которая подверглась минимальным изменениям для поддержки пены вдоль береговой линии. Была убрана камера преломлений и заменена на вызов grabpass. Все дело в том, что для корректного отображения преломлений, камере пришлось отключать матрицу отсечения, т. к. в противном случае — все объекты, выше уровня воды, просто не отбрасывали теней. Из-за этого ограничения, камера дополнительно отрисовывала сцену целиком, и вызов grabpass оказался в этом случае быстрее. Также был изменен параметр LOD множителя на время отрисовки отражений. Таким образом импостеры чуть дольше отображаются в воде, что дополнительно снижает нагрузку.
Все изменения повысили производительность на интегрированном GPU с 6-8 до 22-24 кадров в секунду. По-прежнему низкий показатель, но лучшего добиться пока не удалось. Все еще рекомендую запуск своей игры на дискретной графике.
Полировка
Новый релиз не хотелось выпускать только с изменением в производительности, поэтому было решено закрыть несколько достаточно важных моментов в том, как игра выглядит, а именно — UI.
Все, кто видел мою игру в первом релизе говорили, что UI плох. Он просто мертв, и даже если я не видел этого раньше, сейчас я понимаю что же с ним не так.
В нем отсутствовали звуки, не было движения, жизни. Запустив один из проектов, я начал замечать детали главного меню, которые раньше для меня были просто невидимы. Так что я добавил все недостающее в первом приближении. Теперь выбранная кнопка анимирована, перевод фокуса и нажатие кнопок озвучены из бесплатных ресурсов в Asset-Store.
Все элементы в самой игре анимировать было бы слишком отвлекающим и раздражающим игрока эффектом, поэтому я добавил внутреннее переливающееся свечение, которое очень мягко напоминает о UI элементах.
И последним стал экран медалей. Он был просто статичен, в нем было ужасно скучно находиться. А ведь это должно быть местом в котором игрок с наслаждением наблюдает за своим прогрессом. Так что я добавил немного жизни. Теперь его украшают три системы частиц, создающих мягкие переливающиеся шары света похожие на светлячков. Частицы, к сожалению по умолчанию не доступны на UI, поэтому пришлось создавать дополнительную камеру, которая отрисовывает только эти частицы в текстуру, затем накладывающуюся на UI.
Что было оставлено позади
Мне не удалось запустить игру на мобильных устройствах. Даже самое мощное железо не воспроизводит игру с приемлемой производительностью. Возможно, с будущими версиями Unity3d что-то изменится, но на данный момент, как я уже упоминал, разработка игры с прицелом на мобильные устройства должна проводиться иначе с самого начала.
Также, в моем случае проявлялись очень странные артефакты отрисовки на мобильных устройствах со светящимися в некоторых случаях, буквально как лазеры материалами, которые выглядят вполне нормально на десктопе.
Другая цель, появившаяся во время оптимизаций в виде быстрого запуска приложения, или даже плавного запуска, также не была достигнута. Холодный старт в моем случае длится больше минуты. Причем каждый последующий запуск сокращает это время почти вдвое. Так что, похоже, это какое-то внутреннее необходимое требование Unity-плеера. Самым главным же недостатком является зависание UI потока при активации сцены. Я уже использую асинхронный вариант загрузки сцены, даже переключил его в Additive режим, однако UI просто останавливается после 90% загрузки, когда необходимо переключить флаг allowSceneActivation. Было бы здорово, если кто-то подскажет обходной путь для Unity5.x, или нечто вроде вызова события которое может потокобезопасно и с главным приоритетом изменять Ui-объекты с их перерисовкой, чтобы была хоть какая-то индикация процесса приложения.
P.S.
Конечно, это только моя история и она не решит магическим образом все ваши проблемы. Местами она слишком субъективна, но все же надеюсь, что кто-то найдет эти советы полезными.
Я описал главное, не вдаваясь глубоко в технические детали. Проект подвергся очень большому количеству изменений, и не все было описано — сейчас могу точно вспомнить, что с прошлой публикации просили разобраться с водой просачивающейся сквозь лодку — и это точно было закрыто. Также были добавлены звуки при прохождении чекпоинтов и получения медалей, как и небольшая анимация таймера на чекпоинтах и цветовое информирование о времени.
Если кому-то интересно посмотреть все в действии, к сожалению, видео еще нет, однако триал теперь бесконечен.Как оптимизировать игру на unity
Оптимизация производительности графики
Хорошая производительность критична для многих игр. Ниже даны простые советы по увлечению скорости рендеринга в вашей игре.
Какова стоимость графики
Графическая часть вашей игры нагружает в первую очередь две системы компьютера: GPU (графический процессор) и CPU (центральный процессор). Первое правило любой оптимизации: найти, где возникает проблема, так как стратегия оптимизации для GPU и CPU имеет существенные различия (иногда даже возникает ситуация, когда при оптимизации для GPU больше нагрузки ложится на CPU и наоборот).
Типичные узкие места и их проверка:
- GPU часто ограничен филлрейтом (fillrate) или пропускной способностью памяти.
- Запуск игры с более низким разрешением экрана увеличивает производительность? Тогда вы скорее всего ограничены филлрейтом GPU.
- CPU часто ограничен количеством вещей, которые должны быть отрисованы, также известно, как “draw calls”.
- Проверьте показателль “draw calls” в окне Rendering Statistics; если он составляет больше нескольких тысяч (для PC) или нескольких сотен (для мобильных устройств), то вам может потребоваться оптимизация количества объектов.
- GPU обрабатывает слишком много вершин. Какое количество вершин является нормальным, определяется GPU и набором вертексных шейдеров. Можно посоветовать использовать не более 100 тысяч для мобильных устройств и не более нескольких миллионов для PC.
- The CPU has too many vertices to process. This could be in skinned meshes, cloth simulation, particles, or other game objects and meshes. As above, it is generally good practice to keep this number as low as possible without compromising game quality. See the section on CPU optimization below for guidance on how to do this.
- Рендеринг не создаёт проблем ни для GPU, ни для CPU. Проблема может быть, к примеру, в скриптах или физике. Используйте профайлер для поиска источника проблемы.
CPU optimization
В процессе визуализации любого объекта на экране CPU должен немного потрудиться: выяснить, какие источники света влияют на объект, настроить шейдер и его параметры, отправить коменды отрисовки графическому драйверу, который подготовит команды для отправки на видеокарту. Все эти операции могут быть не очень дешёвыми в своей сумме, если у вас есть много видимых объектов.
All this “per object” CPU usage is resource-intensive, so if you have lots of visible objects, it can add up. For example, if you have a thousand triangles, it is much easier on the CPU if they are all in one mesh, rather than in one mesh per triangle (adding up to 1000 meshes). The cost of both scenarios on the GPU is very similar, but the work done by the CPU to render a thousand objects (instead of one) is significantly higher.
Reduce the visible object count. To reduce the amount of work the CPU needs to do:
- Объединяйте близко расположенные объекты: вручную или используя инструмент draw call batching в Unity.
- Используйте меньше материалов, объединяйте текстуры в большие текстурные атласы.
- Используйте меньше объектов, которые должны визуализироваться несколько раз (отражения, тени, попиксельные источники света и т. п., смотрите ниже).
Объединяйте объекты так, чтобы каждый меш содержал хотя бы несколько сотен треугольников и использоват только один Материал . Важно понимать, что объединение двух объектов, использующих разные материалы, не даст увеличения производительности. Основная причина, по которой два меша используют разные материалы, состоит в том, что они использують разные текстуры. Для оптимизации производительности CPU нужно убедиться, что объекты, которые вы объединяете, используют одну текстуру.
Однако, когда вы используете много пиксельных источников света при Forward rendering path, бывают ситуации, в которых не имеет смысла объединять объекты, это более подробно описано ниже.
GPU: Оптимизация геометрии моделей
There are two basic rules for optimizing the geometry of a model:
- Не используйте треугольников больше, чем необходимо
- Старайтесь держать количество швов на UV-карте и количество жёстких рёбер (удваивающих вершины) как можно более низким
Следует отметить, что количество вершин, которое обрабатывает видеокарта, обычно не совпадает с количеством, показываемым 3D-приложением. Приложения для моделирования обычно показывают геометрическое количество вершин, то есть, количество угловых точек, составляющих модель. Для видеокарты некоторые геометрические вершины необходимо разбить на несколько логических вершин для корректной визуализации. Вершина может быть разбита на несколько, если она имеет несколько нормалей, UV-координат или вертексных цветов. Следовательно, количество вершин в Unity неизменно выше, чем количество вершин в 3D-приложении.
В то время как количество геометрии в моделях оказывает влияние в первую очередь на GPU, некоторые функции Unity предполагают обработку моделей и на CPU, например mesh skinning.
Самое быстрое освещение — это то, которое не рассчитывается. Используйте карты освещения для запекания статичного освещения вместо расчёта освещения в каждом кадре. Процесс создания карт освещения требует много времени, чем простое размещения источников света в сцене, но:
- Это намного быстрее работает (в 2–3 раза по сравнению с 2 пиксельными источниками света)
- Это выглядит лучше, так как вы можете запечь глобальное освещение и с более высоким качеством
Во многих случаях можно заменить размещение источников света правильной настройков шейдеров и контента. Для примера, вместо размещения источника света прямо перед камерой для получения эффекта “подсветка краёв модели” (rim lighting), проще добавить расчёт этого эффекта прямо в шейдере.
Освещение в forward rendering
Освещение в forward rendering
Пиксельное динамическое освещение добавит затраты на визуализацию каждого пикселя и может привести к появлению объектов, визуализируемых в несколько проходов. На маломощных устройствах, таких как мобильные устройства или дешёвые PC, следует избегать использования более чем одного пиксельного источника света , освещающего каждый отдельный объект, и стараться использовать карты освещения. Вершинное динамическое освещение может добавить затраты для случаев вершинных трансформаций. Старайтесь избегать ситуаций, когда несколько источников света освещают один объект.
Если вы используете пиксельное освещение, то каждлый меш будет визуализирован столько раз, сколько пиксельных источников света его освещает. Если вы объедините два меша, находящихся далеко друг от друга, то это увеличит габариты меша. Все пиксельные источники света, освещавшие каждую часть объединённого меша, будут освещать теперь этот меш, поэтому количество проходов визуализации, необходимое для этого меша, увеличится. Как правило, число проходов для объединённого меша равно сумме проходов для составивших его частей, в результате чего нет выигрыша от объединения. По этой причине не стоит объединять меши, которые достаточно далеко друг от друга, чтобы быть освещёнными разными пиксельными источниками света.
В процессе визуализации Unity находит все источники света вокруг меша и рассчитывает их важность для данного меша. В настройках Quality Settings можно задать, сколько источников света в конце концов может использоваться для освещения каждого меша. Для каждого источника света его приоритет вычисляется на основании расстояния до меша и яркости этого источника света. Также имеет значение параметр Render Mode внутри компонента Light, который может принимать два значения: Important или Not Important . Источники света, помеченные как Not Important имеют более низкий приоритет.
Для примера рассмотрим игру, где игрок управляет автомобилем, движущимся в темноте со включёнными фарами. Скорее всего, передние фары будут наиболее важным источником света в игре и параметр Render Mode будет установлен для них в значение Important . Задние огни будут менее важны, не оказывая значительного влияния на конечное изображения, так что для них Render Mode можно установить в Not Important , сэкономив тем самым аппаратные ресурсы.
Оптимизация пиксельного освещения сохраняет ресурсы и CPU и GPU: CPU делает меньше draw calls, а GPU обрабатывает меньше вершин и растеризует меньше пикселей для каждого дополнительного объекта.
GPU: сжатие текстур и мипмапы
Использование сжатых текстур уменьшает размер ваших текстур (в результате они быстрее загружаются и занимают меньше памяти), а также может значительно повысить производительность. Сжатые текстуры используют малую часть пропускной способности памяти, в сравнении с 32-битными RGBA-текстурами.
Использование мипмап для текстур
Как правило, параметр импорта Generate Mip Maps включён для текстур, используемых в 3D-сцене. В этом случае сжатие текстур поможет ограничить количество текстурных данных, транспортируемых в GPU при визуализации. Мимпапы позволяют GPU использовать для маленьких треугольников текстуры пониженного разрешения.
Есть исключение из этого правила: когда один тексель (пиксель текстуры) соответствует одному пикселю экрана, что встречается в элементах пользовательского интерфейса и в 2D-играх.
LOD и послойное задание дистанции для сulling
Culling objects involves making objects invisible. This is an effective way to reduce both the CPU and GPU load.
В некоторых играх целесообразно обрезать мелкие объекты более агрессивно, чем крупные, чтобы снизить разницу между нагрузкой на CPU и GPU. Для примера, маленькие камни и трава могут обрезаться на меньшей дистанции, чем большие здания.
There are a number of ways you can achieve this:
- Use the Level Of Detail system
- Manually set per-layer culling distances on the camera
Это может быть достигнуто использованием системы Level Of Detail или ручной настройкой дистанции обрезки для камеры по слоям. Вы можете поместить мелкие объекты в отдельный слой и задать ему дистанцию обрезки, используя свойство Camera.layerCullDistances.
Тени в реальном времени
Тени в реальном времени хорошо выглядят, но они могут сильно снижать производительность, одновременно добавляя дополнительные draw calls для CPU и дополнительную обработку для GPU. Подробности даны на странице Shadows.
GPU: советы для написания высокопроизводительных шейдеров
Different platforms have vastly different performance capabilities; a high-end PC GPU can handle much more in terms of graphics and shaders than a low-end mobile GPU. The same is true even on a single platform; a fast GPU is dozens of times faster than a slow integrated GPU.
Имейте в виду, что производительность GPU на мобильных устройствах и PC начального уровня скорее всего будет намного ниже, чем на PC, который вы используете для разработки. Как правило, шейдеры нужно вручную оптимизировать, чтобы уменьшить количество расчётов и чтений текстуры для получения высокой производительности. Для примера, некоторые встроенные в Unity шейдеры имеют “мобильные” эквиваленты, которые работают намного быстрее за счёт некоторых ограничений и упрощений.
Ниже приведены рекомендации, которые важны для GPU в мобильных устройствах и PC низкого уровня:
Сложные математические операции
Transcendental mathematical functions (such as pow , exp , log , cos , sin , tan ) are quite resource-intensive, so avoid using them where possible. Consider using lookup textures as an alternative to complex math calculations if applicable.
Avoid writing your own operations (such as normalize , dot , inversesqrt ). Unity’s built-in options ensure that the driver can generate much better code. Remember that the Alpha Test ( discard ) operation often makes your fragment shader slower.
Операции с плавающей точкой
While the precision ( float vs half vs fixed ) of floating point variables is largely ignored on desktop GPUs, it is quite important to get a good performance on mobile GPUs. See the Shader Data Types and Precision page for details.
Подробности о производительности шейдеров можно прочитать на странице Shader Performance.
Список шагов для увеличения производительности вашей игры
- Сохраняйте количество вершин между 200 000 и 3 000 000 в каждом кадре, если целевая платформа — PC
- Если вы используете встроенные шейдеры, проверьте категории шейдеров Mobile и Unlit. Они прекрасно работают и на немобильных платформ, но являются упрощёнными версиями более сложных шейдеров.
- Уменьшите количество различных материалов в сцене — используйте один материал для нескольких объектов, где это возможно.
- Установите свойство Static для неподвижных объектов, чтобы использовать внутреннию оптимизацию static batching.
- Only have a single (preferably directional) pixel light affecting your geometry, rather than multiples.
- Bake lighting rather than using dynamic lighting.
- Используйте сжатие текстур, когда это возможно, а также отдавайте предпочтение 16-битным текстурами перед 32-битными.
- Avoid using fog where possible.
- Узнайте преимущества технологии Occlusion Culling и используйте её для снижения количества видимой геометрии и количества draw calls в случаях со сложными статичными сценами с большим количеством перекрывающих друг друга объектов. Планируйте свои игровые уровни с учётом этой технологии.
- Используйте скайбоксы для имитации далеко расположенной геометрии.
- Используйте пиксельные шейдеры или инструменты для совмещения текстур, чтобы смешивать текстуры вместо многопроходной визуализации.
- Use half precision variables where possible.
- Сводите к минимуму количество сложных математических операций в пиксельных шейдерах: pow , sin , cos и т. п.
- Используйте меньше текстур.
- Unity Profiler Window. Производительность освещения