Уроки Unreal Engine

Unreal Engine 4: пользовательские шейдеры

В этом занятии, посвященном Unreal Engine 4 вы узнаете, как создавать собственные шейдеры с использованием HLSL.

Редактор материалов является прекрасным инструментом для художников. Он очень подходит для создания шейдеров благодаря своей системе на основе узлов. Тем не менее, у него есть некоторые недостатки и ограничения, и чтобы их обойти нужно уметь составлять собственный код. Для этого необходимо использовать узел Custom, который позволит вам вписать нужный HLSL код.

В этом уроке вы научитесь:

  • Создавать пользовательские узлы типа Custom и настраивать выходы;
  • Конвертировать материальные узлы в HLSL;
  • Редактировать файлы шейдеров с помощью внешнего текстового редактора;
  • Создавать функции для HLSL.

Для достижения этих целей вам нужно использовать возможности HLSL.

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

Кроме того, вы можете использовать любой знакомый вам язык программирования, например, как C++, C# или Java.

Это руководство является третьей частью серии, посвященной шейдерам в Unreal Engine:

Введение

Скачайте материалы по ссылке внизу или вверху статьи, которые вам понадобятся в ходе этого занятия. Разархивируйте файл, перейдите к CustomShadersStarter и откройте CustomShaders.uproject. Вы должны увидеть следующую сцену:

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

Создание узла Custom

Перейдите в папку «Materials» и откройте PP_Desaturate —  это и есть материал, который вы будете редактировать для достижения нужного эффекта.

Теперь создайте узел Custom – обратите внимание, как и другие узлы, он может иметь несколько входов, но ограничен только одним выходом:

Убедитесь, что у вас выбран узел Custom и перейдите к панели «Details». Вы должны увидеть следующее окно:

Каждое свойство означает:

  • Code: тут находится код HLSL;
  • Output Type: это значение может варьироваться от одного до четырехканального векторов (CMOT Float 1 — CMOT Float 4);
  • Description: тут показан текст, который отображается на самом узле. Таким образом можно изменять названия для каждого узла типа Custom. Установите сейчас название Desaturate;
  • Inputs: Тут можно добавить и назвать входные контакты и ссылаться на них в коде, используя их имена. Установите для входа 0 имя SceneTexture:

Чтобы изображение стало черно-белым, замените текст внутри кода на следующий:

return dot(SceneTexture, float3(0.3,0.59,0.11));

Примечание: точка () является встроенной функцией в HLSL. Если вам нужна такие функции, как atan () или lerp (), проверьте, существует ли для них настраиваемые параметры.

Соедините все узлы следующим образом:

Резюмируя:

  1. SceneTexture: PostProcessInput0 определяет цвет текущего пикселя.
  2. Desaturate изменяет состояние цвета на черно-белый и выводит результат в Emissive Color.

Нажмите Apply, чтобы сохранить изменения и закройте PP_Desaturate. Вот как должно сейчас выглядеть ваше изображение:

Преобразование узла Material в HLSL

В этом занятии вам предстоит конвертировать узел SceneTexture в HLSL, чтобы затем было удобнее осуществлять размытие по Гауссу. Для этого перейдите в папку «Maps» и откройте файл GaussianBlur. Затем вернитесь в раздел Materials и откройте PP_GaussianBlur:

В Unreal все коды HLSL генерируются для любых узлов, которые влияют на конечный результат. В этом случае Unreal будет генерировать HLSL для узла SceneTexture.

Чтобы просмотреть код HLSL для используемого сейчас материала, вам нужно перейти к Window ⇒ HLSL Code, чтобы открыть отдельное окно с уже сгенерированным кодом:

Примечание: Если окно кода HLSL пустое, необходимо включить функцию Live Preview, которая находится в меню Toolbar.

Поскольку длина сгенерированного кода составляет несколько тысяч строк, ориентироваться в нем довольно сложно. Чтобы упростить поиск, нужно нажать кнопку «Copy» и перекопировать весь текст в любой удобный для вас текстовый редактор (предпочтительнее Notepad ++). После этого можно закрыть окно с кодом HLSL.

Теперь вам нужно найти, где находится код SceneTexture. Самый простой способ сделать это — найти определение для CalcPixelMaterialInputs (). Эта функция дает возможность определять где находятся все выходы нужного узла. Посмотрите на нижнюю часть списка, именно тут находится информация для каждого выхода:

PixelMaterialInputs.EmissiveColor = Local1;

PixelMaterialInputs.Opacity = 1.00000000;

PixelMaterialInputs.OpacityMask = 1.00000000;

PixelMaterialInputs.BaseColor = MaterialFloat3(0.00000000,0.00000000,0.00000000);

PixelMaterialInputs.Metallic = 0.00000000;

PixelMaterialInputs.Specular = 0.50000000;

PixelMaterialInputs.Roughness = 0.50000000;

PixelMaterialInputs.Subsurface = 0;

PixelMaterialInputs.AmbientOcclusion = 1.00000000;

PixelMaterialInputs.Refraction = 0;

PixelMaterialInputs.PixelDepthOffset = 0.00000000;

Поскольку это материал, который участвует в создании изображения, необходимо учитывать только значение EmissiveColor. В данный момент, его значение равно Local1. Переменные LocalX — это локальные переменные, которые функция использует для хранения промежуточных значений:

MaterialFloat4 Local0 = SceneTextureLookup(GetDefaultSceneTextureUV(Parameters, 14), 14, false);

MaterialFloat3 Local1 = (Local0.rgba.rgb + Material.VectorExpressions[1].rgb);

Использование функции SceneTextureLookup

Посмотрите, как выглядит настройка параметров для SceneTextureLookup ():

float4 SceneTextureLookup(float2 UV, int SceneTextureIndex, bool Filtered)

Вот что делает каждый параметр:

  • UV: определяет местоположение для значения определения цвета. Например, ультрафиолетовое излучение (0,5, 0,5) будет выбирать среднее положение пикселя.
  • SceneTextureIndex: определяет из какой именно текстуры берется образец.
  • Filtered: определяет должна ли текстура использовать билинейную фильтрацию. Обычно устанавливается значение false.

Чтобы проверить работу параметров необходимо вывести World Normal. Перейдите в редактор материалов, создайте пользовательский узел с именем Gaussian Blur и введите в поле «Code» следующий текст:

return SceneTextureLookup(GetDefaultSceneTextureUV(Parameters, 8), 8, false);

Таким образом, GetDefaultSceneTextureUV () каждый раз получает значение UV для текущего пикселя.

Примечание: до версии движка 4.19 вы могли получать UV, используя узел TextureCoordinate, однако в версии В 4.19 более праильным будет использование GetDefaultSceneTextureUV () с указанием желаемого индекса.

Отключите узел SceneTexture и подключите Gaussian Blur к Emissive Color, нажмите Apply:

Сейчас вы должны увидеть следующую ошибку:

[SM5] /Engine/Generated/Material.ush(1410,8-76):  error X3004: undeclared identifier 'SceneTextureLookup'

Это говорит о том, что SceneTextureLookup () пока не существует в вашем материале. Однако это легко исправить установив для узла SceneTexture значение WorldNormal и подключив его Gaussian Blur. Введите имя SceneTexture для входного контакта:

Примечание: На момент написания этого урока редактор зависал, если текстуры сцен не совпадали. Однако, как только это исправят, будет возможно менять текстуру сцены прямо в узле Custom.

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

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

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

Использование внешних файлов шейдеров

В первую очередь необходимо создать папку Shaders, чтобы программа сама могла обращаться к этой директории:

Затем, находясь в папке Shaders, создайте новый файл с именем Gaussian.usf. Это и есть внешний файл шейдера:

Примечание: Файлы шейдеров имеют расширение .usf или .ush.

Откройте Gaussian.usf в любом текстовом редакторе и вставьте код, который ниже. Обязательно сохраняйте файл после каждого изменения:

return SceneTextureLookup(GetDefaultSceneTextureUV(Parameters, 2), 2, false);

Это код, который вы использовали ранее, только сейчас вам нужен параметр Diffuse Color.

Чтобы Unreal обнаружил новую папку и шейдеры, необходимо перезапустить редактор. После этого снова откройте PP_GaussianBlur и замените код в Gaussian Blur следующим значением:

#include "/Project/Gaussian.usf"

return 1;

Нажмите Apply и вернитесь в главный редактор, чтобы посмотреть изменения:

Создание размытия по Гауссу

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

Такой способ не очень эффективный, потому что для его реализации приходится использовать определенное количество образцов. Например, в ядре 5 × 5 потребуется 25 образцов, а при размере ядра 10 × 10 это значение увеличится до 100 образцов! Используя узел Custom, можно создать небольшой цикл for, который будет отвечать за выбор каждого пиксель в ядре.

Создание параметра Radius

Вернитесь в редактор материалов и создайте новый узел ScalarParameter с именем Radius. Установите значение по умолчанию =1:

Радиус будет определять, насколько сильно размыто изображение.

Создайте новый вход для Gaussian Blur и назовите его Radius. Затем создайте еще один узел Round и подключите все следующим образом:

Узел Round должен гарантировать, что размеры ядра будут соответствовать целым числам.

При использовании узла Custom вы не сможете создавать функции стандартным способом, потому что компилятор сразу копирует код в функцию.

Создание Global Functions

Если у вас есть значение:

return 1;

Компилятор автоматически вставит его в функцию CustomExpressionX:

MaterialFloat3 CustomExpression0(FMaterialPixelParameters Parameters)

{

return 1;

}

Посмотрите, какие будут изменения, если вы используете этот код:

    return 1;

}

float MyGlobalVariable;

int MyGlobalFunction(int x)

{

    return x;

Сгенерированный HLSL теперь будет выглядеть так:

MaterialFloat3 CustomExpression0(FMaterialPixelParameters Parameters)

{
    return 1;
}

float MyGlobalVariable;

int MyGlobalFunction(int x)

{
    return x;
}

Как видите, MyGlobalVariable и MyGlobalFunction () являются глобальными и могут быть использованы в любом месте.

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

Создание Gaussian Function

Обычная функция Гаусса выглядит следующим образом:

В результате получается график с диапазоном чисел -1 и выводом конечного значения от 0 до 1:

В этом занятии вам необходимо использовать Гауссову функцию в отдельном узле типа Custom. Для этого создайте новый пользовательский узел, назовите его Global и замените текст в коде на:

return 1;

}

float Calculate1DGaussian(float x)

{

 return exp(-0.5 * pow(3.141 * (x), 2));

Calculate1DGaussian () – это ваша функция в кодовой форме.

Чтобы сделать эту функцию доступной, вам нужно использовать Global где-то на графике материала. Самый простой способ сделать это — просто умножить Global на первый узел в графике. В первую очередь установите для параметра Output Type значение CMOT Float 4:

Теперь создайте узел Multiply и соедините все компоненты следующим образом:

Нажмите Apply, чтобы сохранить результат. Теперь любые последовательно соединенные узлы с Custom могут использовать функции, определенные в Global.

Определение свойств для нескольких пикселей

Откройте Gaussian.usf и замените существующий код следующим значением:

static const int SceneTextureId = 14;

float2 TexelSize = View.ViewSizeAndInvSize.zw;

float2 UV = GetDefaultSceneTextureUV(Parameters, SceneTextureId);

float3 PixelSum = float3(0, 0, 0);

float WeightSum = 0;

Вот расшифровка каждой переменной:

  • SceneTextureId: содержит индекс текстуры сцены, которую вы хотите сэмплировать;
  • TexelSize: содержит значение размера texel;
  • UV: UV для текущего пикселя;
  • PixelSum: используется для определения цвета каждого пикселя в ядре;
  • WeightSum: используется для определения веса каждого пикселя в ядре.

Сейчас вам нужно создать два цикла for — один для вертикальных смещений и один для горизонтальных. Под списком переменных внесите следующие данные:

for (int x = -Radius; x <= Radius; x++)

{
    for (int y = -Radius; y <= Radius; y++)

    {

    }

}

Таким образом вы создадите сетку с центром на текущем пикселе. Размеры задаются как 2r + 1. Например, если радиус равен 2, размеры будут (2 * 2 + 1), (2 * 2 + 1) или 5 × 5.

Далее вам нужно добавить следующие данные во внутренний цикл for:

float2 Offset = UV + float2(x, y) * TexelSize;

float3 PixelColor = SceneTextureLookup(Offset, SceneTextureId, 0).rgb;

float Weight = Calculate1DGaussian(x / Radius) * Calculate1DGaussian(y / Radius);

PixelSum += PixelColor * Weight;

WeightSum += Weight;

Расшифровка каждой строки:

  1. Расчет и конвертация пикселя в ультрафиолетовое пространство.
  2. Определение образца текстуры (сейчас это Post Process Input 0).
  3. Расчет веса для выбранного пикселя.
  4. Добавление нового цвета в PixelSum.
  5. Добавление значения веса в WeightSum.

Теперь добавьте следующие данные в конец файла (вне цикла for):

return PixelSum / WeightSum;

Закройте Gaussian.usf, вернитесь в редактор материалов, нажмите Apply и закройте PP_GaussianBlur. Используйте PPI_Blur, чтобы проверить различные радиусы размытия:

Примечание: Иногда кнопка Apply бывает не активна. Чтобы это исправить сделайте любое фиктивное изменение (например, переместив узел), и она снова активируется.

Ограничения

Хотя узел типа Custom является довольно мощным инструментом, он имеет свои недостатки.

  1. Получение доступа

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

  1. Совместимость версии двигателя

Код HLSL, который вы создаете и прописываете в одной версии Unreal, не гарантирует его корректную работу в другой. Как говорилось ранее, до версии 4.19 можно было использовать TextureCoordinate, чтобы получить текстуры UV сцены, а в 4.19 для этого нужно использовать GetDefaultSceneTextureUV().

Что делать дальше?

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

Также вы можете использовать дополнительные возможности узла Custom, прочитав блог Райана Брука, который подробно описывает работы с этим инструментом.

Перевод
Оригинал на англ.
Показать больше

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Закрыть
Закрыть