VFX для начинающих: создаем эффект нанесения урона по площади

Technical Artist Сергей Олейников рассказал о создании AoE-эффектов для мобильных игр на примере нового проекта Plarium Kharkiv.

Последние 2 года я работаю над RAID: Shadow Legends – пошаговой коллекционной RPG в сеттинге фентези. Ко всем эффектам в игре у нас были базовые требования: они должны хорошо выглядеть, передавать механики игры и быть понятными для игрока.

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

В статье я расскажу про огненную волну, которая наносит урон сразу по группе противников.

Сбор референсов

Обязательный пункт, даже если у вас уже есть идея эффекта. Для этого отлично подходят азиатские сайты, например: https://www.cgjoy.com, http://huaban.com и http://bbs.cgwell.com.

REF.png

Основные составляющие эффекта

Любой эффект можно разделить на 3 основных элемента: анимация, геометрия и материалы. Базовая анимация тут довольно простая и была сделана в самом движке Unity.

Animation.gif

Геометрия состоит из хвоста, волны (верхняя и нижняя части) и трех систем частиц: дыма, тлеющего пепла и языков пламени на земле. Мы используем стандартную систему частиц Unity – Shuriken. Каждая система создает частицы в зависимости от пройденного расстояния (Emission: Rate over Distance).

Geometry.gif

Иногда при использовании шейдеров с прозрачностью можно получить артефакт при отрисовке – отдельные фейсы перерисовываются поверх остальных. Это произошло и с геометрией волны. Причин может быть несколько:

  1. геометрия сама себя перекрывает (например, торусы и их производные);
  2. геометрия получилась многослойной, и слои рисуются в неправильном порядке (частный случай первого варианта);
  3. геометрия была сшита из нескольких кусков в произвольном порядке (что и случилось с волной).

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

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

Materials_zoom.gif

Хвост, волна и частицы пламени используют идентичные шейдеры, но есть нюансы.

  1. По хвосту можно двигать текстурную маску, и это создает эффект полета.
  2. Вертексная анимация придает волне объем и усиливает чувство движения.
  3. Скорость движения текстур в материалах не завязана на время, а контролируется вручную – это удобно для анимации. Например, когда волна замедляется, то замедляется не только скорость перемещения самой геометрии, но и движение текстур и вертексов.
  4. Для частиц пепла используется шейдер с альфа-эрозией. Таким образом, когда пепел тлеет, от него отделяются частички, тем самым уменьшая его размер.
  5. Частички дыма наименее интересны – это стандартный шейдер частиц, который идет из коробки.

Создавая шейдер огня, я сложил и перемножил текстуры, стараясь получить базу для материала, которая будет напоминать пламя.
half base = (R1 * G * (R0 + R1) + B) * B;

Я делал всё интуитивно, ориентируясь на подборку референсов, поэтому база прошла несколько итераций. В результате получилось 4 выборки из текстур. Это не самое дешевое решение в плане оптимизации, но результат того стоил.

Base.gif

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

Для этого нужно считать градиент, используя в качестве одного из компонентов UV-координат нашу базу. half3 colBase = tex2D(_gradient, half2(base,0));

Если есть строгие требования по оптимизации (а это еще одна выборка текстуры, которая к тому же зависит от других выборок), то поможет серия интерполяций. Количество таких интерполяций напрямую зависит от количества цветов. half3 colBase = lerp(_Color1, _Color2, base);

Ramp_cut.gif

Читабельность эффекта

Эффект должен хорошо выглядеть и читаться как на светлом, так и на темном фоне. А для этого нужно подобрать правильный блендинг. Alpha blending не дает ощущения свечения, которое нужно для материалов огня, молнии и т. д. Additive же на светлом фоне всё пересвечивает, и при этом теряются цвета. Есть третий вариант – Premultiplied Transparency с контролируемой альфой, который я и использовал.

Для этого в шейдере прописываем сам блендинг Blend One OneMinusSrcAlpha. Потом перемножаем финальный цвет на альфу (используем нашу базу). Получаем финальный цвет col.rgb = colBase * base; и альфа-канал col.a = base * _BlendingFactor; где BlendingFactor – слайдер со значениями от 0 до 1. Если _BlendingFactor = 0, то получим Additive blending, если 1 – обычный альфа-бленд. Я выставил 0,5 и получил нужный результат.

Blending.gif

Теперь перейдем к системе частиц пепла. Чтобы добиться эффекта сгорания, я использовал шейдер, где анимация альфа-канала управляется через модуль custom vertex data.

Это усовершенствованный вариант альфа-эрозии, который отлично подходит для изображения всплесков жидкости или тлеющего пепла. В основе альфа-эрозии лежит простая идея: сделать исчезновение объекта более артистичным и неравномерным с помощью дополнительной черно-белой маски. К маске добавляем слайдер с диапазоном от 1 до -1, результат ограничиваем диапазоном от 1 до 0 и всё это умножаем на альфа-канал.

col.a = col.a * saturate(aMask + _slider);

Усовершенствование подхода заключается в том, что мы добавили возможность контролировать ширину и четкость границы маски. Если представить маску как карту высот, а нижнюю (_slider) и верхнюю (_slider + _step) плоскости как минимальное и максимальное значения, которые ремапятся в 0 и 1, то получим возможность двигаться по этой маске с определенным шагом (_step), который и будет отвечать за ширину нашей границы.

Советы

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

Для сокращения количества текстурных выборок можно положить эту дополнительную маску в один из каналов текстуры.

Step_cut.gif

Код и финальный результат:

col.a = (aMask - _slider) / (saturate(_slider + _step) - _slider);

Alpha.gif

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

Не существует единственного правильного рецепта при создании VFX, решений всегда несколько, и это обеспечивает гибкость подхода. Если есть желание более подробно погрузиться в тему, рекомендую начать с этих ресурсов https://realtimevfx.com/ и https://simonschreibt.de/game-art-tricks/

И напоследок сделал видео с эффектами, которые я создавал для проекта RAID: Shadow Legends. Возможно, пригодится вам в качестве референсов.

629 0 850 8
5
2019-04-03
Неплохо
2019-04-03
Крутой подгон!
2019-04-03
Viktor, где бы мы ещё встретились, конечно же
2019-11-22
Совершенно разносольный подбор эффектов в качестве рефера где видео словно взяты из разных источников... то они мультяшные... то вроде как смешаный стиль реалистичности и мультяшности...
2021-08-13
Мне как "новичку" по описанию шейдера всё понятно, впрямь разжевано =).
За труд спасибо.
RENDER.RU