矩阵变换、齐次坐标与组合顺序
矩阵变换、齐次坐标与组合顺序:保留 LearnOpenGL 3.3 Core 正文机制,以 context—资源—结果合同、GPU 轨迹和章专属单故障完成可重放验收。
学习目标
- 能计算一个二维向量经缩放矩阵、旋转矩阵作用后落在哪,并写出缩放 / 平移 / 旋转矩阵各长什么样
- 能解释齐次坐标里那个
w分量是干嘛的——为什么平移非要把点写成四维不可 - 能区分「矩阵乘列向量」的代数约定与「列主序」的内存布局,不再把两者混成一件事
- 能计算一个点按缩放、旋转、平移三步变换后的中间坐标与最终坐标
- 能回答:先平移再缩放、和先缩放再平移,结果一样吗?为什么矩阵相乘的顺序不能随便换?
为什么要把整张「坐标地图」拉一下、转一下、挪一下
前面你已经能把一个三角形钉在固定的几个坐标上画出来。可现实里的物体要会动——它得能放大缩小、能转身、能从这边走到那边。难道每动一下,就回头去手改每个顶点的坐标吗?几个顶点还行,几万个顶点就要人命了。
换个想法:与其一个个挪顶点,不如对整张坐标地图本身动手——把铺着所有顶点的那张「坐标网格」整体拉伸、旋转、平移,网格上的点自然就跟着一起被带过去了。就像你不用搬动地图上每一座城市,只要把整张地图缩放、转个方向、挪个位置,所有城市的相对关系都还在,只是整体被「掰」了一下。
这一章讲的就是这套「掰整张坐标空间」的工具——它的核心是一个叫矩阵的小数字方阵。没有它,让物体动起来就只能逐顶点死改坐标,既算不动也写不出动画;有了它,一次乘法就把成千上万个顶点一起变换到位。
向量:一截带方向的箭头
先认识被「掰」的对象的最小单位。在图形学里,一个 ↡一截既有长度又有方向的量,写成一组数,如二维 (x, y)、三维 (x, y, z)。可以画成从原点指向该坐标的箭头。常用来表示位置、方向、颜色、速度等。 就是一组数:二维写成 (x, y)、三维写成 (x, y, z)。把它画出来,就是一截从原点指向那个坐标的箭头——既有长度,又有方向。顶点的位置、物体的朝向、移动的方向,都是向量。
向量之间能做几种运算,每种都有直观的几何含义:加减是「首尾相接」、数乘是「沿原方向伸缩」、还有点乘、叉乘。先看下面这张图建立直觉,精确的公式留到 §4 一条条推:
这里先点两个后面常用的词。把向量除以自己的长度,就得到一个长度恰好为 1、只保留方向的 ↡长度恰好为 1 的向量,只表示方向、不含大小。把任意非零向量除以它的长度即可得到。常用于表示朝向、法线、光线方向。(normalize,归一化)。两个向量还能做 ↡两个向量的一种乘法,结果是一个数:a·b = |a||b|cosθ。它和两向量的夹角 θ 挂钩,常用来判断夹角大小、是否垂直(点乘为 0 即垂直)、做投影。(结果是一个数,跟夹角有关)和 ↡两个三维向量的一种乘法,结果是一个新向量,方向同时垂直于这两个向量、大小等于它们张成的平行四边形面积。常用来求一个面的法线方向。(结果是一个同时垂直于两者的新向量,常用来求法线)。
矩阵:把整个空间「掰一下」的数字方阵
这一节的主角 ↡一个按行和列排好的数字方阵,如 2×2、3×3、4×4。在图形里,一个矩阵代表对整个坐标空间的一次变换(缩放/旋转/平移等);用它去乘一个向量,就把那个向量变换到新位置。,就是把一组数排成的矩形方阵——比如 2 行 2 列的 2×2、4 行 4 列的 4×4。它本身只是一堆数字,但在图形里它有个魔力:一个矩阵就代表对整个坐标空间的一次「掰」——缩放、旋转、平移,每种「掰」法都对应一个特定长相的矩阵。
怎么用它去「掰」一个向量?靠 ↡把矩阵和矩阵(或矩阵和向量)相乘的运算:结果的每个元素,是左边某一行与右边某一列对应相乘再求和。它不满足交换律——A·B 通常不等于 B·A,所以顺序很重要。:拿矩阵去乘那个向量,算出来的新向量就是「掰」之后它落到的新位置。本章后面会反复用到这个动作——一次乘法,把空间连同上面的向量一起变换。它在管线里的位置,正是顶点进入 NDC 之前那一连串「掰到位」的步骤(你好三角形那章预告过的)。
这里要先立一条贯穿全章的约定:本站和 GLSL 一样,矩阵采用 ↡把矩阵按「一列一列」存进内存的约定(先存第 1 列、再第 2 列……),与之相对的是行主序(一行一行存)。GLSL / OpenGL 默认列主序;用它乘向量时,向量当成竖着的一列写在矩阵右边(右乘列向量)。,并且把向量当成竖着的一列写在矩阵右边(右乘列向量)。
这里其实有两个经常被混在一起的约定:列向量约定回答「公式怎么写、谁先作用」,所以写成 M · v;列主序回答「矩阵的 16 个数怎样排在线性内存里」。前者决定 从右往左执行,后者决定上传给 OpenGL 的数组顺序。它们常一起出现,但不是同一个概念:
写成 M · v,向量在右;组合 T · R · S · v 从右往左作用。平移量位于矩阵最后一列。
m00,m10,m20,0 · m01,m11,m21,0 · m02,m12,m22,0 · tx,ty,tz,1
几种「掰」法:缩放、平移、旋转
「掰」法主要就三种,先建立画面感,§4 再给出每种对应的矩阵长相与推导:
- 缩放:把空间沿各轴拉伸或压扁——整体放大 2 倍、或只把横向压扁一半。
- 平移:把空间整体挪一段——所有点一起朝某个方向走同样的距离,形状不变。
- 旋转:把空间绕某个轴转一个角度——像转动一张转盘,点都绕着原点转过去。
其中缩放和旋转,可以直接到本章 §5 那个可交互的可视化里亲手拖——调矩阵的四个数,看整片网格被拉伸、转向,建立「矩阵 = 把整张网格掰成什么样」的手感。平移比较特殊,它「掰」不动二维线性网格,得靠一个叫齐次坐标的小技巧,这也是 §4 要重点掰碎的地方。
数学推导:从向量到变换矩阵
这是本章的重心,但别怕——每个公式后面都有一句「人话翻译」,一步都不跳。先把本节用到的符号摊开:
- :向量(竖着的一列)
- :向量的各个分量
- :向量 的长度
- :旋转或夹角的角度(用弧度)
- :矩阵; 缩放、 平移、 旋转
- :各轴的缩放倍数;:各轴的平移量
向量运算的代数式
先把上一节那几个几何直觉写成公式。加法是对应分量相加,几何上就是首尾相接:
这个式子在说:两个向量相加,就是把它们的 x 配 x、y 配 y、z 配 z 各自加起来;画成箭头,就是把 的尾巴接到 的头上。减法、数乘同理(数乘是每个分量都乘那个数,取负是每个分量乘 )。
向量的长度用勾股定理算:
这个式子在说:向量的长度,就是把各分量平方加起来再开根号——和直角三角形求斜边一模一样。把向量每个分量都除以这个长度,就得到长度为 1 的单位向量。
点乘的结果是一个数,它把「对应分量相乘求和」和「夹角」联系了起来:
这个式子在说:点乘既能用分量直接算(左边),又等于两向量长度相乘再乘夹角的余弦(右边)。所以当两向量垂直(、)时点乘为 0——这是判断「是否垂直」的常用招。叉乘 则给出一个同时垂直于两者的新向量(只在三维有定义),公式留作 Glossary 里查阅,这里先记住它的几何意义。
矩阵乘向量、单位矩阵
矩阵怎么「掰」向量?把向量当成一列写在矩阵右边,按「左边每一行 × 右边那一列,对应相乘再求和」来算。以 2×2 为例(列主序,矩阵记作 ,它的两列 、 正是基向量 î、ĵ 被掰到的新位置):
这个式子在说:新向量的第一个分量,是矩阵第一行 和向量 对应相乘求和;第二个分量用第二行同样算。这正是 §5 那个可视化背后的运算。有一个特殊的矩阵 ↡对角线全是 1、其余全是 0 的方阵。任何向量乘以它都原样不变,相当于「什么都不掰」。它是矩阵世界的「1」,常作为搭建组合变换的起点。,二维是 、四维是对角线全 1 其余全 0——任何向量乘它都原样不变,是「什么都不掰」的起点。
缩放矩阵
要把空间沿各轴拉伸 倍,只需让矩阵把每个分量各自乘上对应倍数。用 4×4 ↡对角线(除最后一格)放各轴缩放倍数 Sx、Sy、Sz、其余为 0 的矩阵。乘以一个点,就把它沿各轴拉伸或压扁;倍数大于 1 放大、0~1 缩小、负数翻转。(列主序):
这个式子在说:把缩放倍数放在对角线上,乘以点 就得到 ——每个坐标各自被拉伸对应的倍数。右下角那个 1 先记住,下一小节讲它的来历。比如 就是整体放大 1.5 倍,你能在 §5 Demo 里用「缩放 ×1.5」预设亲眼看到网格被撑大。
平移矩阵与齐次坐标(本章最绕的一步)
缩放和旋转都能写成「每个新分量是旧分量的线性组合」,可平移不行——平移是「每个分量加上一个常量」,2×2 / 3×3 这种纯线性矩阵做不到「凭空加一个数」。解决办法是给点补上第四个分量,凑成四维 (x, y, z, w),这套坐标就叫 ↡给三维点补上第四个分量 w 凑成四维 (x, y, z, w) 的坐标表示法。点取 w=1(可被平移),方向向量取 w=0(平移对它无效)。正是这个 w,让平移能写进 4×4 矩阵。,并约定点取 。
这样就能用一个 4×4 ↡左上 3×3 为单位阵、最后一列放平移量 Tx、Ty、Tz、右下角为 1 的矩阵。乘以一个 w=1 的点,就给它的 x/y/z 各加上对应平移量,把它整体挪一段。(列主序,平移量住在最后一列)把空间整体挪动:
这个式子在说:左上 3×3 是单位阵(所以 x/y/z 本身不变),最后一列塞了平移量。拿它乘点 ,算第一行得 ,其余同理,于是点变成 ——整体挪了一段。关键就在那个 :正因为它是 1,最后一列的平移量才被「乘 1 加进来」;若是方向向量取 ,平移量乘 0、对它无效(方向本就不该被平移)。下面这张图把这一步彻底掰开:
旋转矩阵
旋转绕某个轴转角度 。三维有绕 x、y、z 三根轴的旋转矩阵,形式相似,这里给出最直观的绕 z 轴(在 xy 平面内转,正是 §5 那个二维 Demo 在做的事,列主序):
这个式子在说:左上 2×2 那块 负责把 xy 平面上的点绕原点转 角,z 不变。代入点 就得到 ,正是「绕原点转 」的标准公式。绕 x 轴、绕 y 轴的矩阵把 放进对应的另两行两列,思路一样。到 §5 Demo 点「旋转 45°」预设,就能看到整片网格被转过去。(小提示:连续绕多轴旋转可能撞上「万向节死锁」,工程里常改用四元数——超出本章,后面摄像机章再说。)
组合多个变换:顺序不能乱
真实物体往往要同时缩放、旋转、平移。把多个矩阵相乘就得到一个 ↡把多个变换矩阵相乘合成的单个矩阵,一次乘法就能对向量同时施加缩放、旋转、平移等。因矩阵乘法不交换,相乘顺序决定先后,且作用时从最靠近向量的那个开始(右往左)。。但矩阵乘法不满足交换律——顺序一换,结果就变。常用的安全顺序是「先缩放、再旋转、最后平移」,写成右乘列向量的形式:
这个式子在说:把缩放 、旋转 、平移 依次乘到向量左边。作用顺序是从右往左——最靠近 的 先生效(先缩放),然后 (再旋转),最后 (最后平移)。为什么这个顺序安全?因为若反过来先平移再缩放,连「平移走的那段距离」也会被缩放跟着放大,物体就飞到离谱的地方。下面这张图把「先缩放后平移」和「先平移后缩放」两种顺序的结果摆在一起,一眼看出差别:
下面固定一个可核查的点 ec v=(1,1),依次应用 、逆时针 、。不要只背 T·R·S,每一步都把坐标写出来:
先缩放:只改各轴尺度
。横坐标乘 2,纵坐标乘 1;此时既没有旋转,也没有平移。
T · R · S · v 从右往左执行
坐标结果:(1, 1)
坐标结果:(2, 1)
坐标结果:(-1, 2)
坐标结果:(3, 1)
T · R · S · v 从右往左执行
坐标结果:(1, 1)
坐标结果:(2, 1)
坐标结果:(-1, 2)
坐标结果:(3, 1)
动手:亲手把整片网格「掰」一下
下面就是本章主可视化。它演示的是二维线性变换——拖紫色输入向量 v,或调矩阵的四个数 a / b / c / d,看绿色的 M·v 怎么动;basis 模式还会把整片网格连同基向量 î / ĵ 一起「掰」给你看。下方有现成预设:单位、旋转 45°、缩放 ×1.5、切变,一键填入。
猜一猜:把
a调到 1.5、d也调到 1.5(其余为 0),整片网格会怎样?再点「旋转 45°」预设,网格会朝哪边转?盯住右下角的det(M)读数——它什么时候变成负数、什么时候变成 0?(提示:det 是 0 意味着整片二维空间被压扁成了一条线。)
交互可视化加载中…
调 a、d 改的是横向、纵向的拉伸(这正是 §4 缩放矩阵对角线上的 、);调 b、c 会让网格「斜过去」(切变);旋转预设填的就是那组 。下面再单独看一个「放大 1.5 倍」的辅助实例,对照 §4 的缩放矩阵 ——网格被整体撑大、向量也被拉长:
交互可视化加载中…
注意:这个可视化只能演示能写成 2×2 的线性变换(缩放 / 旋转 / 切变 / 扭曲空间)——平移它做不到,因为平移不是线性变换,得靠 §4 那个带 的齐次坐标 + 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);
}#version 300 es
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 讲的齐次坐标 ——正因为它,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));import { mat4 } from "gl-matrix";
const transform = mat4.create(); // 单位矩阵起步
// 同样:先 translate、再 rotate、最后 scale(= T·R·S)
mat4.translate(transform, transform, [0.5, -0.5, 0.0]);
mat4.rotateZ(transform, transform, Math.PI / 4); // 45° 用弧度
mat4.scale(transform, transform, [1.5, 1.5, 1.5]);
const loc = gl.getUniformLocation(program, "transform");
gl.uniformMatrix4fv(loc, false, transform); // false = 不转置(已列主序)两端思路一致:从单位矩阵起步,依次右乘 translate → rotate → scale,得到的就是 §4 的 。上传时那个 GL_FALSE / false 是关键的一笔——
容易踩的坑
小结
- 变换 = 对整张坐标空间做缩放 / 旋转 / 平移,矩阵就是这套「掰空间」的工具,一次乘法带动所有顶点
- 缩放矩阵把倍数放对角线、旋转矩阵用一组 、二者都能写成线性变换(§5 可亲手拖)
- 平移非线性,得靠齐次坐标:把点补成
(x,y,z,1),让w=1,平移量住在 4×4 矩阵最后一列才生效 - 矩阵乘法不交换,组合按 、作用时从右往左(最靠近向量的先生效),顺序错物体就跑偏
- 着色器里
gl_Position = transform * vec4(aPos, 1.0)一句应用变换;上传矩阵用列主序、不转置、角度用弧度
练习
问题 1(推导 / 手算题) 给二维向量 。①把它经缩放矩阵 作用,手算结果。②再把原始的 经绕原点旋转 90° 的矩阵 作用,手算结果,并说出它在几何上转到了哪。
问题 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」,常作为搭组合变换的起点。详见本章「数学推导」。
- 缩放矩阵
对角线放各轴缩放倍数
Sx、Sy、Sz、其余为 0 的矩阵。乘以一个点,就把它沿各轴拉伸或压扁:倍数大于 1 放大、0 到 1 缩小、负数翻转。详见本章「缩放矩阵」。- 齐次坐标
给三维点补上第四个分量
w凑成四维(x, y, z, w)的写法。点取w=1(能被平移),方向向量取w=0(平移对它无效)。正是这个多出来的w,让「平移」也能写进一个矩阵里。详见本章「平移矩阵与齐次坐标」。- 平移矩阵
左上 3×3 是单位阵、最后一列放平移量
Tx、Ty、Tz、右下角为 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”
冻结输入: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
矩阵变换、齐次坐标与组合顺序:五段轨迹
当前观测:输入点、各矩阵、乘法顺序、uniform 值、clip 坐标与屏幕位置中的初始快照
不变量:同一向量约定下,最右侧变换先作用;CPU 与 GLSL 的矩阵布局约定一致
实验三:单故障与同输入恢复
注入“把 T·R 写成 R·T,物体绕世界原点公转而不是原地自转”,保存首个分岔;撤销后沿用完全相同的 context、资源内容、uniform 和 draw 输入重放。只有输入点、各矩阵、乘法顺序、uniform 值、clip 坐标与屏幕位置一起恢复才算修复。
Single fault · first divergence · replay
矩阵变换、齐次坐标与组合顺序:反例与恢复
故障:把 T·R 写成 R·T,物体绕世界原点公转而不是原地自转
第 1 次沿用同一 context、资源、uniform 与 draw 输入
保持其余输入不变,仅注入“把 T·R 写成 R·T,物体绕世界原点公转而不是原地自转”
同一向量约定下,最右侧变换先作用;CPU 与 GLSL 的矩阵布局约定一致
输入点、各矩阵、乘法顺序、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 结果不同,必须保留差异,不能用最终截图相似掩盖中间状态错误。