GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

チューリングパターン シミュレーション9選 — ライブラリなし、2物質の反応グリッドだけで

チューリングパターン シミュレーションとは、反応し合う2つの物質を格子に敷いて毎フレーム計算すると、点・縞・迷路・サンゴのかたちが勝手に浮かび上がる現象を指します。この9枚は Gray-Scott 近似を Canvas 2D だけで回しています — シードと供給率スライダーを動かすと模様が生まれ直します。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

並びは見た目の順ではありません。供給率 f が模様をどこへ押すかの順です。除去率が低いあいだは点が孤立して生き(01・04・05)、f を下げると点が線へ伸び(02・06)、その中間帯で線は折れ曲がって迷路になります(03・07)。除去率 k を上げると全体が丸い部屋へ縮み(08)、f が最も低い所では成長が fork するのをやめません(09)。累積面積で最も広く動いた盤は01 斑点(52.878パーセント)である一方、1フレームで最も広く塗り替えた瞬間最大面積は03 迷路(50.879パーセント)です。一度シードを決めてあとは計算に任せる描き方が好きなら、シードで模様を引き直す CSS背景パターン と fbmノイズで山を積む canvas地形 が隣にあります。

01斑点 — 反応が勝つ場所に点が浮かぶ

ヒーロー背景の生きた水玉模様、表紙イラストの種:一様な V 場に数点の U 種をまくと、反応が生き残る場所ごとに点が育ち、均等な間隔の並びに落ち着きます。

斑点 — 反応が勝つ場所に点が浮かぶ
新しいタブで開く

01 · 斑点 — 反応が勝つ場所に点が浮かぶ — 計測 累積面積 52.878% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

点はなぜ均等な間隔に並ぶのか

// 1セルの更新 — 5点ラプラシアンの拡散 + Gray-Scott の反応項
var lapU = u[yC + xL] + u[yC + xR] + u[yT + x] + u[yB + x] - 4 * uc;
var lapV = v[yC + xL] + v[yC + xR] + v[yT + x] + v[yB + x] - 4 * vc;
var uvv = uc * vc * vc;
var nu = uc + DU * lapU - uvv + F * (1 - uc);
var nv = vc + DV * lapV + uvv - (F + K) * vc;
u2[i] = nu < 0 ? 0 : (nu > 1 ? 1 : nu);   // 飽和クランプ — これがないと格子が発散する
v2[i] = nv < 0 ? 0 : (nv > 1 ? 1 : nv);

U が V と出会うと両者が結合して V をさらに作り(uvv 項)、V は (F+K)*v の速さで自ら消えます。育った点は周囲の U を吸い上げるので隣の点は飢えて死に、生き残った点同士は影響圏の境界で釣り合って同じ間隔に並ぶ — この盤は最初の描画前に1,500ステップを先行して回し、成熟した点並びから始まります(実測: 累積面積52.88パーセント、強度99.3、23/23フレーム)。

02縞 — パラメータひとつで点が線につながる

動物模様のコンセプトアート、縞模様ジェネレーターの種:同じ格子で供給率だけを下げると点が伸びて互いにつながり、縞になります。

// 種まき — シードが決める位相と振幅の正弦波3本
function seedField(sd) {
  var rng = mulberry32(sd >>> 0);
  u.fill(1); v.fill(0);
  var n = 3;
  for (var s = 0; s < n; s++) {
    var cy = (s + 0.5) * GH / n;
    var ph = rng() * 6.28;
    var amp = 1 + rng() * 2;
    var th = 2 + rng();
    for (var y = 0; y < GH; y++) {
      for (var x = 0; x < GW; x++) {
        var line = cy + Math.sin(x * 0.22 + ph) * amp;
        if (Math.abs(y - line) <= th) { v[y * GW + x] = 0.5; u[y * GW + x] = 0.25; }
      }
    }
  }
}
縞 — パラメータひとつで点が線につながる
新しいタブで開く

02 · 縞 — パラメータひとつで点が線につながる — 計測 累積面積 49.959% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

U が十分にあるうちは各点が自領を守りますが、供給率が下がると点は片側の縁から飢えてそちらへ伸び、隣とつながって縞になります。この盤でスライダーを左へ動かすと01の点並びがその場で縞へ変わります(実測: 累積面積49.96パーセント、強度102.3 — 9枚で最もくっきりした値、23/23フレーム)。

03迷路 — 縞が折れ曲がる中間地点

ゲームマップのテクスチャ、静的な背景パターン:点と縞のあいだのパラメータ帯では、線は折れて枝分かれしながら迷路を刻みます。

// 種まき — シードの乱数で散らす20個の小さな U の断片
function seedField(sd) {
  var rng = mulberry32(sd >>> 0);
  u.fill(1); v.fill(0);
  var n = 20;
  for (var s = 0; s < n; s++) {
    var cx = 2 + Math.floor(rng() * (GW - 6));
    var cy = 1 + Math.floor(rng() * (GH - 4));
    var w = 1 + Math.floor(rng() * 4);
    var h = 1 + Math.floor(rng() * 3);
    for (var y = cy; y < Math.min(GH, cy + h); y++) {
      for (var x = cx; x < Math.min(GW, cx + w); x++) {
        v[y * GW + x] = 0.5; u[y * GW + x] = 0.25;
      }
    }
  }
}
迷路 — 縞が折れ曲がる中間地点
新しいタブで開く

03 · 迷路 — 縞が折れ曲がる中間地点 — 計測 累積面積 52.86% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

点の体制と縞の体制のあいだの供給率では、線の成長端が閉じません — 隣の線に出会うまで走り続け、折れて、また枝分かれして、後ろに通路を残します(実測: 累積面積52.86パーセント — 01に僅差で次ぐ2位、瞬間最大面積50.879パーセントでは9枚最高、23/23フレーム)。

04サンゴ成長 — 種から外へ伸びる反応の前線

ローディング演出、有機的な成長イントロ:U を中央にひと固まり注ぐと、反応の前線がサンゴや苔のように外へ這います。

// 種まき — ノイズで縁をギザギザにした中央の玉
function seedField(sd) {
  var rng = mulberry32(sd >>> 0);
  u.fill(1); v.fill(0);
  var cx = GW / 2 + (rng() - 0.5) * 6, cy = GH / 2 + (rng() - 0.5) * 4;
  var rr = 5.5 + rng() * 2;
  for (var y = 0; y < GH; y++) {
    for (var x = 0; x < GW; x++) {
      var dx = x - cx, dy = (y - cy) * 1.15;
      var d = Math.sqrt(dx * dx + dy * dy);
      var wob = rr * (0.7 + 0.5 * rng());
      if (d < wob) { v[y * GW + x] = 0.5; u[y * GW + x] = 0.25; }
    }
  }
}
サンゴ成長 — 種から外へ伸びる反応の前線
新しいタブで開く

04 · サンゴ成長 — 種から外へ伸びる反応の前線 — 計測 累積面積 41.844% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

玉の外縁でしか U が生きられないため、前線は手つかずの U 場へ外向きに育つしかなく、後ろに残る V の航跡がサンゴや苔の本体に見えます(実測: 累積面積41.84パーセント、17/23フレーム — 成長が一定の速さで進むぶん、フレーム間の変化は前線の帯に集中します)。

05羊の毛 — 複数の種がぶつかり混ざる

テクスチャ背景、イラストの塗り模様:画面全体に散らした十四の種がそれぞれ育ってぶつかり、塊の入った質感に混ざります。

// 種まき — シードが決める座標に広く散らす十四の点
function seedField(sd) {
  var rng = mulberry32(sd >>> 0);
  u.fill(1); v.fill(0);
  for (var n = 0; n < 14; n++) {
    var cx = 5 + rng() * (GW - 10), cy = 4 + rng() * (GH - 8);
    var rr = 2 + rng() * 1.6;
    for (var y = Math.max(0, cy - rr | 0); y <= Math.min(GH - 1, cy + rr + 1); y++) {
      for (var x = Math.max(0, cx - rr | 0); x <= Math.min(GW - 1, cx + rr + 1); x++) {
        var dx = x - cx, dy = y - cy;
        if (dx * dx + dy * dy < rr * rr) { v[y * GW + x] = 0.5; u[y * GW + x] = 0.25; }
      }
    }
  }
}
羊の毛 — 複数の種がぶつかり混ざる
新しいタブで開く

05 · 羊の毛 — 複数の種がぶつかり混ざる — 計測 累積面積 50.932% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

種を多く散らすほど、育つ各 patch が隣と押し合って境界を作り、その境界の網が羊の毛や雲の塊のようなごつごつした質感として読めます(実測: 累積面積50.93パーセント、23/23フレーム)。

06シマウマ縞 — 細長グリッドに縦方向の緩和バイアス

ブランドの縦ストライプ、キャラクターの模様:縦に細長い格子で横の拡散を強めると、縞がシマウマの脇腹のように密に詰まります。

// 種まき — シードが引く x 座標に4〜6本の縦帯
function seedField(sd) {
  var rng = mulberry32(sd >>> 0);
  u.fill(0); v.fill(0);
  for (var p = 0; p < GW * GH; p++) u[p] = 0.98 + rng() * 0.04;
  var nbars = 4 + Math.floor(rng() * 3);
  for (var n = 0; n < nbars; n++) {
    var cx = 6 + rng() * (GW - 12);
    var rr = 2.5 + rng() * 1.5;
    for (var x = Math.max(0, cx - rr | 0); x <= Math.min(GW - 1, cx + rr + 1); x++) {
      var dx = x - cx;
      if (dx * dx < rr * rr) {
        for (var y = 0; y < GH; y++) v[y * GW + x] = 0.4;
      }
    }
  }
}
シマウマ縞 — 細長グリッドに縦方向の緩和バイアス
新しいタブで開く

06 · シマウマ縞 — 細長グリッドに縦方向の緩和バイアス — 計測 累積面積 45.926% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

縞の向きは拡散の速い方向に従います。この盤は格子を縦に引き、ラプラシアンの横項に1.2倍の重みを与えて、縦帯をシマウマの脇腹ほど密に詰めます(実測: 累積面積45.93パーセント、23/23フレーム)。

07波 — 模様全体がゆっくり流れる

瞑想・音楽サービスの背景:落ち着いた縞模様に絶え間なく小さな揺さぶりを注ぎ続けると、模様全体がゆっくり波のように流れます。

// 微量注入 — 8つのソースがリサージュ軌道をなぞって U を注ぐ
function ripple() {
  for (var j = 0; j < SRC_N; j++) {
    var ph = 1.7 + j * 2.4;                       // ソースごとに固有の位相
    var cx = (GW / 2 + Math.cos(t * ph) * GW * 0.36) | 0;
    var cy = (GH / 2 + Math.sin(t * (0.9 + j * 0.8)) * GH * 0.36) | 0;
    for (var dy = -SRC_RAD; dy <= SRC_RAD; dy++) {
      for (var dx = -SRC_RAD; dx <= SRC_RAD; dx++) {
        if (dx * dx + dy * dy > SRC_RAD * SRC_RAD) continue;
        var i = ((cy + dy + GH) % GH) * GW + ((cx + dx + GW) % GW);
        U[i] = 1;
        var nv = V[i] + SRC_BOOST;
        V[i] = nv > 1 ? 1 : nv;                   // クランプが吹き出しを受け止める
      }
    }
  }
}
波 — 模様全体がゆっくり流れる
新しいタブで開く

07 · 波 — 模様全体がゆっくり流れる — 計測 累積面積 36.247% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

放置された落ち着いた縞模様は何もしません — 方程式の不動点だからです。8つのソースが注入点の線を押し、その押しが線づたいに伝わって全体がゆっくり流れます(実測: 累積面積36.25パーセント、強度30.4、23/23フレーム)。

08細胞膜 — 境界が厚くなり震えが残る

科学コンテンツのイラスト、細胞コンセプトUI:除去率を上げると模様が縮んで、丸い細胞のような部屋になります。

// 揺さぶり — 14のソースが膜の周囲を巡って縁を刺激する
function quiver() {
  for (var j = 0; j < SRC_N; j++) {
    var ph = 2.3 + j * 1.9;
    var cx = (GW / 2 + Math.cos(t * ph) * GW * 0.34) | 0;
    var cy = (GH / 2 + Math.sin(t * (1.3 + j * 0.9)) * GH * 0.32) | 0;
    for (var dy = -SRC_RAD; dy <= SRC_RAD; dy++) {
      for (var dx = -SRC_RAD; dx <= SRC_RAD; dx++) {
        if (dx * dx + dy * dy > SRC_RAD * SRC_RAD) continue;
        var i = ((cy + dy + GH) % GH) * GW + ((cx + dx + GW) % GW);
        U[i] = 1;
        var nv = V[i] + SRC_BOOST;
        V[i] = nv > 1 ? 1 : nv;
      }
    }
  }
}
細胞膜 — 境界が厚くなり震えが残る
新しいタブで開く

08 · 細胞膜 — 境界が厚くなり震えが残る — 計測 累積面積 31.474% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

除去率 k が高いほど V は速く燃え尽きるので、模様は式が許すかぎり小さい丸い部屋へ縮みます。部屋の境界は完全には止まりません — 反応と除去が釣り合う境界で震え続け、静的な描画との見分けがつく点です(実測: 累積面積31.47パーセント、23/23フレーム)。

09分岐 — 成長の先端が fork を続ける

インフォグラフィック背景、成長コンセプト演出:供給率が非常に低い所では、伸びる先端が抵抗に出会うたびに二つに fork します。

// 種まき — 下端にギザギザの横線を敷き、RIP_STEPS だけ先行成長させる
function build(seed) {
  var rng = mulberry32(seed >>> 0);
  for (var i = 0; i < GW * GH; i++) { U[i] = 1; V[i] = 0; }
  var y0 = GH - 6;
  for (var x = 2; x < GW - 2; x++) {
    var gap = rng() < 0.30;                       // 隙間が seed ごとの分岐点を変える
    if (gap) continue;
    V[y0 * GW + x] = 1;
    if (rng() < 0.6) V[(y0 + 1) * GW + x] = 1;
    if (rng() < 0.3) V[(y0 - 1) * GW + x] = 1;
  }
  for (var m = 0; m < RIP_STEPS; m++) step();
}
分岐 — 成長の先端が fork を続ける
新しいタブで開く

09 · 分岐 — 成長の先端が fork を続ける — 計測 累積面積 34.187% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

供給率が非常に低いと、伸びる先端は隣の線の領域へ入れず、回り込むために二つに fork し、その fork がまた抵抗で fork を重ねます。04 のサンゴと違って先端は死なないので、分岐は画面が埋まるまで続きます(実測: 累積面積34.19パーセント、23/23フレーム)。

反応拡散モデルはどう模様になるのか — Gray-Scott の2物質

この9枚はすべて同じひとつの考えで動いています。Gray-Scott の反応拡散モデルは、餌 U と触媒 V の2物質を格子に敷き、拡散(隣への広がり)と反応(U+2V→3V)、供給と除去(率 f と k)を毎ステップ計算します。チューリングパターンとは実はこれのことです — どちらの物質も単独では模様を作らないのに、2つが違う速さで拡散するだけで平らな初期場は不安定になり、小さな揺らぎが育って周期的な模様に落ち着きます。供給率 f をひとつ動かして点・縞・迷路・部屋・分岐成長を歩けるのは、この不安定のかたち自体がパラメータで変わるからで、動物の皮膚に模様が載る理由の教科書的な説明も同じ式です。格子は 64×40(縦長盤は 48×72、07・08・09 は 96×60)で固定し、表示寸法はすべてアップスケールで賄うので計算コストはウィンドウ幅に依存しません — そして シードは毎回同じ乱数列を導くので、同じシードは必ず同じ模様を、違うシードは違う模様を返します。全9枚のシード入力と供給率スライダーは、まさにそれを確かめるためにあります。

どこで壊れるか — 成熟した格子が画面を殺した最初の盤

最初に壊れたのは05 羊の毛でした。きれいに育っていた模様がそのまま止まり、2秒のキャプチャで動いていたのは7フレームだけ。原因は構造的なもので、Gray-Scott はパラメータを固定すると必ず数学的に静止した状態へ収束するため、先行成熟を大きく取るほど最初のフレームがすでに死んでいる — 累積面積25パーセントという測定は「1〜2フレーム育って固まった」模様の記録でした。直そうとして事態はいったん悪化します。模様を生かすために U を注入すると注入点の V が1へ張り付き、隣のセルのラプラシアンが U を負に引っ張り、uvv 項の符号が反転して百余りステップで格子全体が NaN に沈み — 画面は真っ黒でした。2つのバグは同じひとつの修正で消えます。毎ステップの後、更新値を [0, 1] へクランプし直すこと(01のコードブロック末尾2行)と、ひとつの大きな注入ではなく複数の動くソースからの穏やかな注入に切り替えること。この修正のあと、9枚すべてがサンプル23フレーム中23フレームで動いています。完成した反応格子9枚と React 版は記事の下のパスワード付き zip にあり、開けるパスワードは 54nx2m3f です。

3つのバリエーションとアクセシビリティ

名前 変更 一言で言うと
供給率 f 02 縞 f 0.045 → 0.030 縞がほぐれて点に戻る
シード 01 斑点 seed 7 → 91 同じ規則で、まったく違う点並び
色 $turing-mint → $turing-peach 冷たい青緑の反応場が暖かい桃色へ

アクセシビリティの扱いは9枚で一貫しています。prefers-reduced-motion が reduce の利用者には rAF ループを起動せず、シードから引いた静止1枚だけを描きます — 01は完成した点並び、02は落ち着いた縞、04は半分ほど外へ出たサンゴの前線です。シード入力と供給率スライダーはこの静止経路でも生きていて、シードを変えれば別の1枚が引き直され、スライダーを動かせば別の模様体制の静止フレームに切り替わります。アニメーションを止めても、模様を計算が作ったという事実までは止まりません。

FAQ

ライブラリなしの チューリングパターン シミュレーション は性能が出る?

出ます。格子は 64×40(2,560セル)に固定で、1フレーム8〜14ステップでも毎フレームの計算は数百回の加算と乗算にとどまり、60fps の予算に余裕で収まります — 表示は低解像度バッファの拡大なので、ウィンドウを広げても計算は増えません。9枚すべてが23/23フレームで動くことを確認済みで、再生は rAF が渡す時間に従うため、遅い端末では溜めずにゆっくり再生されるだけです。

チューリングパターンとは 何のこと? 生物の模様とどうつながる?

2つの物質が違う速さで広がりながら反応すると、平らな状態が不安定になって自発的に周期模様が浮かぶ — これがチューリングパターンです。1952年にアラン・チューリングが生物の形態形成を説明する数理として提唱した機構で、シマウマの縞・熱帯魚の斑点・ヒョウの豹紋など、生物の皮膚模様の多くがこの式で説明されます。この記事の01・02・09は、その生物で見られる模様のそれぞれ点・縞・成長の体制を同じ Gray-Scott 格子で再現したものです。

ソースコードはそのままコピーしていい?

いいです。記事の下のパスワード付き zip を展開すると、バニラ9枚と React 版がまるごと出てきて、貼り付ければすぐ動きます。色は各盤の scss 冒頭のパステルトークン変数で差し替え、シード欄に数字を打ち込めば同じ模様が毎回再現されます。

さらに読む

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。