[Noise 入門 #30] 第3集完結 — 全てを束ねる「Procedural Universe」(プロシージャルな宇宙の錬成)

はじめに

静かな漆黒の空間に、100万の星々が渦を巻き、色彩豊かな星雲(ネビュラ)がうねる。中心には圧倒的な質量を感じさせる光のコア(あるいはブラックホール)が鎮座する。

これを手作業で描くことは不可能です。しかし、私たちには「ノイズという名の数学的魔法」と「GPUの並列計算力」があります。第3集の完結編となる今回は、これまでの知識を総動員して「銀河系」をプロシージャルに生成する設計図を解き明かします。

前回の記事:

1. 宇宙を構成する「4つのレイヤー」

「プロシージャルに銀河を作る」と聞くと、とてつもなく複雑な魔法のように思えるかもしれません。しかし、これまでに私たちが学んできたノイズの技術を「4つの層(レイヤー)」に分解して考えれば、一つひとつは既に手の中にある技術の組み合わせに過ぎないことに気がつくはずです。

いきなり全てを混ぜ合わせるのではなく、背景から手前(あるいはマクロからミクロ)に向かって、層を重ねるようにShaderを設計していきます。

第1層:The Void & Nebula(背景の星雲)

【使用技術:FBM + 多重 Domain Warping】

宇宙は単なる「黒い背景」ではありません。そこには無数のチリや星間ガスが漂う「星雲(ネビュラ)」が存在します。 このキャンバスのベースを作るのが、#05で学んだFBM(フラクタル・ブラウン運動)と、#06・#22で触れたDomain Warping(空間歪曲)です。 単なるFBMでは「もやもやした雲」にしかなりませんが、座標空間そのものを別のノイズでねじ曲げる(多重Warping)ことで、ガスが引き伸ばされ、流体のようにうねる壮大な星雲の質感が生まれます。

🎨 視覚効果: 画面全体を覆う、深海のようにうねる暗い紫や青、そして赤紫のガス雲。

第2層:The Core & Singularity(銀河の中心)

【使用技術:極座標 + 距離関数(SDF)の応用】

銀河の中心には、圧倒的な質量と光を放つコア(あるいは超大質量ブラックホール)が鎮座しています。 ここでは#23で学んだ極座標(Polar Coordinates)の出番です。中心からの距離 $r$ に対して、逆二乗の法則のような指数関数的な減衰(Falloff)を適用します。 例えば、中心からの距離をベースに $\frac{1.0}{r^\gamma}$ のような計算を行うことで、中心は白飛びするほど明るく、外側に向かって急激にフェードアウトする「光の核」を数学的に定義します。

🎨 視覚効果: 画面中央で圧倒的な輝きを放ち、周囲の空間(星雲)を明るく照らし出す光の塊。

第3層:The Spiral Arms(渦巻腕)

【使用技術:対数螺旋の数式 + Simplex Noiseによる崩し】

銀河を銀河たらしめている最大の要素が「渦巻く腕(Spiral Arms)」です。 自然界の美しい渦は、数学的には「対数螺旋(Logarithmic spiral)」として知られ、極座標 $(r, \theta)$ を用いて $r = a e^{b\theta}$ のような数式で表現できます。 しかし、この数式をそのまま描画すると「幾何学的に綺麗すぎて人工物に見えてしまう」という問題が発生します。そこで、#14で学んだSimplex Noiseを螺旋の座標系に加算し、わざと形を崩します。数式の秩序とノイズのカオスが混ざり合うことで、千切れたり太くなったりする「自然な星雲の腕」が形成されます。

🎨 視覚効果: コアから外周へ向かって伸びる、ノイズによって侵食され、引き裂かれた光の帯。

第4層:1,000,000 Stars(星々の流動)

【使用技術:GPGPU + Curl Noise】

ここまでの3層は「ピクセル(フラグメント)の色の計算」による背景レイヤーです。ここに命を吹き込むのが、#28・#29で実装したGPGPUによる100万のパーティクルシステムです。 第3層で作った「渦巻きの形状」と「中心へ向かう重力」、そして#08のCurl Noise(回転ノイズ)を合成して、「速度ベクトル場(Velocity Field)」を設計します。100万個の光の粒子が、銀河の腕の形状に沿って大きな渦を巻きながら、局所的には乱気流に巻き込まれてチカチカと流動していきます。

🎨 視覚効果: 画面手前を無数に舞い、銀河の引力と乱気流に翻弄されながら流れる圧倒的な数の星々。


錬金術の仕上げ:加算合成(Additive Blending)

これら4つのレイヤーは、最終的にGPU上で「光の足し算」として合成されます。 真っ暗な vec3(0.0) の宇宙空間に対して、

vec3 color = vec3(0.0);
color += nebulaLayer;   // 第1層を足す
color += coreLayer;     // 第2層を足す
color += spiralLayer;   // 第3層を足す
// ※第4層(パーティクル)はThree.js側で上から描画し、BlendModeをAdditiveにする

このように純粋な加算合成(Additive Blending)を行うことで、光が重なる部分は白く飛び、まさに「自ら発光する宇宙」がブラウザ上に顕現します。

2. 銀河の骨格を作る:極座標と対数螺旋

宇宙のキャンバスにただの「星間ガス」を描くなら、直交座標系のままでも十分かもしれません。しかし、銀河を銀河たらしめる最大の特徴は、あの巨大で神秘的な「渦(Spiral)」です。

直交座標系 $(x, y)$ のままノイズを適用しても、ただのモヤモヤした霧にしかなりません。ここで、[Noise 入門 #23] で展開した「極座標(Polar Coordinates)」の知識が火を噴きます。

完璧な数式で「螺旋」を定義する

まず、画面中央を原点 $(0,0)$ としたピクセルの座標(UV座標)を、直交座標から極座標 $(r, \theta)$ に変換します。$r$ は中心からの距離、$\theta$ は角度を表します。

$$r = \sqrt{x^2 + y^2}$$

$$\theta = \text{atan}(y, x)$$

GLSLであれば、組み込み関数を使って float r = length(uv);float theta = atan(uv.y, uv.x); で一発ですね。

自然界の銀河や台風の渦は、「対数螺旋(Logarithmic spiral)」と呼ばれる美しい数式に従っています。この性質を利用して、銀河の「腕(アーム)」の密度を計算するベースの式を組んでみましょう。

// 1. 極座標への変換
float r = length(uv);
float theta = atan(uv.y, uv.x);

// 2. 螺旋のパラメータ設定
float arms = 2.0;  // 渦巻きの腕の数(例: 2本や4本)
float twist = 5.0; // 渦の巻きの強さ

// 3. 銀河の腕を生成するベースの計算
float spiral = sin(theta * arms - r * twist);

この式の構造は非常にシンプルです。 theta * arms で腕の数(波の周期)を決め、そこから r * twist を引くことで「中心から離れるほど位相がズレる(=渦を巻く)」という現象を引き起こしています。

秩序をノイズで破壊する(極座標の Domain Warping)

しかし、上記の spiral の数式をそのまま出力するとどうなるでしょうか? 結果は「幾何学的に綺麗すぎる螺旋模様」——まるで蚊取り線香か、催眠術の映像のようになってしまいます。宇宙の美しさは、完璧な数式の中に潜む「カオス(揺らぎ)」にあります。

そこで、極座標の $r$ と $\theta$ に対して、FBMノイズを加算して空間をねじ曲げます。つまり、極座標系における Domain Warping(ドメインワーピング)です。

// FBMを使って空間全体の揺らぎ(カオス)を生成
float noiseVal = fbm(uv * 3.0);

// 距離(r)と角度(theta)にノイズを介入させる
float distortedR = r + noiseVal * 0.2;
float distortedTheta = theta + noiseVal * 0.5;

// 歪んだ座標で螺旋を再計算
float organicSpiral = sin(distortedTheta * arms - distortedR * twist);

// -1.0 ~ 1.0 のサイン波を 0.0 ~ 1.0 に正規化し、境界をパキッとさせる
organicSpiral = smoothstep(0.2, 0.8, organicSpiral * 0.5 + 0.5);

このひと手間を加えるだけで、結果は劇的に変わります。 ノイズによって座標が局所的に歪むため、均一だった螺旋の線が途切れたり、太さが不均一になったり、星間ガスが分散しているような「自然で有機的な星雲の腕」が形成されます。

完璧な数式(秩序)をベースに敷き、ノイズ(混沌)でそれを破壊する。 これこそが、自然界の複雑さを模倣するプロシージャル・アートの真髄です。

3. 星雲(Nebula)の錬成:多重 Domain Warping

銀河の骨格である「渦」ができたら、次はその背景を満たすガスやチリ——「星雲(ネビュラ)」を錬成します。

宇宙空間に漂う星雲は、単なる煙ではありません。途方もない時間の中で重力や星風に引き裂かれ、引き伸ばされ、複雑に絡み合った流体のような質感を持っています。これを表現するために、[Noise 入門 #06] と [#22] で学んだ Domain Warping(ドメインワーピング) の真骨頂を発揮させます。

なぜ普通のFBMではダメなのか?

星雲を描こうとしたとき、最初に思いつくのはFBM(Fractal Brownian Motion)を使って「もやもやした雲」を作ることでしょう。しかし、FBMの出力をそのまま色に変換しても、どうしても「地球の空に浮かぶのっぺりとした雲」になってしまいます。

星雲特有の「うねり」や「引き伸ばされた繊維のようなガス」の質感を生み出すには、ノイズの値を取得するための「座標(UV)自体を、さらに別のノイズで激しく歪める」必要があります。しかも1回ではなく、複数回重ねる「多重 Domain Warping」を行います。

座標の歪みを連鎖させる(GLSL実装)

数学の天才でありShader界の巨匠でもある Inigo Quilez 氏が提唱した、ノイズを入れ子にする美しきアルゴリズムをGLSLで実装してみましょう。

// 1段階目の歪み(ベクトル場 q を生成)
// 空間全体の大きな「流れの方向」を決める
vec2 q = vec2(
    fbm(uv),
    fbm(uv + vec2(5.2, 1.3))
);

// 2段階目の歪み(ベクトル場 r を生成)
// 1段階目の q を座標に足し込み、さらに time を加えて時間変化させる
vec2 r = vec2(
    fbm(uv + 4.0 * q + time * 0.2),
    fbm(uv + 4.0 * q + vec2(8.3, 2.8) - time * 0.15)
);

// 最終的な星雲の密度(nebula)を計算
// 激しくねじ曲がった座標 r を使って最後のFBMをサンプリング
float nebula = fbm(uv + 4.0 * r);

このコードの魔法は、「ノイズがノイズを引っ張る」という構造にあります。 q が大きな川の流れを作り、r がその川の中に発生する局所的な渦を作り出します。そして最後に計算される nebula(0.0〜1.0の値)は、見事に引き伸ばされ、うねりを持った星間ガスの密度マップとなります。

色(Color Palette)の注入

密度マップができたら、最後はそれに「色」を焼き付けます。 GLSLの組み込み関数 mix を使い、nebula の値をブレンド係数として、暗い宇宙の背景色と燃えるようなガスの色を混ぜ合わせます。

// カラーパレットの定義
vec3 colorVoid = vec3(0.02, 0.05, 0.1);  // 外側の深い紺色
vec3 colorCore = vec3(0.9, 0.4, 0.1);    // 内側の燃えるようなオレンジ/ピンク

// nebulaの値を元に色をブレンド
// ※さらに極座標の距離(distance)を掛けて中心ほど明るくする等の調整を加える
vec3 finalNebulaColor = mix(colorVoid, colorCore, nebula);

時間が経過する(time が加算される)と、静止していた数式が動き出します。 多重Warpingによって計算されたガスがゆっくりと流動し、互いに混ざり合いながら宇宙空間を漂う——ブラウザの中に、生きているかのような小宇宙が誕生する瞬間です。

4. 100万の星々の流動:GPGPU × 螺旋ベクトル場

背景の壮大な星雲が完成しました。しかし、宇宙は静止していません。ここに圧倒的なスケールの「命」を吹き込むのが、[Noise 入門 #29] で解放した GPGPU(General-Purpose computing on Graphics Processing Units) の力です。

CPUの限界を突破し、100万個のパーティクル(星々)を銀河の腕に沿って漂わせます。

3つの力で編み出す「螺旋ベクトル場(Velocity Field)」

100万の星を動かすためには、一つ一つの星に「次はどこへ進むべきか」を指示する風向きマップ、すなわち「速度ベクトル場(Velocity Field)」を設計する必要があります。 ただのランダムなノイズで動かすと星が宇宙の彼方へ散り散りになってしまうため、今回は以下の「3つの力(ベクトル)」を合成して銀河の物理ルールを作ります。

  1. 引力(Gravity Vector): 銀河の中心(ブラックホール)に向かって引っ張られる力です。現在の位置から原点に向かうベクトルを計算します。
  2. 回転(Tangential Vector): 中心に吸い込まれるだけでなく、円周に沿って渦を巻くための力です。引力ベクトルと特定の軸(例えばY軸)との外積(Cross Product)を取ることで、渦を巻くための綺麗な接線ベクトルが得られます。
  3. カオス(Curl Noise Vector): [Noise 入門 #08] で学んだ、発散ゼロの流体シミュレーション(乱気流)を加える力です。これにより、星の軌道が機械的な円にならず、有機的なうねりを生み出します。

GPGPUシェーダーでの実装イメージ

これらを GPGPU の Velocity(速度計算)シェーダー内で毎フレーム計算し、星のポジションを更新していきます。

// テクスチャから現在のパーティクル位置と速度を取得
vec3 pos = texture2D(texturePosition, uv).xyz;
vec3 vel = texture2D(textureVelocity, uv).xyz;

// 1. 引力(中心へ向かうベクトル)
// 原点(0,0,0)に向かう正規化ベクトルに強さを掛ける
vec3 gravity = normalize(-pos) * gravityStrength;

// 2. 回転(外積による接線ベクトル)
// posベクトルとY軸(0,1,0)の外積で、円周に沿って回る力を生む
vec3 tangent = cross(normalize(pos), vec3(0.0, 1.0, 0.0)) * rotationSpeed;

// 3. カオス(Curl Noiseによる乱気流)
// 空間位置と時間を元に、うねるようなベクトル場を取得
vec3 turbulence = curlNoise(pos * noiseScale + time) * noiseStrength;

// 3つの力を合成して新しい速度ベクトルを作る
vec3 newVel = vel * damping + gravity + tangent + turbulence;

// 新しい速度をテクスチャに保存
gl_FragColor = vec4(newVel, 1.0);

秩序と混沌が織りなす圧倒的な光景

マクロな視点で見れば、100万の光の点は「大きな渦のうねり(引力+回転)」という明確な秩序に従って流れています。しかしミクロな視点で見ると、局所的には「微細な乱気流(Curl Noise)」によってチカチカと軌道を変え、互いに絡み合いながら進んでいきます。

この「マクロな秩序」と「ミクロな混沌」のバランスこそが、プロシージャル表現の極致です。大きなうねりに逆らわず、しかしノイズによって複雑に流動する100万の星々。GPGPUの力でリアルタイムに計算され、ディスプレイの向こう側で一つの宇宙として顕現する瞬間の鳥肌を、ぜひ味わってください。

5. ポストプロセスによる「宇宙の空気感」

100万の星々と多重Warpingによる星雲。これらがGPU上で計算され、画面に描画されるだけでも十分に美しいですが、今のままでは「計算機が弾き出した綺麗なCG」の域を出ません。

ここに「本物の宇宙を高性能な望遠鏡やシネマカメラで捉えたような空気感」を付与するのが、[Noise 入門 #24] で学んだ Post-Processing(ポストプロセス) です。 レンダリングされた「完成形の一枚の画像(テクスチャ)」に対して、最後のエフェクト処理をかけることで、作品のクオリティを限界まで引き上げます。

Bloom(輝き):限界を超える光の表現

星雲のコア(中心部)や、密集して配置された星々の光は、本来ならモニターの白の限界(RGB: 1.0, 1.0, 1.0)をはるかに超えるエネルギーを持っています。 閾値を超えた明るいピクセルだけを抽出し、ガウシアンブラーで周囲にぼかして元の画像に加算合成(Additive Blend)します。これにより、光がレンズの中で溢れ出すようなHDR(ハイダイナミックレンジ)特有の「眩しさ」を表現できます。銀河の中心が、圧倒的な質量と熱を伴って輝き始める瞬間です。

Gravitational Lensing(重力レンズ):物理法則のハック

銀河の中心に超大質量ブラックホールが潜んでいるとしたら、どうなるでしょうか。ここで [Noise 入門 #22] で実装した「空間歪曲アルゴリズム」を、画面中央のUV座標に対して強烈に適用します。 極端な重力によって光の軌道が曲げられるため、ブラックホールの背後にある星雲や星々の光が、中心付近でぐにゃりと引き伸ばされ、リング状に歪みます。空間そのものをポストプロセスでねじ曲げるこの手法が、プロシージャル宇宙に究極の説得力を持たせます。

Chromatic Aberration(色収差):完璧なCGへの「ノイズ(欠陥)」

最後に、完璧なピクセルデータに対して、あえて「物理カメラのレンズの欠陥」を混ぜ込みます。 画面の中心から端(エッジ)に向かうにつれて、光の波長による屈折率の違いを再現し、テクスチャをサンプリングする際のRGBチャンネルの座標をわずかにズラします。

// 色収差のGLSL実装イメージ
vec2 dir = normalize(uv - 0.5);   // 中心からの方向
float dist = length(uv - 0.5);    // 中心からの距離
float strength = dist * 0.02;     // 端に行くほどズレを強くする

// R, G, Bをわずかに違う座標からサンプリングして合成
float r = texture2D(sceneTex, uv + dir * strength).r;
float g = texture2D(sceneTex, uv).g;
float b = texture2D(sceneTex, uv - dir * strength).b;

赤(R)を少し外側へ、青(B)を少し内側へズラすことで、星々や星雲の輪郭にわずかな「色のにじみ」が生まれます。この微細なノイズが加わることで、デジタルな冷たさが消え、「分厚いレンズ越しに広大な宇宙を覗き込んでいる」という圧倒的なリアリティが完成します。

結び:第3集の終幕、そして次の次元へ

長きにわたる第3集「Shader(GLSL)実践編」、本当にお疲れ様でした。

思い返せば、最初はただの「ランダムな数値の羅列」に過ぎなかったノイズ。それが数式という名のガイドラインに導かれ、GPUという圧倒的な並列計算の炉にくべられることで、ブラウザの中に100万の星々が渦巻く「一つの宇宙」を創り出しました。

これこそが、Procedural(手続き型)アプローチの究極の魅力です。 数メガバイトの重いテクスチャ画像や動画素材を一切使わず、わずか数キロバイトの数式とコードの記述だけで、どんなにズームしても劣化しない「無限の解像度を持つ世界」をゼロから生み出す錬金術。

FBM、Domain Warping、極座標、そしてGPGPU。 第3集を通して、皆さんはこの強力な魔法の杖(GLSL)の振り方を完全にマスターしました。今や皆さんの手の中には、無から有を生み出す力が宿っています。

しかし——私たちが創り上げたこの美しい宇宙は、現時点ではまだスクリーンの中で「ただ、そこにあるだけ」です。

次回からはいよいよ新章、第4集「Three.js編(動くノイズの天国)」が開幕します。 これまでに錬成したノイズの世界を、今度は3D空間に解き放ちます。マウスでぐりぐりと銀河を回し、カメラで星雲の中心へ飛び込み、ユーザーの操作(インタラクション)に呼応してうねる波や炎を生み出す「体験」へと昇華させていきます。

ノイズの旅路は、ここからゲームエンジン開発や最先端のWeb表現の領域へと突入します。 数式が描いた世界に「触れる」感動。次なる次元への扉を開く展開も、どうぞお楽しみに!