[Noise 入門 #16] 4D Simplex Noise × FBM × Domain Warping — 時間を与えられたノイズが「炎」と「プラズマ」になる瞬間

はじめに

Noise 入門シリーズ第16回。

基礎層、アルゴリズム層を抜け、私たちの旅はいよいよ実践的な 「VFX(エフェクト)生成」の応用レイヤー へと突入します。

前回(#15)で手に入れた「時間(Time)」という次元を持つ 4D Simplex Noise。これに、自然界の複雑さを作る FBM(#05)、そして空間をねじ曲げる Domain Warping(#06)をすべて融合させます。

静止していたノイズが「時間」を手に入れたことで、ただの白黒の模様が「うねるプラズマ」や「燃え盛る炎」へと劇的に進化する瞬間を、GLSLの実装とともに体験していきましょう。

前回の記事:

1. 魔法のレシピ:3つの技術の完全融合

シェーダーにおける「魔法(圧倒的なビジュアル)」は、単なる要素の足し算ではなく、技術同士の掛け算から生まれます。

これから私たちが創り出す「炎」や「プラズマ」といったプロシージャル表現は、これまで学んできた3つの強力なアルゴリズムが完璧に噛み合うことで初めて成立します。それぞれの役割と、なぜこの組み合わせが「最強のレシピ」なのかを解き明かしましょう。

① 4D Simplex Noise(時間と滑らかさの土台)

役割: ループしない、無限に続く「滑らかな変化」のエンジン。

ノイズに「動き」を与える際、2Dノイズの座標をただズラす(スクロールさせる)だけでは、全体がスライドして移動するだけで「その場で形を変えて蠢く(うごめく)」ような表現にはなりません。 そこで必要になるのが、空間の3次元(X, Y, Z)に、第4の次元である「時間(Time)」を加えた 4D Simplex Noise です。

  • なぜ Perlin ではなく Simplex なのか? Perlin Noise を4次元に拡張すると、特有の「グリッド状のアーティファクト(不自然な縦横の線)」が目立ちやすくなります。正三角形(単体:Simplex)のグリッドで計算される Simplex Noise は、全方位に対して歪みが少なく、時間が経過しても「どこかで見たようなパターン」を悟らせません。

② FBM(フラクタルな複雑さ)

役割: のっぺりした模様に「自然界のディテール」を刻み込む。

Simplex Noise を単体で出力すると、ただの「ぼやけた雲の塊」にしか見えません。自然界の炎のチリチリとした先端や、プラズマの細かく弾けるようなエネルギーを表現するには、解像度が足りないのです。 そこで FBM(Fractal Brownian Motion) の出番です。

  • 自己相似性が生むリアリティ 大きな波(低周波)に、少し小さな波、さらに小さな波…と、振幅(Amplitude)を下げながら周波数(Frequency)を上げて重ね合わせる(Octave)ことで、ノイズにフラクタルな複雑さが宿ります。FBM があるからこそ、CGっぽさが消え、自然物としての「説得力」が生まれるのです。

③ Domain Warping(流体的なうねり)

役割: 空間そのものをねじ曲げ、「流れ」を生み出す。

ここがノイズを「魔法」に変える最大のブレイクスルーです。 通常のノイズは「指定した座標の色」を計算しますが、Domain Warping は 「色を取得する座標そのものを、別のノイズで歪ませる」 という入れ子構造( )を持ちます。

  • 「模様」から「流体」への進化 FBM だけでは「複雑な雲」止まりですが、座標を歪ませることで、コーヒーにミルクを注いだときのような「マーブル模様」や、エネルギーが渦を巻く「流体力学的なうねり」が発生します。これが炎の揺らめきや、プラズマの渦の正体です。

【掛け算の魔法】3つが揃うと何が起きるか?

これら3つは、どれか一つでも欠けると魔法が解けてしまいます。

  • FBM がないと: 動きは滑らかだが、プラスチックのようなのっぺりした質感になる。
  • Warping がないと: ディテールはあるが、モコモコと沸騰するだけの「ただのノイズ模様」になる。
  • 4D(Time)がないと: 複雑で美しいが、完全に静止した「絵」になってしまう。

「4D Simplex のエンジン」 で時間を動かし、「FBM のディテール」 を与え、「Domain Warping」 で空間をねじ曲げる。この3層構造を GLSL 上で構築することで、ノイズはついに「生命を宿した VFX」へと進化するのです。

2. 実装の土台:4D FBM を定義する

すべてのエフェクトのコア(核)となるのは、「時間経過で滑らかに変化しつつ、細かいディテールを持つノイズ」です。 まずは GLSL 上で、そのための関数 fbm4d を構築しましょう。前回(#15)で長大なコードを乗り越えて実装した snoise(vec4)(4D Simplex Noise)が、ここで最高のパフォーマンスを発揮します。

// ※ 4D Simplex Noise: snoise(vec4 v) は導入済みとする

float fbm4d(vec4 p) {
    float value = 0.0;
    float amplitude = 0.5;
    float frequency = 1.0;

    // 5オクターブでディテールを重ねる
    for (int i = 0; i < 5; i++) {
        value += amplitude * snoise(p * frequency);
        frequency *= 2.0; // Lacunarity(空間的な細かさを倍に)
        amplitude *= 0.5; // Gain(影響力を半分に)
    }
    return value;
}

この短い関数の中に、自然界の複雑さを生み出すフラクタル(自己相似性)の法則が詰まっています。このコードが実際に何をしているのか、少し解像度を上げて見ていきましょう。

引数 vec4 p の魔法:時間を「第4の空間」として扱う

引数には vec4(x, y, z, w) の4次元ベクトルが入ります。私たちが描画するのは2Dの画面ですが、実際にこの関数を呼び出すときは vec4(uv.x, uv.y, 0.0, time) のようにパラメーターを渡します。

空間のZ軸(奥行き)を 0.0 に固定したまま、見えない第4の次元(W軸)に time(時間)を流し込むのです。画面上の座標(X, Y)は動かさず、4次元目の方向に向かってノイズ空間を突き進むことで、「全体がスライド移動する」のではなく、「その場で沸騰するように形を変え続ける」アニメーションが生まれます。

5回の Octave が「自然の質感」を削り出す

内部の for ループは、ノイズを5回重ね合わせる(Octave)処理です。 ループを回すたびに、以下の2つの操作が同時に行われます。

  • frequency *= 2.0(Lacunarity): ノイズの波をギザギザに、細かくする。
  • amplitude *= 0.5(Gain): その波の高さ(影響力)を半分に弱める。

最初は画面全体を覆う「大きなうねり」を作り、その上に「中くらいの波」、「小さな波」、「さざ波」……と、影響力を弱めながら微細なディテールを上乗せしていきます。この足し算により、のっぺりとしたただのグラデーションが、雲や煙のようなリアルな「質感」へと進化するのです。

なぜ 3D(x, y, time)ではなく 4D を使うのか?

「2Dの画面で時間を動かすだけなら、3DノイズのZ軸に時間を渡す snoise(vec3(uv, time)) で十分では?」と思うかもしれません。

確かにその通りですが、後述する Domain Warping(空間のねじれ) を実装する際、「X軸用」と「Y軸用」で、まったく別のノイズ模様を取得したくなります。4Dノイズを使っていれば、Z軸を「別の断面を切り取るためのシード値(オフセット)」として自由に使える余裕が生まれます。 この「1次元の余裕」が、後々複雑なエフェクト(VFX)を構築する上で、圧倒的な自由度をもたらすのです。

これで、最強のエンジンである「4D FBM」が完成しました。 次はこのエンジンを使って、空間をねじ曲げ、エネルギーの奔流を描き出します。

3. うねるプラズマ(Plasma)を生み出す

先ほど作った fbm4d をそのまま画面に出力しても、グネグネと動く「ただの雲」にしかなりません。この雲を「高エネルギーの奔流」や「プラズマ」へと昇華させるには、「Domain Warping(空間の歪み)」 と 「カラーマッピング(発光表現)」 の強力なコンボが不可欠です。

空間をねじ曲げ、そこに色という魂を吹き込むプロセスを見ていきましょう。

空間を歪ませ、光の芯を与える

入力座標(UV)に別のノイズの結果を足し合わせて「ねじれ」を作り、その結果を元に色を塗っていきます。

void main() {
    vec2 uv = gl_FragCoord.xy / resolution.xy;
    float t = time * 0.2; // 時間の進行スピード

    // 1. Domain Warping用のオフセット(歪めるための力)を計算
    vec4 q = vec4(uv, 0.0, t);

    // x方向とy方向で異なるシード(適当なオフセット値)を足して別々のノイズを取得
    float warpX = fbm4d(q + vec4(1.0, 2.4, 0.0, 0.0));
    float warpY = fbm4d(q + vec4(5.2, 1.3, 0.0, 0.0));
    vec2 warpOffset = vec2(warpX, warpY);

    // 2. 歪んだ座標でメインのFBMをサンプリング
    // warpOffset に係数(2.0)をかけて歪みの強さ(Warp Strength)を調整
    float n = fbm4d(vec4(uv + warpOffset * 2.0, 0.0, t));

    // 3. カラーマッピング(暗黒 → 深紫 → シアン → 白)
    // ノイズの基本値(0.0〜1.0)を色のグラデーションに変換
    vec3 color = mix(vec3(0.05, 0.0, 0.2), vec3(0.0, 0.8, 1.0), n);

    // 4. 発光表現(ノイズ値が高い「芯」の部分だけを白く飛ばす)
    color = mix(color, vec3(1.0, 1.0, 1.0), smoothstep(0.7, 1.0, n));

    gl_FragColor = vec4(color, 1.0);
}

このコードが実行された瞬間、画面にはSF映画のワープ空間や、魔法陣に満ちるエネルギーのような「うねるプラズマ」が浮かび上がります。ここで起きている魔法のタネ明かしをしましょう。

① なぜ X と Y で別々のノイズ(異なるシード)を取るのか?

コードの warpXwarpY を計算する際、vec4(1.0, 2.4...) のような適当な数値を足しています。これは「ノイズの取得位置をズラす(別の模様を取ってくる)」ためのシード値として機能します。 もし X と Y で同じノイズ値を使ってしまうと、画面は「斜め45度」の方向にしか歪みません。X方向にはX方向の乱気流、Y方向にはY方向の乱気流を与えることで、初めて「渦を巻くような流体的なうねり」が生まれるのです。

② warpOffset * 2.0(Warp Strength)の役割

歪ませた座標でメインのノイズを取得する際、warpOffset2.0 を掛けています。これは「空間をどれくらい激しくねじ曲げるか」というパラメータ(Strength)です。 この値を 5.010.0 に上げると、模様が引きちぎれるほど激しく歪み、大理石(マーブル)のような鋭い層が現れます。逆に 0.5 くらいに下げると、穏やかな水面のような揺らぎになります。

③ smoothstep が生み出す「疑似ブルーム(発光)」

プロシージャルな絵作りにおいて、配色は命です。 mix 関数で「暗い紫」から「明るいシアン」へのグラデーションを作った後、最後にもう一度 mixsmoothstep を使っています。 smoothstep(0.7, 1.0, n) は、「ノイズの値が 0.7 以上の部分だけを抽出し、滑らかに 1.0 へ近づける」という処理です。これにより、ノイズの「山の頂上(エネルギーが最も高い部分)」だけが純白に塗りつぶされ、ポストプロセス(後処理)のブルームエフェクトを使わなくても、自ら強烈に発光しているような視覚効果を得ることができます。


この「Warping による流体化」と「ピークの白飛び(発光)」のテクニックは、魔法陣、レーザービーム、フォースシールドなど、あらゆる VFX の基本となる超重要テクニックです。

4. 燃え盛る炎(Procedural Fire)への進化

プラズマは全方位へ拡散するエネルギーでしたが、現実の「炎」には明確な「上昇する方向(ベクトル)」と、上へ行くほど消えていく「寿命(消滅)」が存在します。

この2つの物理的な振る舞い(ベクトルとマスク)をノイズの数式に組み込むことで、テクスチャ画像を一切使わずに、GPUの計算だけで燃え盛る Procedural Fire(手続き型の炎) を錬成することができます。

void main() {
    // 画面の正規化座標(0.0 〜 1.0)
    vec2 uv = gl_FragCoord.xy / resolution.xy;

    // 1. 炎が上に昇る動きを作る(動く座標)
    vec2 moveUv = uv;
    moveUv.y -= time * 0.5; // 時間経過でY座標をマイナス方向へズラす

    // 2. Warpingの適用(激しくうねる気流の表現)
    // スクロールする座標(moveUv)を使ってノイズを取得
    float warp = fbm4d(vec4(moveUv * 3.0, 0.0, time * 0.3));
    float n = fbm4d(vec4(moveUv * 2.0 + warp, 0.0, time * 0.4));

    // 3. 炎の形を整えるマスク(静止した座標)
    float mask = 1.0 - uv.y;
    n = n * mask; // 上(uv.yが1.0)に行くほどノイズの値が0に減衰する

    // 4. 温度を表現するカラーマッピング(黒 → 赤 → 橙 → 黄 → 白)
    vec3 color = mix(vec3(0.0), vec3(0.8, 0.1, 0.0), smoothstep(0.0, 0.3, n));
    color = mix(color, vec3(1.0, 0.5, 0.0), smoothstep(0.3, 0.6, n));
    color = mix(color, vec3(1.0, 0.9, 0.2), smoothstep(0.6, 0.9, n));
    color = mix(color, vec3(1.0, 1.0, 1.0), smoothstep(0.9, 1.0, n));

    gl_FragColor = vec4(color, 1.0);
}

このコードを実行すると、画面の底からメラメラと燃え上がる炎が出現します。 単なるノイズが「炎」として成立している裏側には、シェーダーならではの巧妙なテクニックが隠されています。

① 上昇気流を生み出す「引き算」

moveUv.y -= time * 0.5 という処理に注目してください。 時間を「引く」ことで、ノイズを取得する座標はどんどん下へと移動していきます。カメラが下へ向かって移動すると、相対的に映っている景色は「上へ向かってスクロール」して見えますよね。これが炎の上昇気流の正体です。

② 「動く座標」と「静止した座標」の使い分け(超重要)

このコード最大のポイントは、マスク処理にはスクロールしていない元の uv.y を使っている点です。

  • 模様(ノイズ) は moveUv を使って上へスクロールさせる。
  • マスク(グラデーション) は uv を使って画面に固定する。

画面の下端(uv.y = 0.0)では mask1.0 となり、ノイズがそのまま出力(発火)されます。しかし、画面の上端(uv.y = 1.0)に近づくにつれて mask0.0 に近づきます。 n * mask と掛け算をすることで、「スクロールして上がってきたノイズが、上部に到達すると徐々に燃料を失って闇に消えていく」という完璧な炎のシルエットが完成するのです。

③ 温度を視覚化する「カラーランプ」

最後は mixsmoothstep を何重にも重ねたカラーマッピングです。 これはCGソフトにおける「カラーランプ(Color Ramp)」ノードを数式で再現したものです。ノイズの値を「温度」に見立てています。

  • 0.0 〜 0.3(外側の冷たい部分): 暗闇から赤い炎へ
  • 0.3 〜 0.6(少し内側): 赤からオレンジへ
  • 0.6 〜 0.9(高温部): オレンジから黄色へ
  • 0.9 〜 1.0(中心のコア): 黄色から純白へ

このように閾値(スレッショルド)を区切って色を混ぜ合わせることで、Warping で引きちぎられたノイズの形に沿って、本物の炎のような複雑な色の層が生まれます。

テクスチャ画像(jpgやpng)を使わず、すべて数学関数で描画しているため、どれだけカメラを近づけてもピクセルが荒れることはありません。 これが、Procedural(手続き型)アプローチの最大の強みです。

5. 空間の歪み(UV Distortion)としてのノイズ — 見えない力場を操る

これまでは、計算したノイズの値をそのまま「色(グラデーション)」として画面に出力してきました。しかし、実際のゲーム開発や高度な VFX において、ノイズの最も強力な使い道は別にあります。

それは、生成した「うねるノイズ値」を色として使うのではなく、「すでに描画されている画像(テクスチャ)を歪ませるための力」として使う手法です。

いわゆる、真夏のコンクリートの上に立ち込める「陽炎(かげろう)」や、剣を激しく振ったときの「空間歪曲エフェクト」、水中の「屈折」などの表現です。

void main() {
    vec2 uv = gl_FragCoord.xy / resolution.xy;

    // 1. ノイズを使って「歪ませる方向と強さ(ベクトル)」を計算
    vec2 warpOffset = vec2(
        fbm4d(vec4(uv * 3.0, 0.0, time)),
        fbm4d(vec4(uv * 3.0 + 100.0, 0.0, time))
    );

    // 2. 元のUV座標に、ノイズ由来のオフセットを足し引きする
    // 0.05 は「歪みの強さ(Distortion Strength)」
    vec2 distortedUV = uv + (warpOffset * 0.05);

    // 3. 歪んだ座標を使って、背景画像(テクスチャ)をサンプリング
    vec4 texColor = texture2D(u_texture, distortedUV);

    gl_FragColor = texColor;
}

このわずかなコードで、単なる1枚の静止画(u_texture)が、まるで熱気で揺らいでいるかのようにグニャグニャと歪み始めます。

なぜ画像が歪むのか?

通常、画像を表示する際は texture2D(u_texture, uv) のように、ピクセルが元々あるべき正しい座標(uv)の色を取得します。 しかし、ここにノイズの値を足し合わせる(distortedUV)とどうなるでしょう?

あるピクセルは「少し右の色」を取得し、隣のピクセルは「少し下の色」を取得するようになります。この「取得する位置のズレ」が時間とともに滑らかに変化していくため、人間の目には「空間そのものが歪んで揺らめいている」ように錯覚するのです。

このように、ノイズはただ模様を描くだけの道具ではありません。光を屈折させ、空間をねじ曲げ、別の要素を操作するための「見えない力場(フォース)」としても機能します。これが Shader(GLSL) におけるノイズ表現の真骨頂です。

まとめ:ノイズが「アート」になる瞬間

ここまで、本当に長い道のりでした。

第1回の「ただの滑らかな乱数」から始まり、グリッドを引き、勾配ベクトルを理解し、難解なアルゴリズムの沼を抜け……ついに私たちは、数学の力だけで「燃え盛る炎」や「うねるプラズマ」を画面上に召喚できるようになりました。

今回掛け合わせた3つの技術は、これからの Shader / VFX 制作において、あなたを支える「最強の基本装備」になります。

  • 4D Simplex Noise:途切れることのない「時間」の進行(無限の命)
  • FBM:フラクタル構造による「自然なディテール」の付与(リアリティ)
  • Domain Warping:空間の「ねじれ・流体化・歪曲」(魔法の躍動感)

これらが GLSL のピクセルシェーダー上で完璧に噛み合ったとき、単なる白黒の数式は、視覚的なアート(Procedural VFX)へと昇華します。 ベースとなる仕組みは同じでも、色(カラーマッピング)を変え、Warping の強さ(係数)を変えるだけで、魔法陣、ブラックホール、オーロラ、毒の沼など、あらゆるエフェクトを自らの手で錬成できるはずです。

数式で世界を創る楽しさを、ぜひ手元で遊んで体感してみてください。


次回予告:Voronoi への回帰と Cellular Dynamics(仮)

Simplex Noise 系の掛け合わせによって、「滑らかな流体」の表現は一つの極地に達しました。 次回(#17)は、再び「Voronoi / Worley Noise(#12, #13)」の幾何学的な世界へ戻り、さらに深い深淵へと潜ります。

静止していた Voronoi の細胞(Cell)に「時間(Time)」という第4の次元を与えたら、細胞たちはどう分裂し、蠢き始めるのか? そして、Simplex Noise の「滑らかなうねり」と、Voronoi の「幾何学的な鋭さ(ヒビ割れ)」を混ぜ合わせると、一体どんな未知のキメラ(複合マテリアル)が生まれるのか?

次なるテーマは、ノイズの更なる応用「ノイズ同士の融合(Noise Blending)」です。 生物的な鼓動と無機物な冷たさが交差する、新たなプロシージャル表現に挑戦します。お楽しみに!