Продолжаем разбирать шейдеры в Game Maker Studio. В прошлой части урока,
мы уже получили общее представление о строении шейдеров и начали
осваивать работу с цветом. В заключении, мы написали собственный шейдер,
который делал изображение черно-белым. Теперь, давайте придумаем еще
несколько цветовых эффектов. Помимо распространенного эффекта
обесцвечивая, есть еще один часто упоминающийся эффект под названием
сепия.
Сепия или что-то в этом роде.
Сепия – это оттенок коричневого цвета, присущий старым фотографиям.
Давайте подумаем, что потребуется, для подобного эффекта. На самом деле
все просто. Чтобы сохранить структуру изображения, при изменении цвета,
нам нужно знать интенсивности пикселя, таким образом, при любом цвете,
интенсивности будет равна оригиналу и изображение останется различаемым.
Как было сказано выше, сепия – оттенок коричневого, следовательно,
именно на этот цвет нужно сделать убор. Коричневый формируется
преобладанием красного и зеленого компонентов цвета над синим, значит,
все что требуется, это взять подходящий коричневый цвет и добавить к
нему интенсивность оригинала.
Создаем новый шейдер и переходим на вкладку fragment. Здесь в функции main() пишем следующее:
Код
vec4 color = texture2D(gm_BaseTexture, v_vTexcoord);
Как вы, возможно, помните по прошлой части, таким образом мы зададим
4-компонентный вектор color в который передадим информацию о цветах и
прозрачности текстуры.
Теперь получим интенсивность каждого
пикселя исходного изображения. Интенсивность по сути своей, это
составляющая цвета черно-белого изображения. Воспользуемся формулой из
первой части урока, которую я не применил.
Как видно, мы создаем переменную intensity,
которая будет хранить интенсивность каждого рассматриваемого пикселя.
Если сейчас вывести изображение, используя в качестве каждой
составляющей цвета полученную интенсивность, то изображение станет
черно-белым.
А таким образом мы сделали эффект, называемый сепией. Как видите,
руководствуясь сказанным выше, мы увеличиваем значения красной и зеленой
составляющей, предавая изображению коричневатый оттенок.
Остается только передать наши новые цвета на вывод и наслаждаться результатом.
Код
gl_FragColor = vec4(color.r,color.g,color.b,1.0);
Ограничение палитры.
Давайте немного усложним задачу. Что, если нам требуется вывести
изображение с ограниченной палитрой?! Подобный эффект заключается в том,
что близкие по интенсивности пиксели окрашиваются одним цветом, иногда с
использованием единого оттенка. Так как мы вновь работаем с цветом и интенсивностью, то действуем описанным выше образом.
Далее, вновь немного поразмыслим. Чтобы ограничить количество цветов по
интенсивности, нам необходимо взять отдельные диапазоны интенсивностей и
сопоставить им какой-то цвет.
Решение напрашивается само собой
– прописать условие попадания в диапазон и задать конкретные для
данного диапазона цвета. Сделать это можно так:
Код
If (intensity > 0.9) color = vec4(…); else If (intensity > 0.7) color = vec4(…); else …
Но мы пойдем другим путем и придадим данному коду более короткую и легко настраиваемую форму.
Код
float i = 1.0;
for (i=1.0; i>0.0; i-=0.5) { if (intensity > i) { color = vec4(1.0*i, 0.0*i, 0.0*i, texture2D(gm_BaseTexture, v_vTexcoord).a); break; } else { color = vec4(0.0, 0.0, 0.0, texture2D(gm_BaseTexture, v_vTexcoord).a); } }
Задаем знакомый всем цикл for, в котором перебираем значения переменной i с заданным шагом. Переменная i
будет хранить у нас нижнюю границу диапазона, в котором пикселю с
заданной интенсивностью присваивается конкретный цвет. Следовательно,
чем меньше шаг цикла, тем больше будет диапазонов, а значит и цветов. В
цикле проверяем, если полученная интенсивность пикселя больше i,
то задаем этому пикселю цвет. Цвет, как видите, так же рассчитывается
автоматически и в данном случае весь упор делается только на красную
составляющую. Получается, что все пиксели, интенсивность которых больше i, будут окрашены в красный цвет вычисляемого оттенка 1.0*i.
После того, как мы определили, что интенсивность пикселя уже попала в
какой-то диапазон, мы благополучно выходим из цикла командой break. Если же интенсивность меньше всех возможных значений i, то мы окрашиваем этот пиксель в черный цвет, в противном случае такой пиксель просто не будет существовать.
Осталось вывести полученные значения и посмотреть на результат. На этот
раз вывод мы будем осуществлять в более сокращенной форме:
Код
gl_FragColor = color;
Надеюсь, всем понятно, что данная запись равносильна прошлым вариантам вывода, ведь в нашем случае вектор color
– это тот самый 4-компонентный вектор, который мы подаем на выходе. На
этом наш шейдер создан и готов к работе. В результате чего получим мы
примерно следующее:
Влияние извне.
Теперь пойдем дальше и изучим еще оду особенность шейдеров – влияние
извне. Подобно скриптам и их аргументам, в шейдерах можно задать
переменные, влиять на которые можно вне шейдера. Рассмотрим создание
такой переменной на примере шейдера, который мы написали выше. На данный момент, он имеет у нас следующий вид:
for (i=1.0; i>0.0; i-=0.5) { if (intensity > i) { color = vec4(1.0*i, 0.0*i, 0.0*i, texture2D(gm_BaseTexture, v_vTexcoord).a); break; } else { color = vec4(0.0, 0.0, 0.0, texture2D(gm_BaseTexture, v_vTexcoord).a); } } gl_FragColor = color; }
Чтобы создать значение, на которое мы хотим повлиять из все, достаточно объявить его перед функцией main() следующим образом:
Код
uniform float m;
Вне шейдера мы сделаем это значение динамически изменяемым и, чтобы
визуально увидеть, что все действительно работает, давайте заменим
какое-нибудь значение на переменную m. Я выбрал шаг цикла i-=0.5, получив в результате i-=m.
На этом создание шейдера мы закончили, перейдем к его выводу. Как и в
первом уроке, создадим произвольный спрайт, желательно с
детализированным изображением, для более четкого визуально представления
эффекта. Зададим этот спрайт объекту, в событии draw у которого напишем следующее:
Если мы таким образом запустим вариант нашего шейдера без m,
то увидим, что изображение приобрело красный оттенок и имеет заметно
ограниченное количество цветов. Но что же делать с переменной m?!
Для начала, перейдем в событие create и привычным нам образом объявим произвольную переменную, присвоив ей значение 0.0.
Код
n = 0.0;
Вернемся в событие draw и перед запуском шейдера, пропишем незамысловатый принцип, по которому переменная n будет менять свое значение.
Код
if n<=0.5 n+=0.005;
Теперь нам нужно получить из шейдера то значение, которое мы хотим изменять. Сделать это можно по средствам функции shader_get_uniform.
Код
mn = shader_get_uniform(shader0, "m");
Как понятно, из shader0 мы взяли значение "m",
которое будем менять. Остается только задать этому значению нашу
переменную. Делается это после запуска шейдера следующим образом:
Код
shader_set_uniform_f(mn,n);
Теперь значение m из шейдера, которое мы записали в mn, будет равно созданной нами переменной n. Общий код в событии draw выглядит так:
Если запустить наше приложение, то мы увидим, как количество оттенков в
заданном спрайте постепенно уменьшается, зачем увеличение шага в цикле
шейдера.
На этом все, дальше больше и интереснее. Спасибо за внимание.