矩阵专题0-webGL中的矩阵

从 WebGL 的渲染链路来拆解矩阵:先解释它是什么、为什么必须有,再把模型矩阵、视图矩阵、投影矩阵和它们的相乘顺序串起来。

核心概念

  • 在 WebGL 里,矩阵可以理解成一种"批量描述空间变换的表格"。
  • 它最重要的作用是:把一个点从"原来的坐标系"转换到"另一个坐标系"。
  • 这些变换包括:平移旋转缩放投影,以及它们的组合。
  • 如果说"向量"描述的是一个点或一个方向,那么"矩阵"描述的就是"怎么去变换这些点和方向"。

用一句话理解

  • 你在场景里画一个立方体,最开始它可能只是建模时的一组顶点坐标。
  • 但真正显示到屏幕上前,它要经历几次坐标变换:
  • 从"物体自己的局部坐标"变成"场景中的世界坐标"
  • 从"世界坐标"变成"摄像机看到的坐标"
  • 从"摄像机坐标"变成"最终可投影到屏幕的坐标"
  • 这几步,基本都是靠矩阵完成的。

为什么 WebGL 特别依赖矩阵

  • GPU 擅长做大规模并行的线性代数运算。
  • 顶点着色器一次要处理海量顶点,矩阵乘法非常适合这种场景。
  • 多个变换可以预先合并成一个矩阵,避免每个顶点做很多零散计算。
  • 所以图形学里几乎所有"空间变换"都被抽象成矩阵。

矩阵到底是什么

  • 数学上,矩阵就是一个按行列排列的数字表。
  • 在 WebGL / 3D 图形里,最常见的是 4x4 矩阵
  • 原因是:3D 空间中的常见变换,用 4x4 可以统一表示。
  • 尤其是 平移,如果只用 3x3 很难和旋转、缩放统一起来,所以引入了齐次坐标,用 4x4 一把梭。

比如一个 4x4 矩阵长这样:

text 复制代码
| m00 m01 m02 m03 |
| m10 m11 m12 m13 |
| m20 m21 m22 m23 |
| m30 m31 m32 m33 |

它作用在一个顶点上时,通常是:

text 复制代码
v' = M * v

这里:

  • v 是原始顶点
  • M 是变换矩阵
  • v' 是变换后的顶点

为什么是 4x4,不是 3x3 在 2D 里,很多变换用 3x3 就够了。 在 3D 里,为了把以下操作统一起来,通常使用 4x4

  • 缩放
  • 旋转
  • 平移
  • 透视投影

关键点是 齐次坐标

text 复制代码
(x, y, z) -> (x, y, z, 1)

多出来的这个 w,让平移也能写进矩阵乘法里。

一个最直观的例子:平移 假设一个点是 (1, 2, 3),你想把它沿 x 方向移动 5。

平移矩阵大致可以写成:

text 复制代码
| 1 0 0 5 |
| 0 1 0 0 |
| 0 0 1 0 |
| 0 0 0 1 |

点写成齐次坐标:

text 复制代码
| 1 |
| 2 |
| 3 |
| 1 |

相乘后得到:

text 复制代码
| 6 |
| 2 |
| 3 |
| 1 |

也就是点从 (1,2,3) 变成 (6,2,3)

这就是矩阵的本质:用一套统一规则变换顶点

WebGL 里最常见的 3 个矩阵 图形学里最经典的是这三个:

  • Model Matrix,模型矩阵
  • View Matrix,视图矩阵
  • Projection Matrix,投影矩阵

很多时候还会把它们合成:

  • MVP = Projection * View * Model

下面分别讲。

1. 模型矩阵 Model Matrix

  • 作用:把顶点从"模型本地坐标系"变到"世界坐标系"。
  • 本地坐标系可以理解成:模型自己的坐标原点。
  • 比如一个立方体建模时中心在 (0,0,0),这是它的局部空间。
  • 当你把它放到场景中 (10, 0, -5),再旋转 30 度、缩放 2 倍,这些操作都在模型矩阵里。

模型矩阵常包含:

  • 平移
  • 旋转
  • 缩放

举例:

  • 原始顶点:(1, 0, 0)
  • 模型旋转 90 度后,可能变成 (0, 1, 0)
  • 再平移到世界空间某个位置

所以模型矩阵回答的是:

  • "这个物体在世界里放哪?"
  • "朝向哪?"
  • "有多大?"

2. 视图矩阵 View Matrix

  • 作用:把世界坐标变成"摄像机坐标"。
  • 你可以把它理解成:不是"摄像机在动",而是"整个世界相对摄像机反向移动"。

比如:

  • 摄像机在 (0, 0, 5) 看向原点
  • 那么视图矩阵会把整个世界往 z 负方向重新映射到摄像机眼里

核心理解:

  • 模型矩阵决定"物体在哪"
  • 视图矩阵决定"摄像机怎么看"

视图矩阵本质上是:

  • 摄像机变换的逆矩阵

因为图形学里通常不是直接"移动摄像机",而是把所有物体变换到以摄像机为原点的空间里。

3. 投影矩阵 Projection Matrix

  • 作用:把摄像机空间里的点投影到裁剪空间。
  • 它决定"远小近大""视野范围""近平面远平面"等规则。

投影分两种:

  • 透视投影 Perspective
  • 正交投影 Orthographic

透视投影:

  • 远处东西看起来更小
  • 更符合人眼和真实相机
  • 3D 游戏、漫游场景最常见

正交投影:

  • 远近大小不变
  • 常用于 CAD、UI、工程图、编辑器视图

投影矩阵做完后,顶点会进入裁剪空间,后面再做透视除法和视口映射,最后到屏幕。

整个坐标变换链路 一个顶点从模型到屏幕,大致经过:

text 复制代码
局部坐标 -> 世界坐标 -> 观察坐标 -> 裁剪坐标 -> NDC -> 屏幕坐标

对应矩阵过程通常写成:

glsl 复制代码
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);

这句非常重要。

它表示:

  • 先用 modelMatrix 把模型放到世界里
  • 再用 viewMatrix 转到摄像机空间
  • 再用 projectionMatrix 做投影
  • 最后得到 gl_Position

为什么矩阵乘法顺序这么重要 矩阵乘法一般不满足交换律,也就是:

text 复制代码
A * B != B * A

这意味着:

  • 先旋转再平移
  • 先平移再旋转

结果通常完全不同。

举个例子:

  • 一个物体先旋转,再平移,像是"物体原地转完,再搬到别处"
  • 先平移再旋转,像是"物体先离开原点,再围绕原点转圈"

所以在 3D 中,顺序错了,效果就会很怪。

矩阵为什么能把多个变换合并 如果一个物体要经历:

  • 缩放
  • 旋转
  • 平移

你可以写成:

text 复制代码
M = T * R * S

然后顶点只需要:

text 复制代码
v' = M * v

而不需要每次分别算三遍。

这就是图形学里矩阵强大的地方:

  • 可以组合变换
  • 可以提前预计算
  • 可以高效交给 GPU

再说说顶点和方向的区别 在图形学里,方向不完全一样。

常见写法:

  • 点:vec4(x, y, z, 1.0)
  • 方向:vec4(x, y, z, 0.0)

区别在最后一个分量 w

  • w = 1 表示这是一个位置点,会受平移影响
  • w = 0 表示这是一个方向,不受平移影响

这很重要,比如:

  • 法线
  • 方向向量
  • 光照方向

它们不应该因为物体平移就改变方向。

法线为什么也和矩阵有关 很多人刚学 WebGL 时容易忽略:

  • 顶点位置要变换
  • 法线也要变换

但法线不能总是直接用模型矩阵去乘,尤其在有非均匀缩放时会出问题。

通常会用:

  • normalMatrix = transpose(inverse(modelMatrix))

更准确地说,常用的是模型视图矩阵左上角 3x3 的逆转置。

原因是:

  • 法线要保持与表面的垂直关系
  • 普通位置变换矩阵在非均匀缩放时会破坏这种关系

所以法线矩阵本质上是"专门给法线用的修正版矩阵"。

矩阵和坐标系的关系 理解矩阵最好的方式之一,不是把它只看成"数字表",而是看成:

  • 它定义了一个新的坐标系
  • 或者把一个坐标系中的点,映射到另一个坐标系

比如模型矩阵其实描述了模型局部坐标系在世界中的:

  • 原点在哪
  • x 轴指向哪
  • y 轴指向哪
  • z 轴指向哪

所以矩阵不只是"移动点",更是在描述"空间的基底怎么变化"。

WebGL 里矩阵通常长什么样 在 JavaScript 里,WebGL 常把矩阵作为长度为 16 的数组传给 GPU,例如:

js 复制代码
const matrix = new Float32Array(16);

配合:

js 复制代码
gl.uniformMatrix4fv(location, false, matrix);

这里的几个点要注意:

  • uniformMatrix4fv 用来给着色器传 4x4 矩阵
  • WebGL 里传的是一维数组
  • 底层按列主序使用,这一点经常让初学者绕晕

行主序和列主序 这是很多人学矩阵时最头疼的地方之一。

你会经常看到两种说法:

  • 行主序 row-major
  • 列主序 column-major

简单理解:

  • 它们是矩阵在内存里的排布方式
  • 以及你阅读、书写、乘法习惯的差异

在 WebGL / OpenGL 体系里,通常按列主序理解和传递更自然。

但对初学者来说,更重要的不是死记术语,而是统一自己的思维:

  • 你的数学定义是什么
  • 你的库怎么存
  • 你的乘法顺序是什么

只要这三者一致,就不容易错。

WebGL 中一个典型的顶点着色器

glsl 复制代码
attribute vec3 aPosition;

uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;

void main() {
    gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aPosition, 1.0);
}

这段代码做的事就是:

  • 取模型原始顶点 aPosition
  • 扩展成齐次坐标 vec4(..., 1.0)
  • 依次乘上三个矩阵
  • 输出最终裁剪空间坐标 gl_Position

如果没有矩阵会怎样 那你就得手写每个顶点的:

  • 位移公式
  • 旋转公式
  • 缩放公式
  • 透视投影公式

不仅麻烦,而且很难组合,更无法高效放到 GPU 并行执行。

所以矩阵其实是图形学里的一种"统一语言"。

从直觉上理解旋转矩阵 以 2D 为例,旋转矩阵本质是在说:

  • 原来的 x 轴和 y 轴旋转后指向哪里

所以旋转不是"点自己变魔术",而是:

  • 坐标轴基底变了
  • 点在新基底下的位置被重新表达

这个理解对 3D 非常重要。 因为一旦你把矩阵看成"坐标系变换器",很多问题都会清楚:

  • 为什么顺序不能乱
  • 为什么父子节点变换要相乘
  • 为什么摄像机矩阵常常是逆矩阵

和 three.js 里的关系 虽然学的是 WebGL,但如果你接触 three.js,会更容易串起来:

  • object.matrix:对象本地变换矩阵
  • object.matrixWorld:对象世界矩阵
  • camera.matrixWorldInverse:视图矩阵
  • camera.projectionMatrix:投影矩阵

最后在渲染时,本质上仍然会形成类似:

text 复制代码
projection * view * model

所以 three.js 只是帮你把这些矩阵管理好了,底层思想和 WebGL 一样。

父子层级为什么离不开矩阵 如果一个物体挂在另一个物体下面,比如:

  • 手臂挂在身体上
  • 摄像机挂在车辆上

那么子物体的世界矩阵通常是:

text 复制代码
childWorldMatrix = parentWorldMatrix * childLocalMatrix

这意味着:

  • 父对象一动,子对象跟着动
  • 父对象一转,子对象整体跟着转

这就是场景图的核心,也是矩阵在引擎架构里最重要的用途之一。

初学者最容易混淆的几个点

  • 矩阵不是点:矩阵描述的是变换规则,不是某个位置
  • 顺序很重要:乘法顺序一错,结果就变
  • 模型/视图/投影不是一回事:它们负责不同坐标空间的转换
  • 平移能写进 4x4:靠的是齐次坐标,不是普通 3x3
  • 法线不能随便乘模型矩阵:非均匀缩放时要用法线矩阵
  • 摄像机矩阵常常看起来反着:因为视图矩阵本质是摄像机变换的逆

一句话总结

  • 在 WebGL 中,矩阵就是"把顶点从一个空间变到另一个空间的数学工具"。
  • 它是整个 3D 渲染流程的骨架。
  • 你看到的物体位置、旋转、大小、摄像机视角、透视效果,几乎都离不开矩阵。
相关推荐
Asize2 小时前
多模态生图:从 Vite 工程化到前端调用 Qwen Image
javascript·人工智能·后端
陳陈陳2 小时前
从Token到Embedding:一篇文章搞懂大模型的「文字数学变形记」
前端·javascript·ai编程
用户938515635072 小时前
从 O(n²) 到 O(nlogn):一文读懂快速排序的“快”与“妙”
javascript·算法
橘子星2 小时前
LLM 无状态架构实践:从原理到代码落地
前端·javascript·人工智能
To_OC3 小时前
手写快排次次翻车?别死背快排模板了,这才是面试官想听的底层逻辑
javascript·算法·排序算法
风止何安啊4 小时前
网课倍速痛点解决:一套前端代码实现自由控速播放器
前端·javascript·node.js
光影少年5 小时前
原生DOM操作在React 中的注意事项
前端·javascript·react.js
糖拌西瓜皮5 小时前
Node.js核心模块实战:文件、路径、HTTP与流处理
javascript·node.js
糖拌西瓜皮5 小时前
NestJS入门指南:Java开发者的Spring Boot体验
javascript·node.js