Post image

Искажения экрана: Пиксельные шейдер и ImageEffect

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

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

Griding

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

Grid

Чтобы рассчитать значений screenPos используется встроенный метода UnityObjectToClipPos и семантика VPOS. Так как на разных платформах тип VPOS отличается, для максимальной переносимости лучше использовать вспомогательный тип UNITY_VPOS_TYPE( будет преобразован в float4 на большинстве платформ и в float2 на Direct3D 9).

Метод sign возвращает 1, 0 или -1 в зависимости от знака принимаемого аргумента. (GridingShader строка 47)

VPOS доступна с версии 3.0 модели шейдера, поэтому шейдер должен иметь дериктиву компиляции #pragma target 3.0

Distortion

VideoTapeDistortion по задуманной идее должен создать полосу искажения, добавив в неё немного света и передвигать эту полосу с течением времени.

Для получения позиции линии искажения используется встроенная переменная _Time которая возвращает время прошедшее со старта приложения. Время умножается на скорость и делится на единицу (координаты UV находятся в промежутке 0-1), остаток от деления - позиция линии искажения.

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

Distortion graph

Для вычисления коэффициента искажения я использовал формулу коэффициент= (ln( ( позиция − Y )^2 )^2 ) / интенсивность (VideoTapeDistortion строка 48)

После, берем цвет для пикселя со смещением на коэффициент и умножаем его.

Distortion

Old screen shader

В шейдере "OldScreenShader" сочетает в себе несколько эффектов: виньетирование, динамические полосы, рыбий глаз и сдвиг пикселей по маске. Проще разобрать их по очереди.

Виньетирование

Vignette

Виньетирование рассчитывается в два шага: узнаем радиус виньетка, пускай он будет немного изменяться со временем, увеличивая динамичность; от центра изображения накладываем виньетку, затеняя границы экрана. (OldScreenShader строки 66-68)

Динамические полосы

Scratches

Динамические полосы(градиент) каждый кадр должны смещаться к краю экрана. Самое темное место градиента, верхняя грань полосы, это остаток от деления на 1 этой формулы: (Y * размер полосы + время) (OldScreenShader строка 70)

Рыбий глаз

Fish Eye

Делаем изображение выпуклым или наоборот, впалым, в зависимости от знака +/- интенсивности эффекта. Для этого изменяем координаты пикселя на входной текстуре UV. Чем дальше пиксель от центра экрана, тем больше он сдвигается. (OldScreenShader строки 50-56)

Сдвиг пикселей

Shift

Сдвиг пикселей - из маски извлекается цвет соответствующий коэффициенту сдвига пикселя который добавляется к координатам цвета входной текстуре UV. Текстура маски может быть любого размера и содержать любые данные (шум, геометрические фигур, исходно изображение). Лучше всего если маска будет черно-белой без сглаживания, чтобы избежать промежуточных коэффициентов, лишних визуальных артефактов. (OldScreenShader строки 61, 64)

Результат работы шейдера

all together

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

Все шейдера просчитываются как постобработка ImageEffect и присоединены к камере. Это первая версия эффектов, написанная на скорую руку, поэтому они не оптимизированы и могут работать не неправильно или не работать вовсе ;)

Код