齐次坐标到底是什么?为什么要多加一个 w?
23 minutes read •
齐次坐标为什么要多加一个 w?为什么点通常写成 w = 1,向量却写成 w = 0?又为什么算完以后,还要除以 w?
我们先不急着给出定义,而是从平移这个具体问题出发,一步步看看这种表示方式是怎么来的。
一、为什么平移不能直接用矩阵乘法?
理解齐次坐标,可以先从一个很具体的问题入手:为什么旋转、缩放都能用矩阵乘法来做,平移却不行? 普通笛卡尔坐标下,任何矩阵乘法都满足 M · 0 = 0,也就是说,线性变换必须把原点钉死在原点。你可以旋转,可以放大,可以把整个空间拉斜,但原点始终不能挪窝。
平移偏偏就要把原点挪走。比如向右移动 3,原点就应该从 (0, 0) 变成 (3, 0)。可不管怎么填一个 2 × 2 矩阵,把它乘上 (0, 0),结果都只能是 (0, 0)。所以,二维空间里的非零平移,不能只靠一个 2 × 2 矩阵乘以二维坐标来完成;三维空间也一样,单靠一个 3 × 3 矩阵做不到,通常还得另外加上一个位移向量。
这样一来,一连串旋转、缩放、平移,就会把矩阵乘法和向量加法混在一起。虽然可以合成为“先乘一个矩阵,再加一个位移”,却不能直接把所有步骤写成同维度矩阵的连乘。如果能让平移也参与矩阵乘法,整条变换链就能统一合成一个矩阵,让所有顶点重复使用,传给 GPU 的接口也更统一。这正是多加一个分量首先要解决的问题。
二维里做不到,那就升一个维度试试
想象一下,在水平的二维平面上添加一根竖着的 w 轴,把平面上的一个点沿 w 方向竖直向上拉成一条线,就像盖高楼,一层一层往上盖。w 轴是一根有刻度的数轴,w = 0、1、2、3……分别对应不同的高度。然后,轻轻地、水平地把这栋高楼推歪:底部不动,越往上,水平移动得越远。这里想象的是理想的线性推歪,钢筋骨架仍然是一条直线,不是真的把楼推弯了。
这个操作就是高一个维度里的错切,也叫切变(shear)。假设每升高一层,水平位移增加 t,那么第 w 层的水平位移就是 w · t:第一层移动 t,第二层移动 2t,第三层移动 3t。楼层只是方便理解的比喻,w 可以是小数,也可以是负数。把操作写成坐标变化,就是 (X, Y, w) → (X + w·tₓ, Y + w·tᵧ, w)。这里用大写 X、Y 表示升维后的分量,稍后会看到它们与普通二维坐标的区别。
平移量现在变成了“输入分量 w 乘上固定系数”,这正是矩阵乘法能够表达的形式。采用列向量写法,把 tₓ、tᵧ 放进 3 × 3 矩阵的最后一列,就能让 w 自动参与计算。只看 w = 1 这一层,移动量恰好就是 t,于是三维空间中的错切,在这一层上看起来就是普通的二维平移。 二维里的平移虽然不是线性变换,却可以通过升维后的线性变换来实现。
你可能会问:矩阵不是必须让原点不动吗,这里怎么又能动了?关键在于,我们把原来的二维原点放到了 (0, 0, 1),而不是三维原点 (0, 0, 0)。三维原点仍然老老实实待在原地,被移走的是第一层上的那个点,线性变换的规则一点也没破坏。
二、点为什么用 w = 1,向量为什么用 w = 0?
到这里,我们只是把二维点 (x, y) 放到三维空间里的 (x, y, 1),就已经能用同一种矩阵乘法处理旋转、缩放和平移了。这些仿射变换的标准矩阵表示保持 w 不变。因此,如果只做这些操作,让所有点一直待在 w = 1 这一层,算完直接读出前两个分量就够了。
点表示的是“在哪里”,位移向量表示的则是“朝哪个方向、走多远”。把两个点一起平移,它们之间的位移并不会改变,因此位移向量不应受到平移的影响。
沿用高楼的比喻,只要把向量放在 w = 0 的底层,平移对应的错切就不会让它移动。代入前面的坐标变化也能看到:平移量乘上 w,也就是乘上 0,整个平移部分便消失了。
点用 w = 1 的标准表示,向量用 w = 0,同一个矩阵就能让点跟着平移,同时让向量不受平移影响。 这两个数并不是随手贴上的标签,而是与点和位移的运算关系吻合。
三、如果 w 变成了别的数,该怎么办?
从固定的一层,走向任意非零的 w
到这一步,我们已经在高一个维度的线性代数框架里,统一了点和向量的仿射变换。这里采用的约定很简单:把二维点放在 w = 1 这一层,计算后读取前两个分量。
不过,更一般的三维线性变换并不保证把这一层上的点仍然送回这一层。比如,让 (X, Y, w) 变成 (X, Y, X + w),就是一个合法的可逆线性变换。原本在第一层的 (1, 2, 1),经过它以后就成了 (1, 2, 2)。
先不急着给这种表示取名字,继续考虑眼下的问题:我们已经借助升维统一了平移、旋转和缩放,但对二维点的读取规则还只定义在 w = 1 这个切面上。如果想把这套表示推广到其他非零的 w,就需要说明:不同层上的坐标,应该怎样对应到二维点?
先让不同楼层读出同样的平移量
我们尝试最朴素的思路:无论选哪一层,都希望同一个平移矩阵在读取后表示同样的二维平移。 于是,先让 w = 2、3、4……,看看会怎样。
以 X 分量为例,设这一方向上的平移量为 t。在 w = 2 这一层,平移矩阵会把 X 变成 X + 2t,但我们希望读出来的位移仍然只是 t。一个自然的做法,就是在读取坐标时,把这一层的 X 除以 2:
(X + 2t) / 2 = X/2 + t
同样,在 w = 3 时,位移是 3t,那就除以 3。Y 分量也是同样的道理。这样,无论选哪个非零的 w 层,按照 (X/w, Y/w) 读取坐标,都能得到相同的二维平移量。
平移量对了,起点也要保持不变
但这里还有一个问题。如果原来的二维点是 (x, y),只是把它的表示从 (x, y, 1) 改成 (x, y, 2),那么按照刚才的规则,读出来的起点就会变成 (x/2, y/2);改成 (x, y, 3),起点就变成 (x/3, y/3)。
也就是说,虽然读取后的平移量符合要求,起点却已经不是原来的二维点了。要同时保持原来的起点和读取后的平移量,只修改 w 是不行的。
解决办法也很直接:把同一个二维点放到不同层时,同时把 x、y 乘以对应的 w,用 (wx, wy, w) 来表示它。读取时除以 w,就能得到原来的 (x, y);经过平移矩阵后,X 变成 wx + wt,除以 w 得到 x + t,Y 分量同理。
因此,无论选哪个非零的 w 层,包括负的 w,用 (wx, wy, w) 表示同一个二维点,再统一按 (X/w, Y/w) 读取,就既能保持原来的起点,也能让同一个平移矩阵产生相同的二维平移。
成比例的坐标组,表示同一个点
到这里,我们从平移的需求出发,构造出了有限二维点的齐次坐标表示。现在再来看它的几何意义:当 w 取遍非零实数时,(wx, wy, w) 描述的是同一条过原点直线上的所有非零坐标组。我们把这些坐标组,看作同一个二维点的不同代表。
这种选择能与矩阵运算配合,是因为线性变换会保留整体倍数关系:输入整体乘以某个数,输出也会整体乘以这个数。只要输出不是零坐标组,换一个等价代表去计算,最终仍然表示同一个射影点。可逆矩阵保证非零输入不会变成零坐标组。
所谓齐次坐标,核心就在这里:一个非零坐标组与它的任意非零整体倍数,表示同一个射影点。 对于有限点,我们最常用的代表,就是最后一项为 1 的那个。PBRT 的齐次坐标介绍也说明了这种比例等价关系。至于 w = 0 对应的射影点,我们留到下一节讨论。
除以 w,就是回到第一层读数
有了这条直线,怎么从 (X, Y, w) 找回二维位置就很清楚了。只要 w 不为 0,把三个分量一起除以 w,就得到 (X/w, Y/w, 1),也就是同一条直线与第一层的交点,前两项便是二维坐标。除以 w,其实就是沿着这条过原点的直线,回到 w = 1 这一层读数。 所以 (2, 4, 2) 和 (−1, −2, −1) 都读成 (1, 2)。负数没有问题,因为这里用的是一整条直线,包括原点两侧;但 (0, 0, 0) 本身不能表示射影点,因为所有这些直线都经过它,光凭它无法确定是哪一条。
回头看刚才会改变 w 的变换:(1, 2, 1) 变成 (1, 2, 2),除以 2,读回二维就是 (1/2, 1)。如果一开始换用两倍的代表 (2, 4, 2),输出会是 (2, 4, 4),除以 4,仍然得到 (1/2, 1)。中间的数字不同,最后表示的位置却完全一样,这正是比例关系在矩阵运算中的作用。
前面的平移也符合这套规则。二维点 (1, 2) 向右移动 3,用第一层的代表来算,就是 (1, 2, 1) 变成 (4, 2, 1);换用第二层的等价代表,就是 (2, 4, 2) 变成 (8, 4, 2)。第二层的 X 虽然增加了 6,最后除以 2,读出来仍然是 (4, 2)。高楼越往上被推得越远,与最终表示同样的二维平移并不矛盾,因为读数时的比例也跟着变了。
四、w = 0:无穷远点与位移向量
既然要除以 w,那么 w = 0 怎么办?先看它对应的直线:比如 (1, 0, 0) 所在的过原点直线,完全躺在 w = 0 平面里,永远不会与 w = 1 相交,因此无法读成这一层上的某个有限位置。在射影几何里,这样的直线表示无穷远点,用来记录平行线共同的方向。二维里沿水平方向延伸的一组不同直线没有有限交点,但在扩展后的射影平面里,它们共享同一个无穷远点。这里的“无穷远”是一种方向的表示,并不是一个特别大的坐标。
不过,无穷远点与位移向量需要分清。“向右走三米”和“向右走六米”,可以分别写成 (3, 0, 0) 和 (6, 0, 0)。作为位移向量,它们当然不同,因为长度不同;只有把它们当作无穷远点的齐次代表时,才忽略这个倍数,把它们看成同一个射影点。不能因为齐次点允许整体缩放,就把三米和六米的位移也当成一回事。 同样,零向量 (0, 0, 0) 作为位移完全合法,只是不能表示射影点。
前面“点在第一层、向量在底层”的处理方式,针对的是仿射变换。换成更一般的射影变换,w = 0 不一定继续保持为 0,无穷远点也可能变成有限点。因此,用 w = 0 表示位移向量时,不能直接把仿射变换中的结论套到任意射影变换上。
在图形学里,为什么要区分点和向量?
区分点和向量最直接的用处,就是避免把平移加到方向上。比如方向 (1, 0) 遇到向上平移 5,写成 (1, 0, 0),平移后仍然是 (1, 0, 0);如果误写成 (1, 0, 1),结果就成了 (1, 5, 1)。再把前两项拿来当方向,朝向已经变了,即使重新归一化长度,也无法恢复原来的方向。
w = 0 只会去掉平移部分,旋转和缩放照样能影响向量。另外,法线在非均匀缩放下通常需要用可逆线性部分的逆转置来变换,才能继续垂直于表面,光把 w 填成 0 还不够。PBRT 关于点、向量和法线变换的说明给出了相应推导。
五、齐次坐标能直接相加减吗?
理解了点和向量的区别,加减法就顺了。把有限点都写成 w = 1 的标准代表,两个点相减,最后一项是 1 − 1 = 0,得到位移向量;点加位移向量,最后一项是 1 + 0 = 1,得到另一个点;两个位移向量相加,最后一项仍然是 0,得到的还是位移向量。
那两个点直接相加呢?比如 A 是 (1, 2),B 是 (3, 4),用标准代表相加,会得到 (1, 2, 1) + (3, 4, 1) = (4, 6, 2)。如果把结果当作齐次点来读,除以 2,就得到 (2, 3),恰好是 A 和 B 的中点。但这依赖一个关键条件:我们给两个点选的代表,最后一项都是 1。如果把 A 换成等价的 (2, 4, 2),B 仍用 (3, 4, 1),相加就会得到 (5, 8, 3),读出来是 (5/3, 8/3),已经不是中点了。
两个点明明没变,只是其中一个换了代表,结果就变了。这说明,齐次代表虽然可以像普通向量一样相加,但这种加法不会自动成为与代表选择无关的“点的加法”。要用它求中点,就先把两个有限点都统一到 w = 1,再计算 0.5A + 0.5B,最后一项自然是 1,前两项直接给出中点。前面用相减求位移,同样依赖先选好这样的标准代表。
更一般地,对这些标准代表做 0.25A + 0.75B,得到的点会更靠近 B。权重加起来等于 1,这样的运算叫仿射组合;权重都非负时,就是熟悉的加权平均。如果权重加起来等于 0,结果的 w 也等于 0,可以解释为位移向量,其中也包括零向量。B − A 正是这种情况,两个权重分别是 1 和 −1。
六、透视投影为什么也要除以 w?
理解了“沿过原点的直线,回到选定平面读数”,透视中的除法就不突然了。想象一个最简单的相机:相机中心放在原点,成像平面放在 z = 1。从相机出发,穿过空间中的点 (x, y, z) 画一条直线,它与成像平面的交点就是 (x/z, y/z, 1),这里假设点在相机前方,z > 0。横向坐标 x、y 相同,深度 z 越大,投影后的位置就越靠近中心;同样大小的物体放得越远,成像也越小,近大远小就这样出现了。OpenCV 的针孔相机模型说明使用的就是这种投影关系。
在三维图形管线里,我们给点 (x, y, z) 添加第四个分量 w,使用 4 × 4 矩阵。透视投影矩阵会让输出的 w 与深度有关,再通过除以 w 实现按深度缩小的效果。具体正负号和深度表达取决于所用约定,但这个除法与前面读回标准代表的思路相同。通常,裁剪坐标经过这一步得到归一化设备坐标,还需要视口映射,才会变成屏幕上的像素位置。
下次看到一个点经过矩阵以后,w 从 1 变成别的数,就可以沿着本文的思路去理解:只要 w 不为 0,把所有分量一起除以 w,就能回到标准代表,读出它所表示的位置。单独把最后一项改成 1,一般会改变整组坐标的比例关系,也就改变这个点。
回到最初的问题:多出来的 w,先让平移能够参与矩阵乘法;当我们把读取规则推广到任意非零的 w 时,成比例的坐标组便可以表示同一个点。沿着过原点的直线回到选定平面读数,又把这套表示自然地连接到了透视投影。