【免费下载链接】thebookofshaders
Step-by-step guide through the abstract and complex universe of Fragment Shaders.
导读
本文围绕 thebookofshaders 仓库第 08 章 08/README.md 的核心主题展开:用 2D 矩阵对坐标空间进行平移(Translate)、旋转(Rotation)与缩放(Scale)。你将学会如何用 GLSL 内置的mat2构造旋转矩阵和缩放矩阵,理解"移动空间而非移动物体"的着色器思维,并通过仓库中的 cross-translate.frag、cross-rotate.frag、cross-scale.frag 与 yuv.frag 四个可运行示例,掌握把矩阵变换应用到任意图形乃至颜色向量的完整方法。读完本文,你将能独立完成一个由多个图形组合、动态运动的 HUD 风格作品。
矩阵是一种非常强大的向量操作工具。掌握了矩阵的用法,你就能自由地对任何用向量表达的事物进行平移、缩放与旋转——这也是后续章节构造复杂程序纹理的基础。
核心思想:移动坐标空间,而不是移动物体
在上一章(第 07 章"形状")中,我们已经学会如何用smoothstep等函数绘制矩形、圆、三角形等基本图形。要让这些图形动起来,最直接的想法似乎是去修改图形的坐标。但本书采用的技巧恰恰相反:保持物体坐标不变,移动它所在的坐标系本身。
在片段着色器中,st变量保存着每个片段的归一化位置(gl_FragCoord.xy / u_resolution.xy)。所谓"平移图形",只需要给st加上一个向量,整个坐标空间就会跟着移动:
vec2 st = gl_FragCoord.xy / u_resolution.xy; vec2 translate = vec2(cos(u_time), sin(u_time)); st += translate * 0.35; // 整个空间绕圆周移动用仓库中的 cross-translate.frag 亲自验证:取消第 35 行// color = vec3(st.x,st.y,0.0);的注释,你会看到背景上的坐标渐变也随之移动——这正是"空间本身在平移"的直接证据。
练习建议:为十字赋予真实运动的质感
原文给出了一个极佳的实践方向:结合u_time与造型函数(shaping functions),让十字按照你感兴趣的"真实世界"运动方式移动。波浪的来去、钟摆的往复、弹球的反弹、汽车的加速、自行车的刹车,都是值得记录与模拟的参考对象。这一练习为下一节的旋转动画埋下了伏笔。
GLSL 中的矩阵:mat2、mat3 与 mat4
矩阵是按行和列组织的数字集合,用矩阵乘以向量遵循一套精确规则,从而以特定方式改变向量的值。GLSL 原生支持三种方阵类型,仓库术语表 glossary/mat2/README.md、glossary/mat3/README.md、glossary/mat4/README.md 给出了它们的声明与初始化方式:
| 类型 | 维度 | 典型初始化示例 | 说明 |
|---|---|---|---|
mat2 | 2×2 | mat2(1.0, 0.0, 0.0, 1.0) | 2D 变换(平移之外的旋转、缩放) |
mat3 | 3×3 | mat3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0) | 3D 变换或 3 维颜色空间变换 |
mat4 | 4×4 | mat4(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0) | 3D 齐次坐标变换(含平移) |
需要特别注意的是初始化顺序:GLSL 矩阵按列填充。例如mat2的四个参数依次是第一列的两个元素、第二列的两个元素,即mat2(cos(_angle), -sin(_angle), sin(_angle), cos(_angle))实际构造出的矩阵第一列为(cos, -sin)、第二列为(sin, cos)。
在矩阵操作上,GLSL 支持:
- 矩阵乘法
*:矩阵与向量、矩阵与矩阵相乘; matrixCompMult():对两个矩阵做逐分量(component-wise)乘法。其声明与语义见 glossary/matrixCompMult/README.md,例如mat2 matrixCompMult(mat2 x, mat2 y),结果矩阵每个分量result[i][j] = x[i][j] * y[i][j],这与数学上的矩阵乘法截然不同。
仓库中的 08/matrices.frag 是一份矩阵参考速查,包含 2D 旋转、3D 绕 X/Y/Z 轴旋转(rotateX3D、rotateY3D、rotateZ3D及其 4D 版本)、3D 缩放scale4D与平移translate4D的完整构造,可以作为后续练习的对照资料。
旋转:绕原点旋转坐标系
有了矩阵概念,就可以构造旋转矩阵。原文给出了 2D 旋转矩阵的标准公式(仓库中的 08/rotmat.png 展示了该数学表达式),并将它实现为如下 GLSL 函数:
mat2 rotate2d(float _angle){ return mat2(cos(_angle), -sin(_angle), sin(_angle), cos(_angle)); }该函数将二维向量绕vec2(0.0)原点旋转。注意:我们画图形时并不在原点。十字形状画在画布中心vec2(0.5),因此旋转前必须先把空间中心移到原点,旋转之后再移回去,形成经典的三步序列(参见 cross-rotate.frag 的完整实现):
void main(){ vec2 st = gl_FragCoord.xy / u_resolution.xy; vec3 color = vec3(0.0); // 1. 把空间从中心移到 vec2(0.0) st -= vec2(0.5); // 2. 旋转空间(角度随时间正弦变化) st = rotate2d( sin(u_time) * PI ) * st; // 3. 移回原来的位置 st += vec2(0.5); color += vec3(cross(st, 0.4)); gl_FragColor = vec4(color, 1.0); }旋转练习
原文为这一节设计了三个递进式练习,全部可以通过 cross-rotate.frag 的注释开关验证:
- 取消第 45 行注释(
// color = vec3(st.x,st.y,0.0);),观察旋转过程中坐标空间本身的变化; - 注释掉第 37 行与第 39 行的平移,观察缺少"移回中心"的后果——旋转将绕画布左下角进行,图形会飞出视野;
- 用旋转改进上一节平移练习中的动画:将十字的运动从简单的圆周平移升级为"自转 + 公转"。仓库中的 08/cross-animated.frag 正是这一进阶的示例实现——它先用
st = rotate2d( sin(u_time*0.5)*PI ) * st;让图形绕自身中心往复旋转,再叠加st += translate*0.35;的圆周平移,将旋转与平移两种变换组合在同一着色器中。
缩放:对角矩阵
平移与旋转之外,矩阵同样可以缩放物体大小——这正是 3D 建模软件以及 Processing 中pushMatrix/popMatrix的底层机制。2D 缩放矩阵是一个对角矩阵,非对角线元素为零(示意图见 08/scale.png):
mat2 scale(vec2 _scale){ return mat2(_scale.x, 0.0, 0.0, _scale.y); }在 cross-scale.frag 中,缩放同样遵循"移到原点 → 变换 → 移回"的流程,并且用时间驱动缩放系数产生呼吸效果:
st -= vec2(0.5); st = scale( vec2(sin(u_time) + 1.0) ) * st; // 缩放系数在 0~2 之间往复 st += vec2(0.5);缩放练习
- 取消第 42 行注释,观察空间坐标如何被缩放(缩放中心位于原点,空间会被拉伸或压缩);
- 注释掉第 37 行与第 39 行的平移,观察以画布角落为原点的缩放效果;
- 组合旋转矩阵与缩放矩阵:先构造
rotate2d(angle) * scale(scaleVec)之类的复合矩阵再乘向量。注意变换顺序至关重要——矩阵乘法不满足交换律,左乘顺序决定了"先旋转再缩放"还是"先缩放再旋转",两种结果截然不同。
综合创作:用变换拼装 HUD
现在你已经掌握了绘制图形(第 07 章)、平移、旋转与缩放的全部基础。原文给出的收尾练习是:设计并构造一个假的 UI 或 HUD(平视显示器)。将多个十字、矩形等基础图形通过矩阵变换组合成仪表盘、瞄准框、状态栏等界面元素,并让部分元素以u_time驱动运动,即可获得一个可实时交互的着色器 HUD。这也是把"变换"从单个图形推广到"合成场景"的关键一步。
矩阵的另一个应用:YUV 颜色空间变换
矩阵变换的对象不限于空间坐标——颜色同样可以视为向量进行矩阵运算。YUV 是一种用于模拟照片与视频模拟编码的色彩空间,它考虑人类感知特性,通过降低色度(chrominance)分量的带宽来实现压缩。RGB 与 YUV 之间的转换本质上就是一次 3×3 矩阵乘法。
仓库中的 yuv.frag 完整实现了这一思路,直接使用mat3作为转换矩阵:
// YUV to RGB matrix mat3 yuv2rgb = mat3(1.0, 0.0, 1.13983, 1.0, -0.39465, -0.58060, 1.0, 2.03211, 0.0); // RGB to YUV matrix mat3 rgb2yuv = mat3(0.2126, 0.7152, 0.0722, -0.09991, -0.33609, 0.43600, 0.615, -0.5586, -0.05639); void main(){ vec2 st = gl_FragCoord.xy / u_resolution.xy; vec3 color = vec3(0.0); // UV 取值范围是 -1 到 1,需要把 st(0.0~1.0) 重映射 st -= 0.5; // 变为 -0.5 ~ 0.5 st *= 2.0; // 变为 -1.0 ~ 1.0 // 把 st 作为三维向量的 y、z 分量,与 3x3 矩阵相乘 color = yuv2rgb * vec3(0.5, st.x, st.y); gl_FragColor = vec4(color, 1.0); }这段代码展示了三个值得留意的细节:
mat3的 9 个参数同样按列优先填充,每一列对应输出 RGB(或 YUV)的一个分量与输入三个分量之间的线性组合系数;- 为了让屏幕坐标映射到 UV 的有效区间,先把
st从[0,1]重映射到[-1,1]; - 屏幕横纵坐标分别充当 U、V 分量,固定 Y 亮度分量为
0.5,将二维屏幕"映射"成一张完整的 YUV 色度图,再由矩阵乘法还原为 RGB 显示。
由此可见,"用矩阵移动值"的思想完全适用于颜色空间。这类矩阵化的颜色处理也是后续章节(如第 16 章混合与第 18 章 LUT)中高级色彩操作的基础。
小结与工具箱
本章核心收获可以概括为三句话:
- 平移:给
st加向量即可移动整个坐标空间; - 旋转:
rotate2d(angle)构造 2D 旋转矩阵,配合"移到原点 → 变换 → 移回"三步序列完成绕任意中心点的旋转; - 缩放:对角矩阵
scale(vec2)控制各轴缩放比例,变换顺序决定最终效果。
矩阵变换对所有向量表达的事物一视同仁——坐标是向量,颜色也是向量,因此这些技巧将贯穿本书后续全部章节,用于把基础图形组合成漂亮的过程化纹理与图案。
如果你希望进一步探索,可以将 08/matrices.frag 中的 3D 旋转矩阵(rotateX3D、rotateY3D、rotateZ3D、scale4D、translate4D)作为进阶参考,理解如何把本章的 2D 思路推广到三维空间;同时可以对照 glossary/mat2/README.md 与 glossary/matrixCompMult/README.md 弄清矩阵初始化与逐分量乘法等底层细节,为后续章节的创作打好基础。
【免费下载链接】thebookofshaders
Step-by-step guide through the abstract and complex universe of Fragment Shaders.
相关推荐
The Book of Shaders 第 08 章:GLSL 二维矩阵变换实战指南 —— 平移、旋转、缩放与 YUV 色彩空间
The Book of Shaders 第 08 章:GLSL 二维矩阵变换实战指南 —— 平移、旋转、缩放与 YUV 色彩空间 本章( 08/README v
The Book of Shaders 精读:用 GLSL 二维矩阵变换平移、旋转与缩放坐标空间
The Book of Shaders 精读:用 GLSL 二维矩阵变换平移、旋转与缩放坐标空间 本篇文章精读开源项目 thebookofshaders htt
The Book of Shaders 实战精读(08):GLSL 二维矩阵变换——平移、旋转与缩放
The Book of Shaders 实战精读(08):GLSL 二维矩阵变换——平移、旋转与缩放 导读 本章是《The Book of Shaders》(
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考