在 WebGL 中动画纹理
在此演示中,我们将在前一个示例的基础上,用正在播放的 mp4 视频文件的帧来替换静态纹理。这实际上非常容易实现,而且观看效果很有趣,让我们开始吧。您可以编写类似的代码来使用任何类型的数据(例如 <canvas>)作为纹理源。
访问视频
第一步是创建我们将用于检索视频帧的 <video> 元素。
注意:将此声明添加到您的“webgl-demo.js”脚本开头。
// will set to true when video can be copied to texture
let copyVideo = false;
注意:将此函数添加到您的“webgl-demo.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() 函数替换为以下代码。
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”。
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() 的调用替换为以下代码。
const texture = initTexture(gl);
const video = setupVideo("Firefox.mp4");
注意:您还需要将 Firefox.mp4 文件下载到与 JavaScript 文件相同的本地目录。
注意:在您的 main() 函数中,将 render() 函数替换为以下代码。
// 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()。
就这么简单!