You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 22:05:22