[Перевод] Unreal Engine4 — PostProcess эффект сканирования

ebktatsje1u5a3pyfbaqwl39u1w.png

В эти выходные у меня появилось немного свободного времени между занятиями (прим. автор на момент статьи получал степень магистра наук), и я решил вернуться к созданию шейдеров, придумав этот postprocess эффект сканирования. Я представил, что он используется в игре как своего рода эффект сканирования на расстоянии. Также мы используем некоторые простые искажения шума, чтобы эффект выглядел немного интереснее.

В этой статье я расскажу, как реализовать данный эффект на UE4. Существуют несколько способов, с помощью которых вы можете создать этот эффект. Один из этих методов был выбран мною.

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


Основные компоненты


Основная идея этого эффекта состоит в том, чтобы создать версию рендера сцены с помощью оператора Собеля, а затем смешать его с обычным рендером сцены на основе SphereMask, которую мы будем анимировать для создания эффекта сканирования.

Этот эффект состоит из 3 основных компонентов:

  • Масштабируемое поле SphereMask
  • Функция Sobel-Edge (я не буду объяснять, как работает эта функция, поскольку она является отдельной темой, но я сошлюсь на код, который использовал)
  • Наложение спроецированной текстуры на сетку мира


Масштабируемое поле SphereMask


Эта часть про то, как мы создаем масштабируемую SphereMask. Для этого передаем положение blueprint«а в набор параметров материала, после чего используем его следующим образом

jz-dfkjptn55desctdqp4t45olw.jpeg

Соедините результат ноды Clamp к emissive выходу вашего материала, и вы увидите что-то вроде этого

h3qfoekicbzn3kxngd84oryuhyq.jpeg

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

Набор параметров нод, указанный выше, создает поле с радиусом сферы в 1024 юнита. Я использовал его только для показа результате в окне превью. Если вы хотите более детально изучить работу с дистанционными функциями и понять методы их использования, настоятельно рекомендую проверить сайт Inigo Quilez’a.

Теперь мы будем использовать Time для изменения масштаба сферы с установленным промежутком времени.

cnsd7j2jzs1k5igdjrz0yz5tuae.png

Это нам даст следующий результат

b5v3wmsatvch4ezgkzfwvrtz8hy.gif

Frac(time) в основном дает нам постоянный период, который продолжает идти 0–1,0–1,0–1. Мы умножаем время на 0.25, чтобы контролировать скорость масштабирования, а затем умножаем результат на радиус сферы, что приводит к изменению радиуса от 0 до 1024, и дает нам анимированную маску.

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

msvcetcx-nbivesju-m9hi10vum.png

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

wjqfbikt-dezu6jcd_hnwjfqct8.gif

Математические операции в блоке «Edge_Mask» в основном выбирают положение в маске градиента, в данном случае значение 0.5, и определяет край маски от текущей позиции с заданной шириной, что позволяет нам получить кольцо. Я не буду вдаваться в технические подробности получение краев у маски, скорее всего я расскажу об этом в одном из следующих постов.

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

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

Для этого мы будем использовать ноду Vector Noise, который входит в состав UE4. Вы можете почитать о ней здесь, или вы можете использовать текстуру шума, которая содержит в себе World Aligned UV координаты.

В моем шейдере я установил в ноде Vector Noise параметр Function в Cellnoise, не стесняйтесь экспериментировать с другими типами данного параметра, чтобы получить свой собственный уникальный эффект.

ptpztyghnv7d1eehiegmrtncxlg.png

Результат будет выглядеть следующим образом

xbemparkbyjeaya1lldu8aikd_s.gif

На этом первый этап нашего шейдера завершен, далее мы рассмотрим реализацию функции 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 это выглядит следующим образом

fiebib30po02dkdnichwx2cf024.png

Небольшое замечание о реализации функции — убедитесь, что ваши ноды SceneTexture настроены на использование PostProcessInput0

ovjrvfg2ifgorbtscy3k8haowg0.png

Две Custom ноды GradX и GradY, настройте их подобным образом

gnzuwx4ri_uwhpm53i78mksu85c.png

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 выход материала, вы увидите следующее

_n3wqteobpqlyz11yeynr_b-lbm.png

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

lfdsxmpztfwvruscn3d8qvzuyvw.png

В итоге изменяется цвет края

pnqwufgzqldvv6oaktj7r4hk5uw.jpeg

Наложение текстуры на сетку мира


Самая простая часть: мы просто используем текстуру сетки и проецируем ее по всему миру, а затем комбинируем ее с функцией Sobel-Edge, для получения крутого эффекта.

byv3j0lb_k-uigmefr3i0wr6_n0.png

Если вы подключите результат функции в emissive выход, то увидите

m00anxzujqqnujris_krdkkc6s8.png

Собираем все вместе


Теперь мы соберем все три части вместе для нашего пост эффекта!

Сначала мы объединим функцию Sobel-Edge и World-Aligned-Grid, сложив их вместе

-avbzwdxn7c50pqhjdnnveqiezm.png

Затем мы создаем ноду SceneTexture и добавляем в него результат из Sobel-Edge и World-Aligned-Grid.

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

mbr_bsbhkbyvsz3xskjmshfwf4e.png

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

oear9ft9pqe7cjbjfpvizihi3mu.gif

Надеюсь вам пригодится данная информация, всего доброго :)

Пример проекта с данным шейдером можете найти на github.

© Habrahabr.ru