【GLSL】Three.jsのShaderMaterialを使って画像や動画を出力する方法(デモ付き)

2023年12月5日WebGL & Three.js

Three.jsのShaderMaterialを使って画像や動画を出力する方法を紹介します。
WebGL(GLSL)を使うので、これができるとWebサイトなどでは即応用ができるようになります。
デモもあるのでまずは確認してみてください。

もしGLSLを書かずに、Three.jsのみで画像や動画を出力したい場合は、【Three.js】マテリアルのテスクチャーに画像や動画を設定する方法(デモ付き)を参考にしてみてください。

Three.jsのShaderMaterialを使って画像や動画を出力するデモ



※外部から画像をとってくるので、表示までに数秒かかる場合があります。

ソースコード

initializeThreeJS();
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({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  // document.body.appendChild(renderer.domElement);
  document.getElementById("canvas-container").appendChild(renderer.domElement);

  async function loadTex(url) {
    const texLoader = new THREE.TextureLoader();
    const texture = await texLoader.loadAsync(url);
    return texture;
  }
  const geometry = new THREE.BoxGeometry(3, 3, 3);
  // const geometry = new THREE.PlaneGeometry(4, 4);
  // const geometry = new THREE.SphereGeometry(2);
  const material = new THREE.ShaderMaterial({
    //
    uniforms: {
      uTex: {
        // value: await loadTex("https://picsum.photos/1200/1200?random=1"),
       value: await loadTex("/assets/img/kiso/basic3/img01.jpg"),
      },
    },
    vertexShader: `
     varying vec2 vUv;

     void main() {
       vUv = uv;
       gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
     }
   `,
    fragmentShader: `
     varying vec2 vUv;
     uniform sampler2D uTex;
     void main() {
      vec4 texColor = texture2D(uTex, vUv);
       gl_FragColor = texColor;
     }
   `,
  });
  const cube = new THREE.Mesh(geometry, material);
  scene.add(cube);

  camera.position.z = 5;

  const control = new OrbitControls(camera, renderer.domElement);

  function animate() {
    requestAnimationFrame(animate);

    cube.rotation.x = cube.rotation.x + 0.005;
    cube.rotation.y += 0.005;
    control.update();

    renderer.render(scene, camera);
  }

  animate();
}

上の基本のソースコードの流れは、【WebGL&Three.js入門】Three.jsの一番シンプルなサンプルコードのチュートリアルの解説で詳しく解説しているので参考にしてみてください。
ポイントのShaderMaterial部分を解説します。

デモのジオメトリについて

上のデモでジオメトリ部分は単にコメントアウトを入れ替えているだけです。

  const geometry = new THREE.BoxGeometry(3, 3, 3);
  // const geometry = new THREE.PlaneGeometry(4, 4);
  // const geometry = new THREE.SphereGeometry(2);

ShaderMaterialでuniformsを設定する

    uniforms: {
      uTex: {
        value: await loadTex("https://picsum.photos/1200/1200?random=1"),
      },
    },

uniformsを設定することで、uniformで値をとることができます。

uniformとはなに?となるの場合は、【WebGL】の曲者varyingとuniformとは?その挙動の説明と使い方についてで説明しているので参考にしてみてください。

uTexというプロパティに値を設定するときは、valueに対して値をセットします。
他のプロパティも同様で、valueの値をとってくるという仕様なので、その仕様にそってvalueに値を設定しましょう。

sampler2Dというのは型の情報です。

テクスチャを非同期で読み込む関数

// テクスチャを非同期で読み込む関数
async function loadTex(url) {
  const texLoader = new THREE.TextureLoader();
  const texture = await texLoader.loadAsync(url);
  return texture;
}

テクスチャを非同期で読み込む関数を作成します。
【Three.js】マテリアルのテスクチャーに画像や動画を設定する方法(デモ付き)でも説明したように、TextureLoader()を使います。

画像をはりつける

    fragmentShader: `
     varying vec2 vUv;
     uniform sampler2D uTex;
     void main() {
      vec4 texColor = texture2D(uTex, vUv);
       gl_FragColor = texColor;
     }
   `,

フラグメントシェーダ内には以下のような記述で使用することができます。

     uniform sampler2D uTex;
vec4 texColor = texture2D(uTex, vUv);

上の部分は、texture()でも同じで、texture関数はuv座標の位置に対応する色情報をuTexから取得する関数です。

vec4 texColor = texture(uTex, vUv);

vUv(UV座標)自体は2次元のベクトルですが、uTexは画像の情報なので各座標ごとにrgbaという4次元の情報を持っています。
texture2Dでその4次元の色情報を変数texColorに入れるので、vec4で型定義します。

UV座標とは?

UV座標とは、テクスチャ(画像や動画)から色の情報を取得するときに使う座標のことです。
テクスチャの左下が(0,0)で、右上が(1,1)と自動で設定されます。
つまり、左上は(0,1)で右下は(1,0)になります。

上のサンプルコードでも使っているuvですが、Three.jsが設定してくれている変数になります。

    // attribute vec2 uv; という記述がThree.js内にある
vertexShader: `
     varying vec2 vUv;

     void main() {
       vUv = uv;
       gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
     }

attribute vec2 uv; という記述がThree.js内にあるため、

vUv = uv;

とするだけでOKです。
vUvはvaryingなので、UV座標が(0,0)のときは、(0,0)が渡ってきますし、(1,1)のときは、(1,1)が渡ってきます。
それをフラグメントシェーダを通して、texture関数vUvの位置にあるテクスチャの色情報を取得します。

vec4 texColor = texture2D(uTex, vUv);

texColorにはRGBaの色情報が渡ってくるので、あとはそれをgl_FragColorに設定します。

gl_FragColor = texColor;