はじめに

昨日の続きでThree.jsでアプリ開発。

色々できるようになったので、箱庭的な空間を作ってみようと、オブジェクトを増やしたり、テクスチャーを貼ったり、オブジェクトの回転処理、 その他、過去やって来たような画像のキャッシュ処理など。

それ以外にも、3D空間が真っ暗で味気ない為、キューブマップや、球体マップ(スフェリカルマップ)をやってみたので、今日やった内容をAIにまとめてもらいました。


主なモデルデータ、テクスチャデータは、sketchfabからフリー素材をお借りしています。

1. キューブマップの設定

基本的なキューブマップの設定

まず、キューブマップは六つの面を持つ立方体の内部にテクスチャを適用して、360度の景観を再現します。Three.jsでは、THREE.CubeTextureLoader を使ってこれを簡単に実現できます。

const loader = new THREE.CubeTextureLoader();
const texture = loader.load(
  [
    './assets/images/textures/cube_PX.png', // 右
    './assets/images/textures/cube_NX.png', // 左
    './assets/images/textures/cube_PY.png', // 上
    './assets/images/textures/cube_NY.png', // 下
    './assets/images/textures/cube_PZ.png', // 前
    './assets/images/textures/cube_NZ.png'  // 後
  ],
  () => {
    console.log('CubeTexture loaded successfully');
    config.scene.background = texture;  // 背景に設定
  },
  undefined,
  (error) => {
    console.error('Error loading CubeTexture:', error);
  }
);

キューブマップのカスタマイズ方法

キューブマップの設定をカスタマイズするために、以下の点に注目します:

1. テクスチャの補間方法 (Filtering)

テクスチャの読み込みや表示において、補間方法を変更することができます。デフォルトでは、THREE.LinearFilterTHREE.NearestFilter などが使われます。

例えば、以下のように補間方法を設定することができます:

texture.minFilter = THREE.LinearFilter; // ミップマップ使用時のフィルタリング方法
texture.magFilter = THREE.LinearFilter; // 拡大時のフィルタリング方法
texture.anisotropy = renderer.capabilities.getMaxAnisotropy(); // 異方性フィルタリング

これにより、高解像度のテクスチャでも滑らかな補間が行われます。

2. テクスチャの繰り返し設定 (Wrapping)

通常、キューブマップのテクスチャは繰り返しの設定が必要ありませんが、必要に応じて wrapSwrapT を設定することで繰り返しの挙動を制御できます。

texture.wrapS = THREE.RepeatWrapping;  // X軸方向
texture.wrapT = THREE.RepeatWrapping;  // Y軸方向

3. エンコーディング設定 (Encoding)

キューブマップのテクスチャは色空間(エンコーディング)の設定を変更することができます。デフォルトでは THREE.LinearEncoding を使うことが多いですが、HDR (高ダイナミックレンジ) 画像の場合は THREE.RGBEEncoding を使用することがあります。

texture.encoding = THREE.sRGBEncoding; // 一般的な画像の場合
// HDR画像の場合
texture.encoding = THREE.RGBEEncoding; // 高ダイナミックレンジ画像

4. パフォーマンスの最適化

キューブマップを使用する際、特に大きなテクスチャを使う場合、パフォーマンスに影響を与える可能性があります。以下の対策を取ることで、パフォーマンスを最適化できます。

a. テクスチャ圧縮

可能であれば、テクスチャ圧縮(例えば、KTX 形式など)を使用することで、GPUのメモリ使用量を削減し、ロード時間を短縮できます。Three.jsでは KTXLoader を使用して圧縮テクスチャをロードできます。

const ktxLoader = new THREE.KTX2Loader();
ktxLoader.load('path_to/your_compressed_texture.ktx2', (texture) => {
  config.scene.background = texture;
});

b. 解像度の制御

使用するキューブマップの解像度を適切に調整します。例えば、フルHD解像度で十分であれば、解像度を適切に下げることでパフォーマンスが向上します。

// 高解像度テクスチャを使用する場合
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.anisotropy = 16; // 高い異方性を設定

// 解像度を下げたテクスチャを使用する場合
texture.minFilter = THREE.NearestFilter;
texture.magFilter = THREE.NearestFilter;
texture.anisotropy = 1;

5. 複数のキューブマップを切り替える

場合によっては、異なるシーンで異なる背景を使いたいことがあります。THREE.CubeTextureLoaderで読み込んだ複数のキューブマップを切り替える方法を紹介します。

let currentTexture;

function switchCubeMap(newTexturePaths) {
  const loader = new THREE.CubeTextureLoader();
  loader.load(
    newTexturePaths,
    (texture) => {
      currentTexture = texture;
      config.scene.background = texture;  // 新しい背景を設定
    },
    undefined,
    (error) => {
      console.error('Error loading CubeTexture:', error);
    }
  );
}

// 切り替え例
switchCubeMap([
  './assets/images/textures/skybox_2_PX.png',
  './assets/images/textures/skybox_2_NX.png',
  './assets/images/textures/skybox_2_PY.png',
  './assets/images/textures/skybox_2_NY.png',
  './assets/images/textures/skybox_2_PZ.png',
  './assets/images/textures/skybox_2_NZ.png'
]);

6. 背景としての応用

キューブマップを背景として設定することで、360度のシーンを作成することができます。特に、VR環境や360度パノラマ映像で活用されます。

config.scene.background = texture; // キューブマップを背景として設定

これにより、シーン全体に360度の背景が適用され、カメラが回転しても背景が正しく表示されます。

2. 球体マップ(スフェリカルマップ)

スフェリカルマップは、球体内に360度のテクスチャを貼り付け、全方向に背景を作成する方法です。この方法を使うことで、球体の内側にテクスチャを表示し、視覚的に360度の環境を作り上げることができます。特に、VRやパノラマ表示でよく使用されます。

球体マップの設定方法:

球体マップを設定するためには、THREE.TextureLoader でテクスチャを読み込み、THREE.EquirectangularReflectionMapping を設定します。これによって、Equirectangular 形式の画像を球体内に適用できます。

const loader = new THREE.TextureLoader();
loader.load('path_to/your_spherical_map.jpg', (texture) => {
  texture.mapping = THREE.EquirectangularReflectionMapping;  // スフェリカルマッピングを設定
  scene.background = texture;  // 背景に設定
});

解説:

  1. THREE.TextureLoader で画像を非同期で読み込みます。
  2. THREE.EquirectangularReflectionMapping を設定することで、球体内に360度のテクスチャを貼り付けます。
  3. 背景として設定するために、scene.background にこのテクスチャを割り当てます。

ポイント:

  • Equirectangular形式の画像は、球体マップでよく使用される形式です。この形式は、緯度・経度の座標系で表された画像であり、360度のパノラマ画像に適しています。
  • 球体マップを使うことで、視覚的に360度の背景を簡単に作成できます。

追加のカスタマイズ:

1. アスペクト比の調整:

Equirectangular画像は通常、縦横比が2:1です。これにより、画像を球体に合わせる際に歪みが発生しません。もし、異なるアスペクト比の画像を使う場合は、アスペクト比の調整が必要です。

texture.repeat.set(1, 0.5); // 縦横比を調整

2. 画像を圧縮してパフォーマンスを向上:

球体マップで使用するテクスチャのサイズが大きい場合、読み込みやレンダリングに時間がかかることがあります。この場合、画像の圧縮や、KTXBasisフォーマットのような圧縮テクスチャを使うと、パフォーマンスが向上します。

const ktxLoader = new THREE.KTX2Loader();
ktxLoader.load('path_to/your_compressed_spherical_map.ktx2', (texture) => {
  scene.background = texture;
});

3. HDR画像の使用:

高ダイナミックレンジ(HDR)画像を使用すると、よりリアルなライティング効果を得ることができます。HDR画像は、THREE.RGBEEncoding でエンコードする必要があります。

texture.encoding = THREE.RGBEEncoding;

4. リフレクション用の設定:

球体マップを反射のために使う場合、反射マッピングを設定する必要があります。これには、THREE.EquirectangularRefractionMappingTHREE.EquirectangularReflectionMapping を使用します。

texture.mapping = THREE.EquirectangularReflectionMapping;  // 反射用

使用例:VRシーンでの活用

球体マップはVRシーンで背景を作成する際に非常に便利です。例えば、VR空間内に360度の背景を表示する場合、以下のように設定できます。

const loader = new THREE.TextureLoader();
loader.load('path_to/your_spherical_map.jpg', (texture) => {
  texture.mapping = THREE.EquirectangularReflectionMapping;
  // VR用の背景設定
  scene.background = texture;
  camera.position.set(0, 1.6, 0);  // VR用にカメラの高さを設定
});

これで、VRシーンで360度の背景を表示することができます。

  • スフェリカルマップは、球体の内側に360度のテクスチャを貼り付け、パノラマ背景を作成する技術です。
  • Equirectangular形式の画像を使うことで、視覚的に360度の背景を作成できます。
  • テクスチャのエンコーディング設定や圧縮を使うことで、パフォーマンスを最適化できます。
  • VRや360度の体験に活用するため、簡単に背景として設定できるため、非常に便利です。

3. オブジェクトの回転

Three.js でオブジェクトを回転させるには、rotation プロパティを使用します。 rotation は ラジアン(radian) で指定され、内部的には Euler角(x, y, z)として扱われます。

回転の基本

model.rotation.y = Math.PI / 4;  // Y軸回りに45度回転
model.rotation.x = Math.PI / 2;  // X軸回りに90度回転
  • x : 前後に倒す(ピッチ)
  • y : 左右に回す(ヨー)
  • z : 傾ける(ロール)

Three.js では 右手座標系なので、 Y軸回転は「その場で左右を向く」動きになります。


ラジアンと度数の関係

Three.js は 度数法を使わないため、以下の対応を覚えておくと楽です。

0    = 0
90   = Math.PI / 2
180  = Math.PI
270  = Math.PI * 1.5
360  = Math.PI * 2

度数で指定したい場合は変換します。

const deg = 45;
model.rotation.y = deg * Math.PI / 180;

回転を連続させる(アニメーション)

毎フレーム少しずつ回転させることで、回転アニメーションになります。

function animate() {
  requestAnimationFrame(animate);

  model.rotation.y += 0.01; // 少しずつ回転

  renderer.render(scene, camera);
}
animate();

この方法は、

  • 回転するオブジェクト
  • 回転台に乗ったモデル
  • UI的な演出 などに向いています。

初期向きを調整する用途

GLB / GLTF モデルは、 正面の向きが Z+ / Z- でバラバラなことが多いです。

そのため、読み込み直後に向きを補正するのはよくある使い方です。

// 正面をこちらに向ける
model.rotation.y = Math.PI;

特にキャラクターモデルでは、 移動方向とモデルの向きを合わせるために必須になります。


回転順序(重要)

Euler角には 回転順序 があります。 Three.js のデフォルトは以下です。

model.rotation.order === 'XYZ'

複雑な回転を扱う場合、順序によって見た目が変わることがあります。

model.rotation.order = 'YXZ';

キャラクター制御では Y軸 → X軸 の順に回すことが多いため、この設定が役立つ場合があります。


回転と position の違い

  • position → 移動
  • rotation → 向きの変更
model.position.z += 1;   // 前に進む
model.rotation.y += 0.1; // 方向転換

移動ロジックと回転ロジックは、分けて考えると整理しやすくなります。


実装時の注意点

  • 毎フレーム rotation = ... で固定値を入れると回転が止まる
  • 加算(+=)することで連続回転になる
  • GLB のスケール・原点位置によって回転の見え方が変わる

この回転の理解は、

  • キャラクターの向き制御
  • カメラ追従
  • 操作入力(WASD + 回転)
  • 見下ろし / 見回し操作

すべての基礎になります。

4. テクスチャーの設定

Three.jsでは、テクスチャを使ってオブジェクトに色や模様を追加することができます。THREE.TextureLoader を使って画像を読み込み、それをマテリアルに適用することで、オブジェクトにリアルな質感を加えることができます。

テクスチャの設定方法:

const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('path_to/your_texture.jpg');  // 画像を読み込み

// マテリアルにテクスチャを適用
const material = new THREE.MeshBasicMaterial({ map: texture });

// ジオメトリの作成
const geometry = new THREE.BoxGeometry(1, 1, 1);  // 1x1x1 の立方体

// メッシュを作成
const mesh = new THREE.Mesh(geometry, material);

// シーンに追加
scene.add(mesh);

解説:

  • THREE.TextureLoader を使って、画像ファイル(例えば JPG、PNG、WEBPなど)を非同期で読み込みます。
  • 読み込んだテクスチャを MeshBasicMaterialmap プロパティに設定することで、メッシュにテクスチャを適用します。
  • THREE.MeshBasicMaterial はシェーダーによる照明効果を使用せず、テクスチャや色そのものを使うシンプルなマテリアルです。これを使用することで、影や反射などの効果を気にせずテクスチャの表示が可能です。

ポイント:

  1. 解像度と形式の選択:

    • 画像の解像度や形式は、パフォーマンスに影響を与える重要な要素です。例えば、WEBP は高圧縮率でありながら画質を保つことができ、PNGJPG よりも効率的です。
    • 高解像度の画像はメモリを多く消費するため、適切な解像度で画像を選択することが重要です。
  2. テクスチャのタイプ:

    • MeshBasicMaterial は最もシンプルなマテリアルで、ライトの影響を受けません。テクスチャをそのまま表示したい場合に使います。
    • MeshStandardMaterialMeshLambertMaterial などは、ライティングの影響を受けるマテリアルです。これらを使用すると、光源やシャドウの影響を受けて、よりリアルな質感を表現できます。

よく使うテクスチャの種類と設定:

  1. 基本的なテクスチャ:

    • 一般的な画像(模様や色を持つ)のテクスチャを使用します。
    const material = new THREE.MeshBasicMaterial({
      map: texture,  // 基本的なテクスチャを適用
      side: THREE.DoubleSide,  // 両面にテクスチャを適用
    });
    
  2. 法線マップ(Normal Map):

    • 法線マップはオブジェクトに微細な凹凸感を与えるために使用します。これにより、ライトの反射を細かく制御でき、リアルな表面の質感を表現できます。
    const normalMap = new THREE.TextureLoader().load('path_to/normal_map.jpg');
    const material = new THREE.MeshStandardMaterial({
      map: texture,  // 基本テクスチャ
      normalMap: normalMap,  // 法線マップ
    });
    
  3. 粗さマップ(Roughness Map)と反射マップ(Metalness Map):

    • 金属感や粗さをコントロールするためのマップを使うことで、リアルな質感を表現できます。これらは PBR(物理ベースレンダリング) の技術に基づいており、光の反射を細かく調整します。
    const roughnessMap = new THREE.TextureLoader().load('path_to/roughness_map.jpg');
    const metalnessMap = new THREE.TextureLoader().load('path_to/metalness_map.jpg');
    const material = new THREE.MeshStandardMaterial({
      map: texture,  // 基本テクスチャ
      roughnessMap: roughnessMap,  // 粗さマップ
      metalnessMap: metalnessMap,  // 反射マップ
    });
    
  4. 環境マップ(Environment Map):

    • 環境マップを使用することで、メッシュに反射効果を与えることができます。例えば、金属や水面など、反射の強い表面を表現する際に役立ちます。
    const envMap = new THREE.CubeTextureLoader().load([
      'path_to/px.jpg',
      'path_to/nx.jpg',
      'path_to/py.jpg',
      'path_to/ny.jpg',
      'path_to/pz.jpg',
      'path_to/nz.jpg'
    ]);
    const material = new THREE.MeshStandardMaterial({
      map: texture,
      envMap: envMap,  // 環境マップを追加
    });
    

画像の最適化とパフォーマンス改善

テクスチャを適切に管理することで、パフォーマンスの向上が期待できます。以下のような手法でパフォーマンスを最適化できます:

  1. 圧縮画像の使用:

    • 大きな画像ファイルを使用するとメモリ消費が増加し、描画パフォーマンスが低下する可能性があります。画像を圧縮したり、WEBP形式を使用することで、ファイルサイズを削減できます。
  2. ミップマップの使用:

    • THREE.TextureLoader はデフォルトでミップマップを生成し、異なる解像度のテクスチャを必要に応じて選択します。これにより、遠くのオブジェクトに対して低解像度のテクスチャが使用され、パフォーマンスが向上します。
  3. テクスチャの再利用:

    • 同じテクスチャを複数のオブジェクトで使用することで、メモリの使用を最適化できます。テクスチャは一度ロードすれば、他のオブジェクトでも再利用可能です。
  4. THREE.LOD (Level of Detail) を使う:

    • オブジェクトがカメラから遠くなるときにテクスチャを低解像度に変更するなど、LOD (レベルオブディテール) の技術を使ってパフォーマンスを向上させることができます。

  • THREE.TextureLoader を使って画像を読み込み、3Dモデルにテクスチャを適用することで、オブジェクトの質感やディテールを追加できます。
  • テクスチャに法線マップ、粗さマップ、反射マップなどを使用すると、よりリアルな質感を表現できます。
  • パフォーマンスを最適化するために、画像の圧縮やミップマップの使用、LODの利用などの方法を検討しましょう。

これらのテクニックを活用して、効率的かつ美しい3Dシーンを作成できます!

5. 画像の一括読み込み

  • 非同期処理:fetch でJSONファイルを取得し、そのデータを元に画像を非同期で読み込みます。
  • Promise.allPromise.all(imagePromises) によって、すべての画像が読み込まれるまで待機します。これにより、画像がすべて準備できた状態で次の処理を実行できます。
  • キャッシュ:読み込まれた画像は、config.imgageList に保存され、後で再利用できるようになります。

一括読み込みを行う流れ

  1. JSONの読み込み: fetch を使って、画像のパスとその他のメタデータを持つJSONファイルを読み込みます。
  2. 非同期で画像をロード: 各画像を THREE.TextureLoader で非同期にロードします。
  3. 画像をキャッシュ: 画像が正常に読み込まれたら、config.imgageList に名前、テクスチャ、パス、説明を保存します。
  4. すべての画像が読み込まれるのを待つ: Promise.all を使って、すべての画像が読み込まれたら処理を続けます。

例:

{
  "textures": [
    {
      "name": "skybox_noon_px",
      "path": "./assets/images/textures/skybox_noon_px.jpg",
      "description": "Skybox: Right"
    },
    {
      "name": "skybox_noon_nx",
      "path": "./assets/images/textures/skybox_noon_nx.jpg",
      "description": "Skybox: Left"
    }
    // その他の画像
  ]
}

これで、JSONファイル内で指定されたパスに従って画像を一括で読み込むことができます。

const texture = config.imgageList.find(item => item.name === 'skybox_noon_px').texture;
config.scene.background = texture;

まとめ

今日の作業では、以下の内容を学びました:

  1. キューブマップを使って360度の背景を作成。
  2. 球体マップ(スフェリカルマップ)を使って、球体内に360度の背景を表示。
  3. オブジェクトの回転を制御して、シーン内で自由に回転させる方法。
  4. テクスチャーの設定を行い、オブジェクトに画像を適用。
  5. 画像の一括読み込みで、複数の画像をまとめて処理し、効率的にシーンに反映。

これらの技術を駆使することで、よりリアルでダイナミックな3Dシーンを作り出すことができます