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,但实际缓冲区里只有部分有效图像,保存时自然就会出现大部分黑色的情况。
修复步骤
同步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`;正确设置Viewport
此时gl.drawingBufferWidth/Height会等于图片尺寸,你可以直接设置viewport为图片宽高:gl.viewport(0, 0, image.width, image.height);修复纹理加载参数
加载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
相关产品推荐
相关产品推荐

