[Noise 入門 #22] Domain Warping の真骨頂 — GLSLで空間をねじ曲げ「ブラックホール」を錬成する

はじめに

前回の記事では、SDF(符号付き距離関数)とFBMを組み合わせ、何もない空間から燃え盛る「プロシージャルな炎」を錬成しました。局所的なエフェクトを作るという点において、ノイズがどれほど強力な「魔法の杖」になるかをご体感いただけたかと思います。

第3集「Shader実践編」第2回となる今回は、その熱量をさらに別次元へと引き上げます。

今回のターゲットは「空間そのものの歪曲」です。 炎のような局所的な描画から一歩踏み出し、キャンバス全体(あるいは背景の映像)の座標をねじ曲げ、光さえも飲み込む「ブラックホール」をGLSLで実装します。

第6回で学んだ Domain Warping(ドメインワーピング) の力が、Post-Processing(後処理)エフェクトとしていかに凶悪で美しい表現を生み出すのか。その真骨頂をお見せしましょう。

1. 重力レンズと Domain Warping — 座標(UV)を破壊する

ブラックホールの最大の特徴は、その圧倒的な重力によって周囲の時空が歪み、背後にある星々の光が曲げられて届く「重力レンズ効果」です。映画『インターステラー』などで描かれた、あの光の輪が捻じ曲がるような独特の視覚効果を思い浮かべてみてください。

これを Shader で表現するにはどうすればよいでしょうか? 答えは非常にシンプルでありながら、強力なパラダイムシフトを含んでいます。それは、「色を塗る」のではなく、「座標(UV)を読む位置をズラす」 というアプローチです。

「描く」のではなく「観測点を狂わせる」

これまでの入門シリーズで扱ってきたノイズは、主に「そのピクセルの色や高さを決定する」ために使ってきました。つまり、キャンバス上の位置 uv を入力して、白黒のグラデーションという「結果」を直接描画していたわけです。

しかし、Post-Processing(後処理)における Domain Warping の考え方は根本的に異なります。画面上のピクセルは、自分自身の位置 uv の色を出力するのではなく、「本来とは違う別の場所の色を覗き見に行く」 のです。

方眼紙(グリッド)の上に描かれた宇宙の絵を想像してください。 その方眼紙の中心を指でつまみ、ギュッとねじりながら引き寄せると、マス目がグニャグニャに歪みますよね。これが、Shaderの内部で起きている「座標の破壊」です。

数式で見る「空間の歪曲」

通常のテクスチャサンプリングでは、ピクセルは素直に自分の座標の色を取得します。

vec3 color = texture(iChannel0, uv).rgb;

しかし、Domain Warping の概念を用いると、この読み取り座標 uv に対して、ノイズ関数 $\mathbf{N}$ による「ズレ」を加算します。

$$\mathbf{uv}_{distorted} = \mathbf{uv} + \text{strength} \times \mathbf{N}(\mathbf{uv})$$

この短い数式が、空間をねじ曲げる魔法の正体です。 ここで重要なのは、ノイズ関数 $\mathbf{N}(\mathbf{uv})$ が単なる数値(スカラー)ではなく、方向と大きさを持つベクトル(vec2)として機能している という点です。

X方向へどれくらいズラすか、Y方向へどれくらいズラすか。この2つのズレの量を、ブラックホールの中心(特異点)に向かう「引力」や「渦の回転」を定義したベクトル場として計算し、それを uv に足し合わせます。

重力が生み出す「レンズ」の錯覚

引力が強い(中心に近い)場所ほど、この $\text{strength}$(ズレの強さ)を極端に大きく設定します。

するとどうなるか。 中心付近のピクセルは、自分から遥か遠く離れた場所にある星空のピクセルを強制的に読み取らされることになります。結果として、背景の宇宙が中心に向かってズルズルと引き伸ばされ、吸い込まれていくような「重力レンズ効果の錯覚」が画面上に完璧に再現されるのです。

この「座標のねじれ」を数学的に制御し、ブラックホールの引力と渦の動き、そして空間の引き裂け(ノイズ)に結びつけるプロセスを、次のセクションで具体的なコードと共に解き明かしていきましょう。

2. 空間を歪ませる数学 — 渦と引力のベクトル場

ブラックホールの歪みを構築するためには、単なるランダムなノイズをまぶすだけでは不十分です。そこには「中心に向かって巻き込まれ、光すら逃れられない」という、数学的で暴力的な規則性を持ったベクトル場(Vector Field)の設計が必要になります。

美しいブラックホールを錬成するために、以下の3つの力学的なアプローチを組み合わせて uv 座標を変形していきます。

① 中心への引力(Gravity) — 特異点への落下

まずは、画面上のすべてのピクセルを中心座標 (0.5, 0.5) へと引きずり込む力(引力)を計算します。

現在処理しているピクセルの座標から中心までのベクトル(向きと距離)を取得します。GLSLでは、この「中心へのベクトル」を delta とし、その長さ(距離)を $d$ とします。

重力は「距離が近いほど強くなる」という性質を持っています。そのため、距離 $d$ に対して反比例するような減衰関数を設計します。単純に言えば、$1 / d$ のような計算です。 これにより、中心から遠い場所(宇宙空間)ではほとんど歪まず、中心(特異点)に近づくにつれて引力が無限大に跳ね上がるような、リアルな重力のグラデーションを作り出すことができます。

※実際のコードでは、距離が $0$ になった際の「ゼロ除算(Division by Zero)」による画面の崩壊(バグ)を防ぐため、分母に微小な値(例えば $0.01$)を足すといったShader特有のテクニックを用います。

② 空間の回転(Vortex) — 降着円盤の渦

引力だけでは、背景の星空が中心に向かって直線的にズームされるだけの退屈な絵になってしまいます。ブラックホールの周囲には、超高速で回転するガスや光の帯(降着円盤)が存在します。空間そのものを渦巻き状に回転させましょう。

ここで登場するのが、線形代数でおなじみの2Dの回転行列(Rotation Matrix)です。

$$R(\theta) = \begin{pmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{pmatrix}$$

この行列を先ほどの delta ベクトルに掛け合わせることで、座標を回転させることができます。 しかし、ただ画面全体を同じ角度で回すだけでは「渦」にはなりません。重要なのは、「中心からの距離 $d$ に応じて、回転角 $\theta$ を変化させる」ことです。

中心に近く重力が強い場所ほど $\theta$ を大きく(高速に回転)し、遠く離れるほど $\theta$ を小さく(ゆっくり回転)します。この「距離に依存した回転のズレ」が、空間がねじ切れるような美しいスパイラル(渦巻き)を生み出すのです。

③ 次元を引き裂くノイズ(Domain Warping) — 混沌の注入

ここまでの「引力」と「回転」の数式だけでも、Photoshopの「渦巻きフィルタ」のような綺麗な歪みは作れます。しかし、それはあまりにも数学的で「完璧」すぎます。大自然や宇宙が持つ、荒々しく混沌としたエネルギーが足りません。

そこで最後に、FBM(Fractal Brownian Motion)を用いた Domain Warping を介入させます。

綺麗に整った引力と回転のベクトル場に対して、FBMによるランダムな「揺らぎのベクトル」を加算(または乗算)します。すると、均一だった渦巻きの力が局所的に強くなったり弱くなったりと、空間が引き裂かれるように荒々しく歪み始めます。

さらに、このFBM関数の入力パラメータに「時間(iTime)」を加えることで、歪みのノイズ自体がボコボコと沸騰するように動きます。これにより、静止画のフィルタではなく、「常に形態を変化させながら周囲を飲み込み続ける、生きた重力場」が完成するのです。

3. GLSL 実装 — 光を飲み込むコード

理論は揃いました。それでは、実際に Shader で空間を破壊し、ブラックホールを錬成してみましょう。 Shadertoyなどの環境で、背景(iChannel0)に宇宙の画像がセットされている前提で、それを Post-Processing で歪ませるコードです。

// FBM関数の定義(過去記事参照のため省略)
float fbm(vec2 uv) { ... }

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    // 画面中心を (0,0) に正規化し、扱いやすくする
    vec2 uv = fragCoord.xy / iResolution.xy;
    vec2 center = vec2(0.5, 0.5);
    vec2 delta = uv - center;

    // 中心からの距離
    float dist = length(delta);

    // ① 引力と回転の強さ(中心に近いほど無限大に近づく)
    // 特異点でのゼロ除算を防ぐため 0.01 を足す
    float force = 1.0 / (dist + 0.01);

    // ② 空間の回転(Vortex)
    float angle = force * 0.1; // 距離に応じた回転の強さ
    float s = sin(angle);
    float c = cos(angle);
    mat2 rotation = mat2(c, -s, s, c);

    // ③ Domain Warping による空間の引き裂き
    // 時間経過でノイズを動かし、歪みが沸騰するように揺らぐ
    vec2 noiseOffset = vec2(
        fbm(uv * 5.0 + iTime * 0.5),
        fbm(uv * 5.0 - iTime * 0.5 + 100.0) // XとYで違うノイズを生成するためオフセット
    );

    // 座標の合成:元の位置 + 回転 + 引力ノイズ
    // 歪みの影響範囲を制限するために exp 関数で減衰させる
    vec2 distortedUV = center + rotation * delta
                     - normalize(delta) * force * 0.02 * noiseOffset
                     * exp(-dist * 5.0);

    // 歪んだ座標で背景(宇宙)をサンプリング
    vec3 color = texture(iChannel0, distortedUV).rgb;

    // ④ 事象の地平面(Event Horizon)と光の輪(降着円盤)
    float horizonDist = 0.1; // ブラックホールの半径

    // 中心を真っ黒に塗りつぶす(光が逃げられない領域)
    float eventHorizon = smoothstep(horizonDist - 0.02, horizonDist + 0.01, dist);
    color *= eventHorizon; // 黒(0.0)を掛けて無に帰す

    // 周囲に発光(Glow)を追加して熱を持たせる
    float accretionDisk = smoothstep(horizonDist + 0.3, horizonDist, dist) * 1.5;
    vec3 glowColor = vec3(1.0, 0.6, 0.2); // オレンジ色の超高温プラズマ

    // FBMを掛けて光の輪を均一ではなく「ムラのある炎状」に
    color += glowColor * accretionDisk * fbm(distortedUV * 10.0 - iTime);

    // 最終出力
    fragColor = vec4(color, 1.0);
}

コードの解剖学:魔法を構成する4つの術式

このコードは、大きく分けて4つのプロセスから成り立っています。それぞれの数式が空間に対して何をしているのか、視覚的な意味を紐解いてみましょう。

① 引力(Force)の定義 float force = 1.0 / (dist + 0.01); ここが重力の源です。距離 dist0(中心)に近づくほど、force の値は爆発的に大きくなります。0.01 を足しているのは、完全に 0 になった瞬間に値が無限大になり、画面がバグる(NaNが発生する)のを防ぐための Shader 開発における定番の安全装置です。

② 空間の回転(Vortex) float angle = force * 0.1; 回転角 angle に、先ほどの引力 force を掛け合わせています。これが「中心に近いほど高速で回転し、遠いほどゆっくり回る」という降着円盤の物理的な渦の動きを生み出します。

③ 歪曲座標の合成(The Warping) ここが今回の最重要ポイントです。distortedUV を構築するこの一行に、Domain Warping の神髄が詰まっています。

  • center + rotation * delta:まずは空間全体を渦巻き状にひねります。
  • - normalize(delta) * force * 0.02 * noiseOffset:そこへ、中心に向かうベクトル(normalize)に対して、FBMのノイズを掛け合わせて引き算します。これにより、綺麗な渦ではなく、空間が引きちぎられるような荒々しい歪みが生まれます。
  • * exp(-dist * 5.0):仕上げの魔法です。exp(指数関数)を使って、中心から離れると急激に歪みの影響力がゼロになるように減衰させています。これがないと、画面の端(宇宙の果て)まで空間がグニャグニャに歪んでしまいます。

④ 絶対的な「無」とプラズマの「熱」 最後に、歪んだ背景画像(color)に対して直接干渉します。 eventHorizon(事象の地平面)を計算し、color *= eventHorizon と乗算することで、中心の一定領域を強制的に 0.0(純黒)に叩き落とします。これが光すら逃げられない特異点です。 さらに、その周囲に accretionDisk(降着円盤)の計算でオレンジ色の光を加算(+=)します。単なるグラデーションではなく、ここにも fbm を掛けることで、ガスが高速で摩擦し合い燃え盛るような、生々しいプラズマの質感を捏造しています。

4. なぜこれが「ブラックホール」に見えるのか

この Shader を実行した瞬間、背景の映像が中心に向かって凄まじい勢いで吸い込まれ、その周囲をオレンジ色のプラズマがうねるように明滅する光景が広がります。

一見すると非常に複雑な物理シミュレーションを行っているように見えますが、その表現の核は、実は以下の 「3つのシンプルなレイヤーの掛け合わせ」 に過ぎません。Shader における魔法は、常にこのレイヤー構造の中に宿っています。

① 歪み(Warping):光の経路を曲げる

コード上の texture(iChannel0, distortedUV) によって、背景のピクセルが本来あるべき場所から引き剥がされます。 もしここが純粋な数学的回転(Vortex)だけなら、ただの「お風呂の栓を抜いたような渦」になってしまいます。しかし、FBMによる Domain Warping が加わることで、空間の歪みに「ムラ」が生まれます。このムラが、まるで超高温のガラスが溶けているような、あるいは宇宙空間そのものが陽炎のように揺らめく「有機的な重力レンズ効果」を完璧に錯覚させるのです。

② クリッピング(SDF):絶対的な「無」の穿孔

背景がどれだけ美しく歪んでも、中心に「穴」がなければブラックホールにはなりません。 ここで活躍するのが、SDF(符号付き距離関数)の考え方を用いた eventHorizon の計算です。中心からの距離に対して smoothstep で閾値を設け、事象の地平面より内側の空間に強制的に 0.0(黒)を乗算(*)します。 Shader の世界において、色に 0.0 を掛けることは「光の完全な消失」を意味します。この「絶対的な無」をキャンバスのど真ん中に穿つことで、周囲の激しい歪みとの間に強烈なコントラストが生まれ、圧倒的な質量と恐怖感を引き出しています。

③ 発光(Glow + Noise):摩擦熱の捏造

ブラックホールそのものは光を発しませんが、引き寄せられた星のガスが超高速で回転し、凄まじい摩擦熱を持つ「降着円盤(Accretion Disk)」が光り輝きます。 これを表現するために、吸い込まれる境界線に沿ってオレンジ色の光のグラデーションを加算(+)しています。ここでのポイントは、単なる綺麗なグラデーションではなく、光の強度に対しても FBM(ノイズ)を掛け合わせている ことです。 これにより、光の輪が均一に発光するのではなく、プラズマの塊がムラを持ちながら激しく明滅し、うねるような「熱量」を持った炎として視覚化されます。

数式が「現象」に変わる瞬間

  1. texture で空間を引き伸ばし、
    • 0.0 で中心を無に帰し、
    • color で周囲に熱を足す。

たったこれだけのピクセル操作の組み合わせが、人間の脳には「時空を歪め、あらゆるものを飲み込む特異点」として認識されます。これこそが、数式とノイズが織りなすプロシージャル表現の最大の魅力であり、Shader が「魔法」と呼ばれる所以なのです。

5. 次なる魔法への布石 — 空間から「オーラ」へ

今回は Domain Warping という手法を用いて、キャンバスそのものを破壊し、光をねじ曲げる Post-Processing(後処理)エフェクトを実装しました。「色を塗る」のではなく、座標(UV)という「世界を観測する基準」自体をノイズで狂わせる。この発想の転換が、これほどまでに圧倒的でブラックホールのような表現を可能にすることを実感していただけたかと思います。

第3集「Shader実践編」の幕開けから、プロシージャルな「炎」、そして「特異点」と、ノイズが持つ 「無秩序で暴力的なエネルギー」 を連続して解放してきました。

しかし、ノイズが描けるのは混沌や破壊だけではありません。 次回(#23)は、少しベクトルを変えましょう。荒々しい自然現象から一歩離れ、より神秘的で、理知的な「幾何学」の領域へと足を踏み入れます。

直交座標から「極座標(Polar Coordinates)」の次元へ

次回扱うのは、キャラクターやアイテムの周囲を護るように展開し、美しく明滅する 「プロシージャルな魔法陣 / ノイズオーラ」 です。

これを実装するための鍵となるのが、私たちが普段Shaderで使っているXYの直交座標系から、中心からの「距離」と「角度」で位置を表す 「極座標系(Polar Coordinates)」 への変換です。

極座標の世界では、「円」や「回転」といった要素が極めてシンプルな数式で扱えるようになります。そこに、時間の概念を加えた「4D Simplex Noise」を流し込んでみましょう。すると、ただ回転するだけの退屈な幾何学模様が、生物のように脈打ち、神々しいオーラを放つ魔法の陣へと昇華するのです。

数式で完璧に統制された「幾何学(Order)」と、ノイズがもたらす予測不能な「揺らぎ(Chaos)」。 この相反する二つの要素が Shader の中で完璧に交差したとき、真の「魔法」が画面上に顕現します。

空間を破壊した次は、空間に秩序を描き出しましょう。 次回も、数式が描くめくるめく世界でお待ちしています。お楽しみに。