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

WebGL保存图片出现黑边求助:纹理填充画布但保存后异常

WebGL显示大尺寸图片后保存异常问题解析

问题描述

我有一张9248×6936、大小36.6MB的JPG格式图片,尝试通过WebGL完整显示它。使用的顶点着色器与片元着色器代码如下:

const VERTEX_SHADER = `
attribute vec2 a_position;
attribute vec2 a_texCoord;

varying vec2 vTexcoord;
void main() {
    gl_Position = vec4(a_position * vec2(1.0,-1.0), 0.0, 1.0);
    // pass the texCoord to the fragment shader
    // The GPU will interpolate this value between points.
    vTexcoord = a_texCoord;
}`
const FRAGMENT_SHADER = `
uniform mediump sampler2D u_sourceTexture;
varying mediump vec2 vTexcoord;
void main() {
  gl_FragColor = texture2D(u_sourceTexture, vTexcoord);
}`

我将Canvas的宽高设置为与图片一致:

var canvas = document.querySelector("#canvas");
var gl = canvas.getContext("webgl", {preserveDrawingBuffer: true});
gl.canvas.width = image.width
gl.canvas.height = image.height

直接使用该图片创建纹理:

var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);

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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.FLOAT, image);

设置顶点坐标为(-1.0, -1.0)~(1.0, 1.0),纹理坐标为(0.0, 0.0)~(1.0, 1.0):

var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
          -1.0, -1.0,
          1.0, -1.0,
          -1.0, 1.0,
          -1.0, 1.0,
          1.0, -1.0,
          1.0, 1.0
        ]), gl.STATIC_DRAW);

var texcoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        0.0, 0.0,
        1.0, 0.0,
        0.0, 1.0,
        0.0, 1.0,
        1.0, 0.0,
        1.0, 1.0,
      ]), gl.STATIC_DRAW);

若将viewport设置为图片宽高,仅能显示部分纹理,因此最终将WebGL的viewport设置为drawingBufferWidth和drawingBufferHeight:

gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);

此时图片完全填充Canvas,且Canvas宽高与图片一致,显示正常。但保存图片时出现问题:保存的图片尺寸与原图一致,但仅一小部分为真实图像,其余为黑色像素,保存代码如下:

var canvas = document.querySelector("#canvas");
canvas.toBlob((blob) => {
  let a = document.createElement('a')
  a.download = 'tmp.jpg'
  let s = URL.createObjectURL(blob)
  a.href = s
  a.click()
  URL.revokeObjectURL(s)
}, 'image/jpeg', 0.95)

请问为何纹理能填充Canvas,但保存后却出现这种异常情况?


问题原因及解决方案

核心原因

问题出在Canvas绘图缓冲区尺寸与CSS显示尺寸不匹配:

  • 你设置gl.canvas.width和height只是指定了Canvas的像素缓冲区大小,但如果没有同步设置CSS样式,浏览器会自动缩放Canvas到适配页面布局的尺寸,此时gl.drawingBufferWidth/Height返回的是缩放后的显示尺寸(远小于你的图片尺寸)。
  • 用这个小尺寸设置viewport后,WebGL只在缓冲区的左上角一小块区域绘制了纹理,剩余区域都是默认的黑色。
  • 视觉上浏览器把小尺寸的绘制结果放大显示,看起来填满了Canvas,但实际缓冲区里只有部分有效图像,保存时自然就会出现大部分黑色的情况。

修复步骤

  1. 同步Canvas的CSS尺寸与绘图缓冲区尺寸
    设置Canvas宽高后,添加CSS样式确保显示尺寸和像素缓冲区一致,避免浏览器自动缩放:

    gl.canvas.width = image.width;
    gl.canvas.height = image.height;
    gl.canvas.style.width = `${image.width}px`;
    gl.canvas.style.height = `${image.height}px`;
    
  2. 正确设置Viewport
    此时gl.drawingBufferWidth/Height会等于图片尺寸,你可以直接设置viewport为图片宽高:

    gl.viewport(0, 0, image.width, image.height);
    
  3. 修复纹理加载参数
    加载JPG图片时使用gl.UNSIGNED_BYTE而非gl.FLOAT,因为JPG是8位整数格式的图像,浮点格式会导致加载异常:

    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
    

内容的提问来源于stack exchange,提问作者victorman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 05:35:29