Математика · Фрактали
📅 Березень 2026 ⏱ ≈ 13 хв читання 🎯 Середній рівень · Останнє оновлення: 28 травня 2026 р.

Фрактальна геометрія — множина Мандельброта, розмірність Гаусдорфа та системи ітерованих функцій

Фрактальна геометрія описує форми, які надто нерегулярні для класичної евклідової геометрії: самоподібні, нескінченно складні на кожному масштабі, з нецілою розмірністю. Від множини Мандельброта, обчисленої в GPU-шейдері, до берегових ліній, виміряних лінійкою — фрактальне мислення скрізь.

1. Самоподібність і фрактальна розмірність

Фрактал — це множина (або геометричний об’єкт), що виявляє самоподібність: збільшення масштабу відкриває структуру, яка нагадує ціле. Ключова математична ознака — це неціла розмірність Гаусдорфа: фрактал живе «між» класичними розмірностями.

Розмірність Гаусдорфа–Безиковича D:

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₀ = 0
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| для інтерполяції між цілими смугами:

μ = n − log₂( log(|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.

c = −0.7 + 0.27iСпіральні вусики — «кролик Дуаді»
c = 0.285 + 0.01iСкладна мереживна структура
c = −0.4 + 0.6iКласичні дендритні гілки
c = 0 + iДендрит — ледь зв’язний
c = −2 + 0iПил Кантора — повністю незв’язний
c = −0.75 + 0iПоблизу межі головної кардіоїди

Обчислення множини Жюліа ідентичне циклу Мандельброта, але з 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 не збіжиться до кореня:

zₙ₊₁ = zₙ − p(zₙ) / p'(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. Фрактали в природі, науці та іграх