Время от времени мне пишут комментарии о том, что мои работы не выглядят созданными компьютером. Как по мне, это лучшая похвала: мой глубокий интерес к генеративному искусству начался, когда я осознал его способность создавать как раз такие работы. Здесь я расскажу некоторые методы, которые я использую для улучшения естественности своих работ.
Этот пост не о языке и не о фреймворке. Мы будем говорить только о технике.
И давайте сразу договоримся, пожалуйста: я не утверждаю, что работы, которые выглядят натурально, в какой-либо степени лучше тех, которые выглядят цифровыми. Не нужно ставить естественность целью. Я гонюсь за ней просто потому что мне это нравится, таковы мои вкусы.
Ведущий принцип: моделируйте свои работы на основе реальности
Если возможно, подумайте, как бы вы изобразили своё творение на бумаге или как бы оно могло выглядеть в реальном мире. Моделирование реального физического процесса, скорее всего, приведёт к реалистично выглядящей работе.
Например, равномерно распределённые данные обычно не встречаются в природе. Большая часть данных представляет из себя что-то вроде колоколообразной кривой. Использование нормально распределённых случайных величин (те, что со средним значением и стандартным отклонением) чаще всего создаёт более естественный эффект, чем случайный выбор из промежутка или списка.
Линии
Предположим, что у нас есть тип данных, представляющий из себя отрезок. Чтобы нарисовать его, обычно мы делаем как-то так:
- Выбрать начальную точку
- Выбрать конечную точку
- Провести черту между точками
В результате получается что-то такое:
По-моему, слишком идеально!
Случайный наклон
Предположим, что мы рисуем несколько горизонтальных линий, и мы хотим их сделать более естественными. Сперва можно немного подвинуть конечные точки линий. Мы можем сделать это, переместив их, используя нормальное распределение с небольшим стандартным отклонением в х и у направлениях.
Волнообразное колебание
Теперь у нас линии, которые немного смещены от изначально заданной позиции. И немного различаются по длине. Однако, они совершенно прямые. Это не похоже на линии, которые мы встречаем в реальном мире. Давайте сделаем их менее прямыми.
Например, это можно сделать так:
- Выбрать N точек между конечными точками отрезка, чтоб создать новую линию.
- Сместить каждую точку.
- Смягчить линию и нарисовать её.
Есть много вариаций этого паттерна. Простая – взять N интерполированных точек между начальной и конечной точками, поправить каждую из них с помощью статического стандартного отклонения, как делали раньше. Я буду использовать алгоритм изменения кривой Чайкина, чтоб получить сглаженную прямую. Вот так выглядит результат:
Влияние соседних точек
Хорошо, у нас начало что-то получаться. Изменение параметров стандартного отклонения позволяет нам контролировать «колебания» нашей линии. Однако, вы, наверное, заметили, что колебания независимы друг от друга. Линия может колебаться, то приближаясь к соседней верхней, то к нижней линиям. Чтобы уберечься от этого, я предлагаю такой вариант.
Возьмите среднее значение каждой пары смежных точек в вашей полученной линии после корректировки (это очень простой пример ядерного сглаживания). Вы можете сделать его несколько раз, чтобы получить более плавную линию. Нарисуйте теперь через точки кривую. Теперь каждая точка влияет на соседнюю более естественно.
Я бы сказал, что теперь форма выглядит довольно прилично. Если бы я попытался нарисовать прямую линию на бумаге, это, наверное, выглядело бы как раз как-то так. Мы можем менять стандартное отклонение в нашей функции с ручной коррекцей, чтобы смоделировать линию, сделанную будто более или менее аккуратной рукой.
Примечание: в данном случае ещё можно использовать функцию одномерного шума для перемещения точки в прямой. Результатом тоже будет плавная кривая, но она будет более предсказуемой, в зависимости от функции шума, которую вы используете.
Текстурирование линий
Теперь наша линия выглядит достаточно естественно. Но мы всё ещё рисуем идеально точной «ручкой», когда мы вызываем функцию stroke(). В реальном мире, когда мы используем различные инструменты для рисования, у них есть различия в текстуре. Один из способов внести эти различия – использовать технику песчаных сплайнов, популяризованную Андерсом Хоффом (a.k.a inconvergent). Идея проста:
- Совсем чуть-чуть скорректируйте каждую точку линии, используя нормальное распределение.
- Сгенерируйте ещё несколько точек, между каждой соседней парой точек, и нарисуйте их в виде крошечных точечек.
- Повторите несколько раз.
Если ваши точки супер маленькие, то получится такая текстура:
Это только один из множества способов придания текстурности линиям, но также это моя любимая тема для начала. Экспериментируйте со своими текстурами и увидите, что лучше подходит для вас.
«Сон о пустыне» (2018) Текстурированные линии – основа многих моих работ.
Формы
Теперь, когда мы можем рисовать необычные линии, давайте перейдём ко второму измерению — к формам. Простоты ради мы сосредоточимся на четрёхугольниках, но эти техники могут быть применены ко многим другим фигурам.
Мы можем просто нарисовать квадрат.
Можем применить уже упомянутый принцип случайного наклона, понемногу изменив каждый угол.
Можно выбрать и сгладить точки между каждой соседней координатой, чтобы получить «кривой» квадрат.
Мы можем также сгладить их, усреднив и соседние точки. Поскольку отрезок теперь замыкающийся, нужно быть аккуратным, чтобы не зацепить точки в первой координате.
Ничего из этого не ново. Мы здесь, по сути, говорим только о замкнутых отрезках, поскольку мы просто сглаживаем углы. А что насчёт заполнения пространства? Мы можем использовать функцию fill(), но это немного скучно.
Чтобы сделать текстуру поинтереснее, можно заполнить четырёхугольник штриховыми элементами, сгенерировав множество точек внутри него*. Мы упростим фигуру снова, чтобы сделать границы более понятными (можно штриховать и сложные фигуры, но давайте пока по-простому).
* Примечание: можно это сделать, поделив квадрат на 2 треугольника и сгенерировав точку в каждом при помощи равномерного распределения.
Хотя выглядит это немного неаккуратно, поскольку равномерное распределение не настолько интуитивно понятно, как кажется. Для более естественного распределения точек, можем взять значения из одной из моих любимых последовательностей – Последовательности Хальтона (2.3)[можно почитать про нее в классной статье].
Последовательность Хальтона (2.3) генерируют точки в промежутке (0, 1) × (0, 1) в R2. Чтобы заполнить квадрат точками, сгенерированными в этой последовательности, мы можем:
- Найти минимальный ограничивающий квадрат, содержащий в себе «Прямоугольник».
- Сгенерировать N точек в (2.3) последовательности Хальтона.
- Градуировать каждую точку по ширине (или высоте) минимального ограничивающего квадрата.
- Переместить точку по верхней левой координате квадрата.
- Отфильтровать все точки, которые не лежат в «Квадрате».
Это всё может работать на любой фигуре, которая поддерживает ограничение в квадрат и фильтрацию внешних точек. Самый простой способ опробовать это – заполнение квадрата штриховыми элементами, в нём операции ограничения в квадрат и фильтрации проходят несложно.
В общем, (2.3) последовательность Хальтона генерируется просто; прочитайте про псевдокод в википедии (eng). Вот заполнение штрихом на 10000 точек.
Поскольку (2.3) последовательность Хальтона генерируется не рандомно, она создаёт одинаково выглядящие текстуры. Пара способов обойти это:
- Градуирование и (по желанию) лёгкое раскачивание ограничительного квадрата, чтобы сместить центр последовательности.
- Отбрасывание N точек с начала последовательности.
- Рандомное отбрасывание малой части точек из последовательности (но осторожно, это создает больше шума).
Конечно, эти подходы можно совмещать. Экспериментируйте!
Ещё один привычный способ генерации таких текстур – использование избыточной выборки сглаживания. Это стохастический процесс, но он ещё и более требователен к ресурсам для вычисления и внедрения. Но он очень хорош. Я использовал этот способ в моей серии Провод, вот одна из работ.
Провод Т
Конечно же, это не единственный подход к текстурированию.
Цвет
Чувствовать, какие цвета сочетаются друг с другом очень, очень сложно. Легко выбрать цвета, которые по твоему мнению хорошо сочетаются, но потом понять, что они совершенно не сочетаются и заставляют твою работу выглядеть в 10 раз хуже, чем в чёрно-белом варианте.
И я бы сказал, для генеративных художников это создает особую сложность. Стандартный способ варьировать цвета в генеративном искусстве – вручную настраивать жёстко закодированные переменные и генерировать раз за разом картинку, пока не понравится. Игра с цветом – и правда лучший способ научиться чувствовать, как их правильно использовать. Как мы можем его улучшить?
Вот мой подход, который я переделал из идеи Джошуа Дэвиса. Вся суть:
- В отдельной программе (я, например, использую AutoDesk SketchBook) сконструировать горизонтальную градиентную линию и сохранить в png.
- Открыть png как пиксели в программе для создания генеративного искусства, и интерполировать между началом и концом градиента, используя функции плавности (функции с диапазоном выхода [0,1])
Этот подход работает очень хорошо, можно легко изменять градиент и достаточно чётко увидеть, что выглядит красиво, а что – нет. Есть возможность очень быстрых итераций. К тому же, большинство современных программ для рисования имеют функцию имитации вычитательного смешения цветов, которая очень помогает в рисовании естественно выглядящих переходов.
Мы будем использовать вот этот градиент для закрашивания точек в предыдущем примере (только точек в 20 раз больше).
Используемая нами функция плавности линейна по значению y. 0 обозначен на значении y верхней части квадрата, а 1 – в нижней. Стоит заметить, что результат функции плавности (между 0 и 1) слегка смещается вместе с нормально распределённым случайным значением. Это помогает сформировать менее однородную структуру.
Отлично. И это всего лишь абсурдно простая функция плавности (и градиент, который я сделал за 5 минут). Представьте себе возможности!
Используем шум с пользой
Раз уж мы говорим о функциях плавности, а что там с шумом? Шум Перлина не совсем подходит для этой функции, так как его значения варьируются от -1 до 1. Но с умным mapping`ом, ограничением посторонних значений, модульной арифметикой с плавающей запятой и/или другими хитростями, конечно, мы можем сузить диапазон до [0,1].
Вот одна из моих серий «Modular», в которой я использовал 2D шум как функцию плавности в цвете.
Смена цветов тут еле заметна, но в этом и суть. Они дают каждой форме «раскрыться» и придают глубину, не давя на смотрящего слишком большой и ненужной сложностью.
Если взглянуть внимательно, то можно заметить, что штрихованная структура – тоже часть шума.
Стоит отметить, что простой шум Перлина может начать казаться безжизненным со временем. Попробуйте поэкспериментировать с фрактальным броуновским движением или разработать свою собственную шумовую функцию, с которой можно будет поиграться. Эссе «Flow fields» Тайлера Хобба – хорошая вещь для углубления в тему (шум Перлина формирует бесконечные векторные поля, текучие поля – это полезное обобщение).
Ощутимость
Возвращаясь к нашему ведущему принципу, вспомним, что чтобы добавить в свои работы естественности, нужно симулировать реальную физику.
Это идея затронута подробно в Природе кода, которую я вам просто порекомендую, не цитируя Дэниэла Шиффмана. Дэниэл разделяет сложные физические проблемы на без проблем усваиваемые ломтики, упрощая реализацию каждой идеи. Делая свои творения, я часто запускаю на некоторое время физическую симуляцию и делаю снимок в определённый момент, который и становится финальной картиной.
Например, в картине «Слащавая сыворотка» на мяч влияет гравитация, который скачет по каждому квадратику в сетке. Направление гравитации определено дополнительным полем шума, и шарик оставляет след.
В основе «Пылевой чаши» лежит симуляция рвущейся резинки.
Как по мне, эти картины живые. И неудивительно, правда. Они тесно связаны с нашим ведущим принципом:
Моделируйте свои работы на основе реальности
Когда мы фокусируемся на симуляции естественных процессов в коде, мы получаем более естественную работу.
Подводя итоги, скажу, что следовать естественности в своих работах – это то, что мне нравится делать. Мне не в тягость заставлять свой компьютер потратить чуть больше времени на генерацию текстур и симуляцию физики, чтоб в итоге получить более живое творение. Надеюсь, эта статья станет вашей отправной точкой в экспериментах с естественностью в ваших работах.
P. S. Здесь не было никакого кода не просто так. Эти идеи достаточно общие и применимые во многих языках и средах разработки, я не хотел бы никого отталкивать, заставляя перейти на что-то другое.