如何在WebGL中实现与CSS transform 3D效果一致的矩阵变换
问题原因
- 单位不匹配:CSS的
matrix3d是基于像素单位计算生成的,你当前WebGL使用的是[-0.5, 0.5]区间的归一化顶点,尺寸量级和像素完全不对应,透视项的系数过小,导致效果几乎不可见。 - 坐标系统差异:CSS默认y轴向下,WebGL裁剪空间y轴向上,直接套用矩阵会出现上下翻转的问题。
- 顶点维度缺失:你定义的顶点只有x/y两个分量,z值默认是0,绕Y轴旋转后的z偏移量极小,进一步弱化了透视效果。
解决方法
你需要先对齐WebGL和CSS的坐标空间规则,再套用CSS生成的变换矩阵,具体步骤如下:
- 将WebGL顶点改为像素单位,和CSS元素尺寸保持一致,同时将坐标原点对齐CSS默认的变换中心(元素中心)。
- 新增裁剪空间转换矩阵,将像素坐标映射到WebGL的[-1, 1]裁剪区间,同时翻转y轴适配WebGL的坐标规则。
- 矩阵乘法顺序和CSS保持一致,CSS的transform是右乘逻辑,先声明的变换后生效。
修改后可运行代码
HTML部分
<!-- 注意显式设置canvas的width/height属性,避免拉伸变形 --> <canvas id="canvas" width="200" height="200" style="height: 200px;width: 200px"></canvas>
JavaScript部分
function initShaders(gl, vshader, fshader) { let program = createProgram(gl, vshader, fshader); if (!program) return false; gl.useProgram(program); gl.program = program; return true; } function createProgram(gl, vshader, fshader) { let vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader); let fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader); if (!vertexShader || !fragmentShader) return null; let program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); return gl.getProgramParameter(program, gl.LINK_STATUS) ? program : null; } function loadShader(gl, type, source) { let shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); return gl.getShaderParameter(shader, gl.COMPILE_STATUS) ? shader : null; } let canvas = document.querySelector('#canvas'); let gl = canvas.getContext('webgl'); gl.clearColor(0.0, 0.0, 0.0, 0.0); gl.clear(gl.COLOR_BUFFER_BIT); let vSource = `attribute vec4 a_position; attribute vec4 a_color; varying vec4 v_color; uniform mat4 u_projMatrix; uniform mat4 u_transformMatrix; void main() { // 先应用CSS变换矩阵,再应用裁剪空间转换矩阵 gl_Position = u_projMatrix * u_transformMatrix * a_position; v_color = a_color; }`; let fSource = `precision mediump float; varying vec4 v_color; void main() { gl_FragColor = v_color; }`; initShaders(gl, vSource, fSource); // 顶点改为像素单位,100px宽高的矩形,原点在中心对齐CSS默认变换原点 let pointBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, pointBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ -50, -50, 0, 1, 50, -50, 0, 1, -50, 50, 0, 1, 50, -50, 0, 1, -50, 50, 0, 1, 50, 50, 0, 1, ]), gl.STATIC_DRAW); let a_position = gl.getAttribLocation(gl.program, 'a_position'); gl.vertexAttribPointer(a_position, 4, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(a_position); let colorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, ]), gl.STATIC_DRAW); let a_color = gl.getAttribLocation(gl.program, 'a_color'); gl.vertexAttribPointer(a_color, 4, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(a_color); // 生成裁剪空间转换矩阵:适配200x200的canvas,翻转y轴,将像素坐标映射到[-1,1]区间 let halfWidth = canvas.width / 2; let halfHeight = canvas.height / 2; let projMatrix = new Float32Array([ 1/halfWidth, 0, 0, 0, 0, -1/halfHeight, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]); let u_projMatrix = gl.getUniformLocation(gl.program, 'u_projMatrix'); gl.uniformMatrix4fv(u_projMatrix, false, projMatrix); // 直接用CSS生成的matrix3d值,CSS本身是列主序,和WebGL传入参数顺序一致 let u_transformMatrix = gl.getUniformLocation(gl.program, 'u_transformMatrix'); gl.uniformMatrix4fv(u_transformMatrix, false, new Float32Array([ 0.707107, 0, -0.707107, 0.00141421, 0, 1, 0, 0, 0.707107, 0, 0.707107, -0.00141421, 0, 0, 0, 1 ])); gl.drawArrays(gl.TRIANGLES, 0, 6);
内容的提问来源于stack exchange,提问作者HAO
相关产品推荐
相关产品推荐

