Шейдерные программы исполняются попиксельно, поэтому вне зависимости от количества повторений фигуры объём вычислений не изменяется. Это значит, что фрагментные шейдеры хорошо справляются с повторяющимися узорами.
В этой главе мы собираемся применить весь ранее изученный материал, и повторить это в изображении несколько раз. Как и в предыдущих главах, наша стратегия будет основана на умножении пространственных координат (между 0.0 и 1.0), так чтобы фигуры, которые мы рисуем между 0.0 и 1.0 повторялись несколько раз, образуя решётку.
«Регулярная решётка - это то, с чем человеческой интуиции и изобретательности проще всего работать. Повторяющиеся элементы вступают в контраст с хаосом мироздания и создают ощущение порядка. Люди всегда старались украсить и разнообразить окружающее пространство с помощю повторяющихся элементов. Это прослеживается от доисторических узоров на керамике до геометрических мозаик римских бань.» 10 PRINT, Издательство MIT, (2013)
Для начала давайте вспомним функцию fract()
. Она возвращает дробную часть числа, то есть работает как взятие остатка от деления на единицу (mod(x,1.0)
). Другими словами, fract()
возвращает число справа от точки. Переменная с нормализованными координатами (st
) уже пробегает значения от 0.0 до 1.0, поэтому нет смысла делать что-то вроде:
void main(){
vec2 st = gl_FragCoord.xy/u_resolution;
vec3 color = vec3(0.0);
st = fract(st);
color = vec3(st,0.0);
gl_FragColor = vec4(color,1.0);
}
Но если мы увеличим масштаб нормализованной системы координат, скажем, в три раза, то получится три отрезка линейной интерполяции между 0.0 и 1.0: между 0 и 1, между 1 и 2, и наконец между 2 и 3.
Теперь давайте нарисуем что-нибудь в каждом подпространстве, раскомментировав строку 27. Соотношение сторон при этом не изменится и фигуры не будут искажены, ибо мы умножаем по обеим осям.
Для более полного понимания попробуйте выполнить следующее:
-
Поумножайте пространственные координаты на различные числа. Поэкспериментируйте с дробными числами и с различными множителями для x и y.
-
Сделайте функцию создания узоров, пригодную для повторного использования.
-
Разделите пространство на 3 строки и 3 столбца. Придумайте как узнать в какой строке и каком столбце находится текущий поток, и изменяйте фигуру в зависимости от этого. Нарисуйте игру в крестики-нолики.
Каждая клетка решётки является уменьшенной версией нормализованной системы координат, которую мы использовали ранее, поэтому мы можем применить к этим клеткам матричные преобразования переноса, поворота и масштаба.
-
Придумайте интересные анимации для этого узора. Анимируйте цвет, форму и движение. Сделайте три различных анимации.
-
Создайте более сложные узоры, совмещая разные формы.
- Создайте узор шотландского тартана, совмещая узоры в несколько слоёв.
Давайте сымитируем кирпичную стену. Каждый ряд кирпичей в стене смещён на полкирпича по оси x относительно предыдущего ряда. Как это сделать?
Для начала нам нужно узнать чётность номера строки, над которой работает данный поток. Таким образом мы сможем понять, нужно ли делать сдвиг по x в этой строке.
Чтобы определить чётность строки, используем взятие по модулю 2.0 с помощью функции mod()
, и сравним результат с единицей. Посмотрите на формулы ниже и раскомментируйте две последние строки.
Как видите, мы могли бы использовать тернарный оператор для сравнения значения по модулю 2.0
с единицей, но того же эффекта можно достичь с помощью step()
, которая работает быстрее. Почему? Хотя мы и не знаем как графические карты оптимизирует код, безопаснее будет предположить что встроенные функции работает быстрее, чем не встроенные. Если у вас есть возможность использовать встроенную функцию - используйте!
Теперь у нас есть формула вычисления чётности и мы можем сдвинуть нечётные строки для создания эффекта кирпичной стены. В 14 строке следующего кода мы используем эту формулу для «обнаружения» нечётных строк. Как видите, для чётных строк функция возвращает 0.0
, что при умножении на сдвиг 0.5
так же даёт 0.0
, а значит чётные строки не сдвигаются. В нечётных же строках 0.5
умножается на 1.0
, поэтому в них пространство сдвигается на 0.5
по оси x.
Теперь попробуйте раскомментировать строку 32. Это сделает соотношение сторон похожим на соотношение сторон кирпича. А раскомментировав 40 строку вы увидите отображение координат в красный и зелёный цвета.
-
Попробуйте анимировать этот пример, изменяя сдвиг в зависимости от времени.
-
Создайте ещё одну анимацию, где чётные строки движутся налево, а нечётные - направо.
-
Можете ли вы повторить такой же эффект для столбцов?
-
Сделайте сдвиг по x и y одновременно, чтобы получить что-то вроде этого:
Теперь, когда мы научились определять чётность строки и столбца для каждой клетки, мы можем многократно использовать один и тот же элемент в зависимости от его расположения. Рассмотрим плитку Труше, где один элемент дизайна может быть представлен четырьмя различными способами:
Изменяя рисунок в зависимости от расположения плитки, можно создать бесконечно много сложных изображений.
Обратите особое внимание на функцию rotateTilePattern()
, которая разделяет пространство на четыре части и задаёт угол поворота каждой из них.
-
Закомментируйте, раскомментируйте и продублируйте строки с 69 по 72, чтобы скомпоновать новые изображения.
-
Замените чёрно-белый треугольник на какой-нибудь другой элемент, например полукруг, повернутые квадраты или линии.
-
Напишите другие узоры с элементами, повёрнутыми в зависимости от их расположения.
-
Создайте узор, элементы которого меняют другие свойства в зависимости от расположения.
-
Придумайте ещё какое-нибудь изображение (не обязательно повторяющийся узор), в котором можно применить принципы из этой главы, например, Гексаграммы И цзин.
Создание процедурных узоров - это умственное упражнение по поиску минимальных повторно используемых элементов. Это древняя практика. Мы, как биологический вид, издавна использовали узоры для украшения тканей, пола и границ объектов: вспомните извилистые узоры древней Греции или решётчатые узоры из Китая. Магия повторений и вариаций всегда захватывала наше воображение. Рассмотрите декоративные узоры и обратите внимание как художники и дизайнеры балансируют между предсказуемостью порядка и внезапностью изменчивости хаоса. От арабских геометрических узоров и до бесподобных африканских тканей раскинулась целая вселенная узоров, среди которых есть что изучить.
Этой главой мы заканчиваем раздел об алгоритмическом рисовании. В следующих главах мы привнесём в шейдеры немного энтропии для создания генеративного дизайна.