[Noise 入門 #07] Three.js + GLSLでDomain Warpingを実装する — 数式を世界に変換する

はじめに

前回は Domain Warping を理論的に整理した。

  • ノイズは値を揺らす技術ではない
  • 空間そのものを変形させる技術である
  • スカラー場をベクトル場へ拡張することで、方向性が生まれる
  • FBM と組み合わせることで、自己相似構造を破壊できる

そこまで理解した。

しかし、理論だけでは“世界”は生まれない。

数式はまだ抽象のままだ。

今回やること

今回はそれを Three.js + GLSL で実装する。

やることはシンプルだ:

  1. ベースとなるノイズを用意する
  2. 座標を歪ませる(Domain Warping)
  3. FBM と組み合わせる
  4. strength パラメータで空間を制御する
  5. 時間を加えて 4D に拡張する

理論を、GPU上で動かす。

ここから先、ノイズは概念ではない。

描画アルゴリズムになる。

1. なぜ Shader でやるのか?

Domain Warping は「値の操作」ではない。
空間そのものを変形するアルゴリズムである。

この処理は、圧倒的に GPU 向きだ。

ノイズは GPU 向き

ノイズは基本的に

$$f(\mathbf{p})$$

という形の関数であり、 座標 $\mathbf{p}$ に対して値を返す。

つまり:

  • 各ピクセルごとに独立して計算できる
  • 隣接ピクセルとの依存関係がほぼない
  • 同じ処理を大量に繰り返す

これは GPU の得意分野そのものだ。


1ピクセルごとの計算

フラグメントシェーダーでは、

  • 画面上のすべてのピクセルに対して
  • 同じコードが
  • 並列に実行される

Domain Warping も同じ。

各ピクセルがそれぞれ:

  1. 座標を受け取り
  2. ノイズを計算し
  3. 座標を歪ませ
  4. 再度ノイズを評価する

この処理は CPU でループさせるより、 GPUで並列処理した方が圧倒的に高速。


CPUではなく並列処理

CPU は:

  • ロジック制御が得意
  • 分岐が多い処理に強い

GPU は:

  • 同じ計算を大量に行う処理に強い
  • 数学関数に強い
  • ベクトル演算に最適化されている

ノイズは

  • sin
  • dot
  • mix
  • smoothstep

といったベクトル・補間・内積演算の集合体。

これは GPU のために設計されたかのような処理だ。


WebGL の本質

Three.js は JavaScript ライブラリだが、

実際に絵を描いているのは GPU である。

そして WebGL の本質は、

JavaScriptでロジックを書き、 GPUで空間を計算すること

Domain Warping を Shader で実装するということは、

「ノイズを数学として理解する」段階から

「ノイズを描画エンジンの内部処理として扱う」段階へ進むことを意味する。


ここまでで読者の立ち位置は変わる。

ノイズは“数式”ではない。

GPU上で動く、空間変形アルゴリズムである。

2. ベースとなる 2D ノイズ実装

まずはシンプルな 2D ノイズ関数を用意する。

float noise(vec2 p);

ここでは Perlin Noise を使う。

アルゴリズムの詳細は #04 で解説しているため、 今回は“理解済みの部品”として扱う。


シンプルな 2D Perlin Noise 実装例

vec2 hash(vec2 p) {
  p = vec2(
    dot(p, vec2(127.1, 311.7)),
    dot(p, vec2(269.5, 183.3))
  );
  return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}

float noise(vec2 p) {
  vec2 i = floor(p);
  vec2 f = fract(p);

  vec2 u = f * f * (3.0 - 2.0 * f); // smoothstep補間

  float a = dot(hash(i + vec2(0.0, 0.0)), f - vec2(0.0, 0.0));
  float b = dot(hash(i + vec2(1.0, 0.0)), f - vec2(1.0, 0.0));
  float c = dot(hash(i + vec2(0.0, 1.0)), f - vec2(0.0, 1.0));
  float d = dot(hash(i + vec2(1.0, 1.0)), f - vec2(1.0, 1.0));

  return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}

今回重要なのはここではない

重要なのはアルゴリズムの再理解ではない。

重要なのは:

  • 座標 p を入力すると
  • 連続的な値が返る
  • 周波数を変えれば模様が変わる

という事実だけ。

ノイズは

$$f(\mathbf{p})$$

という関数であり、 座標空間をスカラー場へ変換する。


この関数が“基準世界”になる。

次の章で、この座標を歪ませる。

ここから空間が壊れる。

3. Domain Warping の実装

まず理論を思い出す。

$$\mathbf{p'} = \mathbf{p} + f(\mathbf{p}) \cdot strength$$

しかしここで重要なのは、

空間は2次元(または3次元)であるということ。

単一の float 値では方向を持たない。

Domain Warping では、

スカラー場を ベクトル場へ拡張 する必要がある。


ベクトル場を作る

Shader では次のように書ける:

vec2 warp = vec2(
  noise(p + 0.0),
  noise(p + 10.0)
);

p += warp * strength;

ここでやっていることは単純だ:

  • noise(p) を2回評価する
  • 異なるオフセットを与える
  • それを vec2 にまとめる

これで 方向を持つ歪みベクトル が生まれる。


なぜオフセットが必要か?

もしこう書いたら:

vec2 warp = vec2(noise(p), noise(p));

x と y が同じ値になる。

つまり、対角線方向にしか歪まない。

空間が単調になる。

だから、

noise(p + 0.0)
noise(p + 10.0)

のようにオフセットを与えて、

独立した揺らぎを生成する。

これは“疑似的なベクトル場”を作るテクニック。


strength の意味

p += warp * strength;
  • strength が小さい → 柔らかい流れ
  • strength が中程度 → 有機的構造
  • strength が大きい → 空間が破壊される

ここで初めて、

「ノイズが模様を作る」のではなく

「空間が変形している」

と実感できる。

4. FBMと組み合わせる

単一のノイズでは、まだ単調さが残る。

それを壊すのが FBM(Fractal Brownian Motion)。

float fbm(vec2 p) {
  float v = 0.0;
  float a = 0.5;

  for(int i = 0; i < 5; i++){
    v += noise(p) * a;
    p *= 2.0;   // frequency を上げる
    a *= 0.5;   // amplitude を下げる
  }

  return v;
}

ここでは:

  • 周波数を倍々に増やし
  • 振幅を半分ずつ減らす

ことで、自己相似構造を持つ複雑なパターンを生成している。


Domain Warping に FBM を使う

単純な noise() の代わりに fbm() を使う。

vec2 warp = vec2(
  fbm(p + 0.0),
  fbm(p + 5.2)
);

p += warp * strength;

これで歪みベクトル自体が多層構造になる。


何が起きているのか?

1回のノイズ歪みは、滑らかな流れを作る。

FBMを使うと:

  • 小さな歪み
  • 中程度の歪み
  • 大きな歪み

が同時に混在する。

その結果、

  • 川のような流れ
  • 雲の層構造
  • 有機的な模様
  • 地形の侵食感

が現れる。


ここで初めて

“自然っぽさ”が出る。

それは偶然ではない。

フラクタル構造を持つ歪みが、 空間そのものに埋め込まれているからだ。

5. strength を変えると何が起きるか

Domain Warping は、strength によって世界の歪み量が決まる。

p += warp * strength;

たったこれだけの係数が、空間の秩序を左右する。


strength が小さい場合(例:0.1〜0.3)

  • 柔らかい流れ
  • 水面の揺らぎ
  • 軽い風のような変形

元のノイズ構造はまだ保たれている。

「歪み」というより「揺れ」。


strength が中程度(例:0.5〜1.0)

  • 有機的な模様
  • 雲の層構造
  • 地形の侵食感
  • マーブル模様のような構造

ここで一気に“自然らしさ”が出る。

ノイズはもはや単なるパターンではなく、 空間そのものが流動しているように見える。


strength が大きい(例:2.0以上)

  • 空間が裂ける
  • 自己相似構造が崩壊する
  • 渦巻き・断裂・歪曲

ここまで来ると、もはや“自然”ではない。

空間そのものが破壊される。

ブラックホール、電磁嵐、プラズマ、量子揺らぎ。

この領域に入る。


なぜここで興奮するのか

strength を変えるだけで、

  • 世界の物理法則が変わる
  • 模様の秩序が崩れる
  • 自然と異常の境界が揺れる

たった1つのスカラー値が、 空間の性質を支配している。

ここで初めて気づく。

ノイズは“模様生成”ではない。

空間設計である。


ここは必ずスクリーンショットを並べる。

  • strength = 0.2
  • strength = 0.8
  • strength = 2.0

並べた瞬間、読者は理解する。

理論が、視覚になる。

6. 時間を追加する(4D拡張)

これまで扱ってきたのは 2D 空間だった。

$$f(x, y)$$

しかしノイズは、次元を増やせる。

時間を加えることで、

$$f(x, y, t)$$

になる。


Shader での実装

p += warp * strength;
float n = fbm(p + time * 0.1);

ここで time は uniform として渡す。

uniforms: {
  time: { value: 0.0 }
}

そして useFrame で更新する:

material.uniforms.time.value = clock.getElapsedTime();

何が起きているのか?

時間は“値”ではない。

座標の一部として扱われている。

つまり、

空間そのものが時間方向にスライドしている。

これは単なるアニメーションではない。

4次元空間の断面を見ている状態だ。


なぜ自然に見えるのか?

自然界の多くは、

  • 水流

時間とともに変化する。

ノイズに時間を加えることで、

“静的模様”が

“動的構造”へ変わる。


世界が呼吸する

strength が空間の歪み量なら、

time は空間の流速。

FBM + Domain Warping + time。

これで:

  • 雲が流れ
  • 水が揺れ
  • エネルギーが渦巻く

ノイズは完全に“世界生成アルゴリズム”になる。

7. Three.js 側コード

Domain Warping のロジックはすべて Shader 内で完結する。

Three.js 側では、

  • uniform を渡す
  • time を更新する

それだけでよい。


ShaderMaterial の設定

const material = new THREE.ShaderMaterial({
  uniforms: {
    time: { value: 0.0 },
    strength: { value: 0.5 }
  },
  vertexShader,
  fragmentShader
});

ここで渡しているのは:

  • time … 4D拡張のための時間軸
  • strength … 空間歪みの強度

空間を制御するための、最小限のパラメータ。


time の更新(R3F例)

useFrame((state) => {
  material.uniforms.time.value = state.clock.getElapsedTime();
});

これで毎フレーム、時間が進む。

ノイズは再計算され、

空間が動き続ける。


本質

Three.js は「描画エンジン」ではあるが、

実際に空間を計算しているのは GPU である。

JavaScript は命令を渡すだけ。

ノイズは CPU でループしていない。

ピクセル単位で並列に評価され、

空間が変形している。


これで完成。

Domain Warping は理論ではない。

実装された、空間変形アルゴリズムである。

8. まとめ

今回の到達点は明確だ。

  • Domain Warping は理論ではない
  • 空間を書き換える GPU アルゴリズムである
  • FBM と組み合わせることで自然構造を生成できる
  • 2D → 3D → 時間へと拡張可能

ここまで来ると、

ノイズは“模様生成テクニック”ではない。

空間設計の技術になる。


値を揺らすのではない。

座標を歪ませる。

スカラー場をベクトル場に拡張し、

さらに時間を含める。

その結果、

ノイズは

  • 雲になり
  • 水になり
  • 地形になり
  • エネルギー表現になる

数学は抽象ではない。

GPU上で実行される、世界生成アルゴリズムである。


ここから先は応用領域に入る。

ノイズはもう理解した。

あとは“何を作るか”だ。


次回は、

  • ノイズで水を作るか
  • ノイズで雲を作るか
  • あるいは Curl Noise に進むか

理論は終わった。

制作のフェーズに入る。