Фракталы Жюлиа на JavaScript.
Основы
Термин «фрактал» не имеет единого общепринятого чёткого определения. Как правило, фракталами называют фигуру, которая имеет свойства самоподобности или нетривиальности, или имеет дробную размерность. Существует большое количество таких фигур как искуственного (снежинка Коха, салфетка Серпинского), так и естественного (растения, животные) происхождения. С другой стороны, фракталами могут быть алгебраические множества, имеющие эти же свойства. Типичными их представителями являются множества Мандельброта или Жюлиа, о которых мы хотим поговорить более детально.
Да, вам не послышалось, фракталы действительно имеют дробную размерность. Это значит, что они не двумерные, не трёхмерные, а, скажем, 2.5-мерные или 1.31-мерные. Такое тяжело представить, по крайней мере, пока как следует не вникнуть во фрактальную математику. Со временем начинаешь удивляться другому: как это так случилось, что наш мир является ровно трёхмерным, не близко к тройке, а ровно три и это при том, что много природных объектов имеют фрактальную структуру. Но об этом позже, пока что основы. Проще сказать, что фракталы являются самоподобными. То есть каждая часть фрактала при увеличении похожа на весь фрактал в целом. И так до бесконечности - сколько угодно фрактал можно увеличивать, он никогда не превратится в цельную фигуру.
Сосредоточимся на алгебраических фракталах, которые являются образами определённых математических множеств.
Математически задаётся правило, как построить это множество, а если его
представить как точки некоего холста, то эти точки создадут определённый рисунок, который и
будет алгебраическим фракталом. Собственно так, чаще всего, фракталы и строят: берут
за основу определённое правило и дальше точка за точкой формируют множество и его изображение.
Французский математик Гастон Жюлиа предложил следующее правило построения таких множеств:
- координаты точки на холсте (х и у) преобразуются в комплексное число z = x + iy;
- для каждой из точек выполняется преобразование z = z2 + C,
где С - комплексная константа, одинаковая для всех точек;
- после большого количества преобразований число z или начинает неограниченно возрастать (расходиться),
или циклически изменяться в ограниченных рамках (сходиться). Если в данной точке наблюдается расхождение,
то такую точку ставят белым цветом, если схождение - чёрным.
- процесс повторяют для всех точек холста, вследствие чего создаётся определённый образ.
В качестве примера, нажмите на пустой квадрат в этом параграфе, - это запустит построение на нём
фрактала Жюлиа с С = (0.315, 0.025).
Если говорить более широко, то преобразование для построения фракталов не ограничивается приведённым выше выражением z = z2 + C. На самом деле, фрактал можно построить практически из произвольного преобразования z = F(z) и разные виды функций F(z) будут создавать разные формы для образов фракталов. Конечно, существуют такие функции, у которых ни одна из точек не будет чёрного цвета, но такие функции в данный момент нас не интересуют. Чаще всего, функции F(z) являются полиномами, но формальных ограничений нет. На этом рисунке использована функция пятой степени F(z) = z5 + C с С = (0.61448, 0.53110). Нажмите на рамку рисунка для старта его построения. Возможно, на это понадобится некоторое время, дождитесь завершения рисования.
Реализация
Рассмотрим, как практически создаются рисунки фракталов.
Во-первых, следует обратить внимание, что основное фрактальное преобразование z = z2 + C
происходит в комплексных числах. Не погружаясь в глубины математики, отметим,
что комплексное число - это на самом деле два обычных числа (действительная и мнимая части),
а операция умножения комплексных чисел (или возведения в квадрат) совсем не похожа
на умножение обычных чисел. Если отказаться от использования специализированных
библиотек, то алгоритм преобразования будет выглядеть следующим образом.
Вводятся по две переменные для каждого комплексного числа (z и С):
var reC,imC,reZ,imZ;
Далее им присваиваются значения (подробнее об этом дальше), и запускается цикл преобразования.
Один шаг (итерация) такого цикла имеет вид:
let x = reZ * reZ - imZ * imZ + reC;
Именно так выглядят операции умножения и сложения комплексных чисел.
let y = 2 * reZ * imZ + imC;
reZ = x;
imZ = y;
Далее нам нужно исследовать сходимость преобразования. То есть повторять шаг за шагом приведённый выше код и наблюдать за поведением z (reZ,imZ). С точки зрения программирования, сходимость последовательности нельзя исследовать бесконечно. На это просто не хватит времени - бесконечное количество проверок потребует бесконечно длящегося процесса. А с точки зрения математики, точного совпадения (математического равенства чисел) можно достичь только на бесконечности. На любом промежуточном шаге числа равны лишь приблизительно, с определённым отклонением одно от другого. На практике придётся ограничиться некоторым количеством шагов преобразования. Чем больше это количество, тем дольше будет рисоваться фрактал, но, в то же время, тем более детальным он получится. Как правило, количество шагов выбирают от 100 до 1000 в зависимости от желаемого эффекта. Мы установим ограничение на уровне 255 повторений. Почему выбрано именно такое число, будет понятно далее.
Если последовательность является расходящейся, то это расхождение может проявиться значительно раньше, чем
за 255 шагов. Соответственно, цикл повторения должен иметь два условия: или выполнено
граничное количество шагов, или уже можно зафиксировать расхождение последовательности.
С точки зрения математики, последовательность становится расходящейся, если модуль числа z начинает
превышать корень из двух. Считать корни - это достаточно трудоёмкое дело, поэтому
возьмём более простое, хотя и менее точное условие - сумма модулей reZ и imZ превышает тройку.
Возможно, менее точное условие приведёт к тому, что будет выполнен лишний шаг цикла,
но это всё равно быстрее, чем рассчитывать корни на каждой итерации. Таким образом, код приобретает
следующий вид:
var n = 0;
Поскольку цикл имеет два условия завершения, после выхода из цикла следует дополнительно проверить,
какое из условий сработало. Если переменная n достигла 255, то считаем преобразование сходящимся и
ставим чёрную точку, иначе - белую.
while(n < 255 && Math.abs(reZ) + Math.abs(imZ) < 3) {
n++;
let x = reZ * reZ - imZ * imZ + reC;
let y = 2 * reZ * imZ + imC;
reZ = x;
imZ = y;
}
Теперь наша задача просканировать все точки рисунка и для каждой из этих точек проверить
сходимость преобразования. С этой целью добавляем на страницу элемент <canvas id="fig">.
Далее определяем его ширину и высоту и начинаем сканировать:
let W = fig.width;
Циклы по i и j сканируют холст по ширине (W) и высоте (H). В каждой точке определяется
reZ = (-1.2+i*2.4/W); по принципу масштабирования: в границах рисунка reZ изменяется от
-1.2 до +1.2. Именно в этих границах можно ожидать сходимость фрактального преобразования. Это опять таки
приблизительные значения, математика даёт более точные числа, но в нашем случае этого вполне достаточно.
Аналогично выполняется сканирование imZ. Цикл в коде приведён сокращённо, поскольку выше уже была размещена полная версия.
let H = fig.height;
let dc = fig.getContext("2d");
dc.clearRect(0,0,W,H);
dc.beginPath();
for(let j=0; j
imZ = (-1.2+j*2.4/H);
n=0;
while(n<255 && ...
if(n<255){ dc.fillStyle = "black"; }
else{ dc.fillStyle = "white"; }
dc.fillRect(i,j,1,1);
}
dc.closePath();dc.stroke();
Добавим немного цвета и присмотримся поближе
Монохромные фракталы сами по себе являются достаточно интересными, но в цветном исполнении они виглядят ещё красивее. Если говорить более-менее точно, то цвет во фракталах отвечает не за сами точки фрактала, а за точки, которые ему не принадлежат, но являются очень близкими к фракталу. Другими словами, фрактальное преобразование является расходящимся, однако, расхождение начинается не сразу, а через достаточно существенное количество выполненных шагов. Мы ограничили количество шагов величиной 255 как раз для того, чтоб удобнее было устанавливать цвета приграничных точек. Если говорить об оттенках серого, то именно число n после завершения цикла будет отвечать за яркость точки. Точнее, за инверсную яркость: если n=255, то точка должна быть полностью чёрной, а если n=0, - то полностью белой. Другие значения можно преобразовывать в оттенки серого и наносить как цветную информацию на рисунок.
Но серый цвет создаёт эффект плавного перехода и тем самым "смывает" красивые
границы фрактала. Желательно наоборот, создать эффект существенной смены цвета в
зависимости от изменений числа n. Для построения фрактала из предыдущего блока
был использован следующий алгоритм для формирования цвета:
var r, g, b;
Суть алгоритма в том, что для n в диапазоне до 100 выделяется красный компонент (r),
от 100 до 200 - зеленый (g), иначе - голубой (b). Это улучшает контраст между точками
и придаёт цветовой вариантности.
if(n<20){ r = g = b = 255-n; }
else if(n < 100) { r = 255-n+20; g = b = 255-n-5; }
else if(n < 200) { g = 255-n+20; r = b = 255-n-5; }
else { b = 255-n-3; g = r = 255; }
dc.fillStyle = "rgb("+r+","+g+","+b+")";
dc.fillRect(i,j,1,1);
И, наконец, несколько слов о практическом способе исследования самоподобности фракталов.
Для этого их нужно увеличить и посмотреть на какую-нибудь часть.
Но если просто увеличить рисунок, то мы не достигнем желаемого эффекта, поскольку
информацию о точках между точками нельзя получить из имеющихся на рисунке точек.
Необходимо заново проводить перерасчёт и проверять сходимость преобразования в каждой
из точек, которые нас интересуют.
Проще всего изменить правило масштабирования рисунка. Вместо приведённого
выше правила создадим, для примера, следующее:
reZ = (-0.05+i*0.3/W);
Это будет означать, что область фрактала вместо диапазона (-1.2; 1.2) преобразуется
в область с размером 0.3, при этом рисунок занимает всю область холста, то есть
каждая из его точек содержит настоящую фрактальную информацию. Дополнительно в правилах
сделано разное смещение: по горизонтали на величину -0.05, а по вертикали на -0.1.
Это позволяет выбрать произвольную часть фрактала для изображения и исследования.
Нажмите на ближний рисунок: будет построен увеличенный фрагмент фрактала.
imZ = (-0.1+j*0.3/H);
Полный код примера
Данный код можно скопировать в новый HTML-файл и открыть в браузере.
Должны увидеть приблизительно то же самое, что и на рисунке в данном разделе
(нажмите на рисунок для запуска рассчёта фрактала)
<canvas id="fig" width="200" height="200" ></canvas>
<script>
var reC=0.315,
imC=0.027,
W=fig.width,
H=fig.height,
reZ,imZ,n,x,y;
let dc=fig.getContext("2d");
dc.clearRect(0,0,W,H);
dc.beginPath();
for(let j=0;j
imZ = (-1.2+j*2.4/H);
n=0;
while(n<255 && Math.abs(reZ)+ Math.abs(imZ) < 3) {
x = reZ*reZ-imZ*imZ+reC;
y = 2*reZ*imZ+imC;
reZ = x; imZ = y; n++;
}
var r,g,b;
if(n<20){r=g=b=255-n;}
else if(n<100){r=255-n+20;g=b=255-n-5;}
else if(n<200){g=255-n+20;r=b=255-n-5;}
else{b=255-n-3;g=r=255;}
dc.fillStyle = "rgb("+r+","+g+","+b+")";
dc.fillRect(i,j,1,1);
}
dc.closePath();dc.stroke();
</script>
Оставить комментарий
Имя (отображается)E-mail (не отображается)