跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScript大前端算法

WebGL 矩阵基础:平移、旋转、缩放与复合变换实战

WebGL 渲染依赖矩阵乘法处理顶点变换。本文详解平移、旋转、缩放的 4x4 矩阵原理及实现,涵盖纯手写矩阵与 gl-matrix 库的复合变换实战。重点解析变换顺序对结果的影响、单位矩阵作用及列主序格式要求,帮助开发者掌握 GPU 高效并行计算的核心逻辑,避免手动修改顶点坐标的性能陷阱。

leon发布于 2026/4/7更新于 2026/9/1078 浏览
WebGL 矩阵基础:平移、旋转、缩放与复合变换实战

在 WebGL 开发中,矩阵是驱动 3D 图形的核心引擎。想要实现流畅的动画效果,理解矩阵乘法及其在 GPU 中的运作方式至关重要。

为什么矩阵是 WebGL 的灵魂

WebGL 基于 OpenGL ES,其渲染管线高度依赖着色器。矩阵作为连接 JavaScript 逻辑与 GPU 渲染的桥梁,负责处理所有空间变换。

  • 统一逻辑:平移、旋转、缩放均通过「矩阵 × 顶点坐标」完成,符合 GPU 规范。
  • 性能优势:矩阵运算可被 GPU 高效并行处理,确保动画丝滑。
  • 灵活组合:多个矩阵相乘即可实现复杂的复合变换。

关键原则:WebGL 中所有变换的标准写法是 gl_Position = 变换矩阵 * 顶点坐标,而非直接修改顶点的 x/y 分量。

1. 平移变换

平移是最基础的仿射变换。在计算机图形学中,它通过齐次坐标和 4x4 矩阵实现。

原理

三维点表示为齐次坐标 (x, y, z, 1)。平移矩阵的形式如下:

[ 1 0 0 tx ]
[ 0 1 0 ty ]
[ 0 0 1 tz ]
[ 0 0 0 1 ]

执行乘法后,新坐标 x' = x + tx,以此类推。即使处理 2D 图形,也统一使用 4x4 矩阵(z/tz 设为 0)。

实战代码

下面是一个纯矩阵实现的平移动画示例,红色三角形沿 X 轴往复运动。

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>WebGL 矩阵实战:平移的红色三角形</title>
<style>
canvas { border: 2px solid #ff4400; display: block; margin: 20px auto; border-radius: 8px; }
</style>






</head>
<body>
<canvas id="glCanvas">
</canvas>
<script>
const canvas = document.getElementById('glCanvas'); const gl = canvas.getContext('webgl'); if (!gl) { alert('您的浏览器不支持 WebGL,请尝试 Chrome 或 Firefox'); throw new Error('WebGL not supported'); } // 设置背景 gl.clearColor(0.0, 0.0, 0.0, 1.0); // 顶点着色器 const vertexShaderSource = ` attribute vec4 a_Position; uniform mat4 u_TranslateMatrix; void main() { gl_Position = u_TranslateMatrix * a_Position; } `; // 片元着色器 const fragmentShaderSource = ` void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } `; function createShader(gl, source, type) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error('着色器编译错误:', gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; } const vertexShader = createShader(gl, vertexShaderSource, gl.VERTEX_SHADER); const fragmentShader = createShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); // 顶点数据 const vertices = new Float32Array([ -0.2, -0.2, 0.0, 0.2, -0.2, 0.0, 0.0, 0.2, 0.0 ]); const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); const a_Position = gl.getAttribLocation(program, 'a_Position'); gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(a_Position); // 矩阵相关 const u_TranslateMatrix = gl.getUniformLocation(program, 'u_TranslateMatrix'); const translateMatrix = new Float32Array(16); let tx = 0.0; const step = 0.02; function setTranslateMatrix(matrix, tx, ty, tz) { matrix.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1]); } function animate() { tx += step; if (tx > 1.0) tx = -1.0; setTranslateMatrix(translateMatrix, tx, 0.0, 0.0); gl.uniformMatrix4fv(u_TranslateMatrix, false, translateMatrix); gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLES, 0, 3); requestAnimationFrame(animate); } animate();
</script>
</body>
</html>

文章配图

文章配图

2. 旋转变换

旋转的核心在于构建包含三角函数的 4x4 矩阵。二维点绕原点逆时针旋转 θ 角的矩阵如下:

[ cosθ -sinθ 0 0 ]
[ sinθ  cosθ 0 0 ]
[   0      0  1 0 ]
[   0      0  0 1 ]

计算过程等价于 x' = x*cosθ - y*sinθ。将完整矩阵传递给着色器,GPU 会自动完成运算,无需在 Shader 中重复计算三角函数。

实战代码

以下示例实现了三角形绕原点的持续旋转。

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>WebGL 矩阵实战:旋转的红色三角形</title>
<style>
canvas { border: 2px solid #ff4400; display: block; margin: 20px auto; border-radius: 8px; }
</style>
</head>
<body>
<canvas id="glCanvas"></canvas>
<script>
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
    alert('您的浏览器不支持 WebGL');
    throw new Error('WebGL not supported');
}

const vertexShaderSource = `
attribute vec4 a_Position;
uniform mat4 u_RotateMatrix;
void main() {
    gl_Position = u_RotateMatrix * a_Position;
}
`;

const fragmentShaderSource = `
void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;

function createShader(gl, source, type) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error('编译错误:', gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }
    return shader;
}

const vertexShader = createShader(gl, vertexShaderSource, gl.VERTEX_SHADER);
const fragmentShader = createShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);

const vertices = new Float32Array([
    0.0, 0.5, 0.0,
   -0.5, -0.5, 0.0,
    0.5, -0.5, 0.0
]);

const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

const a_Position = gl.getAttribLocation(program, 'a_Position');
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);

const u_RotateMatrix = gl.getUniformLocation(program, 'u_RotateMatrix');
const rotateMatrix = new Float32Array(16);
let angle = 0.0;

function setRotateZMatrix(matrix, angle) {
    const cos = Math.cos(angle);
    const sin = Math.sin(angle);
    matrix.set([cos, -sin, 0, 0, sin, cos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
}

function animateRotate() {
    angle += 0.02;
    setRotateZMatrix(rotateMatrix, angle);
    gl.uniformMatrix4fv(u_RotateMatrix, false, rotateMatrix);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
    requestAnimationFrame(animateRotate);
}
animateRotate();
</script>
</body>
</html>

文章配图

文章配图

3. 缩放变换

缩放通过改变对角线上的元素来实现。二维点沿 x/y 轴缩放的矩阵如下:

[ sx 0  0 0 ]
[ 0  sy 0 0 ]
[ 0  0  sz 0 ]
[ 0  0  0 1 ]

其中 sx, sy, sz 分别为各轴缩放因子。负数可实现翻转效果。

实战代码

此示例让三角形在 0.5 倍到 2 倍之间来回缩放。

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>WebGL 矩阵实战:缩放的红色三角形</title>
<style>
canvas { border: 2px solid #ff4400; display: block; margin: 20px auto; border-radius: 8px; }
</style>
</head>
<body>
<canvas id="glCanvas"></canvas>
<script>
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
    alert('您的浏览器不支持 WebGL');
    throw new Error('WebGL not supported');
}

const vertexShaderSource = `
attribute vec4 a_Position;
uniform mat4 u_ScaleMatrix;
void main() {
    gl_Position = u_ScaleMatrix * a_Position;
}
`;

const fragmentShaderSource = `
void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;

function createShader(gl, source, type) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error('编译错误:', gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }
    return shader;
}

const vertexShader = createShader(gl, vertexShaderSource, gl.VERTEX_SHADER);
const fragmentShader = createShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);

const vertices = new Float32Array([
    0.0, 0.5, 0.0,
   -0.5, -0.5, 0.0,
    0.5, -0.5, 0.0
]);

const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

const a_Position = gl.getAttribLocation(program, 'a_Position');
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);

const u_ScaleMatrix = gl.getUniformLocation(program, 'u_ScaleMatrix');
const scaleMatrix = new Float32Array(16);
let scaleFactor = 1.0;
const step = 0.01;
let isEnlarging = true;

function setScaleMatrix(matrix, sx, sy, sz) {
    matrix.set([sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1]);
}

function animateScale() {
    if (isEnlarging) {
        scaleFactor += step;
        if (scaleFactor >= 2.0) isEnlarging = false;
    } else {
        scaleFactor -= step;
        if (scaleFactor <= 0.5) isEnlarging = true;
    }
    
    setScaleMatrix(scaleMatrix, scaleFactor, scaleFactor, 1.0);
    gl.uniformMatrix4fv(u_ScaleMatrix, false, scaleMatrix);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
    requestAnimationFrame(animateScale);
}
animateScale();
</script>
</body>
</html>

文章配图

文章配图

4. 复合变换:平移 + 旋转 + 缩放

手动组合矩阵容易出错且难以维护。在实际开发中,推荐使用 gl-matrix 库来处理复杂的矩阵运算。

gl-matrix 的优势

  • 封装了所有 4x4 矩阵运算方法。
  • 自动处理矩阵乘法顺序。
  • 针对 WebGL 做了优化,性能更优。

实战代码

以下示例展示了如何使用 gl-matrix 实现同时进行的平移、旋转和缩放动画。

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>WebGL 矩阵进阶:复合动画</title>
<style>
canvas { border: 2px solid #ff4400; display: block; margin: 20px auto; border-radius: 8px; }
</style>
<!-- 引入 gl-matrix 库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>
</head>
<body>
<canvas id="glCanvas"></canvas>
<script>
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
    alert('您的浏览器不支持 WebGL');
    throw new Error('WebGL not supported');
}

const vertexShaderSource = `
attribute vec4 a_Position;
uniform mat4 u_ModelMatrix;
void main() {
    gl_Position = u_ModelMatrix * a_Position;
}
`;

const fragmentShaderSource = `
void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;

function createShader(gl, source, type) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error('编译错误:', gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }
    return shader;
}

const vertexShader = createShader(gl, vertexShaderSource, gl.VERTEX_SHADER);
const fragmentShader = createShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);

const vertices = new Float32Array([
    0.0, 0.5, 0.0,
   -0.5, -0.5, 0.0,
    0.5, -0.5, 0.0
]);

const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

const a_Position = gl.getAttribLocation(program, 'a_Position');
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);

const u_ModelMatrix = gl.getUniformLocation(program, 'u_ModelMatrix');
const modelMatrix = mat4.create();

let tx = 0.0;
let angle = 0.0;
let scaleFactor = 1.0;
let isEnlarging = true;

function animateWithMatrix() {
    // 重置为单位矩阵
    mat4.identity(modelMatrix);
    
    // 按顺序执行复合变换
    // 1. 缩放
    mat4.scale(modelMatrix, modelMatrix, [scaleFactor, scaleFactor, 1.0]);
    // 2. 旋转
    mat4.rotateZ(modelMatrix, modelMatrix, angle);
    // 3. 平移
    mat4.translate(modelMatrix, modelMatrix, [tx, 0.0, 0.0]);
    
    // 更新参数
    tx += 0.015;
    if (tx > 1.0) tx = -1.0;
    angle += 0.02;
    
    if (isEnlarging) {
        scaleFactor += 0.005;
        if (scaleFactor >= 1.5) isEnlarging = false;
    } else {
        scaleFactor -= 0.005;
        if (scaleFactor <= 0.7) isEnlarging = true;
    }
    
    gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
    requestAnimationFrame(animateWithMatrix);
}
animateWithMatrix();
</script>
</body>
</html>

文章配图

文章配图

核心知识点总结

  1. 变换顺序决定结果:矩阵乘法不满足交换律。推荐顺序为:缩放 → 旋转 → 平移。若先平移再旋转,会导致物体围绕原点旋转而非自身中心。
  2. 单位矩阵是基础:mat4.identity() 生成无变换的单位矩阵,是所有操作的起点。
  3. 格式要求严格:WebGL 中矩阵必须是 Float32Array 类型的 16 个元素(列主序),传递时 gl.uniformMatrix4fv 的第二个参数必须为 false。

避坑指南

  • 不要死磕数学公式:先跑通代码,观察 tx、angle、scaleFactor 变化对动画的影响,直观理解矩阵作用。
  • 拒绝手动改顶点:始终遵循「矩阵 × 顶点」的标准写法,直接修改坐标会丧失 GPU 并行计算优势。
  • 优先使用库:实际开发中避免手动拼凑矩阵,减少错误率,专注业务逻辑。
  • 注意缩放中心:默认缩放绕原点进行。若需绕自定义点缩放,需先平移到原点→缩放→平移回原位置。

掌握这些基础后,后续可以进一步探索透视矩阵、视图矩阵以及 MVP 矩阵模型,构建真正的 3D 场景。

目录

  1. 为什么矩阵是 WebGL 的灵魂
  2. 1. 平移变换
  3. 原理
  4. 实战代码
  5. 2. 旋转变换
  6. 实战代码
  7. 3. 缩放变换
  8. 实战代码
  9. 4. 复合变换:平移 + 旋转 + 缩放
  10. gl-matrix 的优势
  11. 实战代码
  12. 核心知识点总结
  13. 避坑指南

更多推荐文章

查看全部
  • libpqxx 安装与配置实战
  • Rust 异步编程实战:构建高性能网络应用
  • 突破 LLM 上下文瓶颈:上下文内存虚拟化 CMV 的设计与实践
  • 基于 Leaflet-Trackplayer 的高速公路轨迹 WebGIS 可视化实战
  • SpringMVC 核心原理与实战应用详解
  • AI 时代,为何“人人都是产品经理”终于落地?
  • Flet 跨平台应用开发实战:用 Python 构建 Android iOS Web 应用
  • Windows 11 环境下 Python 3.12.5 安装与配置指南
  • 多模态模型开发实战:文本、图像与语音融合应用
  • 无人机航拍小目标检测:YOLO11 实战与 PyQt6 桌面应用
  • CSS 定位进阶:Position 属性详解与 Z-Index 层级管理
  • Coze(扣子)全解析:100 个落地用途与发布使用指南
  • PyTorch CUDA v2.7 环境支持 Stable Diffusion 等多种模型架构
  • macOS 手柄配置指南:适配 Steam 游戏
  • Stable Diffusion v1.5 Archive 跨平台效果一致性复现指南
  • Python 数据分析入门:理解集中趋势与离散程度
  • LangGraph:构建具有状态与循环的 AI Agent
  • Java 开发一个编程项目的完整流程
  • 栈的压入弹出序列判断算法详解
  • C++ 仿函数深度解析:状态管理与 STL 实践

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online