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

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

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

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

*****

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

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

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

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

Антиалиасинг 0 и 8, но как видно на большом разрешении, качество картинки улучшается незначительно

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

Выходом могла бы стать настройка под разные девайсы, но по опыту могу сказать, что так делают немногие разработчики 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 на большое количество трансформов — могут возникнуть большие затраты на синхронизацию физика <> трансформ.

    Например, вы хотите, чтобы персонаж красиво умирал по физике с рэгдоллом. У вас есть два варианта. Первый, когда этот рэгдолл по сути является самим персонажем, но физика включается только после смерти — такое состояние может отжирать много производительности. Второй и правильный подход — после смерти персонажа брать префаб с рэгдоллом и уже его использовать для расчета падения.
  • ParticleSystem — на задавать MaxParticles больше чем может быть партиклов. Если, например, в системе их 15, а задано 10 000, то ресурсы могут быть потрачены впустую.
  • Не использовать меши более 30-50 треугольников в качестве партиклов. Желательно, конечно, обойтись билбордами.
  • Ознакомьтесь с руководством от Unity по оптимизации производительности графики.
  • Последний совет — использовать профайлеры, так как одного рецепта на все проекты нет и всегда надо смотреть, где он тормозит. В Unity встроен Frame Debugger — он неплох и поможет понять, как идет отрисовка, нет ли каких-то стелс камер\объектов. Но иногда стоит поглядывать и через Android Studio или Xcode. В крайних случаях можно смотреть профайлером от ARM, если не получается найти причины.

*****

Читать продолжение: Используем профайлер, а также смотрим, куда лезть не стоит

Вернуться в блог