WebGL/WebGL2中如何将大视频分片复制到多纹理以优化性能?
解决WebGL大视频纹理复制卡顿的分块方案
核心思路
通过分区域复制视频内容到小尺寸纹理,避免一次性处理全分辨率视频。以下是两种适配WebGL/ WebGL2的可行方案:
方案1:直接用texSubImage2D指定区域复制(WebGL1/2通用)
利用texSubImage2D的源区域参数,分别将视频左右两半复制到两个独立纹理,无需额外同步操作。WebGL2原生支持完整的源区域参数,WebGL1多数浏览器(包括Firefox)也已支持该扩展。
示例代码:
// 初始化两个半尺寸纹理 const textureLeft = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, textureLeft); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, video.videoWidth / 2, video.videoHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 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); const textureRight = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, textureRight); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, video.videoWidth / 2, video.videoHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 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); // 帧更新逻辑 function updateTextures() { // 更新左半纹理 gl.bindTexture(gl.TEXTURE_2D, textureLeft); gl.texSubImage2D( gl.TEXTURE_2D, 0, 0, 0, // 目标纹理起始坐标 gl.RGBA, gl.UNSIGNED_BYTE, video, 0, 0, // 视频源起始坐标(左半左上角) video.videoWidth / 2, video.videoHeight // 复制区域尺寸 ); // 更新右半纹理 gl.bindTexture(gl.TEXTURE_2D, textureRight); gl.texSubImage2D( gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video, video.videoWidth / 2, 0, // 视频源起始坐标(右半左上角) video.videoWidth / 2, video.videoHeight ); requestAnimationFrame(updateTextures); }
方案2:WebGL2帧缓冲分块渲染
通过帧缓冲将完整视频纹理的局部区域渲染到目标小纹理,适合更复杂的分块需求,灵活性更高。
示例代码:
// 初始化完整视频纹理(仅创建一次) const fullVideoTexture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, fullVideoTexture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); // 创建帧缓冲 const fb = gl.createFramebuffer(); // 顶点着色器(全屏传递纹理坐标) const vsSource = ` attribute vec2 a_position; varying vec2 v_texCoord; void main() { gl_Position = vec4(a_position, 0.0, 1.0); v_texCoord = a_position * 0.5 + 0.5; } `; // 片段着色器(截取指定区域) const fsSource = ` precision mediump float; uniform sampler2D u_video; uniform vec2 u_offset; uniform vec2 u_scale; varying vec2 v_texCoord; void main() { gl_FragColor = texture2D(u_video, v_texCoord * u_scale + u_offset); } `; // 编译着色器程序(省略常规编译流程) const program = createProgram(gl, vsSource, fsSource); const positionLoc = gl.getAttribLocation(program, 'a_position'); const videoLoc = gl.getUniformLocation(program, 'u_video'); const offsetLoc = gl.getUniformLocation(program, 'u_offset'); const scaleLoc = gl.getUniformLocation(program, 'u_scale'); // 全屏顶点数据 const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]); const buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); // 分块渲染逻辑 function splitVideoToTextures() { // 更新完整视频纹理 gl.bindTexture(gl.TEXTURE_2D, fullVideoTexture); gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video); // 渲染左半部分到textureLeft gl.bindFramebuffer(gl.FRAMEBUFFER, fb); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureLeft, 0); gl.viewport(0, 0, video.videoWidth / 2, video.videoHeight); gl.useProgram(program); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.enableVertexAttribArray(positionLoc); gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0); gl.uniform1i(videoLoc, 0); gl.uniform2f(offsetLoc, 0, 0); gl.uniform2f(scaleLoc, 0.5, 1.0); // 水平缩放截取左半 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); // 渲染右半部分到textureRight gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureRight, 0); gl.uniform2f(offsetLoc, 0.5, 0); // 偏移截取右半 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); // 恢复默认渲染目标 gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); requestAnimationFrame(splitVideoToTextures); }
注意事项
- WebGL1中
texSubImage2D的源区域参数属于可选扩展,Firefox已原生支持,无需额外兼容处理。 - 所有纹理、帧缓冲仅在初始化阶段创建,避免帧内重复创建资源导致性能损耗。
- 确保视频
crossOrigin属性正确设置,避免跨域纹理访问报错。
内容的提问来源于stack exchange,提问作者TheGreatRambler
相关产品推荐
相关产品推荐

