Эта глава могла бы называться «Урок с забором от мистера Мияги». Ранее мы отобразили нормализованные координаты x и y в красный и зелёный цветовые каналы. По сути, мы сделали функцию, которая принимает двумерный вектор (x и y) и возвращает четырёхмерный вектор (r, g, b и а). Но прежде чем мы погрузимся глубже в трансформацию данных между измерениями, не помешает начать с более простых вещей. То есть с понимания способов конструирования одномерных функций. Чем больше времени и энергии вы потратите на освоение этого материала, тем сильнее будет ваше шейдерное карате.
Следующий код будет нашим забором. В нём мы визуализируем нормированное значение координаты x (st.x
) двумя способами: с помощью яркости (обратите внимание на градиент от чёрного к белому) и путём построения зелёной линии поверх (в этом случае значение x записывается напрямую в y). Вы можете пока не вникать в функцию построения графика. Она будет детально рассмотрена далее.
На заметку: Конструктор типа vec3
«понимает», что вы хотите присвоить одно и то же значение всем трём каналам, а vec4
понимает, что четырёхмерный вектор нужно собрать из трёхмерного вектора и одного числа. Это число в данном случае отвечает за альфа канал, или прозрачность. Примеры этого поведения вы можете видеть в строках 19 и 25.
Этот код - это ваш забор; важно видеть и понимать его. Вы раз за разом будете возвращаться в пространство между 0.0 и 1.0. Вы изучите искусство смешивания и формирования линий.
Такое отображение один-в-один между x и y (или яркостью) называется линейной интерполяцией. Начиная с этого момента, мы можем использовать математические функции для придания формы линии. Например, мы можем возвести x в пятую степень, чтобы получить кривую линию.
Интересно, правда? Попробуйте различные степени в 22 строке, например 20.0, 2.0, 1.0, 0.0, 0.2 и 0.02. Понимание соотношения между числом и его степенью будет очень полезным. Использование математических функций такого типа даст вам мощное выразительное средство, позволяющее тонко управлять потоком значений.
pow()
- одна из многих встроенных функций языка GLSL. Большинство из них ускорены на аппаратном уровне, а значит при их правильном и осмотрительном использовании ваш код станет быстрее.
Замените функцию степени в строке 22 на какую-нибудь другую. Попробуйте exp()
, log()
и sqrt()
. Некоторые из этих функций более интересны при использовании числа Пи. В восьмой строке я определил макрос, заменяющий любое упоминание PI
на 3.14159265359
.
В GLSL так же есть несколько уникальных функций интерполяции с аппаратным ускорением.
Функция step()
(ступенька) принимает два параметра. Первый параметр задаёт значение порога, а второй - точку, в которой мы хотим вычислить функцию. В любой точке до порога функция возвращает 0.0
, а в любой точке после него - 1.0
.
Попробуйте изменить значение порога в 20 строке в следующем коде.
Ещё одна уникальная функция называется smoothstep()
(гладкая ступенька). Эта функция гладко интерполирует аргумент в интервале между двумя заданными числами. Первые два параметра задают начало и конец переходного интервала, а третий - точку, в которой нужно интерполировать.
В строке 12 приведённого выше кода мы используем smoothstep для рисования зелёной линии в функции plot()
. Для каждой точки вдоль оси x эта функция делает всплеск при нужно значении y. Как? Через соединение двух smoothstep()
. Рассмотрите следующую функцию, вставьте её вместо строки 20 в коде выше и вообразите, что это вертикальный разрез. Фон выглядит как линия, не так ли?
float y = smoothstep(0.2,0.5,st.x) - smoothstep(0.5,0.8,st.x);
Синус и косинус - ваши лучшие друзья, когда вы используете математику для анимации, построения фигур или смешивания значений.
Эти две базовые тригонометрические функции при построении кругов удобны, как швейцарский армейский нож, и обычно они используются в паре. Очень важно знать как они себя ведут и какими способами могут быть скомбинированны. Вкратце, они принимают угол в радианах и возвращают координаты x (косинус) и y (синус) точки на окружности единичного радиуса. Тот факт, что они возвращают нормализованные значения (между -1 и 1) и при этом являются достаточно гладкими, делает их незаменимым инструментом.
Описать все взаимоотношения между кругами и тригонометрическими функциями довольно трудно, но анимация выше отлично их демонстрирует.
Внимательно присмотритесь к этой синусоидальной волне. Обратите внимание на плавное изменение значения y между -1 и 1. Как мы видели в примере со временем в предыдущем параграфе, это ритмичное поведение синуса sin()
можно использовать в анимациях. Если вы читаете этот пример в браузере, вы можете поизменять формулу выше и пронаблюдать как изменяется волна. Не забывайте ставить точку с запятой в конце строки.
Попробуйте проделать следующие действия и посмотрите что происходит:
-
Прибавьте время (
u_time
) к x перед вычислением синуса. Вы увидите движение вдоль x. -
Домножьте x на Пи перед вычислением синуса. Обратите внимание на сужение графика, так что теперь волна повторяется каждые два отсчёта по горизонтальной оси.
-
Умножьте
u_time
на x перед вычислением синуса. Вы увидите, как частота волн увеличивается со временем. Возможно,u_time
к этому времени будет слишком большой, что затруднит восприятие графика. -
Прибавьте 1.0 к
sin(x)
. Вся волна сдвинется вверх и займёт область значений от 0.0 до 2.0. -
Умножьте
sin(x)
на 2.0. Амплитуда увеличится вдвое. -
Вычислите абсолютное значение синуса с помощью
abs()
. График станет похожим на траекторию подпрыгивающего мячика. -
Сложите результаты вычисления синуса, округлённые до целого в большую (
ceil()
) и в меньшую (floor()
) стороны. Получится «цифровой» прямоугольный сигнал со значениями 1 и -1.
В конце предыдущего упражнения мы затронули несколько новых функций. Теперь давайте поэкспериментируем. Попробуйте раскомментировать строки в коде ниже по одной. Запомните эти функции и изучите их поведение. Возможно, вы спросите, зачем это нужно? Быстрый поиск в google по запросу «generative art» даст ответ. Помните, что пока мы осваиваем перемещение в одном измерении, вверх и вниз. Но скоро мы перейдём к двум, трём и даже четырём измерениям!
Голан Левин написал отличный учебник по более сложным функциям, которые могут понадобиться. Начните собирать вашу собственную библиотеку полезных кусочков кода с портирования этих функций на GLSL.
-
Полиномиальные функции: www.flong.com/archive/texts/code/shapers_poly
-
Экспоненциальные функции: www.flong.com/archive/texts/code/shapers_exp
-
Круги и эллипсы: www.flong.com/archive/texts/code/shapers_circ
-
Сплайны Безье и другие параметрические функции: www.flong.com/archive/texts/code/shapers_bez
Подобно поварам, собирающим специи и экзотические ингридиенты, цифровые художники уделяют особое внимание работе над своими собственными формообразующими функциями.
Иниго Квилес собрал хорошую коллекцию полезных функций. После прочтения статьи посмотрите на реализацию этих функций на GLSL. Обратите внимание на незначительность потребовавшихся изменений. Например, пришлось использовать точку в числах с плавающей точкой и заменить функций из C на их GLSL-аналоги: pow()
вместо powf()
и т.п.
Для поддержания вашего вдохновения, посмотрите на элегантный пример использования одномерных функций, написанный автором Danguafer на ShaderToy.
<iframe width="800" height="450" frameborder="0" src="https://www.shadertoy.com/embed/XsXXDn?gui=true&t=10&paused=true" allowfullscreen></iframe>В следующей главе мы сделаем ещё один шаг. Сначала мы посмешиваем цвета, а затем перейдём к рисованию фигур.
Рассмотрите таблицу с формулами, созданную автором Kynd. Он комбинирует функции и их свойства, чтобы контролировать значения между 0.0 и 1.0. Попробуйте воспроизвести эти функции самостоятельно. Помните, что чем больше вы тренируетесь, тем сильнее станет ваше карате.
Здесь собраны ссылки на инструменты, которые упростят визуализацию одномерных функций.
- Grapher: если у вас есть компьютер с MacOS, введите grapher в spotlight и воспользуйтесь этим инструментом.
- GraphToy: уже знакомый нам Иниго Квилес написал инструмент для визуализации GLSL-функций в WebGL.
- Shadershop: этот замечательный инструмент, созданный Тоби Шахманом, научит вас конструировать сложные функции необычайно наглядным и интуитивным способом.