在 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();
    },
    true,
  );

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

  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,
  );
}

您之前见过这段代码。它与之前示例中的图像加载函数几乎相同,只是当我们调用 texImage2D() 时,我们没有传递 Image 对象,而是传递了 <video> 元素。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() 函数。

这就是全部内容!

查看完整代码 | 在新页面中打开此演示

另请参阅