Перейти к содержимому

Как оптимизировать игру на unity

  • автор:

Как оптимизировать игру на 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. Производительность освещения

    Четыре простых шага, как оптимизировать производительность мобильной игры на Unity

    Изучив множество прототипов, я практически не встретил ни одного, который был бы оптимизирован под слабые устройства, даже когда дело касается Hyper Casual. Даже если на мощных устройствах у вас стабильно хорошая производительность, то постобработка, неправильные настройки графики и теней могут уничтожить FPS в пару кликов — а в релизе такие ошибки критичны.

    Поэтому, когда мне приносят очередной проект, я стараюсь дать разработчикам самые быстрые и эффективные решения без необходимости переделывать половину игры. Ниже приведу одни из самых распространенных и простых ошибок в оптимизации, которые можно исправить, потратив минимум ресурсов.

    Это особенно актуально для ГК-проектов, которые разрабатываются и тестируются быстро на реальной аудитории, но должны плавно запускаться на максимум устройствах.

    Очевидная причина проблем с оптимизацией в том, что мощность графического процессора мобильного девайса несравнима с видеокартой ПК, GPU флагманов может быть в десятки раз мощнее, чем у массовых и дешевых девайсов, а постобработка подразумевает дополнительные операции с отрендеренным изображением каждый кадр, то есть 30-60 раз в секунду.

    При этом мобильные игры не всегда требуют высокого качества изображения, хотя бы из-за размеров экрана. Но разработчики все равно часто используют приятные глазу эффекты, знакомые пользователям консолей и ПК: блум, цветокоррекция, антиалиасинг и так далее.

    В итоге игра начинает тормозить и нужно искать причину.

    Причина №1. Антиалиасинг и желание сделать красиво

    Антиалиасинг нужен для устранения эффекта «лесенки» на краях объектов. Красиво, и на устройствах выше среднего это практически «бесплатно», но на слабых может сильно испортить игровой опыт.

    Выходом могла бы стать настройка под разные девайсы, но по опыту могу сказать, что так делают немногие разработчики Hyper Casual. Поэтому проще и быстрее полностью отказаться от антиалиасинга, разница от наличия которого может быть незаметна на экране мобильного девайса.

    Дальше есть два пути, как решить вопрос цветокоррекции.

    1. Это можно сделать на стороне художников, но перерисовка текстур требует много ресурсов, поэтому отбрасываем вариант.
    2. Пройтись по всем шейдерам в проекте и вставить туда пару строчек, которые делают цветокоррекцию. В таком случае цветокоррекция получается практически «бесплатной». Но если шейдеров слишком много, и они, например, покупные, а разработчик не понимает, что куда вставлять — могут возникнуть проблемы.

    Фишка в том, что если мы хотим сделать цветокоррекцию, но не хотим вставлять ее в шейдер, то рендер нельзя сделать сразу на экран. Надо сначала нарисовать всю игру куда-то во внутреннюю текстуру, потом пройтись по ней с цветокоррекцией и только потом вывести на экран. Из-за этой многоступенчатости FPS на слабых устройствах сразу падает в несколько раз.

    Еще один нюанс: вставить цветокоррекцию в шейдер можно только при небольшом количестве источников света. Для большинства мобильных игр это не актуально, но на всякий случай поясню.

    В шейдер нельзя добавить километры кода, так как есть ограничения. Туда можно вписать, например, 10 источников света, чтобы они обрабатывались в этом шейдере. Но если нужно больше, то придется этот объект нарисовать еще раз, а потом приплюсовать освещение от новых источников света. С таким вторым проходом цветокоррекция в шейдере уже не сработает, она будет применяться криво.

    Причина №2. Физика и желание всё сделать «честно»

    Кейс: на сцене одновременно взрывается 50 объектов, что сильно нагружает устройство.

    Решение простое: не обязательно все взрывать по-честному — ближе к камере можно применить качественные и проработанные эффекты, а для всего, что расположено по краям или далеко, сделать более простой эффект в виде дыма или вовсе ничего не проигрывать.

    Или другой пример из моей практики:

    Есть игра, где бежит 3D-человечек и у него есть два варианта смерти — от разрезания по вертикали циркулярной пилой или по горизонтали крутящимися ножами. Чтобы это реализовать, можно поставить тяжелый плагин, который прямо во время игры берет меш человечка, его правильно разрезает на несколько объектов, зашивает «дыру», которая образовалась, и навешивает на каждую часть физику. Это происходит очень медленно и может тормозить даже на ПК. А что если в игре 20 человечков, которые синхронно попадают под нож?

    В этом случае можно просто сделать два префаба: один разрезанный горизонтально, другой — вертикально. Сохранить их, просчитать заранее и использовать в игре уже готовое.

    Причина №3. Большое количество независимых объектов

    Кейс: окружение сцены состоит из кубов, к которым применены разные материалы с разными шейдерами, или просто из большого количества кубов. При достижении 10 000 кубов в сцене игра начинает заметно тормозить, так как каждый из объектов требует один вызов отрисовки (drawcall) на видеокарте.

    Поможет объединение мешей (батчинг) объектов в один большой меш для более быстрой отрисовки в сцене с множеством одинаковых объектов. Это можно сделать инструментами Unity, но лучше вручную.

    При этом статический батчинг (Static Batching) хуже склеивания в один меш, если у вас на сцене много маленьких объектов по паре треугольников. Потому что тогда Unity будет рендерить статик батчинг как один и тот же меш, но по кусочку с кучей вызовов. То есть 10 000 кубов будут рендериться в 10 000 drawcall’ов.

    Решение: использовать один шейдер со всеми необходимыми параметрами для материалов, убедиться что объекты окружения помечены как статические, и что в проекте используется батчинг, который группирует эти объекты во время рендеринга и отрендерит их все за один вызов отрисовки.

    Причина №4. Реалтайм тени и освещение

    Основная причина, почему его не стоит использовать: расчеты освещения происходят во время работы приложения и нагружают устройство.

    Решение: использование в шейдерах окружения заранее просчитанные карты освещения, которые представляют собой битмап. Целевое устройство, на котором будет запускаться игра, будет загружать карту освещения один раз, вместо того, чтобы пересчитывать её постоянно во время игры.

    Полностью запеченный свет (максимальная производительность):

    Один каскад теней без сглаживания, подогнанный под размеры сцены (х2 по ресурсам по сравнению с первым вариантом):

    Стандартно настроенные тени с несколькими каскадами + сглаживание (х2.5 от первого варианта):

    Дополнительно. Tips and tricks

    Ко всему вышесказанному рекомендую сделать еще несколько вещей, чтобы ваш прототип стал еще ближе к успешному релизу.

    • Уберите у всех мешей Light и Reflection пробы, если вы ими не пользуетесь — они отжирают немного производительности, даже если в шейдере нет никаких ссылок на них.
    • Число итераций у Physics Solver можно уменьшить с 6 до 2 (но надо проверить, не повлияет ли это на геймплей), а частоту апдейта физики с 50 до 10-30 (примерно 30-50% от целевого FPS). Положения объектов будут интерполироваться, так что визуально никаких дерганий быть не должно.
    • Меш коллайдеры это очень дорого, особенно когда меши колизятся с мешами. Лучше замените их на сферы, капсулы, кубы и так далее. Для примера вот сколько стоит посчитать коллизию между объектами на ПК (на смартфонах результаты будут еще хуже):
    • Не вешайте очень много Rigidbody на большое количество трансформов — могут возникнуть большие затраты на синхронизацию физика <> трансформ.

      Оптимизируйте игру с помощью Profile Analyzer

      Что вы узнаете на этой странице: информацию об использовании Unity Profile Analyzer для оценки влияния изменений в ассетах или коде, оценки оптимизации, модификации настроек и обновления версии Unity. Эта статья написана по докладу Линдона Хоумвуда на Unite Copenhagen 2019.

      На этой странице

      • Обзор Profile Analyzer
      • Советы по профилировке
      • Анализ производительности отдельной функции
      • Поиск репрезентативных кадров
      • Влияние изменений в структуре проекта
      • Are you GPU-bound?

      Обзор Profile Analyzer

      Хотите узнать, на что обратить внимание при оптимизации? Хотите сравнить производительность до и после изменений? Знаете ли вы о влиянии, которое оказывает перенос проекта со старой версии Unity на новую? Performance Analyzer поможет вам получить необходимую информацию.

      Этот инструмент поможет вам определить наиболее приоритетные области и обеспечит возможность сравнения двух снимков производительности Unity и оценить влияние внесенных изменений. В нем предусмотрены средства визуализации, которые помогут понять, как именно ваши изменения влияют на производительность.

      Profile Analyzer собирает и визуализирует данные по кадрам и маркерам из массива кадров Unity Profiler, помогая понять поведение игры на протяжении множества кадров, дополняя покадровый анализ, доступный в Unity Profiler.

      Profile Analyzer проводит анализ производительности процессора по множеству кадров. В состав включены средства статистики и визуализации, помогающие быстрее проанализировать информацию. Кроме того, инструмент имеет мощные фильтры, позволяющие добраться до интересующих аспектов. Их можно использовать для сравнения двух наборов данных или для экспорта данных для анализа в других системах.

      Profile Analyzer доступен для использования в Unity 2018.4 LTS+ в виде пакета, но его можно также просто загрузить и перетащить в проект. Он поддерживает Unity версии 5.6 и выше. Для запуска инструмента выберите пункт Window > Analysis > Profile Analyzer. В предыдущих версиях Unity его можно запустить непосредственно из меню Window.

      Советы по профилировке

      Вот несколько советов по началу работы с Profile Analyzer.

      1. Сделайте репрезентативную и повторяемую выборку.
      2. Закройте все другие приложения. Можно использовать Profile.logFile в скриптах на C# для записи данных Profile непосредственно из работающей игры, то есть вам не потребуется запускать Editor. На выходе вы получите файл .raw, который можно загрузить в Unity Profiler.
      3. Отключите регулировку частоты процессора, например, Intel SpeedStep или Turbo Boost, чтобы избежать разгона процессора.

      Анализ производительности отдельной функции

      Если вы хотите понять, как вызов функции влияет на производительности, то используйте Unity Profiler для захвата данных. Затем нажмите кнопку Pull Data для загрузки данных в Profile Analyzer. Или можно загрузить ранее записанные данные.

      После этого начнется анализ, и график начнет наполняться данными кадров. Обратите внимание на белые и бирюзовые столбцы. Белые столбцы означают длительность кадров, а бирюзовые — текущие выбранные маркеры, позволяющие оценить их влияние на время кадра.

      Для поиска по маркерам можно использовать фильтры. Сведения о маркере (Marker Details) обновляются автоматически, отображая выбранное вами подмножество, а в колонке Count отображается их количество в подмножестве. Маркер подсвечивается в графике времени кадра. При выборе маркера на экране отображается сводная информация по нему.

      Поиск репрезентативных кадров

      When analyzing performance, you want to make sure that the data you’re looking at is representative. If your data is noisy, it’s easy to ensure that you select an average frame by using the Frame Summary at the top right – just click on the Frame shown as the Median and the Profiler will display the relevant analysis. Or, in the frame time graph, you can right-click and select Select Median Frame.

      You can also limit your analysis to a selection of frames. All of the statistics you see will be updated to reflect the specific selection.

      Rather than rely on a single data point from a single frame, you can analyze multiple data points by selecting a group of representative frames. If you right-click on the frame time graph and Order by Frame Duration, you can then select a set of frames around the median frame for a representative sample that smooths out some of the noise in your data.

      Влияние изменений в структуре проекта

      Profile Analyzer помогает определить разницу в производительности при внесении изменений в структуру проекта, например, включение графических задач (Graphic Jobs). Для использования этой функции возьмите базовый набор данных и сделайте еще один снимок после внесения изменений. Запустите игру, сделайте снимок с помощью Unity Profiler, перенесите полученные данные в Profile Analyzer, а затем перенесите второй набор данных.

      Вы увидите результаты сравнения двух наборов данных в разделе Frame Summary.

      Are you GPU-bound?

      Чтобы убедиться в отсутствии узких мест производительности проверьте маркер Gfx.WaitForPresent. Найдите его, введя Gfx.WaitForPresent в поле фильтра. Если медианное значение этого маркера не равно нулю, то это значит, что ЦП приходится ожидать завершения операций ГП перед продолжением работы.

      Взгляните на этот маркер в графике времени кадра, чтобы узнать, какое влияние он оказывает на время кадра. Если ЦП ожидает завершения операций ГП, то проблемы производительности наверняка кроются в графической составляющей. Воспользуйтесь инструментами профилирования ГП для оптимизации.

      Оптимизация игр на Unity: проверенный в деле план

      Оптимизация игр — отдельная головная боль разработчиков, процесс, который может идти бесконечно. Нужно учесть загрузку процессора, видеокарты и не потерять FPS. Нашли статью, автор которой 13 лет разрабатывает на Unity и делится советами по оптимизации. Под катом есть пошаговый план, как сделать проект на Unity более производительным.

      Оптимизацию невозможно полностью описать в одной статье. Поэтому сосредоточимся исключительно на методе анализа, который подскажет правильные пути оптимизации вашей конкретной игры.

      Распространенные ошибки

      Начнем с того, чего не стоит делать, чтобы не допустить распространенные ошибки. Конечно, из любого правила есть исключения, но новичкам в оптимизации лучше избегать некоторых вещей.

      Аврал за несколько дней до дедлайна

      Невозможно подтянуть производительность за несколько дней и даже недель до релиза, ведь иногда приходится полностью менять работу определенных систем. Игра не обязательно должна идти с 60 FPS на всех стадиях продакшена. Но не стоит оставлять огромный кусок работы и капитальные пересмотры архитектуры на последнюю неделю.

      Отсутствие плана

      Нельзя заниматься профилированием и оптимизацией без плана. Нет смысла работать вслепую и оптимизировать код или арт, не определив узкие места.

      Не создавайте рандомные профили в редакторе или на своей рабочей машине, если они не имеют никакого отношения к целевой платформе. Также не стоит перепрыгивать с одной цели профилирования на другую. Нужно сначала определить основные цели, а потому уже решать как повышать производительность игры. Оптимизация станет более отлаженной, если действовать по плану.

      Программисты порой оптимизируют отдельные куски кода — например, оптимизируют UI с помощью цикла foreach (с 10 мс до 3 мс). А художники рассчитывают полигоны. И то, и другое — улучшение, но зачастую эти действия не дают заметных результатов. Лучше сосредоточиться на опыте игрока, ведь в конечном итоге — это единственный важный результат.

      Некорректные данные при включении GPU Profiler

      Включение профайлера GPU покажет некорректные результаты для некоторых платформ, поэтому лучше отключить его. VSync будет использовать более 90% ресурсов системы, а такие вещи, как GPUProfiler.EndQueries, будут отображаться некорректно и при этом вызывать огромные нагрузки. Профайлер GPU поможет глубже разобраться в ситуации, но только когда точно знаешь, как он работает и зачем он включен.

      Определить кто задерживает исполнение программы — GPU или CPU — можно, используя Timeline профайлера CPU:

      «Сбор данных профайлера GPU может сильно перегружать систему. Закройте эту графу, если эти данные вам не нужны», — я солидарен с Unity.

      • Gfx.WaitForPresent: ограничения GPU, CPU ожидает ответа от GPU;
      • Gfx.WaitForCommands: ограничения CPU, GPU ожидает ответа от CPU.

      Некорректные данные при запуске Deep Profiling

      Не используйте эту опцию, пока не узнаете, когда это нужно делать. Deep Profile можно включить, если есть проблема с конкретной частью программы: это поможет определить, какая часть кода вызывается. При этом не стоит ориентироваться на полученные тайминги, потому что глубокое профилирование слишком сильно перегружает небольшие методы, странным образом искажая все результаты. В качестве альтернативы или дополнения к глубокому профилированию можно расставлять собственные маркеры профиля.

      Использование кастомных маркеров профиля:

      Некорректные данные из-за физики в FixedUpdate

      Профилирование само по себе настолько загружает систему, что это может сильно повлиять на некоторые данные. Игра будет идти хуже, а значит, будет выполняться больше физики FixedUpdates. Даже если профайлер покажет, что на физику ушло 33% фреймрейта, по факту в итоговом билде это значение будет ближе к 10%. Так же как в случае с глубоким профилированием, эти некорректные данные могут сподвигнуть разработчиков заниматься оптимизацией не там, где это принесет значимый результат.

      Сложности с сетевым решением

      Программисты порой полагают, что сетевое решение (например, Photon) сильно снижает производительность. Они видят, что из-за него происходят пики загрузки ЦП, но забывают заглянуть поглубже в стек вызовов. Сетевой инструмент запускает методы, вызываемые из сети (так называемые RPC), а они — часть вашего собственного кода, которая не имеет к сети никакого отношения. В таких ситуациях нужно оптимизировать RPC-методы и/или распределить их рабочую нагрузку.

      План оптимизации

      Каждая задача должна быть выполняемой. Особенно что-то такое пугающее и, казалось бы, бесконечное, как профилирование. Я написал план, следуя которому вы сможете измерить и ощутить прирост производительности игры.

      Подготовка

      1. Возьмите за ориентир самую слабую платформу.

      Выберите один конкретный компьютер или платформу для профилирования. В идеале это должно быть самое слабое устройство из тех, на которых будет запускаться ваша игра. Мы часто берем в качестве такого ориентира Xbox One. На этой консоли довольно медленный диск, устаревшие процессор и видеокарта. Nintendo Switch и современные мобильные устройства работают лучше, чем Xbox One.

      2. Сделайте так, чтобы вам было удобно.

      Важный дополнительный шаг — создать комфортные условия профилирования. Сделайте все возможное, чтобы ускорить подготовку билдов. Их будет много, поэтому эта работа точно принесет пользу и для других задач разработки, например, при поиске и исправлении багов.

      Общие рекомендации

      1. Автоматизируйте билды, чтобы они собирались в один клик.

      Пропускайте неважный игровой контент. Например, в отладочных билдах можно скипать видео-заставку. Создайте специальный интерфейс для отладки, который потребуется только на этапе разработки. Например, можно добавить отдельные кнопки, которые будут включать/выключать определенные задачи профилирования. Так отпадет необходимость создавать отдельные билды.

      2. Ускорьте билды.

      Обеспечьте возможность собирать более быстрые и менее объемные билды (например, с одним уровнем и одной машиной в гоночной игре).

      3. Отключите обфускацию, если она используется.

      Рекомендации по платформам

      Просмотрите все Player Settings в Unity, чтобы найти полезные настройки. Например, у высокопроизводительной консольной платформы есть уровни сжатия билда, которые можно отключить, чтобы ускорить его работу.

      Ускорьте настройку компилятора Il2CPP

      Используйте Release или даже Debug, если компиляция кода не занимает слишком много времени.

      PlayerSettings.SetIl2CppCompilerConfiguration(group, mode);
      Il2CppCompilerConfiguration.Master //Slow build, Quick performance
      Il2CppCompilerConfiguration.Release //Medium build time, Good runtime performance
      Il2CppCompilerConfiguration.Debug //Quickest build, slowest runtime performance

      Петля оптимизации

      Процесс оптимизации должен включать только текущие самые серьезные или самые простые проблемы. Не пытайтесь исправить все сразу. После первой партии исправлений нужно подготовить новый билд, проверить то, что было сделано, а затем снова взяться за оптимизацию. Этот цикл будет повторяться, пока вы не будете довольны производительностью игры или пока не истечет время отведенное на разработку.

      Поскольку подготовка билда и сбор данных профилирования отнимает много времени, перед повторной сборкой и проверкой стоит вносить комбинированные изменения с точки зрения базовой производительности, пиков загрузки и так далее.

      1. Документирование производительности

      Фиксируйте производительность вашей игры, желательно на тех этапах, которые легко воспроизвести. Задокументируйте результаты и сохраните данные профилирования. Стоит записать уровень загрузки CPU (и GPU), чтобы оценить прогресс. Я часто также отслеживаю загрузку памяти, чтобы подготавливать эффективные новые билды и при необходимости сокращать объем используемой памяти.

      Можно использовать Profile Analyzer: он упрощает сравнение данных в профиле. Это поможет обнаружить пики загрузки или другие отличия между разными билдами/конфигурациями настроек. То есть этот инструмент отмечает все произведенные улучшения.

      Profile Analyzer экономит много работы: выберите две области, и он автоматически сообщит, в чем разница.

      2. Базовая производительность

      Сначала мы обычно игнорируем пики загрузки и сосредотачиваемся на том, чтобы базовая производительность была в пределах нормы. Здесь главное довести «нормальный» игровой цикл до приемлемого уровня, будь то 30 кадров в секунду (мобильные платформы, Switch), 60 или даже 120 (VR).

      Используйте профайлер Unity, чтобы понять, что снижает базовую производительность, и решить только самые важные проблемы. Timeline помогает увидеть, как работает игра с точки зрения производительности.

      Режим отображения Timeline куда полезнее, чем Hierarchy. Timeline показывает порядок задач и их зависимость друг от друга. Стоит обратить внимание на следующие моменты:

      • скрипты/плагины, запускающие тяжелый код в Update, FixedUpdate, LateUpdate и т.д.;
      • аудио: звук должен давать не больше 5% нагрузки на процессор (убедитесь, что вы не воспроизводите звуки, которые не слышны);
      • неэффективная реализация пользовательского интерфейса и как следствие перегрузка процессора (избегайте большого количества перерисовок);
      • запуск анимаций, которые не видны;
      • оптимизация настроек Physics Fixed Deltatime: не слишком мало (с ошибками в физике) и не слишком много (слишком сильная потеря производительности). Используйте FixedUpdate() только для того кода, который должен работать во время физики, поскольку этот метод сильно сказывается на FPS.

      Также было бы неплохо время от времени создавать релизный билд. На нем можно проверить фактический FPS.

      3. Пики загрузки

      Пики легко идентифицировать, потому что они очевидны. Здесь тоже можно использовать Timeline, чтобы понять, почему какая-то часть кода обрабатывается дольше, чем нужно.

      Тестируйте игру на устройстве с медленным жестким диском (не SSD). Не обязательно фиксить все пики, иногда можно распределить их нагрузку. Недавно мы провели очень простую оптимизацию.

      Мы вызвали: родной метод Main() платформы; вызов через Monobehaviour Update(); метод (для обновления состояний платформы и ее четырех контроллеров). Это заняло 2,0 мс, что очень много для целевого значения в 16 мс. В итоге удалось оптимизировать процесс примерно до 0,2 мс, распределив его так, чтобы он запускался только на каждый X-й кадр, ведь не было необходимости запускать его на каждый фрейм. А также мы стали обновлять состояние только одного контроллера за один кадр, а не всех четырех сразу. Используя значение Time.frameCount по модулю, можно легко распределить множество различных операций по 16 кадрам в секунду.

      Недавно меня попросили помочь улучшить производительность игры другой студии. Базовая производительность была вполне удовлетворительной, но в процессе игры возникали лаги. Уровень был разделен на части, которые загружались и выгружались динамически: так разработчики хотели сократить нагрузку на графический процессор. Однако их узким местом стал ЦП. Вся игра весила около 2,5 Гб и полностью помещалась в памяти консоли. Чтобы исправить ситуацию, нужно было всего лишь прекратить динамическую загрузку/выгрузку фрагментов уровня и просто сохранить все в консоли.

      4. Повтор

      Пересоберите игру с учетом проведенной оптимизации и начните все заново с первого пункта. Сравните полученные результаты с предыдущими и определите, какие проблемы устранять дальше.

      Что оптимизировать

      Путей оптимизации очень много, поэтому я и предлагаю метод поиска узких мест, а не четкую последовательность действий. Определив проблемные места, можно вносить изменения, которые важны именно для конкретной игры.

      Недостаток производительности GPU: динамическое разрешение

      Можете использовать динамическое разрешение как временное решение. У вас может быть скрипт, проверяющий фреймрейты CPU и GPU, и, если графический процессор — узкое место вашей игры, уменьшите разрешение игровых камер (но не пользовательского интерфейса). Как только возьмете этот момент под контроль, сможете оптимизировать нагрузку графического процессора, чтобы она меньше зависела от этой настройки и можно было улучшить визуал.

      Пики загрузки CPU

      Используйте Incremental Garbage Collection. Благодаря этой функции, возможно, не придется сокращать выделенные сборщики мусора. Часто пиковые загрузки ЦП возникают из-за его работы. Incremental Garbage Collection позволяет значительно уменьшить пики загрузки. Правда, на некоторых проектах нам пришлось отключить эту функцию на нескольких платформах из-за сбоев Unity (Switch — Unity 2019.4).

      Недостаток производительности CPU

      Прекратите использовать Occlusion Culling по умолчанию. Вроде бы отличный инструмент, но на практике мы улучшаем производительность игры, полностью отключив Occlusion Culling. В каждой выпущенной нами игре на Unity, ЦП в определенный момент становится узким местом всей системы, а Occlusion Culling всегда дополнительно нагружает процессор. Конечно, все зависит от игры, но не забудьте проверить, помогает ли вам эта функция или только замедляет.

      GPU и CPU: технология рендеринга

      Хотя на топовых платформах в наших играх часто используется Deferred Rendering, было доказано, что лучше переключиться на Forward Rendering на устройствах более низкого уровня (мобильные платформы, Switch, Xbox One, PS4). Преимущества в производительности Deferred становятся очевидными только при использовании многопиксельной подсветки.

      Сейчас много говорят про новые технологии рендеринга — HDRP и URP, повышающие производительность. Но на практике мы не слышали, чтобы благодаря им игры выиграли в производительности (скорее, наоборот).

      GPU и CPU: отладчик кадров

      Необходимо использовать отладчик кадров Unity (Frame Debugger). Подобно Timeline в профайлере, этот инструмент помогает понять, как на самом деле работает ваша игра, визуализируя ее. Обработка вызовов отрисовки отнимает процессорное время. То есть — важно сократить вызовы отрисовки, использовать слияние шейдеров и/или материалов, GPU Instancing, а также динамический и статический батчинг.

      Отладчик кадров также помог нам отследить неприятные ошибки, из-за которых объекты или весь экран становились черными. Прокручивая вызовы отрисовки, можно точно узнать, когда и как что-то отображается.

      Отладчик кадров Unity

      Прочее

      Помимо Occlusion Culling есть еще две настройки, которые стоит проверить перед использованием.

      Нам пришлось отключить их на многих платформах из-за сбоев Unity (Particle jobs содержат ошибки). Как обычно: перед включением проверьте не страдает ли от этого производительность.

      Сотрудники Unity упоминали, он полезен только при работе на старых устройствах. Динамический батчинг тоже может сильно нагружать ЦП, и выгода от видеокарты не окупится. Пока не смог оценить явные плюсы и минусы этой настройки.

      Нужны ли другие инструменты

      Есть и другие инструменты, которые использовались раньше: от PIX (Xbox) до Intel VTune. Однако современный профайлер Unity предлагает все, что необходимо для внесения наиболее важных изменений. Для некоторых конкретных платформ можно использовать дополнительные инструменты (PIX, XCode, Android Studio и другие), чтобы упростить доступ к информации об устройстве. Но по моему опыту, встроенных инструментов Unity достаточно для выполнения оптимизации.

      Отдельно про ограничение со стороны ЦП

      У всех наших игр на Unity ЦП становится узким местом всей системы. Если проблемы с видеокартой и возникают, то обычно из-за того, что мы не провели какую-то базовую оптимизацию. Дело в том, что Unity очень много задач отправляет на один тред ЦП, хотя современные процессоры часто имеют по восемь ядер.

      Невозможно использовать всю фактическую мощность CPU. Поэтому такие функции, как Graphic Jobs, очень важны. Burst/Jobs/DOTS должны стать решениями этой проблемы в будущем. Но на сегодняшний день мы еще не нашли способ применить их с пользой для наших игр.

      Таск менеджер типичной игры на Unity с задержкой в CPU 3 (основной тред Unity)

      Смежные вопросы

      Сокращение используемых объемов памяти и исправление OOM-сбоев

      Работая над производительностью игры, вы столкнетесь со сбоями Out Of Memory или медленной загрузкой из-за неоптимизированного использования ресурсов. Хотя память не обязательно напрямую влияет на производительность, она все-таки важна. Оптимизацию использования памяти лучше всего проводить при подготовке оптимизационных билдов. Используйте Memory Profiler, чтобы точно определять занимаемые объемы. Совет: велика вероятность, что шейдеры съедают 50% памяти, тут стоит глубже погрузиться в правильное ограничение ключевых слов шейдера (на эту тему стоит написать отдельную статью).

      Заключение

      Надеюсь, эта статья поможет вам с оптимизацией или хотя бы подскажет, как выбрать более эффективный подход к профилированию. Удачи с разработкой.

      • оптимизация
      • профилирование производительности
      • unity
      • геймдев
      • unity profiler

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *