Привет, меня зовут Нед, и я делаю игры! В этом уроке я покажу, как реализовать пользовательское освещение в графе шейдеров Unity для универсального конвейера рендеринга. На данный момент нет узлов, которые предоставляют световые данные или изменяют результат расчета PBR по умолчанию. К счастью, вы можете обойти это и работать с материалами, которые не совсем правильно смотрятся на освещенном графике, такими как листва или кожа, или нуждающимися в стилизованном освещении, например, с мультяшным шейдером.

Протестированные версии Unity: Unity 2020.3 и Unity 2021.1.

Эта пользовательская реализация освещения включает в себя простой алгоритм освещения с рассеянным и зеркальным освещением. Он поддерживает освещение и тени от солнечного света и дополнительных точечных и точечных источников света, запеченное освещение, световые зонды и туман. Примечание. Тени точечного света доступны только в Unity 2021.1 и более поздних версиях.

Я предполагаю, что вы знакомы с основами Unity, URP и графа шейдеров. Вы также должны понимать простой HLSL (язык программирования шейдеров) и что такое управляющие ключевые слова. Наконец, хотя я дам общий обзор различных терминов и методов освещения, я не буду объяснять их подробно.

Настройка проекта

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

Создайте самолет. Создайте сферу, парящую над ним. Создайте неосвещенный граф шейдера URP под названием «TestLighting». Создайте материал. Назначьте ему шейдер TestLighting. Установите его на обоих тестовых объектах. Чтобы упростить повторное использование, создайте подграф шейдера под названием «CustomLighting».

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

Откройте CustomLighting. Создайте свойство float3 «Альбедо». Измените выходной параметр на float3 с именем «Out». Направьте Альбедо наружу.

Откройте TestLighting. Добавьте свойство Texture2D «Альбедо». Попробуйте это. Добавьте узел CustomLighting. Проложите узлы вместе.

Основная часть пользовательского алгоритма освещения должна выполняться в пользовательском функциональном узле, который лучше всего работает с файлом кода шейдера HLSL. Вы не можете создать файл .hlsl напрямую через Unity, поэтому откройте папку ресурсов в вашей операционной системе. Создайте текстовый файл с именем CustomLighting и измените расширение файла на «.hlsl». Откройте его в редакторе скриптов.

Во-первых, установите защитное ключевое слово, чтобы предотвратить повторную компиляцию этого файла. Этот файл состоит из трех основных частей: первая, CustomLightingData, представляет собой структуру данных, содержащую все данные, необходимые для расчета освещения. Второй, CalculateCustomLighting, содержит и выполняет алгоритм освещения (сейчас просто возвращает альбедо). Третий, CalculateCustomLighting_float, — это пользовательская функция-оболочка, которую может вызывать граф шейдера. Обратите внимание на суффикс точности числа. Эта оболочка создает структуру данных и вызывает основную функцию. Отделение оболочки от алгоритма дает вам лучший контроль над потоком данных, а также позволяет легко повторно использовать неграфические шейдеры, если вам когда-либо понадобится.

Вернитесь к подграфу CustomLighting. Создайте узел CustomFunction. Настройте ввод и вывод в соответствии с функцией оболочки в CustomLighting.hlsl. Установите имя «CalculateCustomLighting» (суффикс точности числа не требуется) и источник для вашего файла HLSL. Перенаправьте график через пользовательскую функцию.

Вы можете увидеть некоторые ошибки при настройке функции; однако, если поле предварительного просмотра черное, все готово. Если он пурпурный, ошибка все еще существует. Дважды проверьте правописание — все также чувствительно к регистру. Когда все работает, ваша тестовая сцена будет иметь плоское затенение.

Рассеянное освещение

Рассеянное освещение относится к мягкому свету, который освещает сторону объекта, обращенную к источнику света. Он рассчитывается путем скалярного произведения вектора нормали к поверхности и направления света. Давайте добавим форму нашей сцене, рассчитав рассеянное освещение для основного света.

Откройте CustomLighting.hlsl. Добавьте нормальное векторное поле к CustomLightingData. Перепишите CalculateCustomLighting, чтобы вызвать GetMainLight (из библиотеки шейдеров URP), получив структуру, содержащую направление и цвет основного света. Вызовите новую функцию CustomLightHandling.

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

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

Вернувшись на график CustomLighting, вы получите сообщение об отсутствии существующей функции. Просто добавьте «обычный» вход float3 в пользовательскую функцию и переместите его выше «альбедо», чтобы входные данные снова соответствовали функции-оболочке. Теперь вы получите сообщение об ошибке «GetMainLight» или структуры «Light», которая не существует. Это происходит из-за того, что граф шейдера не включает библиотеку освещения URP при рендеринге окон предварительного просмотра узла.

Чтобы исправить это, нам нужно исключить участки кода из превью шейдерного графа. Добавьте блок if-not-defined вокруг функции CustomLightHandling. Затем в CalculateCustomLighting запишите эту оценку рассеянного освещения для окон предварительного просмотра, поместив старую логику в блок else.

В вашем подграфе ошибки должны исчезнуть. Добавьте «нормальный» атрибут vector3 и направьте его в пользовательскую функцию.

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

Зеркальное освещение

Зеркальное освещение моделирует небольшой блик, видимый на блестящей поверхности. Он рассчитывается путем среднего значения направления взгляда и направления света, а затем скалярного произведения этого «половинного» вектора и вектора нормали к поверхности. Использование этого значения отражения напрямую иногда приводит к бликам в темных областях. Умножьте на значение рассеянного освещения, чтобы избежать этого артефакта.

Откройте CustomLighting.hlsl. Добавьте поле направления взгляда в CustomLightingData и функцию-оболочку. В CustomLightHandling рассчитайте скалярное произведение зеркального освещения. Затем умножьте его на «diffuse», чтобы рассчитать окончательное значение отражения. Наконец, чтобы добавить зеркальный компонент освещения, добавьте «диффузный» и «зеркальный» вместе в окончательный расчет цвета.

В подграфе CustomLighting создайте узел направления взгляда, установив его в мировое пространство. В некоторых версиях Unity направление взгляда не нормализовано; направить его через узел нормализации. Затем обновите и перенаправьте в пользовательскую функцию.

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

Откройте CustomLighting.hlsl. Добавьте поле с плавающей запятой для плавности в CustomLightingData и обновите оболочку пользовательской функции. В CustomLightHandling сузьте зеркальную точку, придав ей степень гладкости, определяемую функцией GetSmoothnessPower. Он преобразует значение в диапазоне от 0 до 1 на экспоненциальной кривой, но математика в конечном счете произвольна. Вы можете отредактировать его по мере необходимости. Это приводит к более высокому значению сглаживания, уменьшающему зеркальный блик.

Прежде чем двигаться дальше, добавьте зеркальное приближение для окон предварительного просмотра узла в CalculateCustomLighting.

Вернитесь в подграф CustomLighting и добавьте плавающее свойство гладкости в пользовательскую функцию и соответствующее свойство графика.

Затем в графе TestLighting добавьте еще одно свойство гладкости. Для удобства редактирования установите его в режим ползунка в диапазоне от нуля до единицы. Оцените свое рукоделие!

Тени

Тени — важная часть освещения, и их нужно немного настроить. Если вы переместите объект, используя специальное освещение, над чем-то с материалом с освещением по умолчанию, вы увидите, что он уже отбрасывает тень! Однако наш пользовательский шейдер освещения не получает теней. Есть несколько вещей, которые нам нужно сделать, чтобы это исправить.

Во-первых, программирование. Откройте CustomLighting.hlsl. Тени, по крайней мере, для основного света, хранятся в текстурах, называемых картами теней. Системе требуется значение, называемое теневой координатой, чтобы точно их считывать; а также положение этого фрагмента в мире. Добавьте оба значения в CustomLightingData — обратите внимание, что координата тени — float4.

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

В CalculateCustomLighting при вызове GetMainLight передайте координату и позицию тени. Третий аргумент — это то, что называется теневой маской, к которой мы вернемся позже. Это позволяет Unity установить поле в структуре Light с именем shadowAttenuation, которое является множителем яркости этого света из-за теней. В CustomLightHandling умножьте его на цвет света при расчете яркости.

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

Вернитесь к подграфу CustomLighting. Добавьте логическое ключевое слово (эта опция находится в меню новых свойств). Имя не важно, но ссылка должна быть _MAIN_LIGHT_SHADOWSexactly — символы подчеркивания и заглавные буквы имеют значение! Установите для определения значение «множественная компиляция», область действия — «глобальный» и значение по умолчанию — «включено».

Теперь в сцене вы получите сообщение об отсутствии теневых координат. Чтобы это исправить, откройте CustomLighting.hlsl и добавьте этот небольшой фрагмент. Обнаруженная Cyanilux, она удаляет часть лишнего кода из шейдеров, сгенерированных графом шейдеров, избегая ошибки.

На этом этапе, если вы не видите никаких теней, убедитесь, что они включены в настройках вашего конвейера URP и на вашей камере (и если вы не используете новый проект, количество каскадов равно единице).

Чтобы они выглядели лучше, добавьте еще два логических ключевых слова в CustomLighting, включив каскады теней и мягкие тени. Оставьте настройки такими же, но установите ссылки на _MAIN_LIGHT_SHADOWS_CASCADE и _SHADOWS_SOFT. Убедитесь, что каскады и мягкие тени также включены в вашем ресурсе настроек.

Больше огней

С одним источником света можно сделать очень мало, поэтому давайте добавим поддержку точечных и точечных источников света. У нас есть готовый фреймворк, так что это не будет слишком сложно.

Откройте CustomLighting.hlsl. Unity хранит дополнительные данные об освещении отдельно от основного в буфере, который нам нужно пройти в цикле. Это основная причина, по которой я предпочитаю реализовывать индивидуальное освещение в одной пользовательской функции, поскольку в графе шейдера невозможны циклы. В любом случае, Unity определяет ключевое слово, если на этот объект воздействуют мои дополнительные источники света, поэтому создайте для него блок if-def.

Внутри вызовите GetAdditionalLightsCount, чтобы получить количество дополнительных огней, затем прокрутите каждый. GetAdditionalLight возвращает структуру данных Light для указанного источника света. Он работает так же, как GetMainLight, за исключением того, что ему не требуется координата тени — это только для теней основного света. Отправьте данные о свете в CustomLightHandling и добавьте возвращенный цвет к окончательному цвету.

В отличие от основного света, мощность точечных и точечных источников света зависит от положения. Это инкапсулируется полем distanceAttenuation в структуре данных Light. В CustomLightHandling умножьте его на расчет сияния. distanceAttenuation всегда 1 для основного источника света.

Вернитесь в подграф CustomLighting и добавьте еще два логических ключевых слова со ссылками _ADDITIONAL_LIGHTS и _ADDITIONAL_LIGHT_SHADOWS. Опять же, установите определение «множественная компиляция», область действия «глобальная» и значение по умолчанию «включено». Затем выберите ресурс настроек URP, установите дополнительное освещение на пиксель и включите отбрасывание теней.

Создайте несколько источников света, чтобы проверить! Убедитесь, что все источники света находятся в режиме реального времени и тип тени не «нет теней». Увеличьте интенсивность, чтобы все было действительно ясно видно. Обратите внимание, что точечные тени доступны только в Unity 2021.1 и более поздних версиях.

Глобальное освещение

Глобальное освещение — это широкая концепция, включающая запеченные карты освещения, зонды освещения, глобальные отражения, теневые маски и сферические гармоники. Мы можем реализовать их все одним махом! Я не буду тратить много времени на объяснение того, как работают эти различные методы или как их использовать — каждый заслуживает отдельного руководства. Просто знайте, что они пытаются добавить в сцену много недорогого освещения.

В CustomLighting.hlsl добавьте два новых поля в CustomLightingData: ambientOcclusion, которое определяет, сколько глобального освещения должен получать этот фрагмент, и bakedGI, которое является запеченным цветом освещения для этого фрагмента.

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

В блоке без предварительного просмотра ниже вызовите OUTPUT_LIGHTMAP_UV , чтобы получить окончательную UV-карту освещения, используя масштаб карты освещения, предоставляемый Unity. OUTPUT_SH вычисляет для этого фрагмента сферические гармоники, которые кодируют цвет ближайших световых зондов. Наконец, разрешите все в одно запеченное значение глобального освещения с помощью SAMPLE_GI. Он сэмплирует текстуру карты освещения, если она доступна.

Чтобы добавить глобальные источники света в наш алгоритм освещения, напишите функцию CustomGlobalIllumination внутри блока preview-not-defined. URP проделал всю тяжелую работу в оболочке пользовательской функции, поэтому просто перемножьте значения альбедо, запеченного GI и окклюзии, чтобы вычислить непрямое рассеяние. Верни это.

В CalculateCustomLighting инициализируйте переменную цвета с помощью CustomGlobalIllumination. Но перед этим нам нужно скорректировать глобальное значение GI. При некоторых обстоятельствах вклад основного света в затенение также включается в глобальную GI. Мы не хотим включать его дважды, поэтому вызовите эту функцию URP, MixRealtimeAndBakedGI, чтобы при необходимости удалить основной источник света из значения globalGI.

Откройте свой подграф и создайте аргументы для Ambient Occlusion и Lightmap UV. Добавьте новое свойство float для occlusion и подключите его к пользовательской функции. UV-карта освещения обычно автоматически сохраняется Unity во втором наборе UV-разверток, которым является UV1. Направьте узел UV в режиме UV1 в поле UV карты освещения. Наконец, добавьте еще одно логическое ключевое слово с LIGHTMAP_SHADOW_MIXING в качестве ссылки, которое включает смешанное освещение.

На графике TestLighting создайте свойство ambient occlusion. Это должен быть ползунок в диапазоне от нуля до единицы. Вы можете, конечно, использовать карту окклюзии, если хотите!

Теперь проверьте все на своей сцене. Создайте несколько новых источников света и установите для них режим запекания. Настройте плоскость земли GameObject на статическую, чтобы она получала карты освещения. Создайте группу световых зондов вокруг вашей плавающей сферы. Чтобы было легче видеть, на противоположных сторонах зондов появляются запеченные огоньки разного цвета. Когда все будет готово, сохраните проект и перейдите на панель освещения (Окно -> Рендеринг -> Освещение). Выберите сгенерировать освещение и подождите, пока лайтмаппер закончит свою работу.

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

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

В CustomLighting.hlsl добавьте эти строки в CustomGlobalIllumination. Сначала нам нужно сэмплировать кубическую карту, вычислив нормаль семпла. Визуализируйте себя, стоящим в его центре и смотрящим на пиксель, который нужно сэмплировать — это семпл-нормаль. В этой ситуации, моделируя зеркало, мы вычисляем его, отражая направление взгляда вокруг вектора нормали.

Я обнаружил, что отражения красиво смотрятся вокруг края объекта, эффект, известный как обод или свет Френеля. Он рассчитывается путем нахождения скалярного произведения нормали и направления взгляда, вычитания его из единицы и увеличения с показателем степени (в данном случае 4).

Вызовите GlossyEnvironmentReflections , чтобы сэмплировать кубическую карту, передав образец нормали, значение шероховатости и окклюзию. Шероховатость равна единице минус гладкость и влияет на размытость отражений. Я использую эту функцию из кода рендеринга PBR URP, который ожидает так называемую «перцептивную шероховатость». К счастью, мы можем преобразовать это, используя RoughnessToPerceptualRoughness. Наконец, умножьте на fresnel и добавьте результат непрямого отражения к окончательному расчету.

Вот и все! Если эффект слишком сильный, можно уменьшить силу отражения в окне освещения.

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

Откройте CustomLighting.hlsl. Пришло время заняться тем значением теневой маски, о котором я говорил ранее. Добавьте поле float4 в CustomLightingData. В функции-оболочке установите теневую маску на ноль в блоке предварительного просмотра и вызовите SAMPLE_SHADOWMASK в конце блока без предварительного просмотра. Затем в CalculateCustomLighting передайте новое значение теневой маски в качестве третьего аргумента в GetMainLight и GetAdditionalLight.

В своем подграфе CustomLighting добавьте еще одно логическое ключевое слово с SHADOWS_SHADOWMASK в качестве ссылки и теми же настройками, что и всегда.

Чтобы протестировать теневые маски, добавьте большой статический объект над вашей плоскостью, установите основной источник света в смешанный режим и снова запеките освещение. Перейдите к настройкам качества и измените режим Shadow Mask на «Shadowmask». Вы должны увидеть тени на всех статических объектах, например, на плоскости земли. Обратите внимание, что световые зонды получили запеченные тени, так что это повлияет на освещение динамических объектов.

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

Туман

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

В файле hlsl добавьте в структуру данных поле fogFactor, которое URP будет использовать для расчета силы тумана. Мы установим его в обёртке функции. Установите fogFactor на ноль в блоке предварительного просмотра. В противном случае вызовите ComputeFogFactor, который использует z-позицию этого фрагмента в пространстве клипа, связанную с его глубиной от камеры.

В CalculateCustomLighting позвоните MixFog. Эта функция URP применяет туман к конечному цвету, заботясь обо всех режимах тумана.

Вернитесь к просмотру сцены и протестируйте ситуацию, включив туман в окне освещения. Попробуйте каждый режим и отрегулируйте настройки. MixFog справится со всеми!

Предстоящие изменения

В версии 2021.2 Unity собирается выпустить множество новых функций для URP и Shader Graph, которые повлияют на этот проект. Самым большим изменением является добавление пользовательских интерполяторов вершин, которые позволят нам поддерживать вершинные источники света и более эффективное глобальное освещение. Они также добавляют световые файлы cookie, смешивание зондов отражения, световые слои, тени экранного пространства и окружающее затенение (для неосвещенных графиков) и отложенный рендеринг. Излишне говорить, что после выпуска 2021.2 вы получите дополнительное руководство.

Заворачивать

Большое спасибо за чтение! Теперь у вас есть удобство Shader Graph и полный контроль над тем, как выглядит ваша игра. Используйте эти методы правильно, и ваш проект действительно выделится! В настоящее время я работаю над учебным пособием по этому фреймворку для создания шейдера травы, листьев и общей листвы. Пожалуйста, следите за этим!

Вот окончательная версия сценария, для перекрестных ссылок.

Если у вас есть какие-либо вопросы, не стесняйтесь обращаться ко мне по любой из ссылок внизу этой статьи.

Если вы хотите посмотреть этот урок под другим углом, я создал видеоверсию, которую вы можете посмотреть здесь: https://www.youtube.com/watch?v=GQyCPaThQnA

Попробуйте мой Interactive Lighting Math Explorer, чтобы визуализировать, как работают диффузные, зеркальные и другие расчеты освещения: https://nedmakesgames.itch.io/lighting-explorer

Я хочу воспользоваться моментом, чтобы поблагодарить Дэвида Крю за его поддержку, а также всех моих покровителей за создание этого урока: Адама Р. Вьерру, Александра Молчанова, Алину Мэтсон, Альваро ЛОГОТОМИА, Антонио Рипа, анжони Манрике. , Ben Luker, Ben Wander, BM, Bohemian Grape, Cameron Horst, Candemir, ChainsawFilms, Chris, Christopher Ellis, Connor Wendt, Danny Hayes, darkkittenfire, David Cru, Evan Malmud, Eren Aydin, FABG Team, Georg Schmidbauer, hyunsookim, Isobel Шаша, Ежи Габ, Джей Пи Ли, jpzz Ким, Картик Гунасекаран, Кайл Харрисон, Лифензо, Лхонг Ли, Люк Хопкинс, Безумная наука, Марк Дэвис, Масахито Нагасака, Мэтт Андерсон, максо, Майк Янг, НГ, Оскар Когут, Пэт, Патрик Бергстен, фанурак рубпол, Ци Чжан, Квентин Аррагон, Рафаэль Людешер, Сэм Слейтер, Себастьян Кай, СлэпЧоп, старби, Стеф, Стивен Сэндлин, Твоягер, Войдс Адрифт, Уилл Таллент, Уинберри, 성진 김

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

Большое спасибо за чтение и создание игр!

🔗 Список обучающих сайтов ▶️ YouTube 🔴 Twitch 🐦 Twitter 🎮 Discord 📸 Instagram 👽 Reddit 🎶 TikTok 👑 Patreon 📧 Электронная почта: nedmakesgames gmail

Кредиты, ссылки и особая благодарность:

Весь код, появляющийся во встраиваниях GitHub Gist, защищен авторскими правами NedMakesGames, 2021 г. и лицензируется в соответствии с лицензией MIT.