参考:

  1. https://sites.cs.ucsb.edu/~lingqi/teaching/games101.html
  2. http://www.songho.ca/math/homogeneous/homogeneous.html
  3. https://zhuanlan.zhihu.com/p/261097735
  4. https://zhuanlan.zhihu.com/p/261097735
  5. https://zhuanlan.zhihu.com/p/65969162
  6. https://zhuanlan.zhihu.com/p/223033896

齐次坐标

齐次坐标的引入

  • 问题:两条平行线可以相交于一点

在欧氏几何空间,同一平面的两条平行线不能相交,这是我们都熟悉的一种场景。然而,在透视空间里面,两条平行线可以相交,例如:火车轨道随着我们的视线越来越窄,最后两条平行线在无穷远处交于一点。

欧氏空间(或者笛卡尔空间)描述2D/3D几何非常适合,但是这种方法却不适合处理透视空间的问题(实际上,欧氏几何是透视几何的一个子集合),2维笛卡尔坐标可以表示为(x,y)。

如果一个点在无穷远处,这个点的坐标将会(∞,∞),在欧氏空间,这变得没有意义。平行线在透视空间的无穷远处交于一点,但是在欧氏空间却不能,数学家发现了一种方式来解决这个问题。

  • 方法:齐次坐标

简而言之,齐次坐标就是用N+1维来代表N维坐标

我们可以在一个2D笛卡尔坐标末尾加上一个额外的变量 ww 来形成2D齐次坐标,因此,一个点 (X,Y)(X,Y) 在齐次坐标里面变成了(x,y,w)(x,y,w),并且有 X=xw,Y=ywX = \frac{x}{w}, Y = \frac{y}{w};

例如,笛卡尔坐标系下 (1,2) 的齐次坐标可以表示为 (1,2,1),如果点 (1,2) 移动到无限远处,在笛卡尔坐标下它变为 (∞,∞),然后它的齐次坐标表示为 (1,2,0),因为(1/0, 2/0) = (∞,∞),我们可以不用 “∞” 来表示一个无穷远处的点了。

我们把齐次坐标转化为笛卡尔坐标的方法是前面 n-1 个坐标分量分别除以最后一个分量即可。

(x,y,w)(xw,yw)(x, y, w) \leftrightarrow (\frac{x}{w}, \frac{y}{w})

转化齐次坐标到笛卡尔坐标的过程中,我们有一个发现,例如:

1.png

你会发现(1, 2, 3), (2, 4, 6) 和(4, 8, 12)对应同一个Euclidean point (1/3, 2/3),任何标量的乘积,例如(1a, 2a, 3a) 对应 笛卡尔空间里面的(1/3, 2/3) 。因此,这些点是“齐次的”,因为他们代表了笛卡尔坐标系里面的同一个点。换句话说,齐次坐标有规模不变性

  • 证明:两条直线可以相交

考虑如下方程组:

2.png

我们知道在笛卡尔坐标系里面,该方程组无解,因为C ≠ D,如果 C = D, 两条直线就相同了。

让我们在透视空间里面,用齐次坐标 x/w, y/w 代替x 与 y,

3.png

现在我们有一个解(x, y, 0),两条直线相交于(x, y, 0),这个点在无穷远处。

齐次坐标在图形学中的应用

齐次坐标表示是计算机图形学的重要手段之一,它既能够用来明确区分向量和点,同时也更易用于进行仿射(线性)几何变换。

对于三维空间中的一个点 (x,y,z)(x, y, z),其齐次坐标形式为 (wx,wy,wz,w)(wx, wy, wz, w)(其中 w0w \neq 0)。可以看出,一个点可以有无数个齐次坐标;若 w=1w = 1,则齐次坐标中的前三项即为点的位置。

对于三维空间中的一个向量 (x,y,z)(x, y, z),规定其齐次坐标形式为 (x,y,z,0)(x, y, z, 0)。而对于一个点 (x,y,z)(x, y, z),规定其齐次坐标形式为 (x,y,z,1)(x, y, z, 1)

三维矩阵可以表示旋转和缩放,它们相乘的结果是正确的,但是平移变换不能加到三维矩阵中的相乘去表达,只能将矩阵相乘的结果加一个三维向量;引入齐次坐标之后会增加一个维度,变为四维矩阵,多出来的一维向量用来表示平移,那么就可以在一个矩阵中统一所有的操作:平移、旋转、缩放。

利用齐次坐标,可以区分点和向量、更好地描述点和向量的变换操作。

二维空间

以二维的情况为例,在二维空间中引入齐次坐标后(即增加一维),就可以将二维空间中的仿射变换(包括平移、旋转、缩放)都表示成一个矩阵的形式。

7.png

5.png

6.png

规定点的齐次坐标最后一个维度值为 1 与向量的最后一个维度值为 0 也保证了点与向量之间的加减运算结果的一致性。(齐次坐标下,点与点相加的结果为两点的中点)。

4.png

三维空间

三维空间中的应用类似,可以使用一个 4*4 的矩阵表示三维空间中的仿射变换

8.png

9.png

应用矩阵时都是先进性线性变换再平移的顺序

10.png

11.png

对于任意过原点的轴的旋转操作就要稍微麻烦一些,可以通过 罗德里格斯旋转公式 计算获得

12.png

13.png

MVP 变换

Model-View-Projection 矩阵变换简称 MVP 矩阵变换,是经模型矩阵、视图矩阵和投影矩阵三步变换将若干3D模型数据转换为视体空间的过程。

14.png

模型矩阵变换

  • 变换前:模型坐标空间

  • 变换后:世界坐标空间

  • 变换目的:计算模型各点在世界空间的位置,以便将模型摆放到世界空间中。

3D模型是基于自身的坐标系存储的。在世界空间中,模型的位置由世界坐标表示;模型不一定是正放的,因此有旋转;在世界中,模型的大小不一定是建模时的原来的尺寸,因此还要考虑缩放。为了把物体摆放到世界空间中的指定位置,我们需要先将物体模型以原点为中心缩放一定比例,然后以坐标轴为轴进行旋转,最后将变换好的物体模型平移到其世界坐标描述的位置。

设齐次坐标下的平移变换矩阵为 TT,旋转变换矩阵为 RR,缩放变换矩阵为 SS,则模型矩阵变换就是对物体模型的所有顶点进行下式所示的变换。

pM=TRSp0\mathbf{p}_M = T \cdot R \cdot S \cdot \mathbf{p}_0

注意矩阵的顺序不能颠倒。

视图矩阵变换

  • 变换前:世界坐标空间

  • 变换后:相机坐标空间

  • 变换目的:计算物体对相机的相对位置,以便进行后续变换。

在叙述视图变换之前,需要先用数学方法描述相机。用 e\mathbf{e} 表示相机在空间中的位置,单位向量 g\mathbf{g} 表示相机正对的方向。规定了正对方向后,相机仍可以绕该方向旋转,因此需要规定其向上方向,用单位向量 t\mathbf{t} 表示。显然 gt\mathbf{g} \perp \mathbf{t}

由此,我们可以定义以 e\mathbf{e} 为原点,以 (t×g),t,g(\mathbf{t} \times -\mathbf{g}), \mathbf{t}, -\mathbf{g} 为基向量的坐标系,将其称为相机坐标系。这样定义是为了后续变换的坐标与屏幕的像素坐标保持一致,同时遵循右手系的习惯。将这三个基向量记为 u,v,w\mathbf{u}, \mathbf{v}, \mathbf{w}。我们定义的相机以 v\mathbf{v} 为向上方向,观察方向为 w\mathbf{w} 的负向。

image.png

image.png

世界空间构建完毕后,需要相机“拍照”,然后将“照片”在屏幕上显示出来。为此,我们需要得知在相机的视角下世界空间的样子。因此,我们需要求出物体在相机坐标系的位置。我们可以通过对所有顶点应用如下变换公式求出所需的结果:

pV=RviewTviewpM\mathbf{p}_V = R_{view} \cdot T_{view} \cdot \mathbf{p}_M

注意这里要先进行平移变换再进行旋转变换

image.png

其中,RviewR_{view} 的求取用到了正交矩阵的逆就是其转置的性质。

对相机使用如上矩阵进行变换,相当于将相机置于了原点位置,朝 Z-Z 方向观察,上方向为 YY,可以理解为标准或归一化了的相机。而对于物体使用了上述变换后得到的 pV\mathbf{p}_V 即是该标准相机观察到的结果。因此,View 矩阵的作用就相当于将模型由世界坐标空间转化到了(标准)相机坐标空间。

投影矩阵变换

投影矩阵,顾名思义就是将三维空间中的物体投影到二维平面上,有正交投影与透视投影两种方式。

image.png

视锥体(viewing volume/frustum)

相机的可视区域是由其屏幕出发,沿着其观察方向(正对方向)按一定规则延伸的几何空间。由于离相机很远的几何对象对画面的贡献有限,因此考虑可视区域延伸一定距离便截止。若屏幕是平面矩形,那么可视区域是由六个平面围成的几何体,这个几何体就是相机的视锥体。分别用字母 t, b, l, r, n, f 表示上、下、左、右、近、远平面。

image.png

若视锥体是一个立方体,且区域可表示为 1x,y,z1-1 \leq x,y,z \leq 1,则称其为标准视锥体。

投影变换是将各种形状的视锥体变换到标准视锥体的过程。根据投影方式的不同,投影变换有不同的形式。对于正交投影,其视体空间是一个长方体,只需将其平移缩放为标准视锥体即可;对于透视投影,其视锥体是一个四棱台,需要先变换为长方体,然后再依照平行投影的方法处理。

正交投影

对于正交投影,其视体空间是一个长方体,只需将其平移缩放为标准视锥体即可。

image.png

image.png

MorthoM_{ortho} 即为正交投影矩阵。

透视投影

image.png

对于透视投影而言,要先将视锥体从四棱台变换为立方体,再进行一次正交投影。

image.png

首先说明将中心投影的四棱台视锥体变换为长方体的过程,我们的思路是,保持 n 面不动,将 f 面收缩到与 n 面相同的尺寸,这样就将四棱台变成了长方体。具体而言,我们对变换做出以下规定。

  1. n 面在变换过程中保持不动,f 面收缩到与 n 面相同的尺寸;
  2. 观察方向轴线上的点在变换后仍然在轴线上;
  3. n 面和 f 面上的点在变换后仍然在 n 面或 f 面上;
  4. Last but not least,变换前对视锥体内几何对象的透视投影与变换后对视锥体内几何对象的正投影 看起来应该是一样的。这表明变换前后几何体的坐标应保持相似关系。

为后续的推导方便,这里规定透视投影的近平面距离为 nn,远平面距离为 zz

既然我们要将将 f 面收缩到与 n 面相同的尺寸,观察视锥体的 ZY 平面可以得知,对于原始坐标为 (x,y,z)(x, y, z) 处的点,投影后的 Y轴坐标 yy' 满足下式。

y=nzyy' = \frac{n}{z} \cdot y

image.png

对于 X轴坐标,同理满足:

x=nzxx' = \frac{n}{z} \cdot x

image.png

由上,我们可以得到投影变换中的部分信息,即: (xyz1)(nx/zny/z?1)==(nxny??z)\begin{pmatrix}x \\ y\\ z\\ 1 \end{pmatrix} \Rightarrow \begin{pmatrix} nx/z\\ ny/z \\ ? \\ 1 \end{pmatrix} == \begin{pmatrix}nx \\ ny\\ ?? \\ z \end{pmatrix}

假设我们需要求的矩阵为 MpersporthoM_{persp \rightarrow ortho},那么结合上述信息已经能够求出矩阵中的一部分数字,还剩第三行的数字未知。

image.png

这就需要用到在前文中提到的两条假设:

  1. 近平面 n 不动,即所有 (x,y,n,1)(x, y, n, 1) 投影后还是自身

    (x,y,n,1)(x,y,n,1)(x, y, n, 1) \Rightarrow (x, y, n, 1)

    image.png

  2. 远平面 f 的 z 轴坐标保持不变,且远平面中心点保持不变

    (0,0,z,1)(0,0,z,1)(0, 0, z, 1) \Rightarrow (0, 0, z, 1)

    image.png

这样,我们就唯一确定了 MpersporthoM_{persp \rightarrow ortho}

image.png

小结

由上述的推导,最终我们可以得到(用字母 t, b, l, r, n, f 表示上、下、左、右、近、远平面的对应轴坐标)。

Mortho=(2rl00r+lrl02tb0t+btb002nfn+fnf0001)M_{ortho} = \begin{pmatrix} \frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l} \\ 0 & \frac{2}{t - b} & 0 & -\frac{t + b}{t - b} \\ 0 & 0 & \frac{2}{n - f} & -\frac{n + f}{n - f} \\ 0 & 0 & 0 & 1 \end{pmatrix}

Mpersportho=(n0000n0000n+fnf0010)M_{persp \rightarrow ortho} = \begin{pmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n + f & -nf \\ 0 & 0 & 1 & 0 \end{pmatrix}

最终,投影矩阵变换为:

pP=MprojpV\mathbf{p}_P = M_{proj} \cdot \mathbf{p}_V

{Mproj=MorthoMpersportho,透视投影Mproj=Mortho,正交投影\begin{cases} M_{proj}= M_{ortho} \cdot M_{persp \rightarrow ortho}, & 透视投影 \\ M_{proj} = M_{ortho}, & 正交投影 \end{cases}

投影变换中对于视锥体内的 Z 轴变换为一个非线性的变换,除了 n 与 f 平面变换后的 Z 轴值保持不变外;可以通过简单的推导得出,在此范围内,所有的 Z 值在经过投影变换后都会被推向 f 平面,在看向 -Z 方向的相机看来,就是投影后的 Z 值变小。

视口变换

在经过了上述变换后,三维空间中的物体已经被变换到了 1x,y,z1-1 \leq x,y,z \leq 1 的标准视锥体中,接下来要做的就是将其变换到屏幕空间中了。

image.png

具体来说,就是要再乘以一个 MviewportM_{viewport},该矩阵非常直观;作用如下式所示。

[1,1]2[0,width]×[0,height][-1, 1]^2 \Rightarrow [0, width] \times [0, height]

image.png

如此一来,原本三维空间中的物体就可以出现在屏幕上了。

一些差异

在看了 Games101 后又查看了许多博客,发现大家描述的变换过程略有出入,这里就 Opengl 中的处理过程再分析一遍。

image.png

根据上图,在 Opengl 中,顶点数据所在的坐标系为 Object Coordinates(对象或模型坐标系);经过 Model、View 两个矩阵的变换后得到了 Eye Coordinates(眼镜或相机坐标系);

到此为止,还和我们分析的 MVP 矩阵处理过程基本相同,只是多了一些坐标系的叫法。

接着经过了 Projection 矩阵的变换,数据来到了 Clip Coordinates(裁剪坐标系),从后面的除以 w 操作可以反推出,这一步的所有坐标分量均被裁剪到了 [w,w][-w, w] 的范围内,所以被称为裁剪空间。

执行透视除法是为了实现透射投影中近大远小的视觉效果,由上面的推导可以看到,经过了投影矩阵Projection的变换后,W分量保留了观察空间中物体Z坐标的信息,所以透视除法才能够根据距离摄像机的远近正确实现透视效果。我们注意到透射除法是由硬件自动执行的,也就是说透视除法在正交投影和透视投影中都会被执行,只不过正交投影变换并没有改变W分量的值(W分量的值仍是1),所以透视除法并没有实际的效果。我们从这里也明白了使用齐次坐标的意义,其实就是为了正确记录下投影变换前(观察空间)中物体的深度信息,也就是Z坐标的值。

值得注意的是,Vertex Shader 的输出就是在 Clip Space 上,接着由 GPU 自己做 透视除法,即四个分量都除以 w 分量,这样一来,就将顶点变换至了 Normal Device Coordinates(标准设备坐标系),即 NDC 空间。最后再经过视口变换最终将顶点数据变换到 Window Coordinates(屏幕坐标系),完成所有操作。

Fragment Shader 的输入就是在 Window Coordinates 下。

(Vertex Shader) => Clip Space => (透视除法) => NDC => (视口变换) => Window Space => (Fragment Shader)

总的来说,Games101 课上描述的 MVP 变换在渲染管线中是不完整的,少了一个透视除法,其余过程基本相同。