中EN
精修课概念页

二维矩阵变换:挪动的其实是坐标纸

2D Matrix Transforms in Shaders

更新于 2026-10-15 约 18 分钟矩阵GLSL变换旋转缩放

形状挪不动,就挪坐标纸

上一课的形状都是用一条式子画在某个坐标空间里的:「离原点多远」「横坐标是不是大于 0.1」。想把形状挪到别处,没办法把它「拿起来」,因为根本不存在一个可以拿的东西。

能动的只有坐标系。**把整张坐标纸挪一下,画在纸上的形状就跟着走了。**这一课的所有技巧都是这句话的变体。

平移:给坐标加减一个向量

给每个像素的坐标减去一个向量 t,得到新坐标 q = p - t,再在 q 空间里照常画形状:

原来的原点tq = p - t十字画在 q 的原点上,q = 0 的地方正是 p = t。整张网格都跟着挪了过去
十字的式子写在 q 空间里,画在 q 的原点上。q = p − t 时,q 的原点就落在 p = t 的位置,整张网格也跟着平移了 t。

注意方向:坐标减去 t,形状往 t 的方向走;坐标加上 t,形状反而往反方向走。这和 3D 距离场里的写法一致:先对位置做逆变换,再代进形状的式子。

移动的是坐标网格0.0s
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

浅色网格就是 q 空间的坐标纸。代码里还有「弹跳」和「绕圈」两种运动,一次只留一行。

旋转:用 mat2 转动坐标系

旋转要用到矩阵。GLSL 原生支持 mat2、mat3、mat4 三种方阵,可以直接用 * 和向量、矩阵相乘;另外还有一个逐元素相乘的 matrixCompMult。

二维旋转矩阵只需要 cos 和 sin:

mat2 rotate2d(float a) {
  return mat2(cos(a), -sin(a),
              sin(a),  cos(a));
}

绕哪一点转?

旋转矩阵总是绕原点转。如果坐标原点在画布左下角,而形状画在中间,直接乘上旋转矩阵,形状就会绕着左下角甩出画面。

正确的做法分三步:先把中心挪到原点,旋转,再挪回去。

① 移到原点② 旋转③ 移回去
绕中心旋转的三步:① 把坐标平移,让中心落到原点;② 在原点旋转;③ 平移回原来的位置。缩放也是同样的套路。

写成代码就是一行:

vec2 q = rotate2d(angle) * (st - center) + center;

下面的例子把原点留在了左下角。拖动「支点」滑块,比较两种转法:

绕左下角转,还是绕中心转0.0s
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

支点为 0 时,十字绕左下角的黑点甩了一大圈;为 1 时,先移到中心再转,十字就在原地自转。

缩放,以及乘法的顺序

缩放矩阵更简单,只在对角线上放两个缩放系数:

mat2 scale2d(vec2 s) {
  return mat2(s.x, 0.0,
              0.0, s.y);
}

和旋转一样,想绕中心缩放,也要先移到原点再移回来。

旋转和缩放放在一起时,乘的顺序很要紧。矩阵乘向量时,离向量最近的那个矩阵最先起作用;连乘几个矩阵,顺序就对应着变换一层层叠上去的顺序。左右两种顺序的结果完全不同:

先缩放,再旋转R · S先旋转,再缩放S · R
同一个正方形(虚线),横向拉宽两倍、旋转 45°。左边先缩放再旋转,得到一个转过去的长方形;右边先旋转再缩放,正方形被拉成了一个斜的菱形。
换一种顺序试试0.0s
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

顺序为 0 时,方块是一个转动的长方形;为 1 时,它在转动中不断被拉斜。横向拖到 1,两种顺序就没有区别了。

拼一个假 HUD

会画形状,又会移动、旋转、缩放,就可以开始做构图了。书里的练习是做一个仿 UI 或 HUD(抬头显示)的画面。下面这个例子用了几个圆环:

  • 外圈的虚线环慢慢顺时针转;
  • 中间三段长弧逆时针转得快一些;
  • 内圈的细环随时间一呼一吸,靠的是缩放;
  • 中心的准星是两个长条,再用差集挖掉中间。
一个会转的 HUD0.0s
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

每个圆环都在自己的坐标系里画:先把 p 转一个角度,再在转过的坐标里画环、切虚线。

矩阵也能变换颜色

矩阵不只用来变换空间。颜色也是向量,同样可以乘矩阵。

YUV 是一种用于照片和视频模拟编码的颜色空间:Y 是亮度,U、V 是两个色度分量。它照顾了人眼的感知特点,用来压缩色度信息占的带宽。RGB 和 YUV 之间的转换,各是一个 3 × 3 矩阵。

下面右半边的画面先转到 YUV,把 U、V 缩放或者在 UV 平面里转一个角度,再转回 RGB:

在 YUV 里改颜色
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

左半边是原图,右半边是改过的。色度拖到 0 时只剩亮度 Y,画面变成灰度;在 UV 平面里旋转,相当于把所有颜色的色相一起挪一挪。

在空间里移动点,在颜色空间里移动颜色,用的是同一套线性代数。

在游戏里怎么用

取舍与常见的坑

小结与练习

  • 片元着色器里移动形状,靠的是移动坐标系:平移是加减向量,旋转和缩放是乘 mat2。
  • 旋转和缩放都绕原点进行,想绕中心就「移到原点 → 变换 → 移回去」。
  • 矩阵的乘法顺序会改变结果;变换坐标时,形状看到的是逆变换。
  • 颜色也是向量,RGB 和 YUV 之间的转换就是一次矩阵乘法。

练习:把第三课的色轮和这一课结合起来:在画布上排几个色轮,每个转速不同、大小不同,有的自转,有的绕着画布中心公转。

  1. The Book of Shaders: 2D Matrices
  2. thebookofshaders.com/08

相关页面

图形学二维 SDF 形状2D SDF Shapes在片元着色器里画形,不是发三角形图元,而是对每个像素问「我在形的里面还是外面」。矩形用 step/smoothstep 做轴对齐边界;圆与更复杂形用距离场 (distance field)——到特征点的距离再阈值。这是 2D SDF 程序化造型的入门路径。图形学程序化图案Procedural Patterns片元着色器按像素执行:形状重复多少次,计算量几乎不变,因此特别适合平铺图案。核心手法是放大 st 再 fract 得到重复的 [0,1] 子空间,再在格子内画形、做矩阵变换,或按奇偶行错位、按格子旋转拼 Truchet。游戏数学基本仿射变换Basic Affine Transforms平移、旋转、缩放、切变是摆放和变形物体的基础矩阵;它们的连乘顺序有关、但满足结合律。刚体变换的逆可以直接写出,法线要用伴随矩阵的转置(传统说法是逆转置)变换。理解这些矩阵的性质能写出更快、更正确的变换代码。图形学塑形函数Shaping Functions塑形函数把归一化到 [0,1] 的一维输入弯成可控制的曲线,是片元着色器里混合颜色、画边、做动画缓动的「基本功」。GLSL 的 pow/step/smoothstep/sin 等多为硬件加速;掌握一维再扩展到二维空间。图形学着色器中的颜色Color in Shaders在 GLSL 里颜色与空间坐标共用 vec2/vec3/vec4:可用 .rgba / .xyzw / .stpq 或下标访问,并 swizzle 任意重排。mix 在 [0,1] 上混合;HSB(HSV)比 RGB 更直观,还可映到极坐标色轮。图形学噪声函数Noise FunctionsKen Perlin 为电影《电子世界争霸战》(Tron) 设计的噪声,解决「看起来随机却不像电视雪花」的程序纹理问题。入门路径是值噪声 (value noise):格子角点取伪随机,格内用平滑曲线插值;进阶有梯度噪声 (gradient noise)、五次插值改进版,以及用单纯形网格降复杂度的 Simplex Noise。图形学着色器纹理采样Shader Texture SamplingGPU 把图像存成 sampler2D:一张浮点向量的「表」,坐标连续时硬件做插值。片元里用 texture2D(sampler, uv)(现代 GLSL 为 texture)按归一化 UV 取色。BoS 强调:通道是 [0,1] 连续域、可越界重复、可用噪声扭曲 UV——图像变成可拉伸的「数码软垫」,为后处理与光学玩具式特效铺路。

输入关键词开始搜索

滚轮或双指缩放 · 拖动平移 · 双击放大 / 还原