Эта статья написана для моего семинара по шейдерам GlitchCon 2016. Если вам нужно более подробное руководство по шейдерам, ознакомьтесь с моими Учебниками Tuts +.

Что можно делать с шейдерами?

Большинство современных игр во многом обязаны своим визуальным эффектам графическому процессору. Вот некоторые из моих любимых примеров:

Что такое шейдер?

Фрагмент кода (написанный на GLSL - OpenGL Shading Language, очень похож на синтаксис C), который выполняется для каждого пикселя одновременно. Он возвращает цвет для установки пикселя. Если вы хотите вообразить это, Разрушители легенд помогут вам:

Документация OpenGL полезна, но shaderific.com также имеет очень хорошую ссылку.

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

Шаг 1. Ваш первый шейдер!

Вот простейший шейдер, с которого мы можем начать:

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
 fragColor = vec4(1.0,1.0,1.0,1.0);
}

Вы можете запустить это в редакторе ShaderToy здесь:

Https://www.shadertoy.com/view/4scSD2

Задача 1: Можете ли вы превратить его в красный? А что насчет серого?

Шаг 2. Создайте переменную

Чтобы объявить любую переменную:

float x = 1.0;//A floating point number
vec4 myVector = vec4(1.0,0.5,0.25,0.1);//A vector of four numbers

Вы также можете создать vec2 и vec3. Чтобы получить доступ к отдельным компонентам, вы можете:

vec4 myVector = vec4(1.0,0.5,0.25,0.1);
myVector.r //This would be 1.0
myVector.g //0.5
myVector.b //0.25
myVector.a //0.1

Вместо «rgba» вы также можете использовать «xyzw». Это эквивалентные способы доступа к компонентам вектора.

Шаг 3: половина и половина

Вам даны координаты пикселя (x, y) в аргументе fragCoord. Можете ли вы использовать это, чтобы сделать половину экрана красной, а другой - синей? Чтобы у вас получилось что-то вроде этого:

Примечание. Если вы получаете сообщение об ошибке ниже, это связано с тем, что GLSL строго придерживается своих типов. Обязательно используйте «1.0» вместо «1» для любого числа.

Если вы хотите увидеть, как я справился, вот мое решение: http://pastebin.com/raw/peV6ZmZG

Задача 2: можете ли вы разделить его по вертикали, а не по горизонтали?

Задача 3. Можете ли вы сделать градиент вместо резкого разделения?

Вот мое решение градиента выше:
http://pastebin.com/raw/RRkUuijP

Шаг 4: ввод шейдера

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

Теперь ShaderToy уже делает это за нас. Если вы нажмете «Shader Inputs» над редактором кода, вы увидите все передаваемые переменные:

Примечание: если вы программировали вне ShaderToy, у вас по умолчанию НЕ будут единые переменные! Их придется передавать шейдеру самостоятельно, в зависимости от того, на каком языке / платформе вы работаете.

iGlobalTime - это переменная, которая постоянно увеличивается. На самом деле это счетчик внизу симуляции:

Задача 4. Можете ли вы использовать iGlobalTime, чтобы заставить его медленно краснеть?

Шаг 5: нормализованные координаты

Когда мы хотели разделить экран на красный и синий, мы сделали что-то вроде этого:

if(fragCoord.x > 500.0){

500 - это только центр, если экран 1000 пикселей. Что, если бы мы хотели получить центр экрана независимо от его размера?

iResolution - вектор, имеющий ширину и высоту экрана. Итак, если мы разделим:

float pixelX = fragCoord.x / iResolution.x; 

pixelX будет равен 0 на крайней левой границе и 1 на крайней правой границе.

Задача 5: Можете ли вы использовать iResolution, чтобы исправить наше красное / синее разделение или градиент, чтобы они выглядели одинаково в любом разрешении?

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

Задача 6: Сможете ли вы создать эффект, показанный ниже?

Подсказка: это зеленый градиент по оси Y и красный градиент по оси X.

Мое решение:
http://pastebin.com/raw/RrWx1PqR

Шаг 6: рендеринг текстуры

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

vec4 color = texture(imageData,point);

Он принимает два аргумента: данные изображения и точку (как двумерный вектор x, y). Он возвращает цвет этого пикселя как RGBA.

ShaderToy НЕ позволяет загружать любое понравившееся изображение. Однако вы можете выбрать установку изображения, щелкнув каналы под окном кода:

Вот пример получения цвета пикселя в точке (0,0) и настройки экрана на этот цвет:

vec2 point = vec2(0.0,0.0);
fragColor = texture(iChannel0,point);//Get the color of the pixel at (0,0) in iChannel0

Примечание: текстура ожидает, что координаты будут нормализованы от 0 до 1.

Задача 7. Сможете ли вы отрендерить все изображение?

Мое решение:
http://pastebin.com/raw/rJPkB1W9

Шаг 7: постобработка

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

Задача 8: удалить весь синий цвет с изображения

Задача 9: заменить весь красный цвет на зеленом и наоборот.

Шаг 8: объединение изображений

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

vec4 color1 = texture(iChannel0,point);
vec4 color2 = texture(iChannel1,point);
fragColor = color1+color2;

Попробуйте это (после выбора изображений для iChannel0 и iChannel1).

Задача 10: Можете ли вы придумать другие способы объединения пикселей двух изображений? Можете ли вы получить эффект, подобный показанному ниже?

Шаг 9: смещение

Что будет, если вместо того, чтобы получить цвет пикселя в x, y вот так:

vec2 point = fragCoord.xy/iResolution.xy;
fragColor = texture(iChannel0,point);

Вместо этого вы получили цвет пикселя немного правее?

vec2 point = fragCoord.xy/iResolution.xy
point.x += 0.05;
fragColor = texture(iChannel0,point);

Задача 11: Можете ли вы заставить изображение двигаться вперед и назад, как на гифке ниже?

Мое решение:
http://pastebin.com/raw/eDeHNChd

Задание 12. Можете ли вы заставить пиксели изображения взорваться, как на гифке ниже?

Вот мое решение:
http://pastebin.com/raw/AsaxjSrV

Задача 13: Сможете ли вы заставить его покачиваться, как на гифке ниже?

Вот мое решение:
http://pastebin.com/raw/ZwH8FW0N

Дополнительное задание 1. Удалите зеленый экран из видеороликов ShaderToy. Можете ли вы удалить столько, сколько сможете, не искажая исходное видео?

Совет. Зеленый фон не является чисто зеленым. Он имеет RGB (13,161,37)
Совет 2: здесь может быть полезна функция GLSL distance

Бонусное испытание 2: Сможете ли вы разобрать солнечную демонстрацию и выяснить, как она работает?

Совет. Это может показаться сложным, но помните, что любой шейдер в мире сводится к возврату только одного цвета. Это просто слои накладываемых друг на друга эффектов. Начните с нижней части кода и проследите свой путь вверх.
Совет 2: замените iChannel0 на другое изображение
Совет 3: сделайте snoise return 1.0, чтобы отменить ее эффект (и при этом выяснить, в какой части изображения он способствует).