Фрактальна геометрія — множина Мандельброта, розмірність Гаусдорфа та системи ітерованих функцій
Фрактальна геометрія описує форми, які надто нерегулярні для класичної евклідової геометрії: самоподібні, нескінченно складні на кожному масштабі, з нецілою розмірністю. Від множини Мандельброта, обчисленої в GPU-шейдері, до берегових ліній, виміряних лінійкою — фрактальне мислення скрізь.
1. Самоподібність і фрактальна розмірність
Фрактал — це множина (або геометричний об’єкт), що виявляє самоподібність: збільшення масштабу відкриває структуру, яка нагадує ціле. Ключова математична ознака — це неціла розмірність Гаусдорфа: фрактал живе «між» класичними розмірностями.
D = log(N) / log(1/r)
N = кількість самоподібних копій після масштабування
r = коефіцієнт масштабування, застосований до кожної копії
Для відрізка: N=2 копії при r=1/2, тож D = log(2)/log(2) = 1. ✔
Для квадрата: N=4, r=1/2, D = log(4)/log(2) = 2. ✔
Для сніжинки Коха: N=4, r=1/3, D = log(4)/log(3) ≈
1.262. Крива з розмірністю >1.
| Фрактал | D (приблизно) | Опис |
|---|---|---|
| Множина Кантора | 0.631 | Пил — між точкою та лінією |
| Сніжинка Коха | 1.262 | Крива — між 1D і 2D |
| Трикутник Серпінського | 1.585 | Між кривою та поверхнею |
| Межа Мандельброта | 2.0 | Сама межа має D=2 (заповнює простір) |
| Губка Менгера | 2.727 | Між поверхнею та об’ємом |
| Берегова лінія Британії | ≈1.25 | Ефект Річардсона: виміряна довжина → ∞, коли лінійка → 0 |
2. Множина Мандельброта й алгоритм часу втечі
Множина Мандельброта M — це множина комплексних чисел c, для яких ітерація zₙ₊₁ = zₙ² + c (починаючи з z₀ = 0) лишається обмеженою — тобто |zₙ| ніколи не перевищує радіус втечі (зазвичай 2).
zₙ₊₁ = zₙ² + c
c ∈ M ⟺ |zₙ| ≤ 2 для всіх n ∈ ℕ
У декартових координатах: z = x + iy, c = cx + icy
x' = x² − y² + cx
y' = 2xy + cy
«Час втечі» — кількість ітерацій, перш ніж |z| > 2 — використовується для зіставлення кожного пікселя з кольором. Точки, що ніколи не тікають (ті, що в M), традиційно чорні; навколишня межа створює нескінченно деталізовану облямівку.
// Час втечі Мандельброта — еталонна реалізація на CPU
function mandelbrot(cx, cy, maxIter = 256) {
let x = 0, y = 0;
for (let i = 0; i < maxIter; i++) {
const x2 = x * x, y2 = y * y;
if (x2 + y2 > 4) return i; // втекла — повертаємо лічильник ітерацій
y = 2 * x * y + cy;
x = x2 - y2 + cx;
}
return maxIter; // не втекла — всередині множини
}
// Рендеринг у Canvas — відображає область перегляду [x0,x1]×[y0,y1] на пікселі полотна
function render(ctx, w, h, x0, x1, y0, y1) {
const img = ctx.createImageData(w, h);
for (let py = 0; py < h; py++) {
for (let px = 0; px < w; px++) {
const cx = x0 + (px / w) * (x1 - x0);
const cy = y0 + (py / h) * (y1 - y0);
const n = mandelbrot(cx, cy);
const t = n / 256; // 0..1
const i4 = (py * w + px) * 4;
// Проста палітра від синього до золотого
img.data[i4] = Math.sin(t * 3.14159 ) * 255 | 0;
img.data[i4+1] = Math.sin(t * 3.14159 * 2) * 255 | 0;
img.data[i4+2] = t < 0.5 ? t * 2 * 255 | 0 : 255;
img.data[i4+3] = 255;
}
}
ctx.putImageData(img, 0, 0);
}
3. Плавне розфарбування — неперервний лічильник ітерацій
Цілочисельні значення часу втечі дають помітні кольорові смуги (смугастість, як у «лава-лампі»). Неперервний (плавний) лічильник ітерацій усуває цей артефакт, використовуючи кінцеве значення |z| для інтерполяції між цілими смугами:
Для радіуса втечі R формула узагальнюється до:
μ = n − log( log(|zₙ|) / log(R) ) / log(2)
Це дає плавне дробове число, яке ви передаєте у функцію палітри.
// Плавний лічильник ітерацій (CPU)
function mandelbrotSmooth(cx, cy, maxIter = 512) {
let x = 0, y = 0;
for (let i = 0; i < maxIter; i++) {
const x2 = x*x, y2 = y*y;
if (x2 + y2 > 256) {
// Плавна втеча (радіус втечі 16, поріг виходу 256 для кращого згладжування)
return i - Math.log2(Math.log2(Math.sqrt(x2 + y2)));
}
y = 2*x*y + cy;
x = x2 - y2 + cx;
}
return maxIter;
}
// Вибірка з плавної палітри (циклічний обхід HSL)
function palette(t) {
const h = (t * 360 * 0.15) % 360;
return `hsl(${h}, 80%, 55%)`;
}
4. Множини Жюліа та простір параметрів
Множина Жюліа Jc обчислюється тією самою ітерацією zₙ₊₁ = zₙ² + c, але цього разу c фіксоване, а початкове значення z₀ змінюється для кожного пікселя. Заповнена множина Жюліа Kc — це множина z₀, що не тікають.
Множина Мандельброта — це «карта» всіх множин Жюліа: кожна точка c у M відповідає зв’язній множині Жюліа; кожна точка поза M відповідає незв’язній (пилу типу Кантора) множині Жюліа. Збільшення масштабу на межі Мандельброта в точці c відкриває локальну самоподібну копію відповідної множини Жюліа Jc.
Обчислення множини Жюліа ідентичне циклу Мандельброта, але з z₀ = координатами пікселя і c як uniform-параметром — тривіально додається до будь-якого шейдера Мандельброта одним булевим перемикачем.
5. Системи ітерованих функцій (СІФ)
СІФ — це скінченна сукупність стискальних відображень {f₁, f₂, …, fₙ} на ℝ² (або ℝ³). За теоремою Банаха про нерухому точку, єдина компактна множина, інваріантна відносно всіх відображень одночасно — атрактор — є фракталом.
Алгоритм випадкової ітерації (гра в хаос) будує атрактор, починаючи з будь-якої точки й багаторазово застосовуючи випадково обране відображення. Орбіта збігається до атрактора після кількох десятків розігрівальних кроків.
// СІФ «папороть Барнслі» (4 афінні відображення, обираються імовірнісно)
const fern = [
// [a, b, c, d, e, f, prob] — перетворює [x,y] → [ax+by+e, cx+dy+f]
[ 0.00, 0.00, 0.00, 0.16, 0, 0, 0.01], // стебло
[ 0.85, 0.04, -0.04, 0.85, 0, 1.60, 0.85], // великі листочки
[ 0.20, -0.26, 0.23, 0.22, 0, 1.60, 0.07], // малі ліві листочки
[ -0.15, 0.28, 0.26, 0.24, 0, 0.44, 0.07], // малі праві листочки
];
function chaosGame(points = 200_000) {
let x = 0, y = 0;
const out = [];
for (let i = 0; i < points; i++) {
const r = Math.random();
let cumP = 0, fi;
for (fi of fern) { cumP += fi[6]; if (r < cumP) break; }
const [a,b,c,d,e,f] = fi;
const nx = a*x + b*y + e;
const ny = c*x + d*y + f;
x = nx; y = ny;
out.push(x, y);
}
return out;
}
Трикутник Серпінського через гру в хаос
// Три вершини рівностороннього трикутника
const verts = [[0.5,0], [0,1], [1,1]];
let [x, y] = [0.5, 0.5];
for (let i = 0; i < 100_000; i++) {
const [vx, vy] = verts[Math.floor(Math.random() * 3)];
x = (x + vx) / 2; // рухаємося на півдороги до обраної вершини
y = (y + vy) / 2;
plotPixel(x, y); // проступає трикутник Серпінського
}
6. Фрактали Ньютона та басейни притягання
Застосуйте метод Ньютона для пошуку коренів до многочлена p(z) у комплексній площині. Для кожного пікселя c = z₀ ітеруйте, доки z не збіжиться до кореня:
Приклад: p(z) = z³ − 1, корені: 1, e^(2πi/3), e^(4πi/3)
Кожен піксель забарвлюється тим, до якого кореня збігається ітерація,
і затіняється тим, наскільки швидко вона збіглася (лічильник ітерацій).
Межі між басейнами притягання є фракталами — множиною Жюліа відображення Ньютона. Для многочленів степеня ≥ 3 межа щільно переплетена й фрактальна між кожною парою коренів.
// Фрактал Ньютона для z³ - 1 = 0
function newton(cx, cy, maxIter = 64) {
let x = cx, y = cy;
for (let i = 0; i < maxIter; i++) {
const r2 = x*x + y*y, r6 = r2*r2*r2;
if (r6 < 1e-15) break; // при z=0 → похідна 0 (рідко)
// p(z) = z³ - 1, p'(z) = 3z²
// z' = z - (z³-1)/(3z²) = (2z³+1)/(3z²)
const denom = 3 * r2 * r2;
// чисельник: 2z³+1 — спершу обчислюємо z³
const z3x = x*(x*x - 3*y*y), z3y = y*(3*x*x - y*y);
const nx = (2*z3x + 1) / (3*r2);
const ny = (2*z3y) / (3*r2);
// обчислюємо (2z³+1) / (3z²) через комплексне ділення
x = (nx * x + ny * y) / r2; // спрощено: Re[(2z³+1)/(3z²)]
y = (ny * x - nx * y) / r2;
}
// Визначаємо найближчий корінь і повертаємо (індекс кореня, лічильник ітерацій)
const roots = [[1,0],[-0.5,0.866],[-0.5,-0.866]];
let minD = Infinity, rootIdx = 0;
for (let r = 0; r < 3; r++) {
const d = (x-roots[r][0])**2 + (y-roots[r][1])**2;
if (d < minD) { minD = d; rootIdx = r; }
}
return rootIdx;
}
7. GPU-рендеринг у WebGL2 — глибоке збільшення на 60 кадр/с
Рендеринг на CPU за 800×600 × 512 ітерацій — це близько 250 мільйонів операцій на кадр — надто повільно для інтерактивного збільшення. Фрагментний шейдер обчислює ітерацію часу втечі для кожного пікселя паралельно на GPU.
// Фрагментний шейдер Мандельброта на GLSL (WebGL2)
#version 300 es
precision highp float;
uniform vec4 uView; // (x0, y0, ширина, висота) у комплексній площині
uniform int uMaxIter;
in vec2 vUv;
out vec4 fragColor;
// Плавний лічильник ітерацій + палітра
vec3 palette(float t) {
vec3 a = vec3(0.5), b = vec3(0.5);
vec3 c = vec3(1.0);
vec3 d = vec3(0.263, 0.416, 0.557);
return a + b * cos(6.28318 * (c * t + d)); // косинусна палітра Iq
}
void main() {
float cx = uView.x + vUv.x * uView.z;
float cy = uView.y + vUv.y * uView.w;
float x = 0.0, y = 0.0;
int n = 0;
for (int i = 0; i < 512; i++) {
if (i >= uMaxIter) break;
float x2 = x*x, y2 = y*y;
if (x2 + y2 > 256.0) { n = i; break; }
y = 2.0*x*y + cy;
x = x2 - y2 + cx;
n++;
}
if (n == uMaxIter) { fragColor = vec4(0.0, 0.0, 0.0, 1.0); return; }
// Плавне розфарбування
float logZn = log(x*x + y*y) * 0.5;
float nu = log(logZn / log(2.0)) / log(2.0);
float t = (float(n) + 1.0 - nu) / float(uMaxIter);
fragColor = vec4(palette(t), 1.0);
}
highp float (32-біт) дає приблизно 7 десяткових цифр —
достатньо для збільшення до ≈10⁻⁶. Для глибших збільшень (10⁻¹⁰ і далі) потрібна
емуляція подвійної точності у GLSL з двома
значеннями float на координату (емуляція FP64) або
розширення OES_texture_float64, де воно доступне.
📐 Симуляція спірографа
Інтерактивні математичні криві — епіциклоїди, гіпоциклоїди та інші, відрендерені за допомогою WebGL.
8. Фрактали в природі, науці та іграх
- Генерація рельєфу: фрактальний броунівський рух (fBm) — шари шуму Перліна на різних частотах — створює ландшафти з фрактальним характером. Показник Гурста H керує шорсткістю; H=0.5 дає броунівський шум, H→1 — гладкий.
- Берегові лінії та річкові мережі: Річардсон (1961) показав, що виміряна довжина берегової лінії зростає необмежено зі зменшенням довжини лінійки. Річкові дренажні мережі демонструють фрактальне розгалуження з D ≈ 1.2.
- Дерева легень і судин: бронхіальні дерева й мережі кровоносних судин — це самоподібні фрактальні дерева. Фрактальне розгалуження максимізує площу поверхні (газообмін / доставка поживних речовин) в обмеженому об’ємі.
- Проєктування антен: фрактальні СІФ-антени (трикутник Серпінського, крива Коха) резонують на кількох самоподібних частотах — компактні широкосмугові антени, що використовуються в мобільних телефонах.
- Стиснення зображень (СІФ-кодування): фрактальне стиснення зображень Майкла Барнслі знаходить СІФ, атрактор якої найкраще наближає задане зображення. Розпакування безкоштовне (ітеруйте СІФ); стиснення дороге (задача пошуку).
- Процедурна генерація в іграх: алгоритми зсуву середньої точки та Diamond-Square генерують карти висот для рельєфу за допомогою рекурсивного самоподібного поділу. Множину Мандельброта використовують для процедурного затінення біомів у кількох інді-іграх.
- Фінансові часові ряди: Мандельброт (1963) змоделював ціни на бавовну стійкими розподілами Леві — графіки цін виявляють статистичну самоподібність на різних часових масштабах (гіпотеза фрактального ринку).