WebGL & Three.js

【Three.js】マテリアルのテスクチャーに画像や動画を設定する方法(デモ付き)

Three.jsでマテリアルに画像や動画を貼る方法です。画像はTextureLoaderで読み込んでmapに渡し、動画はVideoTextureを使います。色が暗くなるときのcolorSpace設定、テクスチャーの切り替えとdisposeまでデモ付きで解説します。

この記事の目次
  1. マテリアルのテスクチャーに画像や動画を設定するコード
  2. 3Dグラフィクスのテクスチャー
  3. 動画をテクスチャーに設定する
  4. テクスチャーを切り替える

Three.jsでマテリアルのテスクチャーに画像や動画を設定する方法を紹介します。
さっそくデモを確認してみましょう。

Three.jsのShaderMaterialを使ってGLSLを記述した画像の貼り方が知りたい場合は、【WebGL】Three.jsのShaderMaterialを使って画像や動画を出力する方法(デモ付き)を参考にしてみてください。
結論から書くと、画像はTextureLoaderで読み込んだtextureをマテリアルのmapに渡します。動画はTextureLoaderでは読み込めないため、video要素をVideoTextureに包んでmapに渡します。
色付きの画像を貼るときは、texture.colorSpace = THREE.SRGBColorSpaceを設定しないと本来より暗く、くすんだ色で表示されます。

マテリアルのテスクチャーに画像や動画を設定するコード

async function initializeThreeJS() {
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  );

  const renderer = new THREE.WebGLRenderer();
  renderer.setSize(window.innerWidth, window.innerHeight);
  
  document.body.appendChild(renderer.domElement);
 
  const geometry = new THREE.PlaneGeometry(20, 10);

  // texture: 質感を意味するが、3Dでは画像や動画も含まれる
  // textureのloaderを読み込み
  const texLoader = new THREE.TextureLoader();
  // texLoaderが非同期処理なのでawait
  // textureを設定
  const texture = await texLoader.loadAsync(
    // "https://picsum.photos/800/400?random=1"
    "/img/img.jpg"
  );
  // mapを使ってtextureを設定
  const material = new THREE.MeshBasicMaterial({ map: texture });

  const cube = new THREE.Mesh(geometry, material);
  scene.add(cube);

  camera.position.z = 30;

  function animate() {
    requestAnimationFrame(animate);
    cube.rotation.x = cube.rotation.x + 0.01;
    cube.rotation.y += 0.01;

    renderer.render(scene, camera);
  }

  animate();
}
initializeThreeJS();

上の一連の流れは以下の記事が参考になります。
もしThree.jsの基本的な操作の一連の流れを知らない場合は、Three.jsの基本的な操作の一連の流れがとても短くまとめた記事なので参考にしてみてください。
具体的なコードとデモが見たい場合は、【WebGL&Three.js入門】Three.jsの一番シンプルなサンプルコードのチュートリアルの解説でデモもみれる状態にしているので参考にしてみてください。
今回はテクスチャーの設定がメインのためtextureの部分に焦点をあてて説明します。

スポンサーリンク

3Dグラフィクスのテクスチャー

textureは質感を意味しますが、3Dでは画像や動画も含まれます。
つまり、textureに画像や動画を設定します。
具体的には以下の記述部分です。

  // textureのloaderを読み込み
  const texLoader = new THREE.TextureLoader();

ここで、textureのloaderをインスタンス化しています。
この処理は非同期処理なのでasync awaitを使います。

  // textureを設定
  const texture = await texLoader.loadAsync(
    // "https://picsum.photos/800/400?random=1"
    "/img/img.jpg"
  );

imgのパスは適宜変更します。
awaitをつけるので、忘れずにfunctionにasyncをつけます。
loadAsyncが返すtextureは、色の扱いを指定しないとリニア色空間として解釈され、見た目が暗くなります。写真やイラストのような「色そのもの」を貼る画像には、次の1行を足します。

  texture.colorSpace = THREE.SRGBColorSpace;

法線マップや高さマップのようにデータとして使う画像には付けません。マテリアル自体の色の指定と色空間の関係は、Three.jsでMaterialの色を設定する方法で詳しく解説しています。

async function initializeThreeJS() {
...
}

そして、mapします。

  // mapを使ってtextureを設定
  const material = new THREE.MeshBasicMaterial({ map: texture });

このマテリアルは、textureを使いますよという意味になります。
MeshBasicMaterialはライトの影響を受けないため、画像をそのままの明るさで表示したい看板や背景に向いています。光の当たり方も表現したい場合はMeshStandardMaterialに同じmapを渡します。マテリアルごとの違いはThree.js Materialの種類と使い方を参照してください。

動画をテクスチャーに設定する

動画は画像と違い、TextureLoaderでは読み込めません。video要素を用意して再生し、それをVideoTextureに渡します。

  const video = document.createElement("video");
  video.src = "/video/sample.mp4";
  video.loop = true;
  video.muted = true; // 自動再生にはミュートが必要
  video.playsInline = true;
  await video.play();

  const videoTexture = new THREE.VideoTexture(video);
  videoTexture.colorSpace = THREE.SRGBColorSpace;

  const material = new THREE.MeshBasicMaterial({ map: videoTexture });

VideoTextureは、描画のたびに動画の現在のフレームをtextureとして更新してくれるため、animate関数で特別な処理を足す必要はありません。
ブラウザは音声付きの動画の自動再生を止めるので、muted = trueにしておくか、クリックなどの操作をきっかけにplay()を呼びます。
黒いままで動画が映らない場合は、play()が拒否されていないか、動画のURLが同じオリジンか(別ドメインならvideo.crossOrigin = "anonymous"とサーバー側のCORS設定が必要)を確認します。

テクスチャーを切り替える

  const material = new THREE.MeshBasicMaterial({ map: texture });

  const newTexture = await texLoader.loadAsync("/img/newImg.jpg");
  setTimeout(() => {
    material.map = newTexture;
  }, 3000);
  const cube = new THREE.Mesh(geometry, material);

テクスチャーを切り替えたい場合、上のように別の画像を新しい変数に読み込んでおき、
mapに入れ直すだけで切り替わります。
mapを「なし」から「あり」に変える、または別の種類のtextureに変えるときはmaterial.needsUpdate = trueも設定します。すでに画像が貼られている状態で別の画像に差し替えるだけなら不要です。
使わなくなった古いtextureはtexture.dispose()でGPUのメモリから解放します。
貼った画像を色々な角度から確認したいときは、OrbitControlsで視点を操作する方法を組み合わせると、マウスで回して見られます。

スポンサーリンク