Пользовательское освещение в Shader Graph: Расширение графиков в 2019 году

С выходом Unity Editor 2019.1 пакет Shader Graph официально вышел из предварительной версии! Теперь, в версии 2019.2, мы добавляем в Shader Graph еще больше возможностей и функций.
Чтобы поддерживать пользовательский код внутри Shader Graph, теперь можно использовать новый узел Custom Function. Этот узел позволяет определять собственные пользовательские входы и выходы, изменять их порядок и вводить пользовательские функции либо непосредственно в сам узел, либо через ссылку на внешний файл.
Субграфики также получили обновление: теперь вы можете определять собственные выходы для субграфиков, с различными типами, пользовательскими именами и перестраиваемыми портами. Кроме того, Blackboard для субграфов теперь поддерживает все типы данных, которые поддерживает основной граф.
Использовать Shader Graph для создания мощных и оптимизированных шейдеров стало еще проще. В 2019.2 вы можете вручную задавать точность вычислений на графике, как в целом, так и для каждого узла. Наши новые цветовые режимы позволяют быстро и легко визуализировать поток Точности, категории узлов или отображать пользовательские цвета для вашего собственного использования!
Дополнительные сведения об этих новых возможностях см. в документации по Shader Graph.
Чтобы помочь вам начать работу с новой пользовательской функцией, мы создали пример проекта с пошаговыми инструкциями. Загрузите проект из нашего репозитория и следуйте за ним! Этот проект покажет вам, как использовать узел Custom Function для написания пользовательских шейдеров освещения для Lightweight Render Pipeline (LWRP). Если вы хотите продолжить работу над новым проектом, убедитесь, что вы используете редактор 2019.2 и пакет LWRP версии 6.9.1 или выше.
Для начала нам нужно получить информацию от главного источника света в нашей Сцене. Начните с выбора Create > Shader > Unlit Graph, чтобы создать новый Unlit Shader Graph. В меню Create Node найдите новый узел Custom Function и нажмите на значок шестеренки в правом верхнем углу, чтобы открыть меню узла.
В этом меню можно добавить входы и выходы. Добавьте два выходных порта для Direction и Color и выберите Vector 3 для обоих. Если вы видите флаг ошибки "undeclared identifier", не волнуйтесь: он исчезнет, когда мы начнем добавлять наш код. В раскрывающемся меню Тип выберите Строка. Обновите название функции - в этом примере мы используем "MainLight". Теперь мы можем начать добавлять наш пользовательский код в текстовое поле.

Сначала мы воспользуемся флагом `#ifdef SHADERGRAPH_PREVIEW`. Поскольку окна предварительного просмотра на узлах не имеют доступа к данным о свете, нам нужно указать узлу, что отображать в окнах предварительного просмотра на графике. `#ifdef` указывает компилятору использовать разный код в разных ситуациях. Начните с определения резервных значений для выходных портов.
Далее мы используем `#else`, чтобы указать компилятору, что делать, когда нет предварительного просмотра. Именно здесь мы получаем данные о свете. Используйте встроенную функцию `GetMainLight()` из пакета LWRP. Мы можем использовать эту информацию для назначения выходов Direction и Color. Теперь ваша пользовательская функция должна выглядеть следующим образом:
Теперь неплохо бы добавить этот узел в группу, чтобы можно было отмечать, что он делает. Щелкните узел правой кнопкой мыши, выберите Создать группу из выделения и переименуйте название группы, чтобы описать, что делает ваш узел. Здесь мы ввели "Получить основное освещение".

Теперь, когда у нас есть данные о свете, мы можем рассчитать затенение. Мы начнем со стандартного ламбертианского освещения, поэтому возьмем точечное произведение вектора нормали к миру и направления света. Передайте его в узел Saturate и умножьте на цвет света. Подключите это к порту Color узла Unlit Master, и в вашем превью появится пользовательское затенение!

Поскольку теперь мы знаем, как получить данные о свете с помощью узла Custom Function, мы можем расширить нашу функцию. Наша следующая функция получает значения ослабления от основного света в дополнение к направлению и цвету. Поскольку это более сложная функция, давайте переключимся в файловый режим и воспользуемся включаемым файлом HLSL. Это позволяет создавать более сложные функции в соответствующем редакторе кода, прежде чем внедрять их в граф. Это также означает, что у нас есть единое место для отладки кода. Начните с открытия включаемого файла `CustomLighting` в папке Assets > Include проекта. Сейчас мы сосредоточимся только на функции `MainLight_half`. Функция выглядит следующим образом:
Эта функция содержит новые входные и выходные данные, поэтому давайте вернемся к узлу Custom Function и добавим их. Добавьте два новых выхода для DistanceAtten (ослабление расстояния) и ShadowAtten (ослабление тени). Затем добавьте новый вход для WorldPos (положение в мире). Теперь, когда у нас есть входы и выходы, мы можем ссылаться на включаемый файл. Измените в раскрывающемся списке Тип на Файл. Во вкладке "Источник" перейдите к включаемому файлу и выберите ассет для ссылки. Теперь нам нужно указать узлу, какую функцию использовать. В поле "Имя" мы ввели "MainLight".

Вы заметите, что в включаемом файле в конце имени функции стоит `_half`, а в нашем варианте имени - нет. Это происходит потому, что компилятор Shader Graph добавляет формат точности к имени каждой функции. Поскольку мы определяем собственную функцию, нам нужен исходный код, чтобы сообщить компилятору, какой формат точности использует наша функция. В узле, однако, нам нужно ссылаться только на имя главной функции. Вы можете создать дубликат функции, использующей значения 'float', чтобы компилировать ее в режиме плавающей точности. Цветовой режим "Точность" позволяет легко отслеживать точность, установленную для каждого узла графика. Синий цвет обозначает плавающую величину, а красный - половину.
Вероятно, мы захотим использовать эту функцию еще где-нибудь, и самый простой способ сделать эту пользовательскую функцию многоразовой - обернуть ее в Sub Graph. Выберите узел и его группу, а затем щелкните правой кнопкой мыши, чтобы найти команду Преобразовать в подграф. Мы назвали свой "Главный свет". В субграфе просто добавьте необходимые порты вывода к узлу вывода субграфа и подключите выход узла к выходу субграфа. Далее мы добавим узел положения мира, который будет подключен к входу.

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

Созданный нами шейдер отлично подходит для матовых объектов, но что, если мы хотим немного блеска? Мы можем добавить в наш шейдер собственные вычисления спекуляра! Для этого шага мы используем еще один узел Custom Function, обернутый в Sub Graph, под названием Direct Specular. Снова посмотрите на включаемый файл `CustomLighting` и увидите, что теперь мы ссылаемся на другую функцию из того же файла:
Эта функция выполняет несколько простых спекулярных расчетов, и если вам интересно, вы можете прочитать о них подробнее здесь. Подграфик для этой функции также включает в себя несколько входов на доске:

Убедитесь, что ваш новый узел имеет все соответствующие входные и выходные порты для работы с функцией. Добавить свойства на доску очень просто: просто нажмите на значок "Добавить (+)" в правом верхнем углу и выберите тип данных. Дважды щелкните по таблетке, чтобы переименовать входной сигнал, и перетащите таблетку, чтобы добавить ее на график. Наконец, обновите порт вывода для Sub Graph и сохраните его.
Теперь, когда расчет спекуляра настроен, мы можем вернуться к неосвещенному графику и добавить его через меню Create Node. Подключите выход Attenuation к входу Color субграфика Direct Specular. Затем подключите выход Direction из функции Get Main Light к входу Direction спекулярного Sub Graph. Добавьте результат NdotL*Attenuation к выходу Direct Specular Sub Graph и подключите его к выходу Color.

Теперь у нас есть немного блеска!
Основной свет LWRP относится к самому яркому направленному свету относительно объекта, которым обычно является солнце. Для повышения производительности на низкопроизводительном оборудовании LWRP рассчитывает основное и дополнительное освещение отдельно. Чтобы убедиться, что наш шейдер правильно рассчитывает все огни в сцене, а не только самый яркий направленный свет, необходимо создать цикл в функции. Чтобы получить дополнительные данные об освещенности, мы использовали новый субграфик, обернув им новый узел Custom Function. Посмотрите на функцию `AdditionalLight_float` в включаемом файле `CustomLighting`:
Как и раньше, используйте функцию `AdditionalLights` в ссылке на файл узла Custom Function и убедитесь, что вы создали все необходимые входы и выходы. Не забудьте выставить Specular Color и Specular Smoothness на доске Sub Graph, в которую обернут узел. Используйте узлы Position, Normal Vector и View Direction, чтобы подключить World Position, World Normal и World Space View Direction в Sub Graph.
После того как вы настроили функцию, используйте ее! Во-первых, возьмите свой основной график Unlit из предыдущего шага и сверните его в Sub Graph. Выберите узлы и щелкните правой кнопкой мыши Преобразовать в подграф. Удалите последний узел Add и подключите выходы к выходным портам Sub Graph. Мы рекомендуем вам также создать входные свойства для Specular и Smoothness.

Теперь вы можете объединить расчеты основного и дополнительного освещения. На основном графике Unlit создайте новый узел для расчетов дополнительного освещения, который будет располагаться рядом с расчетами основного освещения. Сложите выходы Diffuse и Specular основного и дополнительного света вместе. Довольно просто!


Теперь вы знаете, как получить данные от всех светильников в сцене для проекта LWRP, но что вы можете с ними сделать? Одним из самых распространенных вариантов использования пользовательского освещения в шейдерах является классический шейдер тона!
Имея все данные о свете, создать шейдер тона довольно просто. Сначала возьмите все расчеты освещенности, которые вы сделали до сих пор, и оберните их в Sub Graph еще раз. Это поможет улучшить читаемость финального шейдера. Не забудьте удалить последний узел Add и подать Diffuse и Specular в отдельные выходные порты на выходном узле Sub Graph.
Существует множество методов создания теней для тоонов, но в данном примере мы используем интенсивность света для поиска цветов из текстуры Ramp. Эта техника обычно называется Ramp Lighting. Мы включили в примерный проект несколько примеров текстурных ассетов, необходимых для Ramp Lighting. Вы также можете сэмплировать градиент, чтобы использовать динамические темпы в Ramp Lighting. Первый шаг - преобразование интенсивности Diffuse и Specular из значений RGB в значения HSV. Это позволяет нам использовать интенсивность цвета света (значения HSV) для определения яркости в шейдере, а также помогает нам сэмплировать текстуру в разных местах вдоль горизонтальной оси ассета. Используйте статическое значение для канала Y UV, чтобы определить, какая часть изображения должна быть взята сверху вниз. Вы можете использовать это статическое значение в качестве индекса для ссылки на несколько рамп освещения для проекта в одном ассете текстуры.

После того как вы установили значения UV, используйте узел Sample Texture 2D LOD для выборки текстуры Ramp. Sample LOD очень важен; если мы используем обычный узел Sample Texture 2D, рампа автоматически смешается в сцене, и объекты, расположенные дальше, будут иметь разное освещение. Использование узла Sample Texture 2D LOD позволяет нам вручную определить уровень mip. Кроме того, поскольку текстура Ramp имеет высоту всего 2 пикселя, мы создали собственный Sampler State для текстур. Чтобы убедиться, что текстура сэмплируется правильно, мы установили фильтр на Point, а Wrap - на Clamp. Мы открыли это свойство на доске, чтобы вы могли изменить настройки при изменении ассета текстуры.

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


Эта простая пользовательская настройка освещения может быть расширена и применена для самых разных случаев использования во всех видах сцен. В нашем примере мы включили полную сцену, настроенную с шейдерами, которые используют нашу пользовательскую настройку освещения. Он также содержит вершинную анимацию, простую аппроксимацию подповерхностного рассеяния, а также преломления и раскраску, использующие глубину. Загрузите проект и ознакомьтесь с нашими примерами активов, чтобы изучить более сложные методы!
Если вы хотите обсудить Shader Graph и шейдеры, которые вы можете создавать с его помощью, приходите на наш новый форум! Вы также можете найти членов сообщества и (иногда) нескольких разработчиков в сообществе Discord!
Не забывайте следить за записями наших сессий на SIGGRAPH 2019, где мы еще более подробно рассказываем об использовании Shader Graph для пользовательского освещения!
