2
4

フラクタルノイズを使って、リアルな 大気 の動きアニメーション。地球シュミレータのゲーム。

Last updated at Posted at 2024-09-10

スクリーンショット 2024-09-11 050503.png

スクリーンショット 2024-09-11 050443.png

スクリーンショット 2024-09-11 050430.png

パーリンノイズ (Perlin Noise)

パーリンノイズは、コンピュータグラフィックスにおいて自然な見た目のテクスチャやパターンを生成するための技術です。1983年にケン・パーリン(Ken Perlin)によって開発され、彼の名前にちなんで名付けられました。パーリンノイズは、以下のような特徴を持っています。

特徴
滑らかな変化: パーリンノイズは、隣接するピクセル間で連続的かつ滑らかに変化するため、ランダムなノイズとは異なり、自然な見た目になります。これにより、風景や炎、雲、波などの自然なパターンの生成に適しています。

擬似乱数的: 完全なランダムではなく、各ポイントが特定のルールに従って生成されるため、見た目が滑らかで規則的になります。

計算効率が高い: グラフィックスでのリアルタイム処理にも適しており、テクスチャの生成に広く使用されています。

生成の方法
パーリンノイズは、多次元空間内のグリッドポイントにおける擬似乱数勾配ベクトルを生成し、各座標に対してこのベクトルを元に補間計算を行うことでノイズ値を得ます。この方法により、滑らかな変化が可能となります。

使用例
自然なテクスチャの生成: 雲、山、炎などの自然な現象をシミュレーションするための基本要素。
プロシージャルコンテンツ生成: ゲームや映画などで、アルゴリズムを用いて自動的に風景や地形を生成する際に使われます。

フラクタルノイズ (Fractal Noise)

フラクタルノイズは、パーリンノイズを複数のスケールで重ね合わせて作成されるノイズの一種です。複数の異なる周波数のパーリンノイズを組み合わせることで、より複雑で自然な見た目を実現します。これにより、木の葉のパターン、山脈の複雑さ、クラウドの動きなど、複雑で階層的な構造を持つ自然現象を表現することができます。

特徴
多層のノイズ: フラクタルノイズは、異なるスケールで生成されたパーリンノイズを足し合わせます。このとき、低周波(大きなスケール)のノイズに高周波(小さなスケール)のノイズを重ね合わせることで、複雑なパターンを作り出します。

オクターブ: フラクタルノイズを生成する際、複数のパーリンノイズの周波数と振幅を変化させるパターンを「オクターブ」と呼びます。各オクターブで、周波数が高いほど細かい特徴が生成され、振幅が小さいほどその特徴の影響が減少します。

自然な階層構造: これにより、地形や雲などの自然現象でよく見られる複雑な階層的な構造が再現可能です。大きなスケールでの滑らかな変化と、細かい部分での詳細な変化がバランスよく表現されます。

生成の方法
フラクタルノイズは、次の手順で生成されます。

パーリンノイズを異なるスケール(周波数と振幅)で生成します。
生成したノイズを合成し、全体のパターンを作り上げます。各スケールで異なる周波数や振幅を適用し、滑らかさと複雑さを調整します。
使用例
地形生成: フラクタルノイズは地形の生成に非常に適しており、リアルな山や丘、谷などの風景を描写する際に利用されます。
アニメーション: 雲の動きや水の流れなど、フラクタルノイズを使うことでリアルな動きの表現が可能です。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Fractal Noise Fire Simulation</title>
  <style>
    canvas {
      display: block;
      margin: 0 auto;
      background-color: black; /* 炎の背景は黒 */
    }
  </style>
</head>
<body>
  <canvas id="canvas"></canvas>

  <script>
    // Canvasの初期設定
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    // 画面サイズに合わせてキャンバスをリサイズ
    window.addEventListener('resize', () => {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
    });

    // パーリンノイズ生成用の補助関数
    // t値に対して滑らかに変化する補間関数
    function fade(t) {
      return t * t * t * (t * (t * 6 - 15) + 10);
    }

    // 線形補間(tの値でaとbの間を補間)
    function lerp(t, a, b) {
      return a + t * (b - a);
    }

    // グラデーションベクトルを使用したノイズ計算
    function grad(hash, x, y, z) {
      const h = hash & 15;
      const u = h < 8 ? x : y;
      const v = h < 4 ? y : h === 12 || h === 14 ? x : z;
      return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
    }

    // パーリンノイズの生成に必要なランダムな順列テーブルを生成
    const permutation = [...Array(256).keys()];
    for (let i = permutation.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [permutation[i], permutation[j]] = [permutation[j], permutation[i]];
    }
    const p = [...permutation, ...permutation];

    // パーリンノイズの生成
    function perlin(x, y, z) {
      const X = Math.floor(x) & 255;
      const Y = Math.floor(y) & 255;
      const Z = Math.floor(z) & 255;

      x -= Math.floor(x);
      y -= Math.floor(y);
      z -= Math.floor(z);

      const u = fade(x);
      const v = fade(y);
      const w = fade(z);

      const A = p[X] + Y;
      const AA = p[A] + Z;
      const AB = p[A + 1] + Z;
      const B = p[X + 1] + Y;
      const BA = p[B] + Z;
      const BB = p[B + 1] + Z;

      return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z),
                                    grad(p[BA], x - 1, y, z)),
                             lerp(u, grad(p[AB], x, y - 1, z),
                                    grad(p[BB], x - 1, y - 1, z))),
                    lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1),
                                    grad(p[BA + 1], x - 1, y, z - 1)),
                             lerp(u, grad(p[AB + 1], x, y - 1, z - 1),
                                    grad(p[BB + 1], x - 1, y - 1, z - 1))));
    }

    // フラクタルノイズを生成する関数
    // 複数の周波数と振幅のパーリンノイズを合成
    function fractalNoise(x, y, z, octaves, persistence) {
      let total = 0;
      let frequency = 1;
      let amplitude = 1;
      let maxValue = 0;  // 正規化用

      for (let i = 0; i < octaves; i++) {
        total += perlin(x * frequency, y * frequency, z * frequency) * amplitude;
        maxValue += amplitude;
        amplitude *= persistence;
        frequency *= 2;
      }

      return total / maxValue;  // 値を0から1の範囲に正規化
    }

    // アニメーション用の変数
    let zOffset = 0;

    // 炎の色を計算する関数
    // 入力値に基づいて炎の色をRGB値で返す
    function getFireColor(value) {
      const v = Math.max(0, Math.min(1, value));  // 値を 0.0 ~ 1.0 にクリップ

      const r = Math.floor(255 * Math.min(1, v * 2));  // 赤の成分
      const g = Math.floor(255 * Math.min(1, v * 4));  // 緑の成分(縦方向に明るさが増す)
      const b = Math.floor(255 * Math.max(0, Math.min(1, (v - 0.5) * 2)));  // 青はほぼゼロ

      return `rgb(${r},${g},${b})`;
    }

    // 計算負荷を軽減するためにピクセルごとではなくスキップする描画サイズを指定
    const stepSize = 4;

    // アニメーションフレームを更新する関数
    function animate() {
      zOffset += 0.06;  // ノイズの時間軸を進める

      // 画面をクリア
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      const imageData = ctx.createImageData(canvas.width, canvas.height);
      const data = imageData.data;

      // 各ピクセルに対してフラクタルノイズを計算し、色を設定
      for (let y = 0; y < canvas.height; y += stepSize) {
        for (let x = 0; x < canvas.width; x += stepSize) {
          const nx = x / canvas.width * 5;  // 横方向のスケールを調整
          const ny = y / canvas.height * 5; // 縦方向のスケール
          const noiseValue = fractalNoise(nx, ny, zOffset, 5, 0.5);

          const intensity = 1 - y / canvas.height;  // 高さに応じてノイズの強度を調整
          const colorValue = noiseValue * intensity;

          // 色を炎の色に変換
          const color = getFireColor(colorValue);

          // 炎の色を塗る
          ctx.fillStyle = color;
          ctx.fillRect(x, y, stepSize, stepSize);  // 描画サイズをstepSizeごとに
        }
      }

      // 次のフレームをリクエスト
      requestAnimationFrame(animate);
    }

    // アニメーションを開始
    animate();
  </script>
</body>
</html>

コードの説明
パーリンノイズ生成関数 (perlin): パーリンノイズを計算するための関数です。ノイズ生成には勾配ベクトルを使用し、補間処理を行います。

フラクタルノイズ生成関数 (fractalNoise): フラクタルノイズは、パーリンノイズを複数のオクターブで重ねることで作成します。各オクターブで周波数を倍にし、振幅を小さくすることで複雑なパターンを生成します。

アニメーション: animate 関数内で、時間軸に沿って zOffset を進めることで、水が流れているような動きを作り出します。

キャンバスに描画: 各ピクセルごとに計算されたノイズ値に基づき、色を設定します。ここでは青の濃淡を使って水のような見た目を表現しています。

アニメーションフレームの更新: requestAnimationFrame を使ってフレームごとに画面を更新し、滑らかなアニメーションを実現しています。

2
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4