WebGL非正方形画布图像旋转失真问题求助
问题描述
我正在开发一款支持图像变换的Web应用,其中包含图像旋转功能。当前遇到的问题是:当Canvas为正方形(宽高相等)时旋转一切正常,但非正方形时图像旋转会出现失真(见下方截图及动图)。我尝试通过以下公式根据旋转角度调整缩放比例来修复:
const scalingY = 1 + (aspectRatio - 1) * Math.pow(Math.sin(angelInRadian/2), 1); const scalingX = (1/aspectRatio) + (1-(1/aspectRatio)) * Math.pow(Math.cos(angelInRadian), 1);
但旋转过程中图像仍存在失真情况。以下是相关的变换矩阵代码、顶点着色器、片元着色器及WebGL初始化代码,同时附上不同角度的截图及旋转过程动图,寻求解决该失真问题的方案。
变换矩阵代码
const scalingY = 1 + (aspectRatio - 1) * Math.pow(Math.sin(angelInRadian/2), 1); const scalingX = (1/aspectRatio) + (1-(1/aspectRatio)) * Math.pow(Math.cos(angelInRadian), 1) const aspectRatioCompensation = new Float32Array([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]); //const aspectRatioCompensation = new Float32Array([ scalingX, 0, 0, 0, 0, scalingY, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]); const cosTheta = Math.cos(angelInRadian); const sinTheta = Math.sin(angelInRadian); const rotationMatrix = new Float32Array([ cosTheta, -sinTheta, 0, 0, sinTheta, cosTheta, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]); // Combine the aspect ratio adjustment and rotation const combinedMatrix = mat4.create(); mat4.multiply(combinedMatrix, aspectRatioCompensation, rotationMatrix );
顶点着色器(Vertex Shader)
attribute vec4 a_position; attribute vec2 a_texCoord; varying vec2 v_position; varying vec2 v_texCoord; void main() { gl_Position = a_position; v_position = a_position.xy; v_texCoord = a_texCoord; }
片元着色器(Fragment Shader)
precision mediump float; varying vec2 v_texCoord; uniform sampler2D u_texture; uniform vec4 u_color; uniform int u_renderMode; // 0 for texture, 1 for shape void main() { if (u_renderMode == 0) { gl_FragColor = texture2D(u_texture, v_texCoord); } else if (u_renderMode == 1) { gl_FragColor = u_color; } }
WebGL初始化代码(updateScreen函数)
updateScreen() { const { gl, program, positionBuffer, texCoordBuffer } = this; gl.useProgram(program); // Bind the position buffer for this layer gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); const positionLocation = gl.getAttribLocation(program!, 'a_position'); gl.enableVertexAttribArray(positionLocation); gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); // Bind the texCoord buffer for this layer gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); const texCoordLocation = gl.getAttribLocation(program!, 'a_texCoord'); gl.enableVertexAttribArray(texCoordLocation); gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0); const error = gl.getError(); if (error !== gl.NO_ERROR) { console.error('WebGL error:', error); } // Enable blending //TODO: need more understanding of the blending functions gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); const hitTestLayer = globalState.get('hitTestLayer'); this.model?.documents.activeDocument!.layers.forEach((layer: any) => { gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, layer.positionCoordArray, gl.STATIC_DRAW); gl.bindTexture(gl.TEXTURE_2D, layer.texture); const textureLocation = gl.getUniformLocation(program!, 'u_texture'); gl.uniform1i(textureLocation, 0); layer.shapes.performDrawingCalls(gl, program!, 0) const pixels = new Uint8Array(4); gl.readPixels(5, 5, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels); }) gl.useProgram(null); }
问题截图
- 旋转角度0度:

- 旋转角度约45度:

旋转过程动图

解决方案
核心问题分析
当前代码的关键问题有两个:
- 计算好的
combinedMatrix从未传递给顶点着色器,也未用来修改gl_Position,导致旋转和比例补偿完全没生效; - 手动编写的比例补偿公式逻辑错误,非正方形Canvas下旋转时,无法保证图像始终适配视口,导致拉伸失真。
修复步骤
1. 修改顶点着色器,引入变换矩阵
在顶点着色器中添加uniform矩阵变量,用于应用变换:
attribute vec4 a_position; attribute vec2 a_texCoord; varying vec2 v_position; varying vec2 v_texCoord; uniform mat4 u_modelMatrix; // 新增:变换矩阵 void main() { gl_Position = u_modelMatrix * a_position; // 应用变换到顶点位置 v_position = a_position.xy; v_texCoord = a_texCoord; }
2. 修正比例补偿与矩阵组合逻辑
替换原有的缩放计算代码,使用正确的包围盒适配逻辑:
// 计算Canvas宽高比(假设aspectRatio = 宽/高) const aspectRatio = gl.canvas.width / gl.canvas.height; const angleRad = angelInRadian; // 注意:变量名拼写修正为angleInRadian更规范 // 计算旋转后图像的包围盒尺寸 const sinAngle = Math.sin(angleRad); const cosAngle = Math.cos(angleRad); const rotatedWidth = Math.abs(cosAngle) + Math.abs(sinAngle) * aspectRatio; const rotatedHeight = Math.abs(cosAngle) / aspectRatio + Math.abs(sinAngle); // 计算缩放比例,让旋转后的图像刚好适配Canvas视口 const scaleX = 1 / rotatedWidth; const scaleY = 1 / rotatedHeight; // 构建比例补偿矩阵 const aspectRatioCompensation = mat4.fromScaling(mat4.create(), [scaleX, scaleY, 1]); // 构建绕原点旋转的矩阵 const rotationMatrix = mat4.fromRotation(mat4.create(), angleRad, [0, 0, 1]); // 矩阵乘法顺序:先旋转,再应用比例补偿(顺序不可颠倒) const combinedMatrix = mat4.create(); mat4.multiply(combinedMatrix, aspectRatioCompensation, rotationMatrix);
3. 将变换矩阵传递给着色器
在updateScreen函数的图层循环内,添加代码将combinedMatrix传递给顶点着色器:
// 在layer.shapes.performDrawingCalls之前添加 const modelMatrixLocation = gl.getUniformLocation(program!, 'u_modelMatrix'); gl.uniformMatrix4fv(modelMatrixLocation, false, combinedMatrix);
4. 优化纹理采样质量
若旋转后图像出现模糊,设置纹理线性过滤:
gl.bindTexture(gl.TEXTURE_2D, layer.texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
关键说明
- 矩阵顺序:必须先执行旋转,再执行缩放补偿,否则会导致旋转轴偏移;
- 比例逻辑:通过计算旋转后图像的包围盒尺寸,反向推导缩放比例,确保图像在任何角度下都能完整适配Canvas且无拉伸;
- 变量名修正:原代码中
angelInRadian拼写错误,建议改为angleInRadian避免潜在bug。
内容的提问来源于stack exchange,提问作者Andrew Naem
相关产品推荐
相关产品推荐

