如何在WebGL2着色器中使用CSS matrix3d矩阵?
问题描述
已有可用的CSS matrix3d 变换:
transform: matrix3d(1.3453,0.1357,0.0,0.0003,0.2096,1.3453,0.0,0.0003,0.0,0.0,1.0,0.0,-100.0,-100.0,0.0,1.0); transform-origin: 0 0;
希望在WebGL2画布中给绘制的图像应用完全相同的变换,但直接传入CSS矩阵到着色器后无法得到预期效果。
核心原因
CSS和WebGL的坐标空间与矩阵应用逻辑存在差异:
- CSS坐标以左上角为原点,Y轴向下;WebGL裁剪空间Y轴向上,且原有着色器中已通过
clipSpace * vec2(1, -1)翻转了Y轴。 - CSS矩阵直接作用于元素的像素坐标,而原有着色器先将顶点像素坐标转换到[-1,1]的归一化裁剪空间,再应用矩阵,导致变换逻辑不匹配。
解决方案
修改顶点着色器的坐标转换顺序:先对像素坐标应用CSS矩阵,再转换到WebGL的裁剪空间;同时适配WebGL的Y轴方向。
步骤1:调整顶点着色器
将先转裁剪空间再乘矩阵的逻辑,改为先对原始像素位置应用矩阵,再转裁剪空间:
#version 300 es in vec2 a_position; in vec2 a_texCoord; uniform vec2 u_resolution; uniform mat4 u_matrix; out vec2 v_texCoord; void main() { // 先对像素位置应用CSS矩阵 vec4 transformedPos = u_matrix * vec4(a_position, 0.0, 1.0); vec2 pos = transformedPos.xy / transformedPos.w; // 透视除法 // 将转换后的像素坐标转为裁剪空间 vec2 zeroToOne = pos / u_resolution; vec2 zeroToTwo = zeroToOne * 2.0; vec2 clipSpace = zeroToTwo - 1.0; // 适配WebGL的Y轴方向 gl_Position = vec4(clipSpace * vec2(1, -1), 0.0, 1.0); v_texCoord = a_texCoord; }
步骤2:直接使用CSS矩阵(无需修改)
WebGL的uniformMatrix4fv默认是列主序,和CSS matrix3d的参数顺序完全一致,直接传入原矩阵即可:
const cssMatrix = new Float32Array([ 1.3453, 0.1357, 0.0, 0.0003, 0.2096, 1.3453, 0.0, 0.0003, 0.0, 0.0, 1.0, 0.0, -100.0, -100.0, 0.0, 1.0 ]);
完整可运行代码
HTML
<img src="" crossOrigin="" id="image"><br> <canvas id="canvas" width=320 height=240></canvas>
CSS
#image { transform: matrix3d(1.3453, 0.1357, 0.0, 0.0003, 0.2096, 1.3453, 0.0, 0.0003, 0.0, 0.0, 1.0, 0.0, -100.0, -100.0, 0.0, 1.0); transform-origin: 0 0; } img, canvas { border: 1px solid #000; }
JavaScript
// Set up matrices const cssMatrix = new Float32Array([ 1.3453, 0.1357, 0.0, 0.0003, 0.2096, 1.3453, 0.0, 0.0003, 0.0, 0.0, 1.0, 0.0, -100.0, -100.0, 0.0, 1.0 ]); const matrixArray = cssMatrix; // Set up shaders const vertexShaderSource = `#version 300 es in vec2 a_position; in vec2 a_texCoord; uniform vec2 u_resolution; uniform mat4 u_matrix; out vec2 v_texCoord; void main() { vec4 transformedPos = u_matrix * vec4(a_position, 0.0, 1.0); vec2 pos = transformedPos.xy / transformedPos.w; vec2 zeroToOne = pos / u_resolution; vec2 zeroToTwo = zeroToOne * 2.0; vec2 clipSpace = zeroToTwo - 1.0; gl_Position = vec4(clipSpace * vec2(1, -1), 0.0, 1.0); v_texCoord = a_texCoord; }`; const fragmentShaderSource = `#version 300 es precision highp float; uniform sampler2D u_image; in vec2 v_texCoord; out vec4 outColor; void main() { outColor = texture(u_image, v_texCoord); }`; // Load the test image const img = document.getElementById("image"); img.src = "https://i.imgur.com/NIt86ft.png"; img.onload = () => renderImg(img); // WebGL utility functions function createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback) { const errFn = opt_errorCallback || console.error; const program = gl.createProgram(); shaders.forEach(shader => gl.attachShader(program, shader)); if (opt_attribs) { opt_attribs.forEach((attrib, ndx) => { gl.bindAttribLocation(program, opt_locations ? opt_locations[ndx] : ndx, attrib); }); } gl.linkProgram(program); const linked = gl.getProgramParameter(program, gl.LINK_STATUS); if (!linked) { errFn('Error in program linking:' + gl.getProgramInfoLog(program)); gl.deleteProgram(program); return null; } return program; } function loadShader(gl, shaderSource, shaderType, opt_errorCallback) { const errFn = opt_errorCallback || console.error; const shader = gl.createShader(shaderType); gl.shaderSource(shader, shaderSource); gl.compileShader(shader); const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); if (!compiled) { errFn('*** Error compiling shader:\n' + gl.getShaderInfoLog(shader) + `\n` + shaderSource.split('\n').map((l, i) => `${i + 1}: ${l}`).join('\n')); gl.deleteShader(shader); return null; } return shader; } function createProgramFromSources(gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) { const shaders = []; const defaultShaderType = [gl.VERTEX_SHADER, gl.FRAGMENT_SHADER]; for (let ii = 0; ii < shaderSources.length; ++ii) { shaders.push(loadShader(gl, shaderSources[ii], defaultShaderType[ii], opt_errorCallback)); } return createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback); } function resizeCanvasToDisplaySize(canvas, multiplier) { multiplier = multiplier || 1; const width = canvas.clientWidth * multiplier | 0; const height = canvas.clientHeight * multiplier | 0; if (canvas.width !== width || canvas.height !== height) { canvas.width = width; canvas.height = height; return true; } return false; } function renderImg(image) { const canvas = document.getElementById("canvas"); const gl = canvas.getContext("webgl2"); if (!gl) return; const program = createProgramFromSources(gl, [vertexShaderSource, fragmentShaderSource]); const positionAttributeLocation = gl.getAttribLocation(program, "a_position"); const texCoordAttributeLocation = gl.getAttribLocation(program, "a_texCoord"); const resolutionLocation = gl.getUniformLocation(program, "u_resolution"); const imageLocation = gl.getUniformLocation(program, "u_image"); const matrixLocation = gl.getUniformLocation(program, "u_matrix"); const vao = gl.createVertexArray(); gl.bindVertexArray(vao); // Position buffer const positionBuffer = gl.createBuffer(); gl.enableVertexAttribArray(positionAttributeLocation); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0); // TexCoord buffer const 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); gl.enableVertexAttribArray(texCoordAttributeLocation); gl.vertexAttribPointer(texCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0); // Texture setup const texture = gl.createTexture(); gl.activeTexture(gl.TEXTURE0); 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.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); // Set rectangle position gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); setRectangle(gl, 0, 0, image.width, image.height); // Render resizeCanvasToDisplaySize(gl.canvas); gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.useProgram(program); gl.bindVertexArray(vao); gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height); gl.uniform1i(imageLocation, 0); gl.uniformMatrix4fv(matrixLocation, false, matrixArray); gl.drawArrays(gl.TRIANGLES, 0, 6); } function setRectangle(gl, x, y, width, height) { const x1 = x; const x2 = x + width; const y1 = y; const y2 = y + height; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ x1, y1, x2, y1, x1, y2, x1, y2, x2, y1, x2, y2, ]), gl.STATIC_DRAW); }
内容的提问来源于stack exchange,提问作者rebane2001
相关产品推荐
相关产品推荐

