{"id":3826,"url":"\/distributions\/3826\/click?bit=1&hash=aa8ae934c4b405b2d10a14343e22e97821fd478e8a4a31defae61d161d040867","title":"\u041e\u0444\u0444\u0435\u0440 \u0434\u043b\u044f \u0434\u0436\u0430\u0432\u0438\u0441\u0442\u0430-\u043c\u0438\u0434\u0434\u043b\u0430 \u0437\u0430 \u043e\u0434\u0438\u043d \u0434\u0435\u043d\u044c","buttonText":"\u0413\u0434\u0435 \u0442\u0430\u043a\u043e\u0435?","imageUuid":"2b70606f-740c-5d85-8a71-8a33c5f66557","isPaidAndBannersEnabled":false}

Создание стилизованных эффектов для игр. Часть 2: Custom Vertex Streams и Custom Data

Эта статья является продолжением предыдущей статьи, в которой я рассказал о принципах создания эффекта "растворения" или dissolve-эффекта.

Мы создали шейдер и материал, который работает с GameObject'ами. Теперь давайте разберёмся, как заставить этот эффект работать с системами частиц и "растворять" каждую частицу по-отдельности.

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

Отличия при работе с системой частиц

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

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

Новая маска растворения

Если мы начнём менять параметр Progress, мы увидим следующее

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

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

Чтобы достичь этой цели, нам понадобится немного доработать шейдер, а также внести некоторые изменения в саму систему частиц. В честности, мы добавим Custom Vertex Streams.

Что же такое Custom Vertex Streams?

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

Но почему тогда в названии присутствует слово Vertex? Потому что каждая частица состоит из полигонов, каждый их которых состоит из граней и вершин (по-английски vertex'ов). Шейдер работает с самыми базовыми примитивами, в данном случае - с вершинами. По факту, мы передаём в шейдер данные о вершинах частицы. Просто если вершины принадлежат одной частице, они будут получать параметры только от этой конкретной частицы.

По умолчанию, в шейдер передаются следующие значения:

  • Position - позиция вершины частицы в глобальных (мировых) координатах.
  • Normal - направление вектора нормали вершины.
  • Color - цвет вершины.
  • UV - координата вершины на плоскости (uv-развёртка).

Если вы когда-нибудь писали шейдеры "руками", то вы могли видеть похожий набор в разделе appdata.

Потому что это именно оно и есть. Всё что мы будем передавать с помощью Custom Vertex Streams будет задекларировано в этом блоке, потому что appdata - это данные, которые шейдер получает от движка.

Давайте попробуем включить их в нашей системе частиц и посмотрим, что они из себя представляют на практике.

Начало работы с Custom Vertex Streams

Чтобы включить Custom Vertex Streams, надо в системе частиц отрыть модуль Renderer и поставить соответствующую галочку.

При активации Custom Vertex Stream, появится список данных, которые система частиц будет передавать в шейдер.

Данные, передаваемы по-умолчанию неочень помогут нам реализовать задуманный эффект, поэтому давайте посмотрим какие ещё варианты нам предлагает Shuriken. Для этого нажмём "+" в нижнем правом углу списка.

Как видите, в шейдер можно передать много разной информации о частице. Например, положение центра, вектор скорости, вращение, размер и т.д.

Более подробно о каждом stream'е можно почитать в официальной документации Unity.

Раннее мы определили, что хотим, чтобы частица появлялась целиком, а затем постепенно "растворялась". Другими словами, частица должна "растворяться" в зависимости от времени жизни. К счастью, список Custom Vertex Streams включает в себя раздел Lifetime, внутри которого есть AgePercent, который равен нулю в момент "рождения" частицы и плавно изменяется до единицы в момент её "смерти".

Это идеально подходит в качестве замены ползунка Progress в нашем шейдере.

Давайте добавим этот параметр к списку данных, передаваемых в шейдер.

Обратите внимание на то, что написано в скобках, рядом с названием параметра. Это семантика, которая используется внутри шейдера и по которой мы будем обращаться к нашему новому параметру.

Важно, что наш новый параметр имеет ту же семантику, что и параметр UV (TEXCOORD0), но при этом другой её компонент (в нашем случае - z). Так делается, чтобы как можно компактнее упаковать все данные и оптимизировать работу шейдера.

Теперь давайте посмотрим, как мы можем "подхватить" этот новый параметр внутри шейдера.

Модифицируем шейдер

Мы уже выяснили, что новый параметр AgePercent будет использоваться вместо параметра Progress. По этому можем смело удалять этот параметр, он нам больше не понадбится.

Теперь нужно добавить новый параметр в наш граф. В этом нам поможет та самая семантика.

Изначально семантика TEXCOORD использовалась для декларирования текстурных координат, оттуда и пошло название. Но со временем, в целях оптимизации, в них стали хранить всё подряд. Всего шейдер может содержать 4 семантики TEXCOORD, каждая из которых может хранить 4 компонента. В конце названия каждой такой семантики приписывается её номер, начиная с нуля.

Параметр AgePercent соответствует TEXCOORD0.z, то есть третьему компоненту нулевой семантики.

Чтобы получить к нему доступ, в Shader Graph используется нода UV. В самой ноде есть выпадающий список, который позволяет выбрать одну из четырёх семантик. Не знаю почему, но в Shader Graph вместо TEXCOORD0, TEXCOORD1 и т.д. используется UV0, UV1 и т.д.

Давайте добавим ноду UV на наш граф.

Удостоверьтесь, что выбран правильный номер семантики.

Нам нужен компонент z, по этому используем ноду Split и сразу воткнём z-компонент в ноду Remap, где раньше был параметр Progress.

Давайте сохраним наш шейдер и посмотрим, что получилось.

Это гораздо больше похоже на то, что нам нужно!

Но мы можем захотеть, чтобы частицы начинали "растворяться" не сразу, а во второй половине своего жизненного цикла. Теоретически, мы могли бы использовать функцию Remap из предыдущей статьи и это решило бы проблему. Но что если мы захотим, чтобы частицы растворялись не линейно, а с ускорением или замедлением? Или сначала растворялись наполовину, затем делали паузу и через некоторое время растворялись до конца? Remap тут не поможет. Зато поможет Custom Data.

Custom Data

Чтобы получить возможность передавать Custom Data нужно для начала активировать этот модуль в системе частиц.

Давайте рассмотрим его поближе.

Всего нам доступно 2 кастомных переменных с крайне оригинальными названиями Custom1 и Custom2.

У каждой переменной есть 2 режима - Vector и Color.

Каждая может содержать в себе до 4 компонентов.

Компоненты в режиме Vector могуть 4 типов:

  • Константа
  • Случайное значение между двух констант
  • Кривая
  • Случайно значение между двух кривых

Компоненты в режиме Color могуть быть:

  • Цвет
  • Случайное значение между 2 цветов
  • Градиент
  • Случайное значение между 2 градиентов

Значения на кривых и градиентах берутся в зависимости от времени жизни частицы. Крайнее левое положение или 0 - момент "рождения". Крайнее правое положение или 1 - момент "смерти".

Я покажу работу Custom Data на примере переменной в режиме Vector и компонентами типа Curve (кривая).

Я буду использовать 2 компонента - первый для контроля прогресса растворения, а второй для контроля мягкости границы растворения.

Давайте обозначим эти 2 компонента.

Для первой кривой, которая соответствует прогрессу растворения, давайте поставим линеное изменение от 0 до 1, тогда частица будет повторять поведение как с параметром AgePercent.

Вторую кривую пока оставим равной нулю.

Теперь, нам нужно "сказать" системе частиц, что вместо AgePercent мы хотим передавать в шейдер 2 компонента из Custom1.

Для этого переходим в модуль Renderer, выбираем AgePercent и нажимаем "-" в нижнем правом углу списка, чтобы удалить этот параметр. Далее нажимаем там же "+" и выбираем соответствущую позицию из списка.

Снова обращаем внимание на семантику.

X и Y компоненты Custom1 соответтствуют третьему и четвёртому компонентам TEXCOORD0.

Для корректной работы осталось обновить шейдер.

Дорабатываем шейдер

Когда мы использовали параметр AgePercent он соответствовал z-компоненте TEXCOORD0. Теперь вместо AgePercent у нас Custom1.x который выполняет ту же самую роль, поэтому z-компонент остаётся как есть, тут ничего менять не надо.

Мы решили, что будем использовать Custom1.y для контроля мягкости границы. Соответственно, параметр шейдера Falloff нам больше не нужен. Мы можем его удалить и вместо него добавить TEXCOORD0.w который принимает в себя Custom1.y.

Новый компонент нужно воткнуть в две ноды, где раннее использовался Falloff - в ноды Add и Subtract.

Теперь итоговый граф выглядит так:

Если мы сохраним шейдер и запустим систему частиц, то ничего не изменится. Это значит, что шейдер работает правильно.

Теперь я покажу несколько примеров, как, с помощью кривых из Custom Data, можно радикально менять поведение "растворения" частиц.

Примеры

Для каждой анимации я буду приводить скриншоты кривых из модуля Custom Data. Кроме них не будет меняться ничего.

1

2

3

Итог

Как видите, с помощью таких мощных инструментов как Custom Vertex Streams и Custom Data можно очень гибко контролировать параметры материалов на системах частиц и добиваться поведения, которого невозможно добиться другими способами.

Также, ещё одной важной особенностью Custom Data является то, что их можно контролировать с помощью C# скриптов. Но это уже совсем другая история...

Пишите в комментариях, какие бы ещё аспекты работы с шейдерами, частицами и FX'ами вам хотелось бы узнать и о которых стоит написать статью или туториал.

Данный цикл статей я пишу в рамках работы над своим проектом King, Witch and Dragon, который представляет собой метроидванию в стилистике cel-shading и о которой я веду дневник разработки.

Если вам понравилась статья и вы хотите поддержать проект, добавляйте King, Witch and Dragon в вишлист на Steam, это важно не только для моей мотивации, но и для алгоритмов Steam. Чтобы принять участие в обсуждении, вступайте в группу ВК, а также подписывайтесь на меня в Twitter и Instagram.

Спасибо за внимание!

0
5 комментариев
Андрей Боронников

От души благодарю)

Ответить
Развернуть ветку
Андрей Торчинский
Автор

Всегда пожалуйста

Ответить
Развернуть ветку
Remus Croft

Годная статья. 
Че там кстати по инстансингу, не ломает ли такой подход его? Инстансинг буфер, ЕМНИП, позволяет прокидывать в инстансы только численные значения. 

Ответить
Развернуть ветку
Андрей Торчинский
Автор

Насколько мне известно, Shader Graph пока что не умеет работать с per instance properties. Для рукописных же шейдеров просто загоняем нужные нам проперти между UNITY_INSTANCING_BUFFER_START и UNITY_INSTANCING_BUFFER_END и ставим галочку в инспекторе материала.

Ответить
Развернуть ветку
Евгений Онянов

Эх, месяц назад бы эту статейку :) Но всё равно годно!

Ответить
Развернуть ветку
Читать все 5 комментариев
null