2
4

p5.jsを用いた大規模な3D地形ビジュアライゼーションシステム。

Last updated at Posted at 2024-09-19

スクリーンショット 2024-09-20 042938.png

スクリーンショット 2024-09-20 042950.png

スクリーンショット 2024-09-20 042924.png

大規模3D地形ビジュアライゼーションシステムの設計と実装

  1. はじめに
    コンピュータグラフィックスにおける地形生成は、ゲーム開発やシミュレーションにおいて重要な役割を果たす。特に、広範囲な3D地形をリアルタイムで描画することは、計算資源の効率的な活用と正確なアルゴリズムが要求される。本論文では、p5.jsを用いた大規模な3D地形ビジュアライゼーションシステムを設計・実装し、画面いっぱいにリアルタイムで地形を描画する手法について述べる。

  2. システム設計
    2.1 ノイズベースの地形生成
    地形生成の主な手法として、Perlinノイズを使用する。ノイズを基に地形の高さを計算し、ノイズ値に基づいて様々な地表を表現する。この手法により、自然で滑らかな地形が形成される。

ノイズのスケールを 0.003 に設定し、細かな変化が滑らかに表現されるようにした。また、時間依存のノイズを使用することで、アニメーションをつけた動的な地形変化が可能となる。

2.2 ボックスによる地形モデル化
地形は、一定のサイズを持つボックスの集合体として表現される。ボックスは 20×20 ピクセルのサイズを持ち、合計100×100個のボックスが配置され、合計1万個のボックスがワールド全体に配置される。これにより、非常に広大な3D地形を作成し、カメラ位置の調整によって全体を見渡すことが可能である。

2.3 色分けによる地形の分類
ノイズ値に基づいて、地表の種類(岩、砂、草、森林、海)が色で表現される。色分けは以下の基準に従う。

岩 (Rock):ノイズ値が低い部分は、堅牢な岩地として表現される。これは、暗めの青灰色で描画される。
砂 (Sand):中間のノイズ値の範囲にある部分は、砂地として設定され、薄い茶色で表現される。
草 (Grass):砂地から徐々に草地へ変わる部分は、緑色で描画され、より自然な地形の変遷を再現する。
森林 (Forest):高いノイズ値の部分には森林が生い茂り、より濃い緑で描かれる。また、木のモデルも配置され、立体的な表現がなされる。
海 (Sea):海面下の領域には半透明の水色が設定され、水中の視覚表現も考慮される。
3. 実装の詳細
3.1 カメラ制御
カメラは地形全体を見渡せるように、一定の距離を保ちながらワールドを回転させる。具体的には、カメラはシーンの中心を見つめたまま、一定の軌道上を周回する。この手法により、ユーザは地形の全体像を容易に把握でき、画面いっぱいにリアルな地形が描画されるように設計した。

3.2 3Dオブジェクトの描画
各ボックスの高さはノイズ値に基づき変化し、ボックス全体が高さに応じてスケールされる。特に、ボックスの高さは地形の特徴を強調するために、ノイズ値を 1 から 300 の範囲でスケーリングしている。これにより、高さの差が大きくなる地形をリアルに表現することが可能となる。

さらに、地形の各部に木を配置し、特に森林地帯では幹と葉のモデルが追加される。これにより、地形が平坦な地表だけでなく、立体的な自然環境として視覚的に表現される。

3.3 高さのアニメーション
高さのスケールを時間に応じて徐々に変化させ、地形が呼吸するかのような微妙な変化を与える。この動きにより、単なる静的な地形から、動的で生命感のあるワールドへと昇華させた。

  1. 結果
    本システムを通じて、リアルタイムで動的な地形描画が可能となった。画面いっぱいに広がる地形は、ユーザに臨場感のある視覚的体験を提供する。さらに、ノイズベースの地形生成により、多様な地形を自動的かつ簡易に生成できることが確認された。

  2. まとめと今後の課題
    本研究では、ノイズベースのアルゴリズムを用いて大規模な3D地形ビジュアライゼーションシステムを設計・実装した。このシステムにより、効率的に広範囲な地形をリアルタイムで描画することが可能となった。今後の課題としては、生成される地形の複雑さをさらに高め、異なる地形パターンの追加や、気象変動などの動的な環境変化を取り入れることが挙げられる。これにより、さらにリアリティのある仮想環境の構築が期待される。

参考文献

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>大規模な3D地形ビジュアライゼーション</title>
  <!-- p5.jsライブラリの読み込み -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
</head>
<body>
  <script>
    // ワールドのサイズを調整
    const numBoxes = 80; // ボックス数を増やしてワールドを大きく
    const sideLength = 10;

    // 地形の高さの範囲を設定
    const minHeight = 1;
    const maxHeight = 200; // より高い地形

    // ノイズ生成のパラメータ
    const noiseOffset = 100;
    const noiseScale = 0.003; // よりスムーズな地形に
    const timeScale = 0.0002;
    const rotateScale = 0.00005;

    // 地形のレベル
    let seaLevel = 0.4;
    const rockLevel = 0.25;
    const sandLevel = 0.5;
    const treeLevel = 0.75;

    // 色の設定
    const rockColour = "#62718E";
    const sandColour = "#D4A463";
    const grassColour = "#90A944";
    const forestColour = "#6D973E";
    const seaColour = "#1098A688";
    const seaColourSolid = "#1098A6";
    const trunkColour = "#886622";
    const leafColour = "#468343";

    // カメラの設定
    let cam;
    let heightScale = 1;
    let heightDir = 0.05;

    function setup() {
      // ウィンドウ全体にキャンバスを描画
      createCanvas(windowWidth, windowHeight, WEBGL);
      
      // カメラの位置を設定
      cam = createCamera();
      const camHeight = -numBoxes * sideLength * 0.8;
      const orbitRad = numBoxes * sideLength * 1.5;  
      cam.setPosition(0, camHeight, orbitRad);
      cam.lookAt(0, 0, 0);
      
      noStroke(); // ラインなしで描画
    }

    function draw() {
      background(200); // 背景色設定
      
      const t = millis(); // 経過時間を取得
      
      // 高さのスケールを更新
      heightScale = constrain(heightScale + heightDir, 0, 1);
      
      // 回転させてワールド全体を観察可能に
      rotateY(-t * rotateScale);
      
      // 照明設定
      ambientLight(150, 150, 150);
      directionalLight(200, 200, 200, -1, 0.75, -1);
      
      // 地形を描画
      drawTerrain(t);
    }

    function keyReleased() {
      // "h"キーを押すと高さのアニメーションの方向を反転
      if(key === "h") {
        heightDir *= -1;
      }
    }

    function drawTerrain(t) {
      // 各ボックスの位置に基づいて地形を描画
      for(let i = 0; i < numBoxes; i ++) {
        for(let j = 0; j < numBoxes; j ++) {
          const x = (i * sideLength) - (numBoxes * sideLength) / 2;
          const z = (j * sideLength) - (numBoxes * sideLength) / 2;

          drawBox(x, z, t); // 各ボックスを描画
        }
      }

      // 海の描画(2Dモードでない場合のみ)
      if(heightScale != 0) {
        push();
        const waterHeight = getBoxHeight(seaLevel);
        const waterSize = numBoxes * sideLength - 0.1; // 水のサイズ設定
        fill(seaColour); // 海の色を設定
        translate(-sideLength/2, -waterHeight/2, -sideLength/2);
        box(waterSize, getBoxHeight(seaLevel), waterSize); // 水を描画
        pop();
      }
    }

    function drawBox(x, z, t) {
      // ノイズを利用して高さを計算
      const noiseValue = getNoiseValue(x, z, t);
      let h = getBoxHeight(noiseValue);
      h = max(h, 0.01); // 高さが0にならないようにする

      push();
      translate(x, -h/2, z);
      fill(getColour(noiseValue)); // 地形の色を取得して塗る
      box(sideLength, h, sideLength); // ボックスを描画

      // 木を描く(ツリーレベル以上で2Dモードでない場合)
      if(noiseValue >= treeLevel && heightScale > 0) {
        drawTree(h);
      }
      pop();
    }

    function drawTree(h) {
      const trunkLength = 10;
      const leafLength = 10;

      push();
      translate(0, -h/2, 0);

      // 幹の描画
      fill(trunkColour);
      translate(0, -trunkLength/2, 0);
      box(trunkLength/4, trunkLength, trunkLength/4);

      // 葉の描画
      fill(leafColour);
      translate(0, -trunkLength/2 - leafLength/2, 0);
      box(leafLength * 3/4, leafLength, leafLength * 3/4);
      translate(0, -leafLength * 3/4, 0);
      box(leafLength/4);

      pop();
    }

    // ノイズ値を取得
    function getNoiseValue(x, z, time) {
      x = x * noiseScale + noiseOffset;
      z = z * noiseScale + noiseOffset;
      time = time * timeScale + noiseOffset;
      return noise(x, z, time); // ノイズ関数で値を生成
    }

    // ボックスの高さをノイズ値から計算
    function getBoxHeight(noiseValue) {
      return map(noiseValue, 0, 1, minHeight, maxHeight) * heightScale;
    }

    // ノイズ値に応じて色を取得
    function getColour(noiseValue) {
      if(noiseValue < seaLevel && heightScale == 0) {
        return seaColour;
      }
      
      if(noiseValue < rockLevel) {
        return rockColour;
      } else if(noiseValue < sandLevel) {
        return sandColour;
      } else {
        const lerpVal = map(noiseValue, sandLevel, treeLevel, 0, 1);
        return lerpColor(color(grassColour), color(forestColour), lerpVal);
      }
    }

    // ウィンドウサイズに合わせてキャンバスをリサイズ
    function windowResized() {
      resizeCanvas(windowWidth, windowHeight);
    }
  </script>
</body>
</html>

2
4
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
4