在 WebGL 中動畫紋理

在此演示中,我們將在前一個示例的基礎上,用正在播放的 mp4 影片檔案的幀來替換靜態紋理。這實際上非常容易實現,而且觀看效果很有趣,讓我們開始吧。您可以編寫類似的程式碼來使用任何型別的資料(例如 <canvas>)作為紋理源。

訪問影片

第一步是建立我們將用於檢索影片幀的 <video> 元素。

注意:將此宣告新增到您的“webgl-demo.js”指令碼開頭。

js
// will set to true when video can be copied to texture
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;

  // Waiting for these 2 events ensures
  // there is data in the video

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

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

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

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

  return video;
}

首先,我們建立一個影片元素。我們將其設定為自動播放、靜音,並迴圈播放影片。然後,我們設定兩個事件來確保影片正在播放並且時間已更新。我們需要這兩個檢查,因為如果您上傳一個 WebGL 影片但尚無可用資料,則會產生錯誤。檢查這兩個事件可確保資料可用,並且可以安全地將影片上傳到 WebGL 紋理。在上面的程式碼中,我們確認是否收到了這兩個事件;如果是,我們將一個全域性變數 copyVideo 設定為 true,以指示可以安全地開始將影片複製到紋理。

最後,我們設定 src 屬性開始載入,並呼叫 play 來開始載入和播放影片。

為了能夠使用影片幀作為 WebGL 紋理資料,影片必須從安全來源載入。這意味著您不僅需要部署類似使用安全 Web 伺服器的程式碼,還需要一個安全伺服器來進行測試。有關幫助,請參閱如何設定本地測試伺服器?

將影片幀用作紋理

接下來的更改是初始化紋理,這變得簡單多了,因為我們不再需要載入影像檔案。相反,我們建立一個空的紋理物件,放入一個畫素,並設定其過濾屬性以供後續使用。

注意:將“webgl-demo.js”中的 loadTexture() 函式替換為以下程式碼。

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

  // Because video has to be download over the internet
  // they might take a moment until it's ready so
  // put a single pixel in the texture so we can
  // use it immediately.
  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]); // opaque blue
  gl.texImage2D(
    gl.TEXTURE_2D,
    level,
    internalFormat,
    width,
    height,
    border,
    srcFormat,
    srcType,
    pixel,
  );

  // Turn off mips and set wrapping to clamp to edge so it
  // will work regardless of the dimensions of the video.
  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() 時,我們傳入的是 <video> 元素,而不是 Image 物件。WebGL 知道如何提取當前幀並將其用作紋理。

接下來,我們需要從 main() 函式呼叫這些新函式。

注意:在您的 main() 函式中,將對 loadTexture() 的呼叫替換為以下程式碼。

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

注意:您還需要將 Firefox.mp4 檔案下載到與 JavaScript 檔案相同的本地目錄。

注意:在您的 main() 函式中,將 render() 函式替換為以下程式碼。

js
// Draw the scene repeatedly
function render(now) {
  now *= 0.001; // convert to seconds
  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()

就這麼簡單!

檢視完整程式碼 | 在新頁面開啟此演示

另見