FluxBloom は、Perlin ノイズ・Voronoi 分割・L-System・リサジュー曲線・等高線・曼荼羅の 6 種類のアルゴリズムで抽象画を生成するエンジンです。全部素の JavaScript と Canvas で書いていて、種(シード)の値だけで同じ絵を完全に再現できます。
#🎯 「ランダム」と「再現できる」を両立させる
ジェネレーティブアートで最初に決めるのは、乱数をどう扱うかです。
Math.random() を使うと、気に入った絵が出ても二度と同じものを出せません。保存し忘れたら終わりで、URL で人に見せることもできない。
そこで全体をシード付きの疑似乱数で組みました。
// 線形合同法。 同じ seed からは 必ず同じ列が出る
s = (s * 1103515245 + 12345) & 0x7fffffffMath.random() は一切呼びません。粒子の初期位置も、色の選択も、揺らぎも、全部このシードから引きます。結果として URL にシードを載せるだけで、誰の環境でも同じ絵が出ます。
#🌊 Perlin ノイズを自前で書く
6 アルゴリズムのうち 3 つ(フロー場・等高線・曼荼羅)が Perlin ノイズを使います。
Perlin ノイズは「なめらかにつながる乱数」です。ふつうの乱数は隣り合う点が無関係ですが、Perlin は近い座標なら近い値を返します。だから自然な流れや地形に見えるわけです。
実装は 3 つの部品でできています。
// 1. 補間カーブ (5次エルミート) — 6t⁵ - 15t⁴ + 10t³
private fade(t: number): number { return t * t * t * (t * (t * 6 - 15) + 10) }
// 2. 勾配ベクトル — ハッシュ値から 8 方向を選ぶ
private grad(hash: number, x: number, y: number): number {
const h = hash & 7
const u = h < 4 ? x : y
const v = h < 4 ? y : x
return ((h & 1) ? -u : u) + ((h & 2) ? -2 * v : 2 * v)
}fade の 5 次式は Ken Perlin が 2002 年に改良したもので、これを線形補間や 3 次式にすると格子が目に見えてしまいます。なめらかさの正体はこの一行です。
順列表は Fisher-Yates でシャッフルしてから、512 要素に 2 周分展開しています。
this.p = new Uint8Array(512)
for (let i = 0; i < 512; i++) this.p[i] = perm[i & 255]こうしておくと p[X + p[Y]] のような添字アクセスで範囲外を気にせず済みます。毎回 & 255 を書かなくてよくなる、実装上の定石です。
#🎨 フロー場 ── ノイズを「向き」として読む
いちばん気に入っているのがフロー場です。やっていることは単純で、
- 画面にランダムな点を撒く
- その点の位置の Perlin ノイズを角度として読む
- その方向に少し進む。これを繰り返して線を引く
const ang = perlin.noise(x * noiseScale, y * noiseScale) * Math.PI * 4
x += Math.cos(ang) * stepLen
y += Math.sin(ang) * stepLenノイズの値を「高さ」ではなく「向き」として解釈するだけで、粒子が見えない流れに沿って泳ぎはじめます。数百本を薄い不透明度(0.06〜0.2)で重ねると、筆致のような密度差が生まれます。
不透明度を下げて本数で稼ぐ、というのがこの手の絵の基本でした。1 本 1 本を濃く描くと、ただの線の束にしかなりません。
#🌿 L-System ── 文字列の書き換えで木を作る
樹形は L-System(Lindenmayer システム)です。植物の成長を、文字列の置換規則としてモデル化する手法で、実装は再帰で書きました。
function branch(x, y, len, ang, depth, colorIdx) {
// 枝を 1 本描いて、 先端から 2 本に分岐して再帰
}分岐角度と長さの減衰率を変えるだけで、シダにも針葉樹にも珊瑚にもなります。パラメータ 2 つで植物の種類が変わるのが L-System の面白さです。
#🔷 Voronoi ── 最近傍で塗り分ける
Voronoi は「各ピクセルが、どの種に一番近いか」で色を決める分割です。定義がそのまま実装になります。総当たりなので種の数を増やすと重くなりますが、細胞や結晶の見た目はこれでしか出せません。
#😓 苦労したところ
6 つのアルゴリズムに同じパラメータを効かせる。 密度・複雑さ・スケール・揺らぎ・層・シードの 6 つを共通のつまみにしましたが、意味はアルゴリズムごとに違います。フロー場の「密度」は粒子数、Voronoi の「密度」は種の数。同じ名前で違う実装に効かせる設計にしたので、どのアルゴリズムでも同じ操作感で触れます。
const particleCount = Math.floor(80 + params.density * 520)
const stepCount = Math.floor(80 + params.complexity * 220)こういう「0〜1 のつまみを実際の値域に写す」変換を各アルゴリズムに持たせる形です。
「たまたま良い絵」を捨てないこと。 生成系は 9 割がハズレで、たまに良いものが出ます。だからこそシードの再現性が要ります。良い絵が出た瞬間にシードが URL に入っている、という状態を最初に作っておいたのが正解でした。
配色を作りすぎない。 色を自由にすると、たいてい濁ります。6 パレットに絞り、各パレットは背景色 + 数色の組で固定しました。選べる範囲を狭めたほうが、平均的な出来が上がります。
#🔭 今後の拡張
- 反応拡散系(Gray-Scott)の追加 — 有機的な模様の定番
- アニメーション書き出し(現在は静止画の PNG のみ)
- 高解像度書き出し(印刷用)
- 気に入った絵をシード付きでギャラリーに残す
#💡 このサービスから言えること
生成系のサービスでは、再現性が機能そのものです。 「もう一度同じものが出せる」がないと、良い出力に価値が生まれません。シード付き乱数は実装としては地味ですが、体験の土台でした。
そしてもうひとつ、古典的なアルゴリズムは今でも十分に強いということ。Perlin ノイズは 1985 年、L-System は 1968 年、Voronoi にいたっては 1908 年です。数十行で書けて、依存もゼロで、それでも見て面白いものが出ます。
[ ./next_action ]
読んだら、 FluxBloom を実際に動かす。
この開発ログは FluxBloom をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。