Хабрахабр

Под капотом Graveyard Keeper: Как реализованы графические эффекты

Всем привет! Целых 4 года я не писал на Хабр. Последняя моя серия постов была о различных инструментах и приемах, которые мы применяли на нашей прошлой игре (разрабатывая ее на Unity). С тех пор игру ту мы благополучно выпустили, а также выпустили и новую. Так что теперь можно немного выдохнуть и написать несколько новых статей, которые могут быть кому-то полезны.

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

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

Для начала, кратко перечислю из чего собирается картинка в нашей игре:

  1. Изменяемый ambient light — банальное изменение освещенности в зависимости от времени дня.
  2. LUT-цветокоррекция — отвечает за изменение тона картинки в зависимости от времени суток (или типа зоны).
  3. Динамические источники света — факела, печки, лампы.
  4. Карты нормалей — отвечают за придание объема объектами, особенно при движении источников света.
  5. Математика 3D-распределения света — отвечает за то, чтобы источник света по центру экрана корректно освещал объект, который находится выше, но не освещал объект, который находится ниже (т.е. повернут к камере неосвещенной стороной).
  6. Тени — сделаны спрайтами, вращаются и реагируют на положение источников света.
  7. Имитация высоты объектов — для корректного отображения тумана.
  8. Прочие украшалки: дождь, ветер, анимации (в т.ч. шейдерныая анимация листвы и травы) и т.п.

Теперь — подробнее.

Изменяемый ambient light

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

Выглядит это вот так:

LUT-цветокоррекция

LUT (Look-up table) — таблицы замены цветов. Грубо говоря, это трехмерный массив RGB где в каждом узле находится значение цвета, на которое следует заменить соответствующий. То есть если по координатам (1, 1, 1) находится красная точка, это значит что весь белый цвет на картинке будет заменен на красный. Если же по координатам (1, 1, 1) находится белый цвет (R=1, G=1, B=1), то изменения не происходит. Соответственно, LUT без изменений имеет по каждым координатам цвет, соответствующий этим самым координатам. Т.е. в точке (0.4, 0.5, 0.8) находится цвет (R=0.4, G=0.5, B=0.8).

Например, вот так выглядит «дефолтный» LUT (никак не изменяющий цветопередачу): Ну и стоит отметить, что для удобства представляют 3D текстуру в качестве двумерной.

Реализуется элементарно, работает быстро и удобно.

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

Вот так выглядит их настройка: В нашем случае художник немного упоролся и создал аж 10 разных LUT'ов для разного времени суток (ночь, сумерки, вечер и т.п.).

В результате в зависимости от времени суток одна и та же локация выглядит по-разному:

Здесь еще изменяется прозрачность спрайтов света из окон в зависимости от времени суток.

Динамические источники света и карты нормалей

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

Рисуются такие нормали довольно просто. Художник грубо кисточкой прорисовывает свет с 4х сторон:

А потом это скриптом собирается уже в карту нормалей:

Если ищете шейдер (да и софт), который это делает, можете посмотреть в сторону Sprite Lamp.

3D-имитация света

Тут немного сложнее. Нельзя просто так взять и осветить спрайты. Нам нужно учитывать, стоит ли спрайт «за» источником света или «перед».

Обратите внимание на эту картинку:

Оба дерева находятся на одинаковом расстоянии от источника света, однако дальнее дерево освещено, а ближнее — нет (т.к. к камере повернута его неосвещенная часть).

Шейдер высчитывает расстояние по вертикальной оси y между источником света и спрайтом. Эту проблему я решил довольно просто. Сделан именно коэффициент, а не просто «не освещать», чтобы когда источник света двигается и оказывается вдруг за спрайтом, то спрайт не моментально становился черным, а постепенно. И если оно положительное (источник света перед спрайтом), то мы освещаем спрайт как обычно, а вот если отрицательное (спрайт перекрывает источник света), но интенсивность освещения очень затухает от расстояния с очень большим коэффициентом. Но все-таки довольно быстро.

Тени

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

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

Да, получается не очень быстро, т.к. Задачу «найти ближайшие 3 источника света и рассчитать расстояние/угол теней до них» решает скрипт, который крутится в Update'е. Если бы писал сейчас, то воспользовался бы новомодными системами параллельных job'ов в Unity. приходится выполнять много математики. Но тогда этого еще не было, так что просто максимально оптимизировал обычные скрипты.

Т.е. Единственное что важно — я сделал вращение спрайтов не transform'ом, а внутри вершинного шейдера. Просто в спрайт выставляется параметр (я использовал для этого цвет, т.к. rotation не трогается. Так получается быстрее, т.к. все равно все тени черные), а за поворот спрайта ответственнен уже шейдер. не приходится дергать геометрию в Unity.

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

Например, посмотрите тень от забора: Второй недостаток в том, что иногда трудно сделать тень для объекта, у которого пятно контакта с землей очень вытянуто.

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

Тут, правда, стоит отметить, что спрайт еще очень сильно деформирован по вертикали (оригинал теневого спрайта выглядит почти что как круг). Именно по-этому его поворот выглядит не столько как поворот, сколько как искажение.

Туман и имитация высоты

Еще в игре есть туман. Выглядит он вот так (сверху — нормальный вариант, снизу — экстремальный 100% туман, для демонстрации эффекта).

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

Ветер

Ветер в пиксель-арте — отдельная история. Вариантов тут не много. Либо анимировать руками (что при нашем количестве арта почти невозможно), либо писать деформирующий шейдер, но тогда придется терпеть иногда некрасивые искажения. Можно, конечно, вообще не анимировать, но тогда картинка выглядит неживой.

Выглядит это так: Мы выбрали вариант искажений с помощью шейдера.

Если применить этот шейдер к текстуре с шашечками, становится понятно, что происходит:

Стоит также отметить, что анимируем мы не всю крону, а только отдельные листики:

Еще мы шатаем на ветру пшеницу, но тут все просто — вертексный шейдер деформирует x-координаты, при чем учитывает y-составляющую. Чем выше точка, тем сильнее шатать. Это сделано, чтобы шаталась только верхушка, а корень — нет. Плюс — фаза шатания меняется от x/y координат, чтобы разные спрайты на экране качались вразнобой.

Этот же шейдер применяется и для создания эффекта качания пшеницы и травы при проходе через них игрока.

Наверное, на этом пока все. Я намерено не затрагивал вопрос построения сцены и ее геометрии, т.к. это материал для отдельной статьи. В остальном — рассказал об основных решениях, которые применяли в разработке.

Возможно, расскажу в отдельной статье. PS: Если кому-то интересны какие-то технические аспекты, пишите в комментах. Если, конечно, нужно.

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

Показать больше

Похожие публикации

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

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

Кнопка «Наверх»