loading="lazy"

はじめに

Noise 入門シリーズ第21回、いよいよ第3集「Shader(GLSL)実践編」が開幕します。これまで学んだ FBM や Domain Warping などのアルゴリズムを総動員し、GPU の力で画面上に「燃え盛る炎」を錬成します。数式を視覚的な魔法(VFX)へと昇華させる最初の一歩を踏み出しましょう。

前回の記事:

1. 数学から魔法へ — 第3集「Shader 実践編」の始まり

これまでの全20回にわたる連載を通じて、私たちはノイズの深淵を覗き込んできました。

第1集ではグリッドや勾配ベクトルといった「数学の基礎」を固め、第2集では FBM や Domain Warping、Simplex Noise といった「高度なアルゴリズムの沼」を泳ぎ切りました。ここまではいわば、世界を形作るための「見えない骨組み」を理解するフェーズでした。

しかし、ここから始まる第3集「Shader(GLSL)実践編」は全くの別次元です。 これまで一つひとつ積み上げてきた論理と数式を、今度は GPU の圧倒的な並列処理能力を使って、キャンバス(画面上の全ピクセル)へ直接叩きつけます。純粋な数学が、目に見える「圧倒的な視覚効果(VFX)」へと昇華する――まさにエンジニアリングが「魔法」へと変わるフェーズへの突入です。

なぜ最初の魔法に「炎」を選ぶのか?

記念すべき第3集の第一歩として「プロシージャルな炎(Procedural Fire)」を錬成するのには、明確な理由があります。

  • テクスチャからの完全な脱却: 画像素材(PNGやJPG)に頼る必要はもうありません。数式とノイズの組み合わせだけで、自然界の複雑で有機的な現象をゼロから生み出せることを、あなた自身の目で確認するためです。
  • 「時間(Time)」と「FBM」の真価を体感する: これまで学んできた FBM による「空間のゆらぎ」に、時間の経過(4Dノイズ)という要素が加わったとき、静止していたノイズはどうなるのか。それが「燃え上がるエネルギー」へと変貌する過程を、最も直感的に味わえるのが炎の表現です。
  • すべてのエフェクトの「祖」となる: 炎を作るアルゴリズムは、プロシージャル表現の強力なベースになります。ここでの数式や色使い(Color Mapping)を少し書き換えるだけで、毒の沼、プラズマ、オーラ、魔法陣など、無数のエフェクトへ派生させることができるのです。

画像ファイルを読み込んで表示させるだけの時代はここで終わります。 これより先は、コードという呪文(Shader)だけで、揺らめき、燃え上がる炎を自らの手で錬成していきます。

GPUの力を解放する準備はいいですか?

2. 炎を錬成する「3つのステップ」

「画面上に炎を描画する」と聞くと、とても複雑な物理シミュレーションや、高度な流体力学の計算が必要に思えるかもしれません。(もちろん、Curl Noise を使った流体シミュレーションのアプローチもありますが、それは第8回で触れましたね)

しかし、ゲームやリアルタイムWeb表現(WebGL)で使われる「プロシージャルな炎」のプロセスは、驚くほどシンプルで論理的です。大きく分けて、以下の 「3つの要素の掛け算」 だけで完成します。

魔法のレシピを分解してみましょう。

① SDF(符号付き距離関数):炎のベースとなる「芯(形)」を作る

キャンバス全体にノイズを散りばめるだけでは、ただの「モヤ」になってしまいます。炎には、下部が太く、上に行くほど細く消えていくという「形」が必要です。 ここで登場するのが SDF(Signed Distance Field:符号付き距離関数) です。数学的な距離計算を用いて、画面の中心付近に「涙型(ティアドロップ)」や「カプセル型」のベース形状を定義します。これが炎が燃え上がるための「芯」となります。

② 4D Noise & FBM:時間を操り、形を「熱のゆらぎ」で浸食する

綺麗な「芯」ができたら、そこに狂いを与えます。ここで第2集で学んだ FBM(Fractal Brownian Motion) が火を噴きます。 上方向へスクロールするベクトル(時間経過:Time)をノイズに掛け合わせることで、「熱が上へ昇っていく流れ」を作ります。そして、この複雑にうねるノイズを使って、先ほど作ったSDFの形を「削り取る(引き算する)」のです。 これにより、綺麗な涙型だった芯の輪郭がランダムに浸食され、炎特有の「千切れて上空に消えていく」ような視覚的説得力が生まれます。

③ Color Mapping:白黒の数値を「温度(色)」に変換する

ここまでの計算結果は、すべて 0.0 から 1.0 の間の「白黒の数値データ」に過ぎません。魔法を完成させる最後のピースが「色」です。 ノイズによって削り取られた数値の強弱を、「熱のグラデーション」に見立てます。 GLSL の mix 関数や smoothstep 関数を駆使して、数値が低い部分(外側)は黒や透明に、中間の部分は赤やオレンジに、そして数値が最も高い部分(芯の中心)を白熱する黄色や白にマッピングします。


たったこれだけです。 「形を作り(SDF)、ノイズで削り(FBM)、色を塗る(Color Mapping)。」

この3つのステップを順番に GLSL のコードに翻訳していくことで、誰でもブラウザ上に炎を召喚できるようになります。

それでは、エディタを開いてください。 Step 1 の「芯」の作成から、実際に数式を打ち込んでいきましょう。

3. Step 1: SDFで炎の「芯」を作る

いよいよコードを書いていきます。 何もない真っ暗な空間に、まずは炎が燃え上がるための「ベースとなる領域」を定義します。ここでは、画面の下から上に向かって燃え上がるための「芯」を作ります。

キャンバス全体を均等に燃やすわけにはいきません。炎が存在できる範囲を、SDF(Signed Distance Field:符号付き距離関数)の考え方を応用して数学的にくり抜きます。

// 1. 画面中央を原点(0.0)としたUV座標に変換
vec2 uv = gl_FragCoord.xy / resolution.xy;
uv = uv * 2.0 - 1.0;

// 2. 炎のベース形状(SDFの応用)
float shape = length(vec2(uv.x, max(0.0, uv.y))) - 0.5;

たったこれだけのコードですが、中で起きていることを分解して理解しましょう。

座標空間の正規化(UVの変換)

最初の2行は、Shaderを書く際のお決まりの呪文です。 通常、画面の左下を (0.0, 0.0)、右上を (1.0, 1.0) とするUV座標を、uv * 2.0 - 1.0 と計算することで、画面の中央を (0.0, 0.0) とし、端から端までが -1.01.0 となる座標系に変換しています。これで、画面の中央を基準にして形を作りやすくなります。

max(0.0, uv.y) が生み出す「燃え上がる土台」

ここがこの数式の面白いところです。単純な真円を描くなら length(uv) - 0.5 で済みますが、それだと宙に浮いた火の玉になってしまいます。

炎は下から上へと立ち昇るため、下半分の形状は真っ直ぐな柱のようにし、上半分の先端に向かって丸みを帯びるようにしたいのです。 そこで uv.y(縦方向の座標)に対して max(0.0, uv.y) を適用します。

  • 画面の下半分(uv.y がマイナス): 強制的に 0.0 として計算されるため、X軸の距離だけが測られ、真っ直ぐな帯(柱)になります。
  • 画面の上半分(uv.y がプラス): そのまま距離が計算されるため、綺麗な半円のドーム状になります。

結果として、画面下部にどっしりと根を下ろし、上に向かって丸く閉じる「カプセルのようなドーム形状(芯)」の距離場(Distance Field)が生成されます。


まだ「ただのぼんやりした塊」に過ぎない

この shape の値をそのまま画面に出力すると、中央が黒く(数値がマイナス)、外側に向かって白く(数値がプラス)なる、のっぺりとしたグラデーションの塊が表示されるだけです。炎の美しさも、複雑さも、インタラクティブ性もまだありません。

しかし、SDFが生み出したこの「滑らかなグラデーション(距離の数値)」こそが、次のステップでノイズと噛み合うための最高のキャンバスになります。

ベースの形は整いました。 次はこの退屈な形状に、4Dノイズを使って「熱のゆらぎ(狂い)」を与え、一気に炎の形へと削り出していきましょう。

4. Step 2: ノイズで「熱のゆらぎ」を与える

先ほど作った「芯(SDF)」は、そのままでは静止した氷の塊のような、冷たくて退屈な形状です。ここに「熱」と「動き」というエネルギーを注入しましょう。

ここが私たちの最大の武器、FBM(Fractal Brownian Motion)と時間(Time)の出番です。 テクスチャ(画像素材)に頼らず、数式だけで「燃え盛るようなベクトル(動き)」を作り出し、先ほどのベース形状を削り取っていきます。

// 時間経過とともに上方向にスクロールするオフセット
vec2 scroll = vec2(0.0, -time * 1.5);

// FBMを用いて複雑なゆらぎを生成(第5回・第16回の知識!)
float noiseVal = fbm(uv * 3.0 + scroll);

// ベースの形からノイズを引き算し、輪郭を侵食させる
float fireMask = shape - noiseVal * 0.8;

この3行のコードの中で、エフェクトの「生命感」を生み出すための重要な計算が行われています。一つずつ解き明かしていきましょう。

① scroll:熱は上へと昇る(時間のベクトル化)

炎は常に上方向へ向かって燃え上がります。この「熱対流」の動きをシミュレーションするために、Y軸に対してマイナス方向へ進むベクトル scroll を作ります。 -time * 1.5 とすることで、時間が進むにつれてUV座標のサンプリング位置が下へ下へと移動します。結果として、画面上では「ノイズの模様が上に向かってハイスピードでスクロールしていく」という視覚的な錯覚(アニメーション)が生まれます。

② fbm():自然界の複雑さを呼び出す

第5回で学んだ FBM の知識がここで火を噴きます。 ただの Perlin Noise や Simplex Noise を使うと、風船のように「のっぺり」とした不自然な揺れになってしまいます。しかし FBM を使うことで、低周波の大きなうねり(炎の大きな揺らめき)と、高周波の細かなノイズ(炎の先端が千切れるようなディテール)が重なり合い、一気に自然界の複雑な「ガスやプラズマのゆらぎ」に近い質感が得られます。 uv * 3.0 で座標をスケール(拡大)しているのは、炎のディテール(細かさ)を調整するためです。

③ fireMask:引き算による「侵食の魔法」

ここが最も面白く、本質的な部分です。 Step 1 で作った滑らかなドーム型の距離空間(shape)に対して、先ほど作った激しく動くノイズ(noiseVal)を引き算します。

SDFの「形」からノイズの「数値」を引く(または足す)とどうなるか? 綺麗なグラデーションだった空間がノイズの模様に合わせて歪み、「値の境界線(輪郭)」が不規則にえぐり取られるのです。* 0.8 はその侵食の強さ(ノイズの影響力)を決めるパラメータです。

この引き算により、ドーム状だった芯の表面がランダムに削られ、上に向かって千切れながら消えていく「炎のマスク(モノクロのシルエット)」が遂に完成します。


ここまでの計算で、画面には「メラメラと燃え上がる白黒のシルエット」が浮かび上がっているはずです。動きのリアリティは申し分ありません。

あとは、この白黒の世界に「温度」を与えるだけです。 最後の仕上げである、Step 3 の「Color Mapping(着色)」へ進みましょう。

5. Step 3: 数値を「熱」に変換する(Color Mapping)

ここまで計算してきた fireMask は、まだ 0.0 から 1.0 付近を漂う「白黒の数値データ(スカラー値)」に過ぎません。このモノクロのシルエットに、いよいよ「温度」という名の魔法を吹き込みます。

プロシージャル・シェーディングにおける着色(Color Mapping)は、絵の具を塗るのではなく「数値の閾値(しきいち)をグラデーションに変換する」というアプローチをとります。GLSLの強力な組み込み関数である mixsmoothstep を組み合わせることで、ノイズの数値を「炎の温度」に見立てていきます。

// 炎のカラーパレット(温度のグラデーション)
vec3 colorBg = vec3(0.0, 0.0, 0.0);       // 黒(背景・完全に冷えた状態)
vec3 colorRed = vec3(0.8, 0.2, 0.1);      // 赤(外縁・燃え広がり)
vec3 colorYellow = vec3(1.0, 0.8, 0.2);   // 黄(本体・強い熱)
vec3 colorWhite = vec3(1.0, 1.0, 0.9);    // 白(中心・最高温の芯)

// マスクの値に応じて色を多重ブレンドしていく
vec3 finalColor = mix(colorBg, colorRed, smoothstep(0.0, 0.3, fireMask));
finalColor = mix(finalColor, colorYellow, smoothstep(0.3, 0.7, fireMask));
finalColor = mix(finalColor, colorWhite, smoothstep(0.7, 1.0, fireMask));

gl_FragColor = vec4(finalColor, 1.0);

この数行のコードが、エフェクトの「見た目の美しさ」を決定づける最も重要な部分です。本質的なロジックを分解してみましょう。

smoothstep が生み出す「温度の境界線」

smoothstep(edge0, edge1, x) は、値 xedge0 から edge1 の間にあるとき、0.0 から 1.0 へと滑らかに変化するカーブ(エルミート補間)を返す関数です。 ここでは fireMask(ノイズで削られた炎の数値)を 3つの温度帯 に切り分けています。

  • smoothstep(0.0, 0.3, fireMask):マスク値が 0.0〜0.3 の低い領域。つまり、炎の外側の薄い部分です。
  • smoothstep(0.3, 0.7, fireMask):マスク値が 0.3〜0.7 の中間領域。炎が激しく燃え盛っている本体部分です。
  • smoothstep(0.7, 1.0, fireMask):マスク値が 0.7〜1.0 の高い領域。SDFの最も内側にあたる、超高温の「芯」です。

mix による「色の多重上書き」

あとは、切り出した温度帯に対して mix(色A, 色B, ブレンド率) を使って順番に色を上書き(ブレンド)していくだけです。

  1. まず、真っ黒な背景(colorBg)の上に、外縁の赤(colorRed)を乗せます。
  2. 次に、その結果に対して、中間の黄色(colorYellow)を重ねてブレンドします。
  3. 最後に、最も数値が高い中心部分だけを、白熱する白(colorWhite)で強く塗りつぶします。

この多重ブレンドによって、現実の「黒体放射(温度が高くなるほど赤→黄→白と発光する現象)」を数学的にシミュレーションしています。


コードをコンパイルしてみてください。 暗闇の中に、中心が白く輝き、外側に向かって赤く千切れながら上空へ消えていく、完全にコントロール可能な「プロシージャルな炎」が具現化したはずです。画像ファイル(テクスチャ)は1ピクセルも使っていません。すべては純粋な数学とノイズの力です。

6. 次なる魔法への布石 — 数式が描く無限のバリエーション

一見すると高度で「魔法」のように見える炎のエフェクトも、その本質を辿れば「形(SDF)」+「ノイズのゆらぎ(FBM)」+「色のマッピング(Color)」という、極めて論理的な要素の組み合わせに過ぎないことが実感できたはずです。

テクスチャ画像を一切使わないこのプロシージャル手法の最大の強みは、その「圧倒的な汎用性とインタラクティブ性」にあります。 今回は「炎」を作りましたが、コードの数式やパラメータをほんの少し書き換えるだけで、このアルゴリズムは全く別のエフェクトへと姿を変えます。

  • 毒の沼を作る: scroll の方向を縦から「横」に変え、カラーパレットを黒・紫・緑に変更する。
  • プラズマの球体を作る: ベースのSDFを涙型から「真円(length(uv) - 0.5)」に戻し、FBMの周波数(frequency)を上げて青白く発光させる。
  • 魔法陣のオーラを作る: ノイズの値にサイン波(sin)を掛け合わせ、時間経過とともに明滅する光の輪を生み出す。

さらに、JavaScript側(Three.jsなど)からマウスの座標やオーディオ(音響解析データ)の数値を Uniform 変数として Shader に渡せば、ユーザーの操作や音楽のビートに合わせて激しく燃え上がる、完全にインタラクティブな表現すら容易に実装できます。これが、あらかじめ用意された動画や画像では決して真似できない「数式で世界を描く」ことの真の力です。

第3集「Shader実践編」の幕開けとして、まずは基礎となるエフェクトの祖「炎」を錬成しました。 次回(#22)は、この熱量をさらに別次元へと引き上げます。炎のような局所的なエフェクトから一歩踏み出し、キャンバス(空間)そのものを歪曲して光さえも飲み込む「空間をねじ曲げるブラックホール」を実装します。

第6回で学んだ「Domain Warping」の真骨頂を、画面全体に作用する Post-Processing(後処理)エフェクトとしてお見せしましょう。


[次回予告] [Noise 入門 #22] 空間を喰らうブラックホール — 画面全体をねじ曲げる Post-Processing Noise