拡大しても次々とフラクタルな図形が描画される「マンデルブロ集合」をご存じでしょうか。簡単な漸化式を元に描画する図形です。筆者は学生時代に見てなんて不思議な図形なのだろうと魅了されました。今回は、マンデルブロ集合をCanvas APIを使ってブラウザに表示してみましょう。
マンデルブロ集合とは?
そもそも、「マンデルブロ集合(英語: Mandelbrot set)」というのは、数学者のブノワ・マンデルブロ氏の名前にちなんで付けられたものです。氏は図形の部分と全体が自己相似(再帰)になっている「フラクタル」の概念を提唱したことでも有名です。2010年に85歳で亡くなるまで精力的に研究員として多くの論文を発表し情報理論の発展に寄与しました。
マンデルブロ集合は、フラクタル図形であり、どこを切り取って拡大しても、次々と複雑な図形が描画されます。基本的にどこを切り取っても美しく魅惑的な図形が表示されますが、試行錯誤していくことで、自分が好きな図形を表示することができます。この性質から多くの熱烈なマンデルブロ集合の愛好家が存在します。YouTubeなどに投稿している方もいて数学好きならずっと見てられる動画が多く存在します。
次の画像は、今回作るプログラムを利用して筆者が適当に切り取ったものですが、いろいろな図形が描かれるのを楽しむことができます。
マンデルブロ集合の仕組みは?
マンデルブロ集合は、複素数の空間上での関数の反復適用によって定義される複素数力学系の研究に関連しています。そう聞くと、とても難しそうな印象がありますが、具体的には、次のような簡単な漸化式を元にして描画する図形となっています。
この式において、複素数cが鍵となるのですが、繰り返しこの漸化式を実行したとしても、無限に発散しないような複素数c全体が作る集合が「マンデルブロ集合」なのです。それで、この漸化式における複素数cが発散する速さを利用して図形を描画します。なお、発散する速さに応じて色を割り当てることで、カラフルな画像を作成できます。
とにかく作ってみよう
それでは、とにかくマンデルブロ集合の図形を描画するプログラムを作ってみましょう。以下のプログラムが基本的なマンデルブロ集合を描画するプログラムです。
「index.html」という名前で保存しましょう。
<!DOCTYPE html><html><head><meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>マンデルブロ集合</title>
</head>
<body><canvas id="cnavas" width="800" height="800"></canvas></body>
<script>
// キャンバスと描画用のコンテキストを取得 --- (*1)
const canvas = document.getElementById('cnavas');
const ctx = canvas.getContext('2d');
// 描画の為のパラメータを指定 --- (*2)
const [width, height] = [canvas.width, canvas.height];
const maxIter = 100; // 最大繰返し回数 --- (*3a)
// マンデルブロ集合の切り取る範囲を指定 --- (*3b)
let xMin = -2.0, xMax = 2.0, yMin = -2.0, yMax = 2.0;
// マンデルブロの計算 --- (*4)
function mandelbrot(c) {
let z = { x: 0, y: 0 };
let n = 0;
while (n < maxIter && (z.x * z.x + z.y * z.y) <= 4) {
const xTemp = z.x * z.x - z.y * z.y + c.x;
z.y = 2 * z.x * z.y + c.y;
z.x = xTemp;
n++;
}
return n;
}
// 実際にキャンバスに描画を行う関数 --- (*5)
function draw() {
for (let px = 0; px < width; px++) { // x座標の繰り返し
for (let py = 0; py < height; py++) { // y座標の繰り返し
const x0 = xMin + (xMax - xMin) * px / width;
const y0 = yMin + (yMax - yMin) * py / height;
const c = { x: x0, y: y0 }; // cを指定
const m = mandelbrot(c); // マンデルブロ集合の計算
// 色を計算 --- (*6)
const per = m / maxIter * 100
ctx.fillStyle = (m === maxIter) ? 'white' : `hsl(0, 100%, ${per}%)`;
ctx.fillRect(px, py, 1, 1); // 描画
}
}
}
draw();
</script>
</html>
保存したファイルをブラウザにドラッグ&ドロップしてみましょう。すると、次のような画像が描画されます。わずか45行のHTML/JavaScriptでこれほど複雑な図形が描画できるというのは、とても面白いと思いませんか。
プログラムを確認してみましょう。(*1)では描画先のキャンバスと、Canvas APIを使うためのコンテキストを取得します。(*2)以降の部分では描画の為のパラメータを指定します。特に描画に大きな影響を与えるが、(*3a)の最大繰り返し回数と、(*3b)の切り取り範囲です。(*3a)の最大繰り返し回数を1000回と大きくすると図形がより複雑になります。そして、(*3b)の切り取り範囲を調整すると図形が大きく変化します。
(*4)の関数mandelbrotでは、マンデルブロ集合の計算を行います。変数nを使って発散する回数を数えます。繰り返し回数が最大回数を表す変数maxIterに達するか、発散してしまった場合には、その時点で計算を中止します。
(*5)では左上から右下に向けて一列ずつ図形を描画します。(*6)では色の計算を行います。色の指定では、一般的には光りの三原色であるRGB(赤緑青)を指定することが多いのですが、ここでは、HSL(色相/彩度/明度)を利用して色を指定しています。色相を0にしているため、赤色をベースに明度を変更して描画を行います。
表示範囲を変更してみよう
プログラムを理解するには、パラメータを変更するのがオススメです。切り取り範囲プログラムの(*3b)を下記のように変更してみましょう。
// マンデルブロ集合の切り取る範囲を指定 --- (*3b)
let xMin = 0.11, xMax = 0.17, yMin = -0.67, yMax = -0.61;
プログラムを書き換えて、ブラウザの更新ボタンを押してみましょう。すると、次のように表示されます。
他にも、(*3a)の最大繰返し回数を変更してみると、詳細な図形が描画されるようになります。いろいろ書き換えて図形を楽しんでみてください。
画面クリックで拡大範囲を指定できるように改良しよう
それでも、何度も数値を書き換えるのは面倒です。画面上をクリックすることで、そこを中心として拡大するようにプログラムを変更してみましょう。
以下のプログラムを、プログラムの末尾(</script>の直前)に追加しましょう。これは、マウスボタンを押した時に発生するマウスイベント(mousedown)に反応して、切り取り位置の移動と拡大率(0.5倍)を変更する処理を加えて再描画を行うものです。
// マウスイベントで拡大率を指定する
canvas.addEventListener('mousedown', function (event) {
// Canvas上のクリックした位置を計算
const rect = canvas.getBoundingClientRect();
const mouseX = event.clientX - rect.left;
const mouseY = event.clientY - rect.top;
// クリックした位置を中心に拡大
const x0 = xMin + (xMax - xMin) * mouseX / width;
const y0 = yMin + (yMax - yMin) * mouseY / height;
const zoomFactor = 0.5;
const newWidth = (xMax - xMin) * zoomFactor;
const newHeight = (yMax - yMin) * zoomFactor;
xMin = x0 - newWidth / 2;
xMax = x0 + newWidth / 2;
yMin = y0 - newHeight / 2;
yMax = y0 + newHeight / 2;
draw();
});
色鮮やかになるように改良してみよう
上記で紹介したプログラムでは、マンデルブロ集合の発散する速さを、色空間HSLの明度に指定していました。しかし、これを色相に反映させると、いろいろな色が登場する面白い図形になります。