Приветствую, читатель! В первой части статьи, посвященной разработке The Light Remake в общих чертах был рассмотрен процесс переноса игры на новую версию Unity. Я немного рассказал об используемых шейдерах и эффектах, о том, какие решения были реализованы в работе со светом, какой дополнительный контент был создан, какой контент из старой версии был переработан и т.д. Во второй части речь пойдет о других аспектах разработки, о постэффектах, структуре проекта, работе со звуком, оптимизации и прочих нюансах.

Часть 2


Постэффекты


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

Гамма

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

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





Volumetric light

Очень важным элементом в данном ремейке стали постэффекты, связанные со светом. В старых версиях Юнити был очень симпатичный эффект объемных солнечных лучей Sun Shaft. Однако его функционал был ограничен, ведь он мог быть привязан лишь к одному источнику света в сцене.

В какой то момент мне на глаза попался очень занимательный эффект объемного освещения Volumetric lights Он позволяет создавать очень приятное, плотное свечение и лучи, которые могут исходить из разных источников. Атмосфера в сцене становится более объемной, объекты начинают утопать в легкой воздушной дымке, что меня крайне впечатлило. Единственным минусом стала высокая ресурсоемкость эффекта. Тем не менее, я решил его применить и благодаря Volumetric light удалось создать приятный свет от солнца во всей сцене, а также мистические направленные пучки света в отдельных местах: в коридоре на старте игры, в помещении с воробушком. Также эффект использовался для подчеркивания акцентов на светящихся ночью фонарных столбах, в эпизоде с проектором, в бойлерной для усиления света из окон, в финальной сцене вознесения персонажа к свету.



SSAO и SSR

Другими наиболее тяжелыми эффектами стал SSAO (Ambient Occlusion ), создающий мягкие затенения по углам и под поверхностями, без него сейчас никуда. Также был добавлен SSR (Screen Space Reflections ), с которым правда было много проблем еще на этапе разработки. SSR создает имитацию отражений на глянцевых поверхностях, в результате чего можно получить симпатичные рефлексы на кафельной плитке, металле и т.д. Проблема в том, что эффект оказался очень тяжелым и понижал FPS на моем железе почти в два раза. Путем некоторых манипуляций в коде постэффекта мне удалось немного снизить качество рассчета и несколько улучшить производительность. В целом частота кадров стала приемлемой, но в некоторых условиях (например при включенном Vsync) SSR вызывал периодические фризы и рывки при движении персонажа.



Другие

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



Кстати говоря, сейчас при работе с графикой часто вспоминается мой личный опыт обучения в художественной школе, где нас учили подчеркивать контрастные тени под объектами, отображать рефлексы на поверхностях предметов и накладывать в работе с пейзажами дымку на горизонте для подчеркивания воздушной перспективы. Теперь-то я знаю, что это все было SSAO, SSR и Global Fog!

Как это работает?


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

Система

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

Поскольку игра состоит по большому счету из одной локации, я решил не заморачиваться с подгрузками сцен, префабов и компонентов, разместил все необходимое в одной главной сцене. Были созданы контроллеры, которые управляют всем необходимым, переключают настройки, сохраняют игру и считывают сохраненные данные, считывают тексты для субтитров и записок из специального файла в корне игры и т.д. Для простоты реализации как и в прошлых играх было решено использовать систему сохранения в чекпоинтах. На локации находится несколько десятков интерактивных объектов вроде дверей, геймплейных предметов, керосиновых ламп и т.д. Для каждого сохранения необходимо записать идентификаторы состояния объекта, представляющие из себя обычно Int переменную. Например, дверь закрыта и заперта на ключ: DoorOpen — o, DoorLocked — 1. Не буду вдаваться в подробности относительно программерской темы, поскольку мои навыки в этой области несколько специфичны и поверхностны, однако, для реализации собственных проектов вполне достаточны.

Два финала

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



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

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

Субтитры и локализация

Для ремейка был проработан новый для меня подход к локализации и вообще отображению текстов и сообщений. Точнее, подход этот ранее уже был использован в проекте 7th Sector, однако там объемы контента были значительно меньше. За основу взят метод сохранения данных в хмл документ. Вся текстовая информация для локализации изначально хранится в xml файле в корне игры. Сообщения поделены на группы и имеют индивидуальную метку, принадлежат определенным категориям и определенным языкам. Для переноса строки я решил использовать символ ( * ), а для начала нового сообщения ( # ).



В нужный момент ( при выборе языка и старте сцены) контроллер считывает всю текстовую информацию, делит ее на строки и отдельные группы и записывает в своего рода библиотеку или словарь. Затем уже отдельные скрипты считывают эту информацию, например при открытии меню или активации субтитров на экране. Система мне лично показалась удобной и самое главное — понятной. Она позволяет достаточно легко ввести новый язык и вносить изменения в уже имеющийся текст.

Звук


Некоторая часть базовых звуков была перенесена из оригинальных исходников. Амбиент, звуки пения птиц, звук кинопроектора и т.д. было решено оставить для сохранения узнаваемости проекта. Однако, требовалось большое количество и нового контента. Были добавлены звуки самого персонажа, шагов, активируемых предметов, дыхания героя в определенных моментах, эффекта сердцебиения и т.д. Большее разнообразие было внесено в набор звуков окружения, добавлен стрекот кузнечиков, рандомные звуки падения предметов и хлопков дверей. Кстати говоря, на добавление шума цикад или кузнечиков меня вдохновили пейзажи новой Half Life Alyx, в которой очень здорово передана атмосфера жаркого летнего дня. Некоторое время наслаждался, прослушивая и просматривая на Youtube записи с амбиентами из City17.

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

Целым пластом ответственной и кропотливой работы стало создание саундтрека. Композиции в оригинальном проекте не были лицензионными, это был набор из треков Ludovico Einaudi, композитора Thomas Newman и OST из игры “Afraid of monsters”. Тем не менее, обычно нам западает в душу то, что мы услышали впервые, например оригиналы треков чаще всего кажутся более приятными, чем их ремиксы, по крайней мере для меня. В данном случае при создании новых композиций очень хотелось сохранить стиль и атмосферу оригинальных треков. С данной задачей, как мне кажется, отлично справился композитор Дмитрий Николаев, с которым мы уже работали ранее над игрой 35MM. Особое впечатление на меня произвел новый взгляд на динамичную композицию, звучащую во время показа кинофильма в лекционном зале. Трек сохранил оригинальный энергичный и немного психоделичный стиль, стал звучать свежо, но узнаваемо. Кстати, сам кинофильм тоже был сильно переработан и включал в себя много нового материала. Контент для видео более тщательно выбирался во избежании нарушения авторства, а часть фрагментов была создана самостоятельно.

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



Хотя в игре нет диалогов, нашлось место и для голосовой озвучки. Помощь в записи оказал Всеволод Петрыкин, уже участвовавший ранее в проекте “35ММ” и подаривший свой голос главному персонажу Петровичу. Его речь можно услышать из громкоговорителей в момент появления самолетов, из телефонной трубки в эпизоде с запуском ракет и по радио, на втором этаже главного корпуса.

Оптимизация


Одной из наиболее болезненных тем и задач в работе над ремейком стала оптимизация. Так вышло, что почти все проекты, созданные мной ранее на старой версии Unity ( 4.6) были достаточно просты в плане нагрузки на железо. Игра 35ММ на моей карте GTX 970 местами выдавала 200- 300 fps в достаточно сложных и загруженных сценах. Оригинальный Свет, собранный на еще более ранней версии движка показывал FPS еще выше. Но при переходе на Unity 2017 частота кадров просела в 2-3 раза. Понятно, что сцена стала намного сложнее, добавились просчеты света и отражений, дополнительные постэффекты и т.д. но настолько разительного снижения производительности я не ожидал. Загвоздка в том, что даже убрав из сцены практически все наполнение, показатель FPS у меня не поднимался выше 200-300. Это полупустая сцена, Карл! Была проведена большая работа по упрощению некоторой геометрии, созданию Lod групп, настройке Occlusion culling и т.д.



Также камерам с помощью скрипта назначалась дистанция отсечения определенных слоев. Для реализации использовался пример из мануалов Unity. Объекты разных размеров были присвоены отдельным слоям, которые перестают рендериться, если камера находится слишком далеко. Мелкие объекты, вроде банок, мусора, деревянных обломков, книг отсекаются после 20-30 метров. Более крупные — после 60-80. Все перечисленные меры позволили значительно снизить число Drawcalls. В среднем количество вызовов отрисовки в сцене колеблется в диапазоне 800 — 2000 DC. Количество полигонов в кадре не превышает 1 миллиона.



Отсечение светильников

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

Немного деталей


  • В подвальных коридорах стоя под бетонными канализационными шахтами с капающей водой можно заметить как мутные капли появляются на экране ( глазах/очках персонажа). Сделано просто — при условии попадания героя в триггер и при направлении камеры вверх включается префаб системы частиц с мутным Grabpass материалом. Также в этот момент капли можно наблюдать на поверхности карты, если взять ее в руки.



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



  • Во время нахождения персонажа в подвалах здания в меню можно заметить темные фигуры людей, стоящие напротив главного входа в корпус.

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



  • В катакомбах в одном из мест используется прием зацикленного коридора или петли. Похожая задумка была реализована в игре “STALKER Зов Припяти” на локации, где располагался Оазис. Когда мы попадаем в определенный триггер, скрипт переносит нас в почти такой же коридор, но уже зеркально и в обратном направлении. В результате, блуждая по туннелям мы несколько раз выходим в один и тот же зал. Телепортация не всегда срабатывает корректно, поэтому можно заметить резкий скачок и изменение дымки/ тумана перед собой.

  • Специально для проекта был снят видеоматериал с видами реальной территории, взятой в качестве референса — 3 корпус Московского Гуманитарного университета. Из материалов был смонтирован ролик, который стал секретным бонусом, доступным после прохождения игры. Для проникновения на территорию пришлось перелезать через старый бетонный забор. При переброске бойцов в целевой сектор ни одни штаны порваны не были.



  • Почти к финалу разработки проекта в качестве эксперимента мной был создан шейдер сосуда с жидкостью, аналогично бутылкам с жидкостями из Half Life Alyx, которые были добавлены с патчем. Качество не сравнимо, но выглядело все равно интересно. Я был настолько вдохновлен, что решил добавить бутылку в проект. Взять в руки ее нельзя, но можно несколько раз «пошевелить», после чего она упадет на стол. По ссылке можно посмотреть небольшую демонстрацию.



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



Парочка мыслей


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

Желаю всем удачи, творческого вдохновения и высокого FPS!