В эти выходные у меня появилось немного свободного времени между занятиями (прим. автор на момент статьи получал степень магистра наук), и я решил вернуться к созданию шейдеров, придумав этот postprocess эффект сканирования. Я представил, что он используется в игре как своего рода эффект сканирования на расстоянии. Также мы используем некоторые простые искажения шума, чтобы эффект выглядел немного интереснее.
В этой статье я расскажу, как реализовать данный эффект на UE4. Существуют несколько способов, с помощью которых вы можете создать этот эффект. Один из этих методов был выбран мною.
Вы можете открыть изображения в новой вкладке, чтобы посмотреть их в более высоком разрешении.
Основные компоненты
Основная идея этого эффекта состоит в том, чтобы создать версию рендера сцены с помощью оператора Собеля, а затем смешать его с обычным рендером сцены на основе SphereMask, которую мы будем анимировать для создания эффекта сканирования.
Этот эффект состоит из 3 основных компонентов:
- Масштабируемое поле SphereMask
- Функция Sobel-Edge (я не буду объяснять, как работает эта функция, поскольку она является отдельной темой, но я сошлюсь на код, который использовал)
- Наложение спроецированной текстуры на сетку мира
Масштабируемое поле SphereMask
Эта часть про то, как мы создаем масштабируемую SphereMask. Для этого передаем положение blueprint’а в набор параметров материала, после чего используем его следующим образом
Соедините результат ноды Clamp к emissive выходу вашего материала, и вы увидите что-то вроде этого
«TexLoc» — это vector3, который определяет местоположение источника сферы, в моем случае он читается из набора параметров материала, так что его можно считывать и из самой игры, например, для определения положения игрока.
Набор параметров нод, указанный выше, создает поле с радиусом сферы в 1024 юнита. Я использовал его только для показа результате в окне превью. Если вы хотите более детально изучить работу с дистанционными функциями и понять методы их использования, настоятельно рекомендую проверить сайт Inigo Quilez'a.
Теперь мы будем использовать Time для изменения масштаба сферы с установленным промежутком времени.
Это нам даст следующий результат
Frac(time) в основном дает нам постоянный период, который продолжает идти 0-1,0-1,0-1. Мы умножаем время на 0.25, чтобы контролировать скорость масштабирования, а затем умножаем результат на радиус сферы, что приводит к изменению радиуса от 0 до 1024, и дает нам анимированную маску.
Это хороший результат, но это не то, что мы хотим от эффекта. Нам нужно масштабирующее кольцо. Это может быть легко сделано с помощью несложных вычислений.
Это даст нам то, что мы хотим, растущее кольцо, с хорошим затуханием градиента, которым можно управлять.
Математические операции в блоке «Edge_Mask» в основном выбирают положение в маске градиента, в данном случае значение 0.5, и определяет край маски от текущей позиции с заданной шириной, что позволяет нам получить кольцо. Я не буду вдаваться в технические подробности получение краев у маски, скорее всего я расскажу об этом в одном из следующих постов.
Как вы видите, у вас полный контроль над шириной кольца без скалярного параметра, и если бы мы захотели, мы могли бы даже управлять затуханием краев, но в данном эффекте нам это не требуется.
Следующим шагом будет использование шума, чтобы создать визуально интересный вариант кольца.
Для этого мы будем использовать ноду Vector Noise, который входит в состав UE4. Вы можете почитать о ней здесь, или вы можете использовать текстуру шума, которая содержит в себе World Aligned UV координаты.
В моем шейдере я установил в ноде Vector Noise параметр Function в Cellnoise, не стесняйтесь экспериментировать с другими типами данного параметра, чтобы получить свой собственный уникальный эффект.
Результат будет выглядеть следующим образом
На этом первый этап нашего шейдера завершен, далее мы рассмотрим реализацию функции Sobel-Edge.
Функция Sobel-Edge
Существует много различных вариантов этой функции, некоторые из которых более оптимизированы, чем другие, я не буду объяснять ее суть, поскольку это является отдельной темой, но обычный поиск в Google по ключевым словам «Sobel Edge» или «Оператор Собеля» выдаст вам множество вариантов. Или воспользуйтесь статьей на хабре от Gepard_vvk — Алгоритмы выделения контуров изображений.
Основная идея оператора Собеля заключается в следующем: мы берем RenderTarget сцены (представьте, что это текстура, которая содержит то, что вы в данный момент видите в вашем окне просмотра) и сравниваете каждый пиксель со всеми соседними пикселями вокруг него. Далее мы сравниваем разницу в яркости, и если разница выше определенного порога, мы помечаем его как край, и в этом процессе мы получаем черно-белую маску текстуры RenderTarget, в которой на края налаживается маска.
Приведенный ниже код является простым примером функции оператора Собеля, которую создал RebelMoogle на сайте Shadertoy (скорее всего этот вариант не полностью оптимизирован, так что можете попробовать другую реализацию), мы воссоздадим ее в UE4 в нашем материале.
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord.xy / iResolution.xy;
vec3 TL = texture(iChannel0, uv + vec2(-1, 1)/ iResolution.xy).rgb;
vec3 TM = texture(iChannel0, uv + vec2(0, 1)/ iResolution.xy).rgb;
vec3 TR = texture(iChannel0, uv + vec2(1, 1)/ iResolution.xy).rgb;
vec3 ML = texture(iChannel0, uv + vec2(-1, 0)/ iResolution.xy).rgb;
vec3 MR = texture(iChannel0, uv + vec2(1, 0)/ iResolution.xy).rgb;
vec3 BL = texture(iChannel0, uv + vec2(-1, -1)/ iResolution.xy).rgb;
vec3 BM = texture(iChannel0, uv + vec2(0, -1)/ iResolution.xy).rgb;
vec3 BR = texture(iChannel0, uv + vec2(1, -1)/ iResolution.xy).rgb;
vec3 GradX = -TL + TR - 2.0 * ML + 2.0 * MR - BL + BR;
vec3 GradY = TL + 2.0 * TM + TR - BL - 2.0 * BM - BR;
fragColor.r = length(vec2(GradX.r, GradY.r));
fragColor.g = length(vec2(GradX.g, GradY.g));
fragColor.b = length(vec2(GradX.b, GradY.b));
}
В UE4 это выглядит следующим образом
Небольшое замечание о реализации функции — убедитесь, что ваши ноды SceneTexture настроены на использование PostProcessInput0
Две Custom ноды GradX и GradY, настройте их подобным образом
GradX:
return -TL + TR - 2.0 * ML + 2.0 * MR - BL + BR;
GradY:
return TL + 2.0 * TM + TR - BL - 2.0 * BM - BR;
Это не обязательно должно быть сделано в Custom, я использовал ее просто для удобства, так как в противном случае бы слишком много нод и образуется спагетти.
Если вы подключите результат функции в emissive выход материала, вы увидите следующее
Также мы умножаем результат на обычный vector3, чтобы сделать края любого цвета, который мы захотим.
В итоге изменяется цвет края
Наложение текстуры на сетку мира
Самая простая часть: мы просто используем текстуру сетки и проецируем ее по всему миру, а затем комбинируем ее с функцией Sobel-Edge, для получения крутого эффекта.
Если вы подключите результат функции в emissive выход, то увидите
Собираем все вместе
Теперь мы соберем все три части вместе для нашего пост эффекта!
Сначала мы объединим функцию Sobel-Edge и World-Aligned-Grid, сложив их вместе
Затем мы создаем ноду SceneTexture и добавляем в него результат из Sobel-Edge и World-Aligned-Grid.
Затем мы интерполируем между обычной сценой и добавленной, используя результат маски кольца, которую мы создали в первой части
И вуаля, мы это сделали. Финальный результат будет выглядеть примерно так. Вы можете, конечно, настроить параметры и попробовать изменить их значения, чтобы получить более интересные варианты.
Надеюсь вам пригодится данная информация, всего доброго :)
Пример проекта с данным шейдером можете найти на github.