Жертвы ради Nintendo Switch

Что приходится вырезать ради портативности.

Жертвы ради Nintendo Switch

Когда-то я мог только мечтать о релизе игры на консоли. Даже Steam казался далёкой целью — я работал в индустрии звукозаписи, но новость, что мощный игровой движок Unreal Engine 4 стал условно-бесплатным и игры может делать любой желающий, заставила меня стряхнуть пыль с незаконченного высшего режиссёрского образования и приняться за разработку кинематографичного хоррора о девочке, болеющей астмой.

Спустя четыре года работы моя игра Never Again вышла в Steam. Это был май 2019-го, а уже 30 января 2020-го состоится её релиз на Nintendo Switch. Учитывая, что я никогда не планировал релиз на этой консоли, портирование принесло немало трудностей.

​Скриншот из порта игры Never Again для Nintendo Switch
​Скриншот из порта игры Never Again для Nintendo Switch

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

Меня завораживала мысль, что я сделал игру на движке, которым пользуются огромные игровые студии, создающие ААА-игры. Конечно же, я не испытывал угрызений совести от того, что старался пользоваться всеми возможностями UE4 по максимуму, часто не понимая, как это отразится на производительности. Совсем беспредельничать мне мешал маломощный компьютер с GT740, без которого я бы наворотил таких тяжеловесных эффектов, что ни о каком порте на Nintendo Switch не шло бы и речи.

Целью было добиться на гибриде стабильных 30 FPS. Чтобы оценить, насколько трудной станет эта задача, я запустил Never Again на девките вообще без какого-либо вмешательства в структуру игры. Пусть она поработает, словно бы это ПК.

​Как же это было долго...
​Как же это было долго...

Если не брать в расчёт две бесконечности: сборку движка и компиляцию шейдеров под весь проект, запустить игру на консоли удалось с первого раза. Запущенное приложение встретило меня изумительными… 0 FPS. По ощущениям картинка менялась раз в час, а по факту на CPU было 6000 мс. Оптмизация предстала передо мной в виде хитрого ребуса, разгадать который оказалось той ещё задачей. Но по истечению двух недель, которые я занимался портированием 24/7, этот первый этап перестал казаться чем-то сокрушительным и неразрешимым.

Если вы столкнетесь с такой же проблемой, вот первый совет:

Важный совет №1. Полностью отключаем симуляцию физики одежды Apex Cloth.

Жертвы ради Nintendo Switch

Чтобы это понять и запрофайлить игру на Switch, пришлось накрутить такие костыли, что об этом мне не позволяет рассказывать NDA. В целом, это наконец заставило меня глубже разобраться с профайлингом в Unreal Engine 4.

​Прощай, красивое платьишко

Ура! Never Again полноценно запускается на Switch, и можно даже пройти игру, но все еще с большим трудом — вилка fps была от 10 до 15. Идей, что с этим делать, у меня было не так уж и много…

Я отключил тяжелые постпроцессы (Depth of field, Motion Blur (не понимаю, кому вообще нравится этот эффект ;)), Ambient Occlusion и так далее. За последний я очень боролся и не хотел выключать, т.к. картинка очень сильно менялась не в лучшую сторону. Но ради оптимизации придется потерпеть.

Затем пришло время сказать «до свидания» и сглаживанию. Отключил. Но его отсутствие я решил немного компенсировать понижением Resolution Scale в ручном режиме до 66.66%, а в консольном — до 80%.

Далее я настраивал разрешение экрана для консольного режима, установив 1920х1080 (меньше — не круто), а для портативного режима ограничился 1280х720. С разрешением у меня возник казус, полностью перевернувший моё представление о портировании. Но об этом — в конце.

Данные настройки помогли поднять fps до вилки от 18 до 24, что, в каком-то смысле, даже приемлемо для прохождения, но всё ещё не круто. Перфекционист внутри меня бил кулаком по столу и требовал стабильности.

Настало время убивать графику

Первое, что пришло в голову — отключить траву. Начальная локация в Never Again — освещенная детская комната, через окно которой видно улицу. А там, в свою очередь, уже куча источников света, порождающих огромное количество теней. Я не мог их запечь в силу обстоятельств, о которых можно написать целую отдельную статью. Освещение должно было оставаться стационарное (полудинамическое). Больше нигде в игре травы нет, так что жалеть её я не стал.

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

На скриншотах можно заметить, что последняя картинка какая-то зернистая. Нет, это не низкое разрешение скриншота, это Dithering (Дитеринг).

Важный совет №2. Забываем о режиме Translucent. Прозрачные материалы делаем с помощью режима Masked + Dither.

Жертвы ради Nintendo Switch

Зачем это нужно? Видеокарте очень тяжело рендерить прозрачные материалы — нужно максимально облегчить ей эту задачу. Тут два варианта: либо вообще убрать прозрачность, либо зафейкать её с помощью дитеринга. Результат получается не идеальный, но вполне приемлемый:

​В консольном режиме такое стекло выглядит на удивление хорошо

Это, конечно, сильно разгружает GPU, но сказать, что вызывает бурный скачок FPS — нельзя. Тем не менее это ещё один маленький камешек в фундамент оптимизации.

Далее я начал профайлить GPU и увидел, что львиную долю мс занимает освещение. А если конкретно — тени. Это, конечно, очевидно, а ещё более очевидно, что нужно с этим что-то делать. Я полез смотреть на то, как реализованы тени в других 3D-играх на Switch. Наиболее привлекательной мне тогда казалась графика у Outlast. Интересно, как они справились с тенями?

​Очень качественный порт

Динамические тени оказались очень низкого разрешения. Это наблюдение сняло с меня груз ответственности и позволило сделать так же.

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

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

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

Первым делом под руку попались физические объекты.

В Never Again много физики, что для Nintendo Switch — не очень хорошая новость, так как любая физика — серьёзная нагрузка на процессор. В целом, физические объекты были достаточно оптимизированы ещё для ПК: когда они валялись без дела, то погружались в сон и тем самым переставали нагружать процессор. К таким объектам можно отнести куски от кукол в пещере, которыми я завалил весь пол. Число таких объектов пришлось сократить, чтобы уменьшить количество их столкновений друг с другом.

​Наверное, я — больной ублюдок, раз решил вообще в эту игру физику вставить...

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

Важный совет №3. Используйте физику в играх разумно, а лучше — вообще от неё откажитесь.

Вот мы и дошли до логики.

Я начинал разрабатывать игру с очень низкими навыками программирования. Я на ходу изучал возможности Blueprints, и это дало о себе знать при портировании.

За четыре года разработки накопилось очень много фундаментальных ошибок, исправление которых, особенно в квестах и кат-сценах, могло сделать игру непроходимой. Пару лет Never Again была в «раннем доступе», и всё это время я упорно возводил шаткую башню из костылей. И если в Steam при обнаружении пользователем бага я мог моментально перезалить билд и всё исправить, то со Switch такое не прокатывает: каждое обновление тщательно проверяется, и это занимает немало времени. Поэтому переписывать код было опасно. Проще отрезать функции, которые не влияют на геймплей.

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

Каждый кадр срабатывали три LineTrace, которые определяли координату под каждой ногой и туловищем персонажа, чтобы красиво сгибать ноги в зависимости от рельефа поверхности, на которой стоит главная героиня (Inverse Kinematics). В связи с этим, следующий важный совет.

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

Стоило мне убрать IK, как сразу высвободилось 7 мс с CPU, и игра стала изредка выдавать желанные 30 FPS.

На этом моменте я застрял, ведь больше откручивать было нечего… Все, что осталось — попробовать включить Forward Shading.

Что это значит? Помните, в начале статьи я говорил, что боролся за близкий моему сердцу постпроцесс Ambient Occlusion? Дело в том, что Forward Rendering был придуман для VR игр. Он отключает некоторые тяжёлые визуальные эффекты и тем самым в разы уменьшает нагрузку как на GPU, так и на CPU. Ambient Occlusion, соответственно, тоже был отключен.

Сразу бросилось в глаза, что вместе с Ambient Occlusion перестали отображаться декали. Я заменил декали на плейны с текстурой, и это было нетрудно. По Ambient Occlusion я горевал до тех пор, пока первый раз не зашоукейсил игру на Nintendo Switch. Игроки ничего странного в визуале не увидели и в целом охарактеризовали порт как «красивый».

Помните, я упомянул про казус с разрешением экрана? Я долго не мог понять, почему в проекте после перехода на Forward Rendering FPS поднялся до небес, а в консоли все также оставалось от 25 до 30 FPS. Проблема оказалась в Device Profiles: я неправильно указал смену разрешений для ручного режима и консольного. После исправления я почувствовал великое облегчение, статистика показала эпичные 55 FPS в меню, вместо стабильных 30, за которые я боролся две недели.

Да, записал гифку с видоса записанного на телефон, что такого?

Тут можно было бы и закончить, но есть ещё пара моментов, на которые нужно обратить внимание.

Важный совет №5. Уменьшаем размер текстур.

Крупные текстуры — распространённая ошибка, которой мне удалось избежать по причине особенности стиля игры и слабого компьютера. Но, тем не менее, многих эта ошибка может коснуться. Я поставил себе ограничение, что размер текстур не должен превышать 1024х1024, а чаще всего использовал вообще 512х512. Помните: размер памяти у Nintendo Switch — не безграничный.

Важный совет №6. Пользуйтесь Blueprint Interfaces.

Важно, чтобы ваши классы были независимы друг от друга. В случае с Blueprint это еще важнее, так как все связанные между собой блупринт-классы сразу падают в память.

В целом, это всё. Можно было бы ещё рассказать про прохождение всех проверок билда компанией Nintendo, но это совсем другая история, и прячется она под NDA. Все обладатели Switch смогут увидеть плод моей боли, пота и слез в Nintendo eShop уже 30 января 2020 года.

Never Again - Nintendo Switch Trailer
192192
126 комментариев

Комментарий недоступен

145
Ответить

Комментарий недоступен

3
Ответить

Идеальный баланс

4
Ответить

Комментарий недоступен

58
Ответить

Не могу не вставить свои 5 копеек
Небольшая памятка для правильной оптимизации без урезаний в UE4:
1)По освещению - старайтесь не пересекать множество лайтов друг с другом; избегайте по возможности поинт лайтов (вместо них споты); избегайте волюметрик опций (это дорогая вещь);
2) Используете запеченный свет - для каждого меша делайте свое разрешение лайтмапы, саму лайтмапу делайте руками; лайтмапа разрешением до 254 как максимум
3)Используете динамическое освещение - подходите к каскадным теням с умом - максимальное качество включайте только перед игроком, выключайте полностью тени на удалении (используйте вместо них Mesh Distance Field - увеличите память, но не потеряете в качестве)
4)Для мобильных платформ максимум треугольников в сцене 300к
5)Если используете много растительности и маскированных материалов - включайте Early Z-Pass
6)Не используйте сложную коллизию
7)Ограничивайтесь в количестве материалов на один меш - каждый наложенный материал это доп. drawcall
8)Используйте HLOD - займет больше памяти но снизит нагрузку
9)Текстуры в редком случае делают 4К разрешения (даже в AAA проектах), 2К вполне хватает;
метод компрессии ETC (не учитывает альфа канал), либо ATC;
текстуры собирайте в атлассы с использованием RGB каналов (порой в одну атлас-текстуру 2К можно всунуть 12 текстур 1К)
10)Ambient Occlusion работает через пост процесс (как и TAA и FXAA), поэтому его можно оптимизировать настройками оттуда.
TAA дорогая вещь и с ним есть много проблем, но по соотношению качество/скорость он лучше чем MSAA, который нормально себя показывает только в 8кратном режиме
Для мобилок лучше использовать FXAA либо MSAA 2x
11)Используйте динамическое разрешение (последние версии движка это поддерживают)
12)Не делайте много блюпринтов на сцене в игре. Все блюпринты обсчитываются каждый фрейм вне зависимости используете вы Event Tick или нет (Event Tick только еще больше нагружает систему)
13)Forward Rendering будет лучшим решением только если у вас в сцене мало источников света, иначе сделаете только хуже

32
Ответить

Комментарий недоступен

1
Ответить

Можно про пункт 12 поподробнее. Первый раз от таком слышу.

Ответить