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

如何在WebGL中实现与CSS transform 3D效果一致的矩阵变换

问题原因
  • 单位不匹配:CSS的matrix3d是基于像素单位计算生成的,你当前WebGL使用的是[-0.5, 0.5]区间的归一化顶点,尺寸量级和像素完全不对应,透视项的系数过小,导致效果几乎不可见。
  • 坐标系统差异:CSS默认y轴向下,WebGL裁剪空间y轴向上,直接套用矩阵会出现上下翻转的问题。
  • 顶点维度缺失:你定义的顶点只有x/y两个分量,z值默认是0,绕Y轴旋转后的z偏移量极小,进一步弱化了透视效果。
解决方法

你需要先对齐WebGL和CSS的坐标空间规则,再套用CSS生成的变换矩阵,具体步骤如下:

  1. 将WebGL顶点改为像素单位,和CSS元素尺寸保持一致,同时将坐标原点对齐CSS默认的变换中心(元素中心)。
  2. 新增裁剪空间转换矩阵,将像素坐标映射到WebGL的[-1, 1]裁剪区间,同时翻转y轴适配WebGL的坐标规则。
  3. 矩阵乘法顺序和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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.18 14:24:02