▍AI Dev Lab
FluxBloom

FluxBloom ができるまで — Perlin と Voronoi で絵を生む

ジェネレーティブアートの開発ログ。 Perlin ノイズ・Voronoi 分割・L-System の枝分かれ・リサジュー曲線・曼荼羅など 6 種類のアルゴリズムを素の JavaScript で実装し、 種の値だけで同じ絵を再現できるようにするまでの記録です。

FluxBloom は、Perlin ノイズ・Voronoi 分割・L-System・リサジュー曲線・等高線・曼荼羅の 6 種類のアルゴリズムで抽象画を生成するエンジンです。全部素の JavaScript と Canvas で書いていて、種(シード)の値だけで同じ絵を完全に再現できます。

#🎯 「ランダム」と「再現できる」を両立させる

ジェネレーティブアートで最初に決めるのは、乱数をどう扱うかです。

Math.random() を使うと、気に入った絵が出ても二度と同じものを出せません。保存し忘れたら終わりで、URL で人に見せることもできない。

そこで全体をシード付きの疑似乱数で組みました。

ts
// 線形合同法。 同じ seed からは 必ず同じ列が出る

s = (s * 1103515245 + 12345) & 0x7fffffff

Math.random() は一切呼びません。粒子の初期位置も、色の選択も、揺らぎも、全部このシードから引きます。結果として URL にシードを載せるだけで、誰の環境でも同じ絵が出ます。

#🌊 Perlin ノイズを自前で書く

6 アルゴリズムのうち 3 つ(フロー場・等高線・曼荼羅)が Perlin ノイズを使います。

Perlin ノイズは「なめらかにつながる乱数」です。ふつうの乱数は隣り合う点が無関係ですが、Perlin は近い座標なら近い値を返します。だから自然な流れや地形に見えるわけです。

実装は 3 つの部品でできています。

ts
// 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 周分展開しています。

ts
this.p = new Uint8Array(512)
for (let i = 0; i < 512; i++) this.p[i] = perm[i & 255]

こうしておくと p[X + p[Y]] のような添字アクセスで範囲外を気にせず済みます。毎回 & 255 を書かなくてよくなる、実装上の定石です。

#🎨 フロー場 ── ノイズを「向き」として読む

いちばん気に入っているのがフロー場です。やっていることは単純で、

  1. 画面にランダムな点を撒く
  2. その点の位置の Perlin ノイズを角度として読む
  3. その方向に少し進む。これを繰り返して線を引く
ts
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 システム)です。植物の成長を、文字列の置換規則としてモデル化する手法で、実装は再帰で書きました。

ts
function branch(x, y, len, ang, depth, colorIdx) {
  // 枝を 1 本描いて、 先端から 2 本に分岐して再帰

}

分岐角度と長さの減衰率を変えるだけで、シダにも針葉樹にも珊瑚にもなります。パラメータ 2 つで植物の種類が変わるのが L-System の面白さです。

#🔷 Voronoi ── 最近傍で塗り分ける

Voronoi は「各ピクセルが、どの種に一番近いか」で色を決める分割です。定義がそのまま実装になります。総当たりなので種の数を増やすと重くなりますが、細胞や結晶の見た目はこれでしか出せません。

#😓 苦労したところ

6 つのアルゴリズムに同じパラメータを効かせる。 密度・複雑さ・スケール・揺らぎ・層・シードの 6 つを共通のつまみにしましたが、意味はアルゴリズムごとに違います。フロー場の「密度」は粒子数、Voronoi の「密度」は種の数。同じ名前で違う実装に効かせる設計にしたので、どのアルゴリズムでも同じ操作感で触れます。

ts
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 をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。

[ ./related_logs ]

関連する開発ログ

all logs →
ShadowTwin

立体パズルの別解を作る:同じ影と最少の積み木

ShadowTwinの開発記録。3方向の図から別々の最少配置を2つ探す仕組み、5問の独立全探索、スマホの横並び比較、保存・再編集と入力検査を解説します。実照明の計算や能力診断ではありません。

read log →
SlitCard

スリットアニメーションを作る:4コマの合成と透明原稿の設計

SlitCardの開発記録。4コマの帯を交互に合成し、すき間と黒帯で別のコマを見せる仕組みを解説。同縮尺の台紙・透明シートSVG、50mm確認線、手動プレビュー・初期非表示、再編集と実出力検査を整理します。

read log →
WindowLight

日当たりシミュレーションを作る:窓の光を床へ投影する幾何モデル

WindowLightの開発記録。太陽位置近似から窓の4隅を床へ投影し、凸多角形を部屋で切り取る設計を解説。国立天文台の16時刻との限定照合、大気差・低高度・実際の採光との違い、SVGと設定の再編集を整理します。

read log →
GridLights

電源構成の比較を作る:割合と実量を100個の電球で読み解く

電源構成比較GridLightsの開発記録。12か国の年次発電データを最大剰余法で100個の電球に変換し、割合とTWhを比較する設計を解説。欠損を0補完しない7分類、予想と実値、出典付きSVGと実データ検証を整理します。

read log →
SoundLoom

音のお絵かきを作る:実WAVから絵を測り直す設計

音のお絵かきの開発記録。描画を正弦波のPCMとWAVへ変換し、保存音声の周波数をGoertzel法で測って比較する仕組みを解説。音量・測定窓・量子化の限界と、編集・離脱時の古い結果を抑止する検証を整理します。

read log →
MazeLoom

宝探し迷路を作る:寄り道まで含めた最短ルートの探索

宝探し迷路の開発記録。通路を木として生成し、現在地と3つの宝の取得状態を組み合わせて最短ルートを探す仕組みを解説。問題コードの版固定、画面と解答付きPDFの一致、手数と印刷の検証を整理します。

read log →