Flow Fields: градієнт шуму + частинки
Одна з найпростіших ідей у генеративному мистецтві дає одні з найгіпнотичніших зображень: беремо функцію шуму, щоб отримати кут у кожній точці простору, а потім дозволяємо тисячам часток дрейфувати вздовж цих кутів, залишаючи позаду згасаючий слід.
1. Що таке flow field?
Flow field (векторне поле для малювання) присвоює напрямок кожній точці полотна. Уявіть це як невидиму карту вітру: у координаті (x, y) є кут θ(x, y), який підказує частці, що проходить через цю точку, куди повернути. Якщо запустити у цей «вітер» достатньо часток і дати їм рух, сукупність їхніх шляхів розкриває приховану структуру поля — вихори, гребені, переплетені потоки.
Техніка стала популярною завдяки роботам генеративних художників на кшталт Тайлера Гоббса та референсним реалізаціям на Processing/p5.js, але базовий алгоритм достатньо короткий, щоб написати його з нуля на чистому Canvas 2D менш ніж за годину.
Flow fields виглядають природно, бо побудовані на неперервній, просторово-корельованій випадковості, а не на незалежному шумі по пікселях. Сусідні точки отримують схожі кути, тож частки, що стартують поруч, певний час дрейфують разом, перш ніж поле розведе їх — саме така поведінка характерна для диму, піску на вітрі чи ліній течії ріки.
2. Від шуму Перліна до векторного поля
Функція кута θ(x, y) має плавно змінюватися у просторі. Шум Перліна — класичний вибір: він повертає неперервне значення приблизно у [-1, 1] для будь-якої точки (x, y), без видимих артефактів сітки. Ми перетворюємо це скалярне значення на кут, масштабуючи його на діапазон обертів:
де: s — просторовий масштаб шуму (менші значення = більші, плавніші вихори), s_t — часовий масштаб (наскільки швидко саме поле дрейфує), n — кількість повних обертів, на які мапиться діапазон шуму (типово n = 1–4)
Додавання часового доданку t·s_t означає, що саме поле повільно еволюціонує, тож частка, яка інакше осіла б у замкнутій петлі, продовжує досліджувати нову територію — саме це утримує анімацію flow field візуально живою, а не застиглою у статичну картинку через кілька секунд.
Simplex проти класичного Перліна
Simplex-шум (наступник Перліна 2001 року) трохи дешевший для вибірки у 2D і має менше напрямкового зсуву, ніж класичний Перлін, але для flow field візуальна різниця незначна — класичний Перлін, як на сторінці Шум Перліна, працює цілком добре.
3. Частинки: позиція, швидкість, слід
Кожна частка — крихітний скінченний автомат: позиція і, неявно,
коротка історія того, де вона побувала (слід). На відміну від
фізичної системи часток, тут зазвичай не інтегрують повну
швидкість — кут поля напряму задає напрямок руху щокадрово, а
стала speed задає, наскільки далеко вона зміщується:
x += cos(angle) · speed
y += sin(angle) · speed
Коли частка виходить за межі екрана, її можна або обгорнути навколо протилежного краю (зберігає кількість часток сталою, добре для щільних полів), або відродити у випадковій позиції (краще для композиції, що поступово «заповнюється»).
3000–5000 часток при 60fps комфортні на Canvas 2D для більшості ноутбуків. Понад ~10 000 варто пакетувати малювання (див. розділ про рендер нижче) або перейти на WebGL points, якщо потрібні стабільні 60fps.
4. Рендер без очищення canvas
Фірмовий вигляд flow field виникає завдяки тому, що canvas
не очищається повністю щокадру. Замість
ctx.clearRect(...) малюємо напівпрозорий прямокутник
поверх усього полотна перед новою позицією часток:
ctx.fillRect(0, 0, width, height)
Нижча альфа (0.01–0.02) залишає довгі, примарні смуги; вища альфа (0.08–0.15) залишає короткі кометні хвости і чіткіше показує миттєву форму поля. Цей єдиний параметр — найбільший візуальний важіль у всьому скетчі.
Кожна частка малюється як один піксель або короткий відрізок від
попередньої позиції до нової — відрізки виглядають плавніше при
малій кількості часток, тоді як окремі пікселі з
globalCompositeOperation = "lighter" дають класичний
«жар вугілля» при великій кількості часток.
5. Колір: hue від кута чи швидкості
Прив'язка кольору частки до локального кута поля перетворює невидиме векторне поле на видиме — можна буквально побачити напрямок потоку, закодований у hue:
ctx.strokeStyle = `hsla(${hue}, 80%, 60%, 0.8)`
Альтернативні прив'язки варто спробувати: hue від відстані до центру (радіальні градієнти), hue від часу життя частки (частки «старіють» від теплих до холодних тонів), або фіксована підібрана палітра (Ocean, Van Gogh, Fire, Mono), обрана за індексом, а не обчислена — саме це зазвичай відрізняє «математичну демонстрацію» від «галерейного шедевра».
6. Псевдокод
Один кадр рендерера flow field (спрощено):
function stepFlowField(particles, t, dt):
// 1. Згасання попереднього кадру замість очищення
ctx.fillStyle = "rgba(10,10,20,0.03)"
ctx.fillRect(0, 0, width, height)
for each particle p:
// 2. Вибірка поля у позиції частки
angle = noise(p.x * scale, p.y * scale, t * timeScale) * 2 * PI * turns
// 3. Рух уздовж поля
prevX, prevY = p.x, p.y
p.x += cos(angle) * speed
p.y += sin(angle) * speed
// 4. Малюємо короткий відрізок сліду, забарвлений за кутом
hue = (angle / (2 * PI)) * 360
ctx.strokeStyle = hsla(hue, 80, 60, 0.8)
ctx.beginPath()
ctx.moveTo(prevX, prevY)
ctx.lineTo(p.x, p.y)
ctx.stroke()
// 5. Відродження, якщо поза межами canvas
if outOfBounds(p):
p.x, p.y = randomEdgePosition()
7. Налаштування параметрів
- Масштаб шуму — малі значення (0.001–0.003) дають великі, ліниві вихори; великі значення (0.01–0.02) дають тісні, турбулентні вузли.
- Часовий масштаб — наскільки швидко саме поле еволюціонує. Занадто повільно — картинка виглядає статичною; занадто швидко — шляхи часток перестають читатися як когерентні криві.
- Кількість часток — компроміс між щільністю покриття і частотою кадрів; більше часток заповнюють полотно швидше, але коштують дорожче за кадр.
- Альфа сліду — контролює довжину смуг, див. розділ про рендер вище.
- Швидкість — дуже повільні частки точно простежують поле, але довго формують композицію; швидкі частки пропускають тонку структуру.
Хороша відправна точка: 3000 часток, масштаб шуму 0.0025, часовий масштаб 0.0004, альфа сліду 0.04, швидкість 1.5px/кадр. Змінюйте по одному слайдеру за раз у симуляції нижче й спостерігайте, як зсувається композиція.
🌊 Спробувати симуляцію flow field
Регулюйте масштаб шуму, швидкість часу, кількість часток, альфу сліду й палітру кольорів наживо, і збережіть композицію як PNG.
Відкрити симуляцію →