D:\sideБлогGameMaker Studio: шейдеры, uniform

⚠️ Обращайте внимание на даты.
Этот блог больше не ведётся с 17 января 2023, и на тот момент с написания этой страницы (12.07.2013) прошло 9 лет.

Как и обещал - разбор оставшихся шейдеров из демонстрации GameMaker Studio. К сожалению, не весь. Поскольку по ходу дела демонстрируются новые приёмы, разборы получаются несколько больше, чем ожидалось. Но я не стою на месте.

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

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

gl_FragColor.bg = vec2(0.0, 0.0);   // Вот эта

Отметим, что действие это выполняется уже после всех предыдущих действий - то есть, к моменту начала этой строчки мы имеем просто обработанную картинку. Значит, эта строчка выполняет некий эффект уже с ней. Разберёмся, какой именно.

gl_FragColor - это vec4, вектор из четырёх компонент (у нас это цвета), к таким принято обращаться, по порядку, через r, g, b и a. Там находится цвет пикселя, для которого выполняется шейдер. С помощью точки берётся некая часть этого вектора - bg. Взяты две буквы компонент и склеены. В результате - вектор из двух чисел, представляющих значения b и g (синее и зелёное) из исходного вектора gl_FragColor. Это особенность языка. Можно просто взять с помощью точки у любого вектора некоторые из его компонент, причём в нужном вам порядке, просто записав их подряд. Справедливо это дело и на чтение, и на запись. Скажем, попробуйте такие эффекты:

gl_FragColor.bg = gl_FragColor.gb; //Так
gl_FragColor.arbg = gl_FragColor.argb; //Или так
gl_FragColor.rbg = gl_FragColor.rgb; //Или даже так, то же самое!

Далее присваивание. Всё как обычно - взять то, что справа, записать в то, что слева.

Последнее - vec2. Это, с одной стороны, тип. С другой - функция, принимающая 2 аргумента, возвращающая значение типа vec2. Что делает? Очевидно же - собирает вектор из ваших чисел.

Далее - разбор следующего шейдера из демки. Я отдыхаю, поэтому новых шейдеров пока не пишу (хотя те, кто следит за группой в ВК, уже могли видеть кое-какие результаты моих экспериментов). Но с новой механикой вам будет достаточно материала, чтобы поиграться. Сейчас поговорим о том, что такое uniform.

Обойдёмся без заумных понятий - это просто константа. Для шейдера. То есть, некое значение, которое для всех экземпляров шейдера в текущем кадре будет одинаковым. Вы можете изменять его снаружи, из GML, но из шейдера - уже нет. В каком-то смысле, это то, что идёт из GML в шейдер, но не наоборот. И поскольку это стык между языками - тут не обошлось без весёлых костылей. Поехали по порядку.

Поскольку это данные, с которыми работает шейдер - это переменная, объявляемая в его “шапке”. Всё, что в коде шейдера идёт до строчки void main() - шапка шейдера. И в шапке (смотрим шейдер 3, если у вас открыта демка, или просто читаем дальше) такая строчка:

uniform vec4 f_ChannelMask;

4-компонентный входной вектор. Сначала тот факт, что это данные на вход (ключевое слово uniform), затем тип (vec4), и название (f_ChannelMask). Хорошо, нам известно название, что дальше? Нужно получить из шейдера индекс этой переменной, чтобы её изменять.

channel_mask = shader_get_uniform(sShaderDemo3, "f_ChannelMask");

Если вы уже работали со структурами данных в GM, то уже представляете, что будет дальше. Мы сейчас будем дёргать функции, которые изменяют указанную им переменную в шейдере. Но они принимают не название, а индекс. Его-то мы сейчас и получили. Теперь применим. Процедура рисования:

shader_set(sShaderDemo3); //Включить наш шейдер
shader_set_uniform_f(channel_mask, 1,0,0,1); //Записать туда вектор (1, 0, 0, 1)
draw_sprite(sprite_index,image_index,x, y); //Нарисовать спрайт
shader_reset(); //Сбросить шейдер

Теперь для каждого пикселя выполнится шейдер, в котором переменная f_ChannelMask передана из GML. Почитаем сам код шейдера:

//На вход от графической системы:
varying vec2 v_vTexcoord; //Текстурные координаты для фрагмента
varying vec4 v_vColour; //Цвет от вершин для фрагмента
//А это на вход от нас:
uniform vec4 f_ChannelMask; //цвет-маска
void main()
{
    gl_FragColor = (v_vColour * texture2D( gm_BaseTexture, v_vTexcoord )) * f_ChannelMask;
    //Абсолютно то же самое, что пропускающий шейдер. С одним исключением.
    //Весь результат умножается на переданный снаружи цвет.
}

Практической пользы от этого эффекта мало. У нас уже есть image_blend, и вся эта конструкция позволяет лишь использовать сразу два цвета. Исходный цвет сначала смешается с image_blend, потом с переданным через uniform. Но что же, вся работа насмарку? Нет, во-первых, мы узнали о том, что такое uniform и зачем это нужно.

Во-вторых - мы можем передавать туда, на самом деле, не только цвет. По спецификации, обычный цвет в RGBA - это вектор из 4 чисел от 0 до 1. Что если число больше единицы? Из GM такой цвет просто не закодировать, потому что это не цвет. Это маска. То есть, мы можем таким образом сделать спрайт не только темнее (что позволял сделать image_blend), но и светлее! К примеру, заменим соответствующую строчку:

shader_set_uniform_f(channel_mask, 1.2, 1.2, 1.2, 1); //Записать туда вектор (1.2, 1.2, 1.2, 1)

Что получится?  Попробуйте сами! А заодно догадайтесь, почему я не стал трогать последнее значение.