03. 向量与矩阵操作
在 GLSL 中,向量不仅仅是数据容器,它们有一套极其强大的操作语法。
1. 分量访问 (Accessing Components)
你可以用三种方式访问向量的分量,它们在功能上是完全一样的,只是为了语义清晰:
x, y, z, w: 用于位置坐标r, g, b, a: 用于颜色s, t, p, q: 用于纹理坐标
vec4 v = vec4(10.0, 20.0, 30.0, 40.0);
float x = v.x; // 10.0
float r = v.r; // 10.0 (完全等价,只是语义不同)
2. 魔法特性:Swizzling (重组)
这是 GLSL 最酷的特性。你可以随意组合分量来创建新的向量。
vec3 color = vec3(1.0, 0.0, 0.5); // 红色略带蓝
// 提取前两个分量
vec2 rg = color.rg; // (1.0, 0.0)
vec2 xy = color.xy; // 同上
// 交换顺序
vec3 bgr = color.bgr; // (0.5, 0.0, 1.0)
// 重复分量
vec3 rrr = color.rrr; // (1.0, 1.0, 1.0)
// 甚至可以用于赋值(掩码)
vec4 dest = vec4(0.0);
dest.xy = vec2(1.0, 2.0); // dest 变成 (1.0, 2.0, 0.0, 0.0)
3. 向量运算
GLSL 自动支持向量的逐分量运算。这意味着你不需要写循环。
逐分量数学运算
vec3 a = vec3(1.0, 2.0, 3.0);
vec3 b = vec3(2.0, 2.0, 2.0);
vec3 c = a + b; // (3.0, 4.0, 5.0)
vec3 d = a * b; // (2.0, 4.0, 6.0) - 注意:这不是点积,是逐项相乘!
vec3 e = a * 2.0; // (2.0, 4.0, 6.0) - 向量乘标量
几何运算 (内置函数)
这些函数是图形学的核心:
dot(v1, v2)(点积):- 结果是
float。 - 公式:
|v1| * |v2| * cos(θ) - 几何意义:
- 当向量归一化时:纯粹衡量两个向量方向的相似度(等于夹角的余弦值)。
- 1.0: 方向相同
- 0.0: 垂直
- -1.0: 方向相反
- 当向量未归一化时:包含长度信息,可用于计算投影长度。
- 当向量归一化时:纯粹衡量两个向量方向的相似度(等于夹角的余弦值)。
- 常用于光照计算(详见第 5 章)。
- 结果是
cross(v1, v2)(叉积):- 结果是
vec3。 - 仅用于
vec3。 - 几何意义:得到一个垂直于 v1 和 v2 所在平面的新向量。常用于计算法线。
- 结果是
length(v)(长度):- 结果是
float。 - 计算向量的模长(勾股定理)。
- 结果是
distance(p1, p2)(距离):- 结果是
float。 - 等同于
length(p1 - p2)。
- 结果是
normalize(v)(归一化):- 结果是同维度的向量。
- 保持方向不变,将长度变为 1.0。
- ⚠️ 重要:涉及方向计算(如光照)时,几乎总是需要先归一化。
- 💡 直观理解:
- 想象成调节音量:把所有向量的长度(音量)都调到标准的 1.0,但保留方向(歌曲内容)不变。
- 在光照计算中,我们只关心方向是否一致,如果不归一化,较长的向量会导致光照计算结果偏大(像音量过大一样失真)。
数值限制函数
这些函数用于限制数值范围,防止超出有效区间:
clamp(x, min, max)(限制范围):- 功能:将值限制在
[min, max]范围内。 - 等价于:
min(max(x, min), max) - 常见用途:限制颜色值在 0.0~1.0 之间。
- 功能:将值限制在
min(a, b)/max(a, b): 返回较小/较大的值。
4. 矩阵运算
矩阵 = 一组基向量(Basis Vectors)
请把矩阵看作三个“新坐标轴”的组合。一个 mat3 矩阵实际上就是 3 个 vec3 列向量 并排站在一起:
mat3 m = mat3(
1.0, 0.0, 0.0, // 第一列:新的 X 轴 (Right)
0.0, 1.0, 0.0, // 第二列:新的 Y 轴 (Up)
0.0, 0.0, 1.0 // 第三列:新的 Z 轴 (Forward)
);
矩阵乘法 = 坐标系变换
当你写 m * v 时,GPU 实际上在做线性组合:
$$ \text{结果} = (\text{新 X 轴} \times v.x) + (\text{新 Y 轴} \times v.y) + (\text{新 Z 轴} \times v.z) $$
线性代数视角:列向量派
在 GLSL 中,我们坚定地做“列向量派”!
- 向量是竖着的列向量。
- 矩阵在左,向量在右:
matrix * vector。
5. 实战详解:光照与法线
这是本章最核心的应用场景,将向量运算(点积、归一化)结合起来实现 3D 光照效果。
5.1 光照计算核心公式
Lambert 光照模型的核心公式非常简单:
float brightness = dot(normal, lightDir);
normal(法线):表面朝向哪里?lightDir(光线方向):光从哪里来?dot(点积):计算夹角余弦值。- 夹角 0° (正对) → 1.0 (最亮)
- 夹角 90° (垂直) → 0.0 (无光)
- 夹角 > 90° (背光) → < 0.0 (黑)
📝 编程作业
难度:⭐⭐ 中等 | 预计时间:45-60 分钟
任务要求
- 绘制圆形:使用
distance()和step() - 径向渐变:使用
length()和normalize() - 方向性光照:使用
dot()创建 Lambert 光照效果
代码框架
// 片段着色器
precision mediump float;
varying vec2 vUv;
void main() {
vec2 center = vec2(0.5, 0.5);
// 任务 1:绘制圆形
float d = distance(vUv, center);
float circle = step(d, 0.3);
vec3 circleColor = vec3(circle);
// 任务 2:径向渐变
float dist = length(vUv - center);
float gradient = 1.0 - dist * 2.0;
gradient = clamp(gradient, 0.0, 1.0); // 限制在 0-1 范围
vec3 gradientColor = vec3(gradient);
// 任务 3:简单光照(Lambert 模型)
// 1. 构造法线 (详见第 5 章)
// 建议 Z 分量设为 0.5 以避免暗部过黑
vec3 normal = normalize(vec3(vUv - center, 0.5));
// 2. 定义光线方向 (从右上方照射)
vec3 lightDir = normalize(vec3(0.5, 0.5, 1.0));
// 3. 计算光照强度 (点积)
float light = dot(normal, lightDir);
light = max(light, 0.0); // 避免负值
vec3 lightColor = vec3(light);
// TODO: 组合这些效果
gl_FragColor = vec4(lightColor, 1.0);
}
normal z 分量通俗理解:从“陡峭山峰”到“平缓土包”
这一段可能有点抽象,我们用一个生活中的例子来理解。
想象你在平地上堆了一个土堆(模拟凸起效果):
法线 Z 分量很小 (0.1) = 陡峭的山峰
- 这个土堆非常陡峭,像一面墙。
- 左边的坡面几乎是垂直的,完全朝向左边。
- 光线从右边照过来,完全照不到左边的坡面(背光)。
- 结果:左边一片漆黑。
法线 Z 分量变大 (0.5) = 平缓的小土包
- 我们把山峰拍扁,变成一个平缓的土包。
- 左边的坡面现在只是微微向左倾斜,大部分还是朝上的。
- 光线从右边照过来,虽然不是直射,但能照到这个缓坡。
- 结果:左边不再是全黑,而是有淡淡的光照。
代码中的操作:
// 0.1 -> 陡峭 -> 容易出现全黑死角
// 0.5 -> 平缓 -> 光照过渡更柔和
vec3 normal = normalize(vec3(vUv - center, 0.5));
这行代码本质上就是决定你是要造一座“险峰”还是一个“缓坡”。
为什么左下方会全黑?(原理分析)
如果你还是想知道数学原理,看这里:
- 左下方区域:
vUv - center是负值(比如指向左下)。 - Z 分量太小:导致法线主要指向左下方(几乎平行于屏幕)。
- 光线方向:从右上方射来。
- 冲突:法线指左下,光线指左下(光线的去向),两者几乎相反。
- 或者说:法线指左下,光线从右上来。法线和光线来源方向夹角 > 90°。
- 结果:点积为负,光照强度被截断为 0(全黑)。
解决方案:增加 Z 分量(如改为 0.5)。让法线更“竖直”(指向观察者),避免与光线夹角过大。
检查清单
- 圆形边缘清晰(使用
step) - 径向渐变平滑过渡
- 光照效果有明显的明暗变化(调整 Z 分量避免全黑)
- 所有向量在使用前都进行了归一化 (
normalize)
参考资源
- 完整作业清单:编程作业清单.md