В этой статье я объясню технику, используемую для создания «акварельных» генеративных изображений. Моё решение схоже с техниками, которые я описывал в статье Generating Soft Textures. Алгоритм не особо сложен. Концептуально он прост, но при этом хитро настроен.
Источник вдохновения
Я часто экспериментирую с акварелью в своём скетчбуке. Особенно мне нравятся безумно детализированные и разнообразные эффекты, которые способна создавать краска. Моё внимание привлёк один скетч, ставший источником вдохновения для разработки этой техники.
Моя задача не заключалась в реалистичном воспроизведении всех свойств акварели. Скорее, я хотел передать суть того, что мне в ней нравится.
Рекурсивный алгоритм деформации полигонов
Техника имитации акварели в генеративном искусстве заключается в наложении друг на друга множества почти прозрачных слоёв. Чтобы создать ощущение акварели с её плавным размытием, слои должны обладать большой вариативностью форм. Однако в областях с более чёткими границами слои должны обладать меньшей вариативностью.
Ядром техники имитации акварели является рекурсивный алгоритм деформации полигонов.
Он довольно прост, его смысл заключается примерно в следующем:
- Для каждого отрезка A -> C в полигоне находим среднюю точку B. Из гауссова распределения, центрированного в точке B, выбираем новую точку B'.
- Обновляем полигон, заменяя отрезок A -> C двумя отрезками: A -> B' и B' -> C
- Если мы не достигли максимальной глубины рекурсии, то повторяем с шага 1, разделяя дочерние отрезки.
При определённых параметрах вариативности гауссова распределения и глубины рекурсии мы получим полигон с изломанными детализированными рёбрами.
Как работает техника деформации полигонов
Чтобы показать, как работает техника деформации полигонов, создающая эффект акварели, я применю её к простому правильному многоугольнику:
Базовая форма акварельного рисунка создаётся выполнением одного раунда деформации полигона. Берётся входящий полигон и несколько раз пропускается через функцию деформации (примерно 7 раз). Получившийся полигон становится «базовым полигоном» для всех слоёв, которые мы создадим.
Это выглядит примерно так:
Создание каждого слоя начинается с того, что мы берём «базовый полигон» и пропускаем его через функцию деформации ещё несколько раз (4-5 раз). При этом создаётся полигон, который похож на базовый, но отличается от него мелкими деталями. Отрисовываем полигон с высокой прозрачностью (opacity около 4%). Повторяем этот процесс для 30-100 слоёв.
Улучшаем форму благодаря областям с высокой и низкой вариативностью
Проделанные ранее шаги создали форму с довольно плавными краями. Чтобы придать границе чёткие края в некоторых областях и плавные в других, можно присвоить каждому отрезку свой уровень «вариативности». Отрезки с высокой вариативностью будут подвергаться большим изменениям в каждом раунде мутаций, а отрезки с низкой вариативностью — меньшим изменениям. Когда отрезок разделяется на два дочерних отрезка, эти дочерние отрезки наследуют вариативность родителя. Разумеется, вариативность должна немного уменьшаться, кроме того, неплохо немного рандомизировать эту вариативность при создании каждого дочернего отрезка.
После реализации этих изменений капли выглядят намного интереснее:
Улучшаем изображение текстурной маской
Настоящая акварель не идеально плавная, её прозрачность варьируется. Чтобы немного передать это, можно для каждого из 30-100 слоёв использовать свою текстурную маску. Маска сделает некоторые части слоя полностью прозрачными, создавая вариативность. В этом примере я случайным образом располагаю на изображении около 1000 маленьких кругов в качестве текстурной маски
; draw the watercolor blob shape
(with-graphics the-blob-mask
(background 0 0 0)
(stroke 0 0 layer-alpha)
(fill 0 0 layer-alpha)
(begin-shape)
(doseq [[x y] final-poly]
(vertex x y))
(end-shape))
; draw the circles onto the texture mask
(with-graphics the-texture-mask
(background 0 0 0)
(no-stroke)
(fill 0 0 layer-alpha)
(doseq [j (range 900)]
(let [x (random 0 (w))
y (random 0 (h))
len (abs-gauss (w 0.03) (w 0.02))
[hue sat bright] (color-fn)]
(fill hue sat bright)
(ellipse x y len len)))
; Blend the watercolor blob shape layer into the current
; layer, only taking the darkest pixel from each. This
; effectively means we're taking the "intersection" of
; the two masks.
(blend the-blob-mask 0 0 (w) (h) 0 0 (w) (h) :darkest))
(with-graphics the-overlay
; make the whole background red
(background 5 80 80)
; apply the combination blob/texture mask
(mask-image the-texture-mask))
; apply the masked layer
(image the-overlay 0 0)
Результат выглядит следующим образом:
Смешение цветов
Смешение нескольких цветов в этой технике хорошо работает, если использовать слои попеременно. Например, сначала накладываем пять слоёв красных пятен, потом пять слоёв жёлтых пятен, потом пять красных и т.д. Это не отражает то, что происходит с реальной акварелью, но выглядит красиво.
Если соединить эту технику с текстурными масками, то это будет выглядеть так:
Примеры работ
Вот пара работ, которые я создал на основе этих техник.