我们知道我们在屏幕上看见的画面其实都是二维的,那么是怎么做到把三维空间中所展示的内容显示到一个二维空间上呢?这里就需要我们的视图变换以及投影变换来实现了。
整个过程我们可以理解为生活中拍照片,例如我们想去一个摄影棚拍全家福,我们可以分如下三步:
上诉这些变换操作就是我们常说的MVP变换,它们对应的矩阵即MVP矩阵。
平时大家一定也都拍过照片,想要拍出想要的照片,我们肯定要选好拍照的位置,对准要拍摄的物体,以及拿好相机(因为可以横着拍,竖着拍或者斜着拍)。在图形学中,也是一样的道理,我们首先需要定义摄像机如下几个属性:
注:我们约定在右手坐标系中,Camera的LookAt方向为自身的-z轴方向(例如3DMax中的Camera),而在左手坐标系中LookAt方向为自身的z轴方向(例如Unity中的Camera)。
通过相对运动我们知道,如果摄像机所观察的物体和摄像机一起运动,那么摄像机所观察到的画面是保持不变的,例如下图中,两个摄像机的位置朝向都不相同,看见的画面确是一样的。
那么假如有个变换矩阵 ,使我们的摄像机变换为位置在世界坐标原点,看向-z方向,摄像机的y轴即为世界坐标y轴。那么摄像机所观察的物体同样使用矩阵 进行变换,即可保持所观察到的画面不变。这样的操作就是我们所谓的视图变换,变换矩阵 即为视图变换矩阵。至于为什么要执行这样的一步操作,是为了使后续在执行投影变换时,简化计算。
接下来我们来看看矩阵 的值应该如何计算,根据前面的描述我们可以把视图变换拆分为如下几步:
通过复合变换我们可知,只需要把上面两部所对应的矩阵相乘得到的结果及时我们的视图变化矩阵 的值。
首先是平移变换的矩阵,这个很简单,设 ,我们只需要移动 即可将摄像机移动到原点,对应矩阵(设为 )即为:
移动到原点后,我们要通过旋转,使 变为 (0,0,-1) , 变为 (0,1,0)。这一步我们有点无从下手,因为我们之前讨论旋转矩阵时都是绕什么什么轴旋转多少多少度,而在这里我们并不知道应该绕什么轴旋转多少度。
那么应该如何解决呢?这里有一个逆向思维,即我们不知道如何将 变为 (0,0,-1) , 变为 (0,1,0),但是我们能够知道如何将 (0,0,-1) 变为 ,(0,1,0) 变为 ,也就是上诉旋转的逆变换,我们假设我们原先要求的矩阵为 ,那么它的逆变换矩阵即为 。
因为通过前面的知识我们知道,旋转矩阵中的值即为x,y,z三个轴的单位向量旋转后的值。在这里 (0,1,0) 变为 ,(0,0,1) 变为了 ,因为代表的是摄像机自身的-z轴方向,代表的是摄像机自身的y轴方向,根据叉乘的定义可得摄像机自身的x轴方向即为 ,即 (1,0,0) 变为 。那么我们就可得:
同时又因为我们的旋转矩阵为正交矩阵,其逆矩阵即等于转置矩阵,,因此的值即为的转置,可得:
所以即可求得视图变化矩阵 的值,即为上面两个矩阵相乘:
摄像机对好要拍摄的物体后,就差最后按下快门变成照片这一步了,而这一步也就是我们的投影变换,即从三维变成二维。在图形学中,投影变换分为如下两种,分别为正交投影和透视投影。
正交投影示意图如下:
可以发现,正交投影没有近大远小的现象,视线是互相平行的,类似于平行光照,这种投影更多的用于工程图。图中我们还可发现有一个Near clip plane和一个Far clip plane,它们分别代表该摄像机能看见的最近的距离以及最远距离,即摄像机只能看见两个平面之间的物体。在正交投影中,Near clip plane的大小等于Far clip plane,能被摄像机拍摄到的空间即为一个长方体。
由于已经进行过了视图变换,即摄像机在原点,看向-z轴方向,摄像机的y轴与世界坐标y轴重叠。因此我们的所拍摄的空间也能够确定下来,如下图:
该空间的宽度即为L点和R点的距离,设L点和R点的x轴坐标分别为 l 和 r ,高度即为T点和B点的距离,设T点和B点的y轴坐标分别为 t 和 b ,长度即为N点和F点的距离,设N点和F点的z轴坐标分别为 n 和 f 。可得:r > l 和 t > b,但是 f < n,因为是看向-z方向,所以更远的位置z的值越小。同时还要注意,虽然摄像机看向-z轴方向,但是N点并不一定是Near clip plane的中心点,因此 r 和 l 的绝对值不一定相等, t 和 b 的绝对值也不一定相等。
从图中我们也可以发现当从三维变为二维时,空间内的物体的x轴和y轴位置不会发生变换,而是在z轴方向进行了压缩。我们可以把空间中的物体的z轴都设置为0,那么空间中的所有物体都会被压缩到xy平面上,也就是变为二维的了。
然而更规范化的做法是将上诉摄像机所观测的空间(即长方体,设为 S )变换成一个标准立方体(canonical cube)。何为标准立方体?即以原点为中心,边长为2的立方体,也就是立方形在x,y,z三个轴上都是从-1到1。
整个变换过程就是我们的正交投影变换,其对应矩阵即为正交投影变换矩阵。该变换我们可以分为如下两步(这也体现了视图变换的重要性,使得我们做投影变换变得很简单):
同样的,我们来看看这两个变换的矩阵:
首先是移到原点的平移变换,即把长方体S的中心点移动到原点,那么我们就要求出长方体中心点的坐标。根据前面的数据,我们可以知道中心点x的坐标即为 ,中心点y的坐标即为 ,中心点x的坐标即为 ,因此平移矩阵(设为 )即为:
接着是将长方体S变为边长为2的立方体的缩放变换,那么我们只需要知道长方体的长宽高即可。我们设x轴方向的为长度,即为 r-l ,y轴方向的为高度,即为 t-b ,z轴方向的为宽度,即为 n-f 。
所以缩放矩阵(设为 )即为:
因此可以得出最终的正交投影变换矩阵(设为 )为:
注:该变化会导致物体的被拉伸(因为长方体变成了立方体),在后续的视口变换过程中会再对此进行处理。
与正交投影不同的是,透视投影会有近大远小的现象,类似于我们的人眼或者照相机拍照,这种投影更具有真实感,被广泛使用。从图中我们可以看出Far clip plane的面积要大于Near clip plane,因此我们摄像机所观测的区域不再是一个长方体,而是变成了一个四棱台(即四棱锥去掉顶部),也就是我们常说的视锥体(Frustum)。
我们来看下在视图变换后,透视投影在坐标系中的样子,如下图:
通过前面我们知道,投影变换是把相机观测的空间压缩成一个标准立方体,对于这个视锥体我们应该如何压缩呢?有个思路是这样的:
那么我们只需要计算出第一步的变换矩阵(设为 ),然后将它乘以正交投影变换矩阵,即可得到我们的透视投影变换矩阵(设为 )了。
要使视锥体变为长方体,首先我们要使T1T2变为y=T1N的直线,B1B2变为y=-B1N的直线,L1L2变为x=-L1N的直线,R1R2变为x=R1N的直线。
首先我们来看看如何使T1T2变为y=T1N的直线,做这一步前我们可以从x轴方向看向yz平面,来观察这个视锥体,方便理解。
设N点的z值为 n,F点的z值为 f ,(f < n < 0)。
通过图中我们可以发现, 和 是相似三角形,可得: ,即 。扩展开来可得在T1T2直线上的任意一点,设(x,y,z),其y值只需乘以 即可与T1点的Y值相等,这样直线T1T2即变为一条y=T1N水平线,B1B2也是同理。
对于L1L2和R1R2,我们只需要从y轴方向观察xz平面即可,同样是乘以 即可。
因此对应视锥体里的任意一点 (x,y,z),我们只需要将其x和y的值乘以 即可使该视锥体变为长方体。但是z值是否会发生变换我们暂时还不知道,先当做未知来处理(看着似乎不会变换,但实际上并不是这样,后面会在解释)。
关于上面的结论,我们可以得到如下一个变换矩阵:
这个矩阵有个问题,那就是矩阵中的z是一个变量,导致该矩阵并不是一个常量。
因此我们这里需要引入齐次坐标的概念,设我们有个常量k,通过其次坐标我们可以知道,(x,y,z,1) 是等价于 (kx,ky,kz,k) 的,那么可以得知:
因此我们可以把上面的矩阵修改为:
这样我们矩阵中的变量z就被消灭掉了。
接下来我们要看看z值的变换,从图中我们可得知的信息为变为长方体后:
可得:
设我们矩阵中的四个未知数分别为 A,B,C,D,可得:
可知 A=B=0,简化得:
即可算出,C=n+f,D=-nf,因此视锥体压缩为长方体的矩阵为:
因此也可以知道在该变换过程中,任意点的z的值可能是会发生变化的。举个例子,视锥体的中心点 变换后为:
我们来比下大小:
由于f < n < 0,所以 ,2(n+f) < 0,可得(负数除以负数为一个正数)
即视锥体的中心点在变换后离摄像机更远了。
变成长方体后,只需在执行一次正交投影变换即可,因此我们的透视投影变换矩阵为:
注:在有些文章里可以看见 的第三列元素和本文的正好是取负的关系,那是因为他们在计算时,把 n 和 f 当做正数使用,即 N 点和 F 点的z轴坐标为 -n 和 -f 。
在上图中 我们称之为 Field of View (for Y / for Vertical),也就是常说的FOV。自然而然 自然是x轴或者是水平方向的FOV,我们只需要知道其中一个即可。我们设 。
L1R1的长度(即为宽,设值为w)与T1B1的长度(即为高,设值为h)比例我们称之为宽高比(Aspect ratio),我们假设为该值为 a,即 。
此外我们的视锥体应为对称视锥体,即N点为Near clip plane的中心点,即可得:
带入 中,得:
所以可得,FOV为 ,宽高比为 a 时,透视投影矩阵为:
同时知道了FOV和宽高比,我们就可以计算出之前的 r,l,t,b 这些值。