2
3

パーリンノイズで遊んでみるゲーム。 パラメタ調整機能付き。

Last updated at Posted at 2024-09-13

WebGLによるパーリンノイズのシュミレーションについての論文風テキスト。

WebGLによるパーリンノイズと大気循環モデルにおける圧力勾配の計算

要約
本稿では、WebGLを用いて生成されるパーリンノイズと、大気循環モデル(GCM: General Circulation Model)との関連において、圧力勾配の計算がどのように利用されているかについて探求する。パーリンノイズは主に視覚的なテクスチャを生成するために用いられる一方で、大気循環モデルは気象のシミュレーションに利用される。両者は異なる目的であるにもかかわらず、圧力勾配の計算という共通の概念を持ち、その計算手法には深い類似性が存在する。

  1. パーリンノイズの圧力勾配計算

パーリンノイズは、自然界に存在する滑らかなノイズを再現するために用いられる技術であり、以下の主要な計算要素を含む:

フェード関数: ノイズの滑らかさを調整するための関数であり、圧力勾配の計算におけるスムージングと似た役割を果たす。
線形補間: ノイズの連続性を確保するために使用される手法で、異なるノイズレベルを滑らかに繋ぐ。
ランダムベクトル生成: ノイズのランダム性を生成するための手法であり、これは気象シミュレーションにおけるランダムな変数と似ている。
パーリンノイズ生成: 上記の要素を組み合わせて、ノイズパターンを生成する。
これらの要素は、視覚的な効果を得るための計算手法として機能し、圧力勾配計算の観点から見ると、滑らかな変化を再現するための基盤を提供する。

2 大気循環モデルにおける圧力勾配計算
大気循環モデルは、地球の大気の運動をシミュレーションするために、以下の計算要素を含む:

圧力勾配: 大気中の圧力差に基づいて風の力を計算し、これが気象システムの主要なドライバーとなる。
圧力と温度の分布: 大気中の圧力と温度の分布をシミュレーションし、気象現象を再現する。
風のベクトル場: 圧力勾配に基づいて風の流れを計算し、これにより大気の運動をシミュレーションする。
乱流と対流: 大気中の複雑な動きをモデル化し、圧力勾配により生成される風の影響を考慮する。
これらの要素は、気象シミュレーションにおける圧力勾配の計算に基づき、大気の動きをリアルに再現するために使用される。

  1. 類似性の考察
    3.1 スムージングと補間
    パーリンノイズのフェード関数と線形補間は、圧力勾配に基づく計算におけるスムージング処理と類似している。大気循環モデルでは、圧力勾配が風の流れを滑らかにするために用いられるのに対し、パーリンノイズではノイズの滑らかさを確保するために使用される。

3.2 ランダム性の導入
パーリンノイズにおけるランダムベクトル生成は、大気循環モデルにおけるランダムな気象変数の導入と類似している。これにより、ノイズパターンと大気中のランダムな変動の両方が生成され、自然な現象を模倣する。

3.3 数値的アプローチ
両者は、数値的なアプローチを用いて計算を行う点で共通している。パーリンノイズでは数値的な補間とランダム生成が行われ、大気循環モデルでは物理法則に基づく数値計算が実施される。この数値的アプローチによって、両者はそれぞれの目的に応じた精度を持って計算を行う。

  1. 結論
    WebGLによるパーリンノイズ生成と大気循環モデルにおける圧力勾配の計算には、スムージング、補間、ランダム性の導入などの共通の計算手法が存在する。

WebGLによるパーリンノイズと大気循環モデルとの類似性

要約
本稿では、WebGLを用いて生成されたパーリンノイズと、大気循環モデル(GCM: General Circulation Model)との類似性について考察する。両者は、スケールやオクターブ数を調整することで複雑なパターンを生成する点において共通しており、ノイズ生成技術と大気循環のシミュレーションとの関連性が示される。

  1. パーリンノイズの概要
    パーリンノイズは、グラフィックスにおける自然なテクスチャやエフェクトを生成するために広く用いられる手法である。特に、滑らかで連続的なノイズパターンを生成することができる。このノイズは、主に以下の関数で構成される:

フェード関数: ノイズのスムーズさを調整する。
線形補間関数: ノイズの連続性を保つための補間。
ランダムベクトル生成: ノイズのランダム性を実現する。
パーリンノイズ生成: 上記の要素を組み合わせてノイズを生成する。
乱流効果の生成: 複数のノイズレイヤーを組み合わせて複雑なパターンを作り出す。

  1. 大気循環モデルの概要
    大気循環モデルは、地球の大気の動きをシミュレートするために用いられる数値モデルである。これらのモデルは、以下の主要な要素を含む:

風のベクトル場: 大気中の風の流れを示す。
圧力と温度の分布: 大気の温度と圧力の変化をモデル化する。
乱流と対流: 大気中の複雑な動きや熱の交換をシミュレートする。
スケールとオクターブ: さまざまなスケールでの気象現象を捉えるために、複数の解像度を用いる。

  1. 類似性の考察
    3.1 スケールとオクターブ
    パーリンノイズと大気循環モデルの両者は、異なるスケールやオクターブ数を利用して複雑なパターンを生成する点で共通している。パーリンノイズでは、ノイズのスケールやオクターブ数を調整することで、さまざまな細かさや複雑さのノイズを生成できる。大気循環モデルでも、異なるスケール(例えば、地域的な気象と全球的な気象)を用いて大気の動きをシミュレートする。これにより、複数の解像度での気象現象を捉えることができる。

3.2 乱流と複雑なパターン
パーリンノイズの乱流効果は、複数のノイズレイヤーを組み合わせることで複雑な動きを生成する。これは、大気循環モデルにおける乱流のシミュレーションと類似している。大気中の乱流や対流は、複数のスケールでの動きが組み合わさることで複雑な気象パターンを生み出す。両者は、乱流を通じて自然な動きやパターンを再現する。

3.3 数値シミュレーション
パーリンノイズの生成には、数値的な手法が用いられる。例えば、ランダムベクトルの生成や線形補間は、数値計算に基づいてノイズを生成する。大気循環モデルでも、数値計算が用いられ、物理法則に基づいて大気の動きをシミュレートする。両者は、数値的なアプローチによって自然現象を模倣する点で類似している。

  1. 結論
    WebGLによるパーリンノイズと大気循環モデルは、スケールやオクターブ、乱流の生成において類似したアプローチを取っている。両者の共通点を理解することで、ノイズ生成技術の応用範囲や、大気循環のシミュレーションの精度向上に寄与する新たな知見が得られる。本研究の結果は、自然現象のモデル化やコンピュータグラフィックスにおけるノイズ生成の理解を深めるものである。

パーリンノイズ CPU パラメタ調整機能付き。

スクリーンショット 2024-09-13 160728.png

スクリーンショット 2024-09-13 160704.png

スクリーンショット 2024-09-13 160620.png

<!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; /* 炎の背景は黒 */
    }
    #controls {
      position: absolute;
      top: 10px;
      left: 10px;
      color: white;
    }
    #controls label {
      display: block;
      margin: 5px 0;
    }
  </style>
</head>
<body>
  <canvas id="canvas"></canvas>
  <div id="controls">
    <label for="scale-slider">ノイズのスケール:</label>
    <input type="range" id="scale-slider" min="1" max="10" step="0.1" value="5">
    <span id="scale-value">5</span>
    <br>
    <label for="octaves-slider">オクターブ数:</label>
    <input type="range" id="octaves-slider" min="1" max="10" step="1" value="5">
    <span id="octaves-value">5</span>
  </div>

  <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;
    });

    // パーリンノイズ生成用の補助関数
    function fade(t) {
      return t * t * t * (t * (t * 6 - 15) + 10);
    }

    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;
    }

    let zOffset = 0;
    let scale = 5;
    let octaves = 5;

    function getFireColor(value) {
      const v = Math.max(0, Math.min(1, value));
      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 * scale;
          const ny = y / canvas.height * scale;
          const noiseValue = fractalNoise(nx, ny, zOffset, octaves, 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);
        }
      }

      requestAnimationFrame(animate);
    }

    animate();

    document.getElementById('scale-slider').addEventListener('input', function () {
      scale = parseFloat(this.value);
      document.getElementById('scale-value').textContent = scale;
    });

    document.getElementById('octaves-slider').addEventListener('input', function () {
      octaves = parseInt(this.value, 10);
      document.getElementById('octaves-value').textContent = octaves;
    });

  </script>
</body>
</html>


パーリンノイズ GPU パラメタ調整機能付き。

スクリーンショット 2024-09-13 161221.png

スクリーンショット 2024-09-13 161248.png

スクリーンショット 2024-09-13 161306.png

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Perlin Noise with WebGL</title>
  <style>
    body, html {
      margin: 0;
      padding: 0;
      overflow: hidden;
      background-color: black;
    }
    canvas {
      display: block;
    }
    .controls {
      position: absolute;
      top: 10px;
      left: 10px;
      color: white;
    }
    .controls label {
      display: block;
      margin: 5px 0;
    }
    .controls input {
      width: 200px;
    }
  </style>
</head>
<body>
  <div class="controls">
    <label for="scale">ノイズスケール: <span id="scaleValue">1.0</span></label>
    <input id="scale" type="range" min="0.1" max="10.0" step="0.1" value="1.0">
    
    <label for="octaves">オクターブ数: <span id="octavesValue">4</span></label>
    <input id="octaves" type="range" min="1" max="10" step="1" value="4">
  </div>
  
  <canvas id="canvas"></canvas>
  
  <script type="text/javascript">
    const canvas = document.getElementById('canvas');
    const gl = canvas.getContext('webgl');

    // キャンバスサイズの設定
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    // ウィンドウリサイズ時にキャンバスを再設定
    window.addEventListener('resize', () => {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
      gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
    });

    // スライダーの初期設定
    const scaleSlider = document.getElementById('scale');
    const octavesSlider = document.getElementById('octaves');
    const scaleValue = document.getElementById('scaleValue');
    const octavesValue = document.getElementById('octavesValue');

    // スライダー値の更新
    scaleSlider.addEventListener('input', () => {
      scaleValue.textContent = scaleSlider.value;
    });
    octavesSlider.addEventListener('input', () => {
      octavesValue.textContent = octavesSlider.value;
    });

    // 頂点シェーダーコード
    const vertexShaderSource = `
      attribute vec2 a_position;
      varying vec2 v_uv;
      void main() {
        v_uv = a_position * 0.5 + 0.5;
        gl_Position = vec4(a_position, 0.0, 1.0);
      }
    `;

    // フラグメントシェーダーコード
    const fragmentShaderSource = `
      precision highp float;
      varying vec2 v_uv;
      uniform float u_time;
      uniform float u_scale;
      uniform int u_octaves;

      float fade(float t) {
        return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
      }

      float lerp(float t, float a, float b) {
        return a + t * (b - a);
      }

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

      float perlin(vec2 st) {
        vec2 i = floor(st);
        vec2 f = fract(st);

        vec2 u = f * f * (3.0 - 2.0 * f);

        return lerp(u.y, lerp(u.x, dot(random2(i), f - vec2(0.0, 0.0)),
                              dot(random2(i + vec2(1.0, 0.0)), f - vec2(1.0, 0.0))),
                    lerp(u.x, dot(random2(i + vec2(0.0, 1.0)), f - vec2(0.0, 1.0)),
                              dot(random2(i + vec2(1.0, 1.0)), f - vec2(1.0, 1.0))));
      }

      float turbulence(vec2 st, float time, int octaves) {
        float value = 0.0;
        float scale = 1.0;
        float amplitude = 1.0;

        for (int i = 0; i < 10; i++) {
          if (i >= octaves) break;
          value += amplitude * abs(perlin(st * scale + time));
          scale *= 2.0;
          amplitude *= 0.5;
        }

        return value;
      }

      void main() {
        vec2 uv = v_uv * u_scale;
        float noise = turbulence(uv, u_time * 0.3, int(u_octaves));
        vec3 color = vec3(noise * 1.0, noise * 0.0, noise * 0.0);
        gl_FragColor = vec4(color, 1.0);
      }
    `;

    // シェーダー作成関数
    function createShader(gl, type, source) {
      const shader = gl.createShader(type);
      gl.shaderSource(shader, source);
      gl.compileShader(shader);
      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error(gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
      }
      return shader;
    }

    // プログラム作成関数
    function createProgram(gl, vertexShader, fragmentShader) {
      const program = gl.createProgram();
      gl.attachShader(program, vertexShader);
      gl.attachShader(program, fragmentShader);
      gl.linkProgram(program);
      if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        console.error(gl.getProgramInfoLog(program));
        gl.deleteProgram(program);
        return null;
      }
      return program;
    }

    // 頂点とフラグメントシェーダーの作成
    const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
    const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
    const program = createProgram(gl, vertexShader, fragmentShader);

    // 頂点位置のバッファを作成
    const positionLocation = gl.getAttribLocation(program, "a_position");
    const positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    const positions = [
      -1, -1,
      1, -1,
      -1, 1,
      1, 1,
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

    // uniformの位置を取得
    const timeLocation = gl.getUniformLocation(program, "u_time");
    const scaleLocation = gl.getUniformLocation(program, "u_scale");
    const octavesLocation = gl.getUniformLocation(program, "u_octaves");

    // シェーダープログラムの使用
    gl.useProgram(program);
    gl.enableVertexAttribArray(positionLocation);
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

    // レンダリング関数
    function render(time) {
      gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
      gl.clear(gl.COLOR_BUFFER_BIT);

      // uniformに値を渡す
      gl.uniform1f(timeLocation, time * 0.001);
      gl.uniform1f(scaleLocation, scaleSlider.value);
      gl.uniform1i(octavesLocation, parseInt(octavesSlider.value));

      // 四角形を描画
      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
      requestAnimationFrame(render);
    }

    render(); // レンダリングを開始
  </script>
</body>
</html>


2
3
0

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
3