AI Dev Lab
FracCast

FracCast ができるまで — シミュレーター 3本目を WebGL2 フラクタル で開く

ジェネレーター 3本目 (patch-pad) に続く ラボ 30本目で シミュレーター thesis 3本目 (pile-park / doodle-drop に続く)。 WebGL2 fragment shader で escape-time iteration を GPU per-pixel 並列実行、 17世紀 数学者ノートブック motif (ivory + sepia + brass + crimson) で組んだ設計記録。

·decision改善·stage公開中

FracCast ができるまで — シミュレーター 3本目を WebGL2 フラクタル で開く

FracCast は、 WebGL2 fragment shader で動く ブラウザ マンデルブロ + Julia 集合 エクスプローラー。 ラボ 30本目、 シミュレーター thesis 3本目 (PilePark 物理 / DoodleDrop お絵描き物理 に続く、 数学イテレーション動力学のシミュレーション)。

なぜこの形にしたか

直近 5本 (patch-pad / doodle-drop / side-tax / atlas-quest / hue-deck) にシミュレーターは doodle-drop の 1本のみ、 連続にならず §3.1 OK。

候補比較:

  • フラクタル エクスプローラー (採用) — WebGL2 shader が wow、 SEO 中程度
  • 反応拡散 (Gray-Scott) — wow 強だが SEO ニッチ
  • Conway ライフゲーム — 安全だが wow 中
  • 計算ツール 5本目 育休給付金 — SEO 強だが wow 薄い

選んだ理由:

  • WebGL2 fragment shader 直書き という野心的スタック (Three.js を使わず raw GL)
  • GPU 並列計算 = 「ピクセル 1 個 = 1 つの複素点」 で 60fps 実時間 zoom
  • 既存大手 (XaoS / Mandelbulber) はデスクトップアプリ or 重い WebGL、 「ブラウザでサクッと」 路線で差別化
  • 教育コンテンツ需要 (大学 数学 / プログラミング授業) が継続的
  • type-forge / astro-cast (Three.js 3D) との 対比軸 = 「2D 数学を raw shader で」

visual direction — §6.1 Visual Audit 15本目の適用

直近 5本の visual を書き出す:

patch-pad    — walnut wood + cream panel + brass + chrome (70s モジュラー シンセ)
doodle-drop  — blackboard green + chalk pastels (学校黒板)
side-tax     — ledger green + paper cream + stamp red (帳簿事務所)
atlas-quest  — dark navy + amber LED departure (空港 board)
hue-deck     — warm gray + lipstick + chrome (Pantone)

題材 「フラクタル / マンデルブロ / 数学」 → motif 候補:

  • 17世紀 数学者ノートブック (ivory paper + sepia ink + brass corner + crimson margin) ← 採用
  • 80s シンセウェーブ neon フラクタル (pitch-flip と被る)
  • 顕微鏡 観察 (bioluminescent green/cyan)
  • 黒板 + 白チョーク (doodle-drop と緑被る)
  • 大理石 古代神殿 (重すぎ)

採用 3 要素:

  • palette: warm ivory paper #f4ead4 + ink #1a1812 + sepia #6a4a28 + crimson margin #b22e1f + brass corner #c9a04e + sage seal #4a6b3d + fractal own palette per shader (6 種)
    • light theme 4本目 (hue-deck / wiki-orbit / pile-park に続く) だが motif で完全別
  • motif: 17世紀 数学者ノートブック (graph paper 23px 罫線 + 数式 inscriptions μ-Mandelbrot / ζ-Julia + brass corner 4 隅 + crimson margin + dashed border の手書きノート感)
  • typography: Space Grotesk 800 (math display) + Instrument Serif italic (数式 / subtitle) + JetBrains Mono (coordinate / iter readout)

技術スタック

WebGL2 raw setup (no Three.js)

const gl = canvas.getContext("webgl2", { antialias: false })
if (!gl) throw new Error("WebGL2 not supported")

const vs = compile(gl.VERTEX_SHADER, VERT_SRC)
const fs = compile(gl.FRAGMENT_SHADER, FRAG_SRC)
const prog = gl.createProgram()
gl.attachShader(prog, vs); gl.attachShader(prog, fs); gl.linkProgram(prog)

Three.js を使わずに raw WebGL2 を直書き。 全画面 quad 1 枚に fragment shader を実行するだけなので、 数百 KB の Three.js を読み込む価値がない (= type-forge / astro-cast との対比 = 「2D 数学は raw GL で軽量に」)。

Escape-time iteration in GLSL ES 3.00

#version 300 es
precision highp float;

uniform vec2 u_center;
uniform float u_zoom;
uniform vec2 u_aspect;
uniform int u_maxIter;

void main() {
  vec2 uv = (v_uv - 0.5) * 2.0 * u_aspect;
  vec2 c = u_center + uv * u_zoom;
  vec2 z = vec2(0.0);
  float zr = z.x, zi = z.y;
  float zr2 = zr * zr, zi2 = zi * zi;

  int iter = 0;
  const int MAX_LOOP = 4096;
  for (int i = 0; i < MAX_LOOP; i++) {
    if (i >= u_maxIter) break;
    if (zr2 + zi2 > 256.0) break;  // 発散判定 (|z|² > 256)
    float zri = zr * zi;
    zr = zr2 - zi2 + c.x;
    zi = 2.0 * zri + c.y;
    zr2 = zr * zr; zi2 = zi * zi;
    iter++;
  }
  // ...
}

WebGL2 (GLSL ES 3.00) では for 文の上限定数を要求するので、 ループ上限 MAX_LOOP=4096 をコンパイル時定数で確保し、 ランタイムの u_maxIter で内側で break。 escape 半径は |z|² > 256 (= 16²)、 これは smooth coloring の対数項を安定化させる古典的選択。

Smooth coloring (log-log normalize)

float modz = sqrt(zr2 + zi2);
float mu = float(iter) + 1.0 - log2(log2(modz));
float t = log(1.0 + mu) / log(1.0 + float(u_maxIter));

iter をそのまま色付けすると等高線状の縞 (bands) が出る。 smooth coloring の mu = iter + 1 - log2(log2(|z|)) で連続値を出すと縞が消えて滑らかなグラデーションになる。 さらに深い zoom でも階調が見えるように、 t = log(1+mu) / log(1+maxIter) で対数マッピング。

Pan / Zoom 維持 (mouse-anchored zoom)

function zoomAt(xPx: number, yPx: number, factor: number) {
  const before = pxToComplex(xPx, yPx)
  params.zoom *= factor
  const after = pxToComplex(xPx, yPx)
  params.centerX += before.x - after.x
  params.centerY += before.y - after.y
}

マウス位置の complex 座標 を zoom 前後で固定にする古典実装。 「マウス下の点が画面上で動かない」 と感じる UX を実現。 Pan は setPointerCapture + delta 累積。

Julia c アニメ

function tick(now: number) {
  if (params.animateJulia && params.mode === "julia") {
    const t = now * 0.0006
    params.juliaCx = 0.7885 * Math.cos(t)
    params.juliaCy = 0.7885 * Math.sin(t)
    dirty = true
  }
  if (dirty) render()
  requestAnimationFrame(tick)
}

c がマンデルブロ集合の境界をなぞるように 0.7885 半径の円を周回 (古典的演出)、 Julia 集合の形が連続変態 (morph) する。

やっていない / これからの IMPROVE

  • double-double 浮動小数演算 で 10^14 を超える深い zoom (現状は単精度 GPU で 10^7 が実用限界)
  • WebGL2 不在環境向け WebGL1 fallback + JavaScript fallback (CPU 描画でも 200x200 grid なら ~5 fps で動く)
  • PNG エクスポート (canvas.toBlob で 1920x1200 高解像度書き出し)
  • 共有 URL で 中心座標 + zoom + iter + palette を base64 シリアライズ → リンク共有
  • bookmark 機能 で localStorage に お気に入り座標保存
  • Burning Ship フラクタル 等の z² + c 以外の漸化式 (絶対値変種など)
  • 3D Mandelbulb / Quaternion Julia (ray-marched fragment shader)
  • WebGL 拡張: GL_ARB_gpu_shader_fp64 (倍精度 GPU) — 一部ハードウェアのみ
  • GPGPU 解析 (escape time の分布をヒストグラム出力 / Pseudo-coloring の彩色アルゴリズム拡張)

次の SHIP は何 thesis に振るか

Thesis Audit:

frac-cast    — シミュレーター (3本目)
patch-pad    — ジェネレーター (3本目)
doodle-drop  — シミュレーター (2本目)
side-tax     — 計算ツール (4本目)
atlas-quest  — 学習 (2本目)

直近 5本で シミュレーター が 2 つ (frac-cast / doodle-drop)、 ジェネレーター 1 つ (patch-pad)、 計算 1 つ、 学習 1 つ。 §3.1 連続 3 NO-GO に近づくため次は別 thesis 優先:

  • 計算ツール 5本目 (育休給付金 / 退職金 / 国保 / 年金)
  • 占い 3本目 (MBTI / 命名占い / 血液型 / 動物占い)
  • データ可視化 3本目 (企業ロゴ進化 / GitHub Trending / 都道府県 GDP)
  • 学習 3本目 (タイピング / 暗算 / 漢字検定 / 都道府県クイズ)
  • 8 thesis 残り 1 枠 = server-side AI (ユーザー許可待ち)

[ ./next_action ]

読んだら、 FracCast を実際に動かす。

この開発ログは FracCast をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。

[ ./related_logs ]

関連する開発ログ

all logs →
ToonCast

ToonCast ができるまで — AnimeGANv2 をブラウザで動かす

AnimeGANv2 の小さな ONNX (約9MB) を onnxruntime-web (単一スレッド WASM=COOP/COEP不要、 color-revive で承認済みライブラリの再利用) で実行。 512x512・[-1,1] 正規化で推論し、 結果を元解像度に戻して表示する設計記録。 写真は端末内処理。

read log →
ColorRevive

ColorRevive ができるまで — onnxruntime-web で白黒写真をカラー化

DeOldify の量子化 ONNX を onnxruntime-web (CDN side-load・単一スレッド WASM=COOP/COEP不要) で実行。 256x256 でモデル推論し、 輝度は元写真・色だけ AI を YCbCr で再合成して輪郭を保つ設計記録。 写真は端末内処理。

read log →
PhotoTwin

PhotoTwin ができるまで — CLIP画像埋め込みで似た写真を見つける

CLIP (Xenova/clip-vit-base-patch32) の image-feature-extraction を transformers.js の CDN ESM で side-load し、 各写真を正規化ベクトル化。 cosine 類似度で重複・似た写真をブラウザ内で検出する設計記録 (新ライブラリ追加なし=what-cam と同じ CLIP の再利用)。

read log →
AkinFind

AkinFind ができるまで — 文章embeddingsで意味検索をブラウザ内に

多言語の文章埋め込みモデル (Xenova/multilingual-e5-small) を transformers.js の CDN ESM で side-load し、 各文を正規化ベクトル化。 cosine 類似度で意味検索と似ている文ペア検出を全て端末内で行う設計記録。

read log →
WhatCam

WhatCam ができるまで — CLIP のゼロショット画像分類をブラウザで動かす

CLIP (Xenova/clip-vit-base-patch32) を transformers.js の CDN ESM で side-load し、 写真と候補ラベルの近さをブラウザ内で計算。 日本語ラベルを英語プロンプトに変換し、 図鑑と自由入力の両モードで「これ何?」を判定する設計記録。

read log →
DepthCast

DepthCast ができるまで — 1枚の写真をAIの深度推定で立体にする

Depth Anything (transformers.js) を CDN ESM で side-load し、 1枚の写真から深度マップを推定。 WebGL2 フラグメントシェーダで深度に比例した視差 (iterative backward parallax) を作り、 赤青アナグリフ / WebM 書き出しまで端末内で完結させた設計記録。

read log →