Удар молнии с VFX Graph

Публикация направлена на новичком или людей плохо знакомых с VFX Graph.

Перед началом:

  • Использована версия Unity 2022.3, должно работать на всех версиях выше 2020.2

  • Перед началом поставьте галочку в Preferences > Visual Effect > Experemental Operators/Blocks

  • Данные кривых будут указаны как «значение» = «точка х» или «промежуток от х до у». Вы можете использовать свои кривые, я даю их в основном для справки. Это не удобно для чтения, но способа лучше не нашлось.

  • Если значения кривой не указаны в каком-то промежутке, значит они свободно интерполируют между указанными точками.

Создаём следующий скелет:

Эффект будет состоять из Strip'ов, так что добавляем все необходимые блоки в System. Создаём две первые переменные, они нам понадобятся в дальнейшем.

10940ee6e3c84198f63228e51ff2b125.png

Добавляем Set custom attribute в Initialize, в инспекторе даём ему имя Progress. Устанавливаем атрибут при помощи оператора Get Attribute: spawnIndexInStrip (обычный spawnIndex даёт неверные значения) делённого на переменную PartAmount. Теперь в этом атрибуте хранятся числа в диапазоне от 0 до 1 в зависимости от положения на линии.

Добавляем Get Custom Attribute, в инспекторе указываем имя атрибута Progress, добавляем оператор Sample Bezier, добавляем Set Position. Соответствующим образом подключаем их друг к другу. Теперь мы можем увидеть дугу, поместив ассет на сцену! Всё что будет дальше, это операции над линией, с целью придания ей характеристик молнии.

15aefbec5b84a12ed72f9df00834dc6d.pngae4b6b6fba32cb6d19b81c0fae89f03d.png

Чтобы было удобнее размещать молнию на сцене, создаём четыре переменных для задания позиции кривой Безьера, переводим позиции из local в world. Добавляем на GameObject с Visual Effect VFX Property Binder, соответствующим образом устанавливаем значения всех переменных как позиции других GameObject. Теперь мы можем контролировать форму и позицию кривой Безьера прямо в редакторе меняя позиции указанных GameObject'ов, избегая хардкодинга.

Промежуточный результат со значением Set Size = 0.05.

Промежуточный результат со значением Set Size = 0.05.

Теперь добавим оператор шума и необходимые для его контроля переменные. Быстро перечислю переменные и их значения: NoiseFrequency (2), NoiseOctaves (3), NoiseRoughness (0.5), NoisePower (0.1), MoveDelta (1.5). Все их подключаем к оператору Value Noise 3D. Noise Power и -Noise Power используется для создания Vector 2, и передаётся в Range. Чтобы молния слишком не отклонялась от первой и конечной точки, умножаем Vector2 на Sample Curve, в качестве Time которой использован Custom Progress. Sample Curve равна 0 = 0, 1 = от 0.1 до 0.9, 0 = 1. Get Position передаём в качестве координат шуму. Результат в виде Derivatives передаём в Add Position в Udate.

d9d29313f80f837f714bd367b27544f4.pngС добавлением шума.

С добавлением шума.

3d771277409606adf16b103757581945.png

Однако, молния каждый раз одинаковая, поскольку шум детерминистический. К тому же, молния не двигается. В реальной жизни удар молнии состоит из двух этапов: 1) сначала молния движется сверху вниз и ветвится, 2) как только одна из ветвей достигает земли по ней проходит мощный той, происходит основная вспышка. Чтобы не перегружать эффект симуляцией ветвления и оставить туториал простым (к тому же, все кроме первой достигшей поверхности ветви должны быть тусклее основной), будем просто менять координаты шума в первую половину эффекта. После вспышки молния будет замирать и исчезать.

Добавляем новый кастомный атрибут MoveSpeed и устанавливаем его равным Total Time(Game). В Update также устанавливаем значение MoveSpeed = Delta Time(VFX) * Noise Speed * Sample Curve + MoveSpeed. Для T кривой используем Age Over Lifetime. Sample Curve равна 1 = от 0 до 0.5, 0 = от 0.6 до 1. Добавляем результат MoveSpeed к координатам шума. Кривая будет регулировать количество движения.

Добавляем HDR цвет (Мой равен R 3.08 G 7.12 B 22.6) (Чтобы HDR работал, добавьте в Post Procces Bloom). Умножаем цвет на кривую с T AgeOverLife равную 0.1 в промежутке от 0 до 0.5, 1 = от 0.6 до 0.8, и 0.4 = на координате 1.

Устанавливаем альфу тоже как кривую с таким же T, значения 0 = 0 до 0.1, 0.1 = 0.2, 0 = 0.3 до 0.5, 1 = 0.5 до 0.85, 0 = 1. Я также умножаю альфу на кривую использованную с NoisePower, чтобы скрыть острые края начала и конца молнии.

4c4a8eebb43af758d4e6fcb916079b6f.pngПолучается уже довольно неплохо!

Получается уже довольно неплохо!

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

Для вспышки создаём простой (копеечный) Initialize Particle с Output Particle Quad. Настраиваем его по своему вкусу, здесь главное чтобы момент вспышки совпал с началом второй части молнии. Для удобства использования ассета я также нахожу центр между Pos1 и Pos2, и устанавливаю его в качестве координаты вспышки, а расстояние между этими точками использую для Scale по вертикали. Момент вспышки регулирую с помощью Curve 0 = 0 до 0.5, 1 = 0.6, 0 = 0.75 до 1.

d09bf176fa191361aa9b30bfd58d839d.png12a30587f5ffd9b57167521cccd2b06d.png

Теперь, тёмная часть. Это будет декаль на поверхности, в которую ударяет молния. Всё просто, создаём партикль, поворачиваем его на -90 градусов по х, чтобы он проецировался на поверхность снизу. Для цвета использую Color2 (R 4.28 G 1.09 B 0) (понадобиться дальше) делённый на 4, а для альфы кривую 0 = 0 до 0.5, 0.6 = 0.6, 0 = 1.

b5e6784e229354355218278eb5dfc794.pngb79a482f140b6ff46c1b1334703e6bbd.png78acbd9c105afae1012cb1044534f518.gif

Я дополнительно добавлю стилизованное перекрестье в нижней точке молнии + небольшие отлетающие частицы, чтобы передать импакт.

Единственный примечательный момент графа перекрестья состоит в том, что чтобы корректно повернуть Quad к камере, для угла z используется Camera.transform.angles.z + spawnIndex * 90 + 45. То есть, первая линия перекрестья появится под углом 45 градусов к камере (spawnIndex считает с нуля), а вторая линия под углом 135.

4bfb81708ba866c2917cfc8e1e5b810e.pngdb28db50171d73f2e0fb37a1d61eb695.png

Для разлетающихся частиц располагаем их на сфере в Initialize, а в Update добавляем Conforn To Sphere Attraction Force относительно сферы с большим радиусом (или же отрицательный Attraction Force для сферы (или точки) меньшего диаметра).

6f3047bafea644359095712e76c43974.png09b6bbf8c87402590dcea17aff302d7b.pngИтого

Итого

Итого мы имеем ассет, который имеет:

  • Удобные якоря GameObject для расположения на сцене.

  • Форму мелких неровностей, которая условно случайная.

  • Баланс контраста.

  • Ощущение импакта.

© Habrahabr.ru