矩阵变换、齐次坐标与组合顺序

矩阵变换、齐次坐标与组合顺序:保留 LearnOpenGL 3.3 Core 正文机制,以 context—资源—结果合同、GPU 轨迹和章专属单故障完成可重放验收。

学习目标

  • 能计算一个二维向量经缩放矩阵、旋转矩阵作用后落在哪,并写出缩放 / 平移 / 旋转矩阵各长什么样
  • 能解释齐次坐标里那个 w 分量是干嘛的——为什么平移非要把点写成四维不可
  • 能区分「矩阵乘列向量」的代数约定与「列主序」的内存布局,不再把两者混成一件事
  • 能计算一个点按缩放、旋转、平移三步变换后的中间坐标与最终坐标
  • 能回答:先平移再缩放、和先缩放再平移,结果一样吗?为什么矩阵相乘的顺序不能随便换?

为什么要把整张「坐标地图」拉一下、转一下、挪一下

前面你已经能把一个三角形钉在固定的几个坐标上画出来。可现实里的物体要会动——它得能放大缩小、能转身、能从这边走到那边。难道每动一下,就回头去手改每个顶点的坐标吗?几个顶点还行,几万个顶点就要人命了。

换个想法:与其一个个挪顶点,不如对整张坐标地图本身动手——把铺着所有顶点的那张「坐标网格」整体拉伸、旋转、平移,网格上的点自然就跟着一起被带过去了。就像你不用搬动地图上每一座城市,只要把整张地图缩放、转个方向、挪个位置,所有城市的相对关系都还在,只是整体被「掰」了一下。

这一章讲的就是这套「掰整张坐标空间」的工具——它的核心是一个叫矩阵的小数字方阵。没有它,让物体动起来就只能逐顶点死改坐标,既算不动也写不出动画;有了它,一次乘法就把成千上万个顶点一起变换到位。

向量:一截带方向的箭头

先认识被「掰」的对象的最小单位。在图形学里,一个 就是一组数:二维写成 (x, y)、三维写成 (x, y, z)。把它画出来,就是一截从原点指向那个坐标的箭头——既有长度,又有方向。顶点的位置、物体的朝向、移动的方向,都是向量。

向量之间能做几种运算,每种都有直观的几何含义:加减是「首尾相接」、数乘是「沿原方向伸缩」、还有点乘、叉乘。先看下面这张图建立直觉,精确的公式留到 §4 一条条推:

这里先点两个后面常用的词。把向量除以自己的长度,就得到一个长度恰好为 1、只保留方向的 (normalize,归一化)。两个向量还能做 (结果是一个数,跟夹角有关)和 (结果是一个同时垂直于两者的新向量,常用来求法线)。

矩阵:把整个空间「掰一下」的数字方阵

这一节的主角 ,就是把一组数排成的矩形方阵——比如 2 行 2 列的 2×2、4 行 4 列的 4×4。它本身只是一堆数字,但在图形里它有个魔力:一个矩阵就代表对整个坐标空间的一次「掰」——缩放、旋转、平移,每种「掰」法都对应一个特定长相的矩阵。

怎么用它去「掰」一个向量?靠 :拿矩阵去乘那个向量,算出来的新向量就是「掰」之后它落到的新位置。本章后面会反复用到这个动作——一次乘法,把空间连同上面的向量一起变换。它在管线里的位置,正是顶点进入 NDC 之前那一连串「掰到位」的步骤(你好三角形那章预告过的)。

这里要先立一条贯穿全章的约定:本站和 GLSL 一样,矩阵采用 ,并且把向量当成竖着的一列写在矩阵右边(右乘列向量)。

这里其实有两个经常被混在一起的约定:列向量约定回答「公式怎么写、谁先作用」,所以写成 M · v列主序回答「矩阵的 16 个数怎样排在线性内存里」。前者决定 TRSvT\cdot R\cdot S\cdot v 从右往左执行,后者决定上传给 OpenGL 的数组顺序。它们常一起出现,但不是同一个概念:

代数:矩阵乘列向量

写成 M · v,向量在右;组合 T · R · S · v 从右往左作用。平移量位于矩阵最后一列。

存储:一列接一列

m00,m10,m20,0 · m01,m11,m21,0 · m02,m12,m22,0 · tx,ty,tz,1

列向量写在矩阵右边决定代数作用顺序;列主序只描述这 16 个数在线性内存中的存放顺序。

几种「掰」法:缩放、平移、旋转

「掰」法主要就三种,先建立画面感,§4 再给出每种对应的矩阵长相与推导:

  • 缩放:把空间沿各轴拉伸或压扁——整体放大 2 倍、或只把横向压扁一半。
  • 平移:把空间整体挪一段——所有点一起朝某个方向走同样的距离,形状不变。
  • 旋转:把空间绕某个轴转一个角度——像转动一张转盘,点都绕着原点转过去。

其中缩放和旋转,可以直接到本章 §5 那个可交互的可视化里亲手拖——调矩阵的四个数,看整片网格被拉伸、转向,建立「矩阵 = 把整张网格掰成什么样」的手感。平移比较特殊,它「掰」不动二维线性网格,得靠一个叫齐次坐标的小技巧,这也是 §4 要重点掰碎的地方。

数学推导:从向量到变换矩阵

这是本章的重心,但别怕——每个公式后面都有一句「人话翻译」,一步都不跳。先把本节用到的符号摊开:

  • a,b,v\vec{a}, \vec{b}, \vec{v}:向量(竖着的一列)
  • x,y,zx, y, z:向量的各个分量
  • a|\vec{a}|:向量 a\vec{a} 的长度
  • θ\theta:旋转或夹角的角度(用弧度)
  • M,S,T,RM, S, T, R:矩阵;SS 缩放、TT 平移、RR 旋转
  • Sx,Sy,SzS_x, S_y, S_z:各轴的缩放倍数;Tx,Ty,TzT_x, T_y, T_z:各轴的平移量

向量运算的代数式

先把上一节那几个几何直觉写成公式。加法是对应分量相加,几何上就是首尾相接:

a+b=(ax+bx, ay+by, az+bz)\vec{a} + \vec{b} = (a_x + b_x,\ a_y + b_y,\ a_z + b_z)

这个式子在说:两个向量相加,就是把它们的 x 配 x、y 配 y、z 配 z 各自加起来;画成箭头,就是把 b\vec{b} 的尾巴接到 a\vec{a} 的头上。减法、数乘同理(数乘是每个分量都乘那个数,取负是每个分量乘 1-1)。

向量的长度用勾股定理算:

a=ax2+ay2+az2|\vec{a}| = \sqrt{a_x^2 + a_y^2 + a_z^2}

这个式子在说:向量的长度,就是把各分量平方加起来再开根号——和直角三角形求斜边一模一样。把向量每个分量都除以这个长度,就得到长度为 1 的单位向量。

点乘的结果是一个数,它把「对应分量相乘求和」和「夹角」联系了起来:

ab=axbx+ayby+azbz=abcosθ\vec{a} \cdot \vec{b} = a_x b_x + a_y b_y + a_z b_z = |\vec{a}|\,|\vec{b}|\cos\theta

这个式子在说:点乘既能用分量直接算(左边),又等于两向量长度相乘再乘夹角的余弦(右边)。所以当两向量垂直(θ=90°\theta = 90°cosθ=0\cos\theta = 0)时点乘为 0——这是判断「是否垂直」的常用招。叉乘 a×b\vec{a} \times \vec{b} 则给出一个同时垂直于两者的新向量(只在三维有定义),公式留作 Glossary 里查阅,这里先记住它的几何意义。

矩阵乘向量、单位矩阵

矩阵怎么「掰」向量?把向量当成一列写在矩阵右边,按「左边每一行 × 右边那一列,对应相乘再求和」来算。以 2×2 为例(列主序,矩阵记作 [abcd]\begin{bmatrix} a & b \\ c & d \end{bmatrix},它的两列 (a,c)(a, c)(b,d)(b, d) 正是基向量 î、ĵ 被掰到的新位置):

[abcd][xy]=[ax+bycx+dy]\begin{bmatrix} a & b \\ c & d \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} a x + b y \\ c x + d y \end{bmatrix}

这个式子在说:新向量的第一个分量,是矩阵第一行 (a,b)(a, b) 和向量 (x,y)(x, y) 对应相乘求和;第二个分量用第二行同样算。这正是 §5 那个可视化背后的运算。有一个特殊的矩阵 ,二维是 [1001]\begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}、四维是对角线全 1 其余全 0——任何向量乘它都原样不变,是「什么都不掰」的起点。

缩放矩阵

要把空间沿各轴拉伸 Sx,Sy,SzS_x, S_y, S_z 倍,只需让矩阵把每个分量各自乘上对应倍数。用 4×4 (列主序):

S=[Sx0000Sy0000Sz00001]S = \begin{bmatrix} S_x & 0 & 0 & 0 \\ 0 & S_y & 0 & 0 \\ 0 & 0 & S_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

这个式子在说:把缩放倍数放在对角线上,乘以点 (x,y,z,1)(x, y, z, 1) 就得到 (Sxx, Syy, Szz, 1)(S_x x,\ S_y y,\ S_z z,\ 1)——每个坐标各自被拉伸对应的倍数。右下角那个 1 先记住,下一小节讲它的来历。比如 Sx=Sy=1.5S_x = S_y = 1.5 就是整体放大 1.5 倍,你能在 §5 Demo 里用「缩放 ×1.5」预设亲眼看到网格被撑大。

平移矩阵与齐次坐标(本章最绕的一步)

缩放和旋转都能写成「每个新分量是旧分量的线性组合」,可平移不行——平移是「每个分量加上一个常量」,2×2 / 3×3 这种纯线性矩阵做不到「凭空加一个数」。解决办法是给点补上第四个分量,凑成四维 (x, y, z, w),这套坐标就叫 ,并约定点取 w=1w = 1

这样就能用一个 4×4 (列主序,平移量住在最后一列)把空间整体挪动:

T=[100Tx010Ty001Tz0001]T = \begin{bmatrix} 1 & 0 & 0 & T_x \\ 0 & 1 & 0 & T_y \\ 0 & 0 & 1 & T_z \\ 0 & 0 & 0 & 1 \end{bmatrix}

这个式子在说:左上 3×3 是单位阵(所以 x/y/z 本身不变),最后一列塞了平移量。拿它乘点 (x,y,z,1)(x, y, z, 1),算第一行得 1x+0y+0z+Tx1=x+Tx1\cdot x + 0\cdot y + 0\cdot z + T_x\cdot 1 = x + T_x,其余同理,于是点变成 (x+Tx, y+Ty, z+Tz, 1)(x + T_x,\ y + T_y,\ z + T_z,\ 1)——整体挪了一段。关键就在那个 w=1w = 1:正因为它是 1,最后一列的平移量才被「乘 1 加进来」;若是方向向量取 w=0w = 0,平移量乘 0、对它无效(方向本就不该被平移)。下面这张图把这一步彻底掰开:

旋转矩阵

旋转绕某个轴转角度 θ\theta。三维有绕 x、y、z 三根轴的旋转矩阵,形式相似,这里给出最直观的绕 z 轴(在 xy 平面内转,正是 §5 那个二维 Demo 在做的事,列主序):

Rz=[cosθsinθ00sinθcosθ0000100001]R_z = \begin{bmatrix} \cos\theta & -\sin\theta & 0 & 0 \\ \sin\theta & \cos\theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

这个式子在说:左上 2×2 那块 [cosθsinθsinθcosθ]\begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix} 负责把 xy 平面上的点绕原点转 θ\theta 角,z 不变。代入点 (x,y)(x, y) 就得到 (xcosθysinθ, xsinθ+ycosθ)(x\cos\theta - y\sin\theta,\ x\sin\theta + y\cos\theta),正是「绕原点转 θ\theta」的标准公式。绕 x 轴、绕 y 轴的矩阵把 cos/sin\cos/\sin 放进对应的另两行两列,思路一样。到 §5 Demo 点「旋转 45°」预设,就能看到整片网格被转过去。(小提示:连续绕多轴旋转可能撞上「万向节死锁」,工程里常改用四元数——超出本章,后面摄像机章再说。)

组合多个变换:顺序不能乱

真实物体往往要同时缩放、旋转、平移。把多个矩阵相乘就得到一个 。但矩阵乘法不满足交换律——顺序一换,结果就变。常用的安全顺序是「先缩放、再旋转、最后平移」,写成右乘列向量的形式:

vnew=TRSv\vec{v}_{\text{new}} = T \cdot R \cdot S \cdot \vec{v}

这个式子在说:把缩放 SS、旋转 RR、平移 TT 依次乘到向量左边。作用顺序是从右往左——最靠近 v\vec{v}SS 先生效(先缩放),然后 RR(再旋转),最后 TT(最后平移)。为什么这个顺序安全?因为若反过来先平移再缩放,连「平移走的那段距离」也会被缩放跟着放大,物体就飞到离谱的地方。下面这张图把「先缩放后平移」和「先平移后缩放」两种顺序的结果摆在一起,一眼看出差别:

下面固定一个可核查的点 ec v=(1,1),依次应用 S(2,1)S(2,1)、逆时针 R(90°)R(90°)T(4,1)T(4,-1)。不要只背 T·R·S,每一步都把坐标写出来:

分步1 / 3

先缩放:只改各轴尺度

S(2,1)(1,1)=(2,1)S(2,1)\cdot(1,1)=(2,1)。横坐标乘 2,纵坐标乘 1;此时既没有旋转,也没有平移。

T · R · S · v 从右往左执行

0. 局部点v = (1, 1)

坐标结果:(1, 1)

1. 先缩放S(2, 1)

坐标结果:(2, 1)

2. 再旋转R(90°)

坐标结果:(-1, 2)

3. 后平移T(4, -1)

坐标结果:(3, 1)

同一点按 S → R → T 演算:公式写作 T · R · S · v,最靠近 v 的 S 先执行。

T · R · S · v 从右往左执行

0. 局部点v = (1, 1)

坐标结果:(1, 1)

1. 先缩放S(2, 1)

坐标结果:(2, 1)

2. 再旋转R(90°)

坐标结果:(-1, 2)

3. 后平移T(4, -1)

坐标结果:(3, 1)

同一点按 S → R → T 演算:公式写作 T · R · S · v,最靠近 v 的 S 先执行。

动手:亲手把整片网格「掰」一下

下面就是本章主可视化。它演示的是二维线性变换——拖紫色输入向量 v,或调矩阵的四个数 a / b / c / d,看绿色的 M·v 怎么动;basis 模式还会把整片网格连同基向量 î / ĵ 一起「掰」给你看。下方有现成预设:单位、旋转 45°、缩放 ×1.5、切变,一键填入。

猜一猜:把 a 调到 1.5、d 也调到 1.5(其余为 0),整片网格会怎样?再点「旋转 45°」预设,网格会朝哪边转?盯住右下角的 det(M) 读数——它什么时候变成负数、什么时候变成 0?(提示:det 是 0 意味着整片二维空间被压扁成了一条线。)

可交互

交互可视化加载中…

ad 改的是横向、纵向的拉伸(这正是 §4 缩放矩阵对角线上的 SxS_xSyS_y);调 bc 会让网格「斜过去」(切变);旋转预设填的就是那组 cos/sin\cos/\sin。下面再单独看一个「放大 1.5 倍」的辅助实例,对照 §4 的缩放矩阵 [1.5001.5]\begin{bmatrix} 1.5 & 0 \\ 0 & 1.5 \end{bmatrix}——网格被整体撑大、向量也被拉长:

可交互

交互可视化加载中…

注意:这个可视化只能演示能写成 2×2 的线性变换(缩放 / 旋转 / 切变 / 扭曲空间)——平移它做不到,因为平移不是线性变换,得靠 §4 那个带 ww 的齐次坐标 + 4×4 矩阵才行。所以平移请回看 §4 的图,缩放和旋转在这里随便拖。

代码对照:在着色器里应用变换

把上面的数学落到代码上其实很短:在你的程序里把缩放 / 旋转 / 平移乘成一个组合矩阵,作为 uniform 传给顶点着色器,着色器里一句 gl_Position = transform * vec4(aPos, 1.0) 就把每个顶点掰到位。先看顶点着色器——两端 GLSL 几乎一样:

#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 transform;        // 组合矩阵(平移×旋转×缩放)
void main() {
  // 顶点补成齐次坐标 (x,y,z,1),左乘组合矩阵 → 掰到新位置
  gl_Position = transform * vec4(aPos, 1.0);
}

注意 vec4(aPos, 1.0) 那个补出来的 1.0 就是 §4 讲的齐次坐标 w=1w = 1——正因为它,transform 里的平移量才生效。版本声明照例有差异:

CPU 侧负责把三个矩阵乘成一个 transform 再上传。C++ 用 GLM,WebGL2 这边常用 gl-matrix:

glm::mat4 transform = glm::mat4(1.0f);   // 从单位矩阵起步
// 注意调用顺序:先 translate、再 rotate、最后 scale
// 等价于数学上 T · R · S(作用时 S 先生效)
transform = glm::translate(transform, glm::vec3(0.5f, -0.5f, 0.0f));
transform = glm::rotate(transform, glm::radians(45.0f),
                        glm::vec3(0.0f, 0.0f, 1.0f));
transform = glm::scale(transform, glm::vec3(1.5f, 1.5f, 1.5f));
int loc = glGetUniformLocation(program, "transform");
glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(transform));

两端思路一致:从单位矩阵起步,依次右乘 translate → rotate → scale,得到的就是 §4 的 TRST \cdot R \cdot S。上传时那个 GL_FALSE / false 是关键的一笔——

容易踩的坑

小结

  • 变换 = 对整张坐标空间做缩放 / 旋转 / 平移,矩阵就是这套「掰空间」的工具,一次乘法带动所有顶点
  • 缩放矩阵把倍数放对角线、旋转矩阵用一组 cos/sin\cos/\sin、二者都能写成线性变换(§5 可亲手拖)
  • 平移非线性,得靠齐次坐标:把点补成 (x,y,z,1),让 w=1,平移量住在 4×4 矩阵最后一列才生效
  • 矩阵乘法不交换,组合按 TRST\cdot R\cdot S、作用时从右往左(最靠近向量的先生效),顺序错物体就跑偏
  • 着色器里 gl_Position = transform * vec4(aPos, 1.0) 一句应用变换;上传矩阵用列主序、不转置、角度用弧度

练习

问题 1(推导 / 手算题) 给二维向量 v=(2,1)\vec{v} = (2, 1)。①把它经缩放矩阵 [1.5001.5]\begin{bmatrix} 1.5 & 0 \\ 0 & 1.5 \end{bmatrix} 作用,手算结果。②再把原始的 v\vec{v} 经绕原点旋转 90° 的矩阵 [0110]\begin{bmatrix} 0 & -1 \\ 1 & 0 \end{bmatrix} 作用,手算结果,并说出它在几何上转到了哪。

问题 2(改 Demo 题) 在 §5 的可交互可视化里做两件事:①只用滑块,把矩阵调成「绕原点旋转 90°」的样子(参考问题 1 ②那个矩阵),看 î / ĵ 转到哪。②把矩阵调成 a=2, b=0, c=0, d=0,观察 det(M) 变成几、整片网格变成什么样,解释为什么。

问题 3(排错题) 你把一个 GLM mat4 原样传给 glUniformMatrix4fv,矩阵在 CPU 端已经按列主序存储。第三个参数应该填 GL_TRUE 还是 GL_FALSE?再说明「列主序」与公式写成 M · v 分别约束什么。

名词解释

本章出现的专业名词,用大白话再讲一遍。

向量

一截既有长度又有方向的量,写成一组数:二维 (x, y)、三维 (x, y, z),画出来就是从原点指向那个坐标的箭头。位置、朝向、颜色、速度都能用向量表示。详见本章「向量」一节。

单位向量

长度恰好为 1 的向量,只表示「朝哪个方向」、不含大小。把任意非零向量除以它自己的长度(归一化)就能得到。常用来表示朝向、法线、光线方向。详见本章「向量」一节。

点乘

两个向量的一种乘法,结果是一个数a·b = |a||b|cosθ。它和两向量的夹角 θ 挂钩——夹角越小值越大、垂直时为 0。常用来判断夹角、是否垂直、做投影。详见本章「向量」一节与「数学推导」。

叉乘

两个三维向量的一种乘法,结果是一个新向量,方向同时垂直于这两个向量(按右手定则),大小等于它们张成的平行四边形面积。常用来求一个面的法线方向。详见本章「向量」一节。

矩阵

一个按行和列排好的数字方阵(如 2×2、4×4)。在图形里,一个矩阵代表对整个坐标空间的一次变换(缩放 / 旋转 / 平移);拿它去乘一个向量,就把向量「掰」到新位置。详见本章「矩阵」一节。

矩阵乘法

把矩阵和向量(或矩阵和矩阵)相乘:结果的每个元素,是左边某一与右边某一对应相乘再求和。它不满足交换律——A·B 通常不等于 B·A,所以顺序很重要。详见本章「数学推导」。

列主序

把矩阵「一列一列」存进内存的约定(先第 1 列、再第 2 列……),与之相对的是行主序(一行一行存)。GLSL / OpenGL 默认列主序;用它乘向量时,向量当成竖着的一列写在矩阵右边。本站全程用列主序。详见本章「矩阵」一节。

单位矩阵

对角线全是 1、其余全是 0 的方阵。任何向量乘以它都原样不变,相当于「什么都不掰」——它是矩阵世界的「1」,常作为搭组合变换的起点。详见本章「数学推导」。

缩放矩阵

对角线放各轴缩放倍数 SxSySz、其余为 0 的矩阵。乘以一个点,就把它沿各轴拉伸或压扁:倍数大于 1 放大、0 到 1 缩小、负数翻转。详见本章「缩放矩阵」。

齐次坐标

给三维点补上第四个分量 w 凑成四维 (x, y, z, w) 的写法。点取 w=1(能被平移),方向向量取 w=0(平移对它无效)。正是这个多出来的 w,让「平移」也能写进一个矩阵里。详见本章「平移矩阵与齐次坐标」。

平移矩阵

左上 3×3 是单位阵、最后一列放平移量 TxTyTz、右下角为 1 的 4×4 矩阵。乘以一个 w=1 的点,就给它的 x / y / z 各加上对应平移量,把它整体挪一段。详见本章「平移矩阵与齐次坐标」。

旋转矩阵

用一组 cosθsinθ 排成的矩阵,乘以一个点就把它绕某根轴转 θ 角。三维有绕 x、y、z 三根轴各自的形式。连续多轴旋转可能撞上「万向节死锁」,工程里常改用四元数。详见本章「旋转矩阵」。

组合矩阵

把多个变换矩阵相乘合成的单个矩阵,一次乘法就对向量同时施加缩放、旋转、平移。因矩阵乘法不交换,相乘顺序决定先后;作用时从最靠近向量的那个开始(右往左)。常用顺序 T·R·S。详见本章「组合多个变换」。

版本、来源与运行边界

本章以 Joey de Vries 的 LearnOpenGL 原章 为授权改编依据,教学运行基线是 OpenGL 3.3 Core Profile。Khronos 当前发布的规范参照是 OpenGL 4.6 Core Profile;这里用 4.6 规范核查术语和状态合同,但不把 4.6 API 偷偷倒填为原教程内容。GLFW、GLAD、Assimp 与驱动版本都属于运行环境,不能拿“编译通过”替代对 context、资源和 framebuffer 结果的验证。

正式概念与状态责任

  • transformation:在“矩阵变换、齐次坐标与组合顺序”中由CPU 侧 GLM 矩阵与 program 的 transform uniform负责解释其输入、受控状态和可观察结果;运行时以输入点、各矩阵、乘法顺序、uniform 值、clip 坐标与屏幕位置定位它的第一处变化。
  • matrix:在“矩阵变换、齐次坐标与组合顺序”中由CPU 侧 GLM 矩阵与 program 的 transform uniform负责解释其输入、受控状态和可观察结果;运行时以输入点、各矩阵、乘法顺序、uniform 值、clip 坐标与屏幕位置定位它的第一处变化。
  • glm:在“矩阵变换、齐次坐标与组合顺序”中由CPU 侧 GLM 矩阵与 program 的 transform uniform负责解释其输入、受控状态和可观察结果;运行时以输入点、各矩阵、乘法顺序、uniform 值、clip 坐标与屏幕位置定位它的第一处变化。
  • homogeneous:在“矩阵变换、齐次坐标与组合顺序”中由CPU 侧 GLM 矩阵与 program 的 transform uniform负责解释其输入、受控状态和可观察结果;运行时以输入点、各矩阵、乘法顺序、uniform 值、clip 坐标与屏幕位置定位它的第一处变化。

章专属 OpenGL 状态实验

先预测“构造 T·R·S,上传 mat4,再由顶点着色器计算 clip-space position”发生后,CPU 侧 GLM 矩阵与 program 的 transform uniform应怎样改变列向量约定、矩阵乘积、角度单位、uniform location 和变换后坐标;再操作三个实验。实验不生成变化率或正确率等虚构总分,只显示真实 GL 状态、资源、命令和可观察结果。

实验一:Context—资源—结果合同

选择任一正式概念与基线/单故障场景,核对它是否进入本章状态合同。正式概念只有同时出现在解释、可视状态和交付证据中才算覆盖。

Context · resource · observable result

矩阵变换、齐次坐标与组合顺序:状态合同

用齐次矩阵明确缩放、旋转、平移的组合顺序并把同一矩阵上传给 shader

验证场景

官方教程正式概念

logl-06 · 基线帧

transformation固定 context、资源内容与输入事件,执行“构造 T·R·S,上传 mat4,再由顶点着色器计算 clip-space position”

状态所有者CPU 侧 GLM 矩阵与 program 的 transform uniform
受控状态/资源列向量约定、矩阵乘积、角度单位、uniform location 和变换后坐标
触发命令构造 T·R·S,上传 mat4,再由顶点着色器计算 clip-space position

冻结输入:transformation

CPU 侧 GLM 矩阵与 program 的 transform uniform记录列向量约定、矩阵乘积、角度单位、uniform location 和变换后坐标

结果:得到可重复的初始 GL 状态与资源身份

观测:输入点、各矩阵、乘法顺序、uniform 值、clip 坐标与屏幕位置中的初始快照

预期:CPU 侧 GLM 矩阵与 program 的 transform uniform得到可复查结果,并持续满足“同一向量约定下,最右侧变换先作用;CPU 与 GLSL 的矩阵布局约定一致”

实验二:CPU 命令到 GPU 结果的五段轨迹

逐段执行“构造 T·R·S,上传 mat4,再由顶点着色器计算 clip-space position”,在每一步记录资源身份、状态变化与第一个可观察结果,并持续核对“同一向量约定下,最右侧变换先作用;CPU 与 GLSL 的矩阵布局约定一致”。

CPU command · GL state · GPU result

矩阵变换、齐次坐标与组合顺序:五段轨迹

选择一段命令—资源—结果1 / 5

当前观测:输入点、各矩阵、乘法顺序、uniform 值、clip 坐标与屏幕位置中的初始快照

不变量:同一向量约定下,最右侧变换先作用;CPU 与 GLSL 的矩阵布局约定一致

实验三:单故障与同输入恢复

注入“把 T·R 写成 R·T,物体绕世界原点公转而不是原地自转”,保存首个分岔;撤销后沿用完全相同的 context、资源内容、uniform 和 draw 输入重放。只有输入点、各矩阵、乘法顺序、uniform 值、clip 坐标与屏幕位置一起恢复才算修复。

Single fault · first divergence · replay

矩阵变换、齐次坐标与组合顺序:反例与恢复

故障:把 T·R 写成 R·T,物体绕世界原点公转而不是原地自转

1. 冻结输入一致

第 1 次沿用同一 context、资源、uniform 与 draw 输入

2. 注入单故障一致

保持其余输入不变,仅注入“把 T·R 写成 R·T,物体绕世界原点公转而不是原地自转”

3. 定位首差一致

同一向量约定下,最右侧变换先作用;CPU 与 GLSL 的矩阵布局约定一致

4. 清理并重放一致

输入点、各矩阵、乘法顺序、uniform 值、clip 坐标与屏幕位置

最小可重放检查

unit: logl-06
owner: CPU 侧 GLM 矩阵与 program 的 transform uniform
state_or_resource: 列向量约定、矩阵乘积、角度单位、uniform location 和变换后坐标
command: 构造 T·R·S,上传 mat4,再由顶点着色器计算 clip-space position
pass_invariant: 同一向量约定下,最右侧变换先作用;CPU 与 GLSL 的矩阵布局约定一致
single_fault: 把 T·R 写成 R·T,物体绕世界原点公转而不是原地自转
required_evidence: 输入点、各矩阵、乘法顺序、uniform 值、clip 坐标与屏幕位置

复核者先仅依据以上合同写出预期,再运行基线、单故障和清理后重放。若两次基线的资源身份、首个状态变化或 framebuffer 结果不同,必须保留差异,不能用最终截图相似掩盖中间状态错误。

出处声明

本文为改编重写,改编自 Joey de Vries 的 LearnOpenGL。原文:learnopengl.com

原作及译作以 CC BY-NC 4.0 协议授权,本改编版同样遵循该协议(署名—非商业性使用)。

讨论

评论区加载中…