Перевод статьи о шейдерах из блога разработчиков GMS. Оригинал
тут. Сразу прошу прощения за мой посредственный английский. Просьба сообщать в ЛС о неточностях перевода (или если у вас есть более удачный вариант перевода).
Первая часть
тутАвтор: Mike Dailly
Перевод: Dmi7ry
Обзор шейдеров: часть 2В первой части этого обзора мы разобрали, что такое шейдеры и как они реализованы в Студии, а в этой части мы создадим простейший шейдер и посмотрим, как мы сможем использовать его.
Сначала давайте пробежимся по основным переменным, заданным по умолчанию, которые устанавливает Студия и какие входы использует. Если вы помните, в первой части я говорил о параметрах вершин и что они означают, и здесь сначала мы рассмотрим, как эти данные попадают в шейдер.
attribute vec3 in_Position;
attribute vec4 in_Colour;
attribute vec2 in_TextureCoord;
attribute vec3 in_Normal;
Добавление этих строк в верхнюю часть вашего вершинного шейдера объявляет "входы" в шейдер. Обратите внимание, что
vec4 содержит 4 значения с плавающей точкой в одной переменной (например,
in_Colour.argb), в то время как
vec3 содержит 3 значения (
in_Position.xyz) и
vec2 - 2 значения (
in_TextureCoord.xy).
vec4 имеет наибольший размер (за исключением массивов), но вы можете также иметь только одно значение с плавающей запятой, если вам такое понадобится.
Это значения, с которыми работает ваш шейдер. Они должны быть названы так, чтобы Студия могла назначить соответствующие вводы на платформе. В этом так же есть преимущество того, что при обмене шейдерами между пользователями будет некоторая общая база вводов.
Сейчас, пока они находятся наверху, Студия будет устанавливать позицию, цвет, координаты текстуры и нормали, так как вы ожидали бы, но если вы не используете один из параметров (например, нормаль), то вам не нужно включать его.
Теперь, когда мы объявили входы вершинных шейдеров, мы должны объявить выходы - эти значения передаются в пиксельный шейдер.
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
В данном случае наш вершинный шейдер будет отправлять координаты текстуры и цвет в пиксельный шейдер. Эти две строки также будут в верхней части нашего пиксельного шейдера.
Каждый шейдер должен содержать функцию
main(), которая является основной точкой входа для каждого вершинного и пиксельного шейдера. Мы можем определить пустую функцию, вроде этого...
void main()
{
}
Конечно, тут ничего не делается и, на самом деле, вполне может выдать ошибку, так как внутри вершинного шейдера вы должны сохранять результат во встроенную GLSL-переменную
gl_Position (более подробную информацию о встроенных переменных можно найти
здесь ). Итак, давайте взглянем, как выглядит простейший "проходной" вершинный шейдер
(то есть шейдер, который не вносит никаких искажений - он получает данные и просто передаёт их дальше - прим. пер.).
//
// Simple passthrough vertex shader
//
attribute vec3 in_Position;
attribute vec4 in_Colour;
attribute vec2 in_TextureCoord;
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
void main()
{
vec4 pos= vec4( in_Position, 1.0);
gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * pos;
v_vColour = in_Colour;
v_vTexcoord = in_TextureCoord;
}
Как вы можете увидеть, сначала мы копируем информацию о местоположении в новую переменную
pos. Смысл копирования заключается в том, чтобы расширить значение до полного
vec4. Как только мы получили позицию в удобном формате, мы умножаем её на матрицу мировой проекции, что даст нам окончательное местоположение клипа в пространстве (позицию, которая нужна видеокарте). Студия содержит множество готовых к использованию матриц, избавляя вас от головной боли при необходимости устанавливать их самостоятельно или обеспечивать ими каждый шейдер, который вы делаете. Каждая из них может быть использована для получения различных эффектов, но если вы хотите просто прямой вывод на экран, вы можете использовать приведённую выше строку.
В двух последних строках мы просто копируем цвет и координаты текстуры из входа данных вершины в выход - данные, подходящие для пиксельного шейдера.
Теперь давайте взглянем на пиксельный шейдер. Так как у нас всего лишь "проходной" шейдер, это будет просто передача данных дальше.
//
// Simple passthrough fragment shader
//
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
void main()
{
gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
}
В верхней части имеется два наших входа из вершинного шейдера
(в оригинале "пиксельного", но мне кажется, что здесь ошибка - прим. пер.) в верхней части, которые используются с другой встроенной переменной -
gm_BaseTexture. Все спрайты, фоны, поверхности и т.п. - все они находятся на текстурах, поэтому, когда вы что-нибудь рисуете, вы, в той или иной форме, указываете текстуру, и когда вы используете шейдер, Студия устанавливает переменную, чтобы текстурой был ваш рисунок. Это также облегчает установку шейдеров и помогает вам сразу взяться за дело.
Итак, что же делает эта строка? Функция texture2D() - это шейдерная функция GLSL ES, которая ищет пиксель в текстуре, используя UV-развертки. Таким образом, используя UV-развертки, смотрится текстура (вашего спрайта/фона/поверхности и т.п.) из вершины и оттуда возвращается ARGB-значение пикселя. Дальше оно умножается на цвет вершины, что даёт возможность подкрасить пиксель другим цветом. И наконец, значение передаётся во встроенную переменную
gl_FragColor для аппаратного отображения.
И это все по шейдерам! Итак, как это всё можно использовать? На самом деле, нет ничего проще...
shader_set(PassThroughShader);
draw_self();
shader_reset();
Если мы хотим наш шейдер PassThroughShader, то простая установка шейдера, отрисовка спрайта и сброс установок, пошлют все данные через него, хотя в нашем случае внешне всё будет выглядеть без изменений - но это пока что!
Теперь начинается самое интересное. Так как сейчас мы всё пропускаем через шейдер, давайте посмотрим, что случится, если мы просто сохраним значение вместо этого.
void main()
{
gl_FragColor = vec4( 1,1,1,0.5 );
}
Если вы запустите сейчас, то увидите простой, немного прозрачный (значение 0.5), залитый белый квадрат
Теперь давайте объединим вышесказнное. Векторы - это такие замечательные небольшие структуры с компонентами x,y,z и w (либо r,g,b и a), каждый из которых вы можете читать/писать так, как вам это захочется. Давайте очистим синий и зелёный каналы и посмотрим, что произойдёт. Добавьте эту строку в нижнюю часть пиксельного шейдера:
gl_FragColor.bg = vec2(0,0);
Здесь голубой и зелёный каналы устанавливается в 0, оставляя активными только красный и альфа каналы.
Как вы можете видеть, мы остались с красной версией нашего спрайта. Если мы также установим в 1 альфа-канал, мы получим точно такое же изображение, но с чёрным прямоугольником вокруг спрайта.
gl_FragColor.a = 1.0;
В следующей части мы более внимательно рассмотрим, как на самом деле шейдеры обрабатываются видеокартой и как они получаются такими быстрыми.