WebGL におけるテクスチャのアニメーション

今回のデモンストレーションでは、前回の例で用いた静的なテクスチャを Ogg ビデオファイルのフレームに置き換えます。実はこれを行うのはとても簡単ですが、見ていて楽しいです。さっそく始めましょう。なお同様のコードを、どんな種類のデータ(<canvas> など)をテクスチャのソースとして用いる場合でも使用することができます。

動画へのアクセス

始めに、ビデオフレームを取り出すのに使う <video> 要素を作成します。

メモ: この宣言を "webgl-demo.js" スクリプトの始めに追加しましょう。

js
// 動画がテクスチャにコピーできる場合は true に設定する
let copyVideo = false;

メモ: この関数を "webgl-demo.js" スクリプトに追加しましょう。

js
function setupVideo(url) {
  const video = document.createElement("video");

  let playing = false;
  let timeupdate = false;

  video.playsInline = true;
  video.muted = true;
  video.loop = true;

  // 動画のデータが確実にあることを保証するために、
  // この 2 つのイベントを待つ

  video.addEventListener(
    "playing",
    () => {
      playing = true;
      checkReady();
    },
    true,
  );

  video.addEventListener(
    "timeupdate",
    () => {
      timeupdate = true;
      checkReady();
    },
    true,
  );

  video.src = url;
  video.play();

  function checkReady() {
    if (playing && timeupdate) {
      copyVideo = true;
    }
  }

  return video;
}

最初に動画要素を作成します。動画を自動再生し、音をミュートし、ループ再生するように設定します。次に、動画が再生され、時刻が更新されたことを確認するために 2 つのイベントを設定します。まだ利用できるデータがない動画を WebGL にアップロードするとエラーになるため、この両方のチェックが必要です。これらのイベントをどちらも調べることで、利用できるデータがあることが確認され、WebGL テクスチャへの動画のアップロードを始めるのには安全であることが保証されます。上記のコードでは、これらのイベントをどちらも取得したことを確認しています。取得した場合は、グローバル変数 copyVideo に true を設定し、動画をテクスチャにコピーし始めても安全であることを示しています。

そして最後に、 src 属性を設定し、 play を呼び出して動画の読み込みと再生を開始します。

WebGL にテクスチャデータを提供するために使用するためには、元の動画を安全なソースから読み込む必要があります。つまり、安全なウェブサーバーを使用するようにコードを展開する必要があるだけでなく、テストするための安全なサーバーも必要になります。ローカルにテスト用サーバーを用意するには?を参照してください。

動画フレームをテクスチャとして使用する

次に変更するのは initTexture() です。画像ファイルを読み込む必要がなくなったため、とても単純になります。画像を読み込む代わりに、空のテクスチャオブジェクトを作成して、後で使用するフィルターを設定します。

メモ: "webgl-demo.js" の loadTexture() 関数を以下のコードに置き換えましょう。

js
function initTexture(gl) {
  const texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // 動画はインターネット経由でダウンロードしなければならない
  // ため、準備ができるまで時間がかかる可能性があります。
  // そのため、テクスチャに単一のピクセルを置くことで、
  // 直ちにそれを利用することができます。
  const level = 0;
  const internalFormat = gl.RGBA;
  const width = 1;
  const height = 1;
  const border = 0;
  const srcFormat = gl.RGBA;
  const srcType = gl.UNSIGNED_BYTE;
  const pixel = new Uint8Array([0, 0, 255, 255]); // 不透明の青
  gl.texImage2D(
    gl.TEXTURE_2D,
    level,
    internalFormat,
    width,
    height,
    border,
    srcFormat,
    srcType,
    pixel,
  );

  // mips をオフにし、 wrapping を clamp to edge に設定することで、動画の大きさに関係なく動作するようになります。
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

  return texture;
}

メモ: 以下の関数を "webgl-demo.js" に追加しましょう。

js
function updateTexture(gl, texture, video) {
  const level = 0;
  const internalFormat = gl.RGBA;
  const srcFormat = gl.RGBA;
  const srcType = gl.UNSIGNED_BYTE;
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texImage2D(
    gl.TEXTURE_2D,
    level,
    internalFormat,
    srcFormat,
    srcType,
    video,
  );
}

このコードは以前にも見たことがあるでしょう。前回の例の画像 onload 関数とほぼ同じですが、 texImage2D() を呼び出すときに Image オブジェクトを渡す代わりに <video> 要素を渡している点が異なります。 WebGL は現在のフレームを取り出し、テクスチャとして使用する方法を知っています。

次に、これらの新しい関数を main() 関数から呼び出す必要があります。

メモ: main() 関数で、 loadTexture() の呼び出しを次のコードに置き換えましょう。

js
const texture = initTexture(gl);
const video = setupVideo("Firefox.mp4");

メモ: また、Firefox.mp4 ファイルを JavaScript ファイルと同じローカルディレクトリーにダウンロードする必要があります。

メモ: main() 関数で、 render() 関数をこのように置き換えましょう。

js
// シーンを反復して描画
function render(now) {
  now *= 0.001; // 秒へ変換
  deltaTime = now - then;
  then = now;

  if (copyVideo) {
    updateTexture(gl, texture, video);
  }

  drawScene(gl, programInfo, buffers, texture, cubeRotation);
  cubeRotation += deltaTime;

  requestAnimationFrame(render);
}

copyVideo が true の場合、 drawScene() 関数を呼び出す直前に updateTexture() を呼び出します。

以上です。

コードを確認する | 新しいページでデモを開く

関連情報