1.世界坐标系

在DirectX中,采用左手坐标系,OpenGL和Vulkan采用右手坐标系:

  图形API坐标系对比以及坐标系之间变换_API

2.VIew/Camera坐标系

在DirectX中,View的坐标系统和OpenGL/Vulkan就已经统一了。都是左手系坐标系:  

图形API坐标系对比以及坐标系之间变换_API_02

3.NDC坐标系

  某个View坐标系中的点 通过投影矩阵(Projection),并进行透视除法后,点变换到了NDC空间中。 DirectX、OpenGL、Metal这些图形API,NDC坐标系都和View坐标系的手性是一致的,都是左手坐标系(Y轴向上),取值范围为【-1,1】。注意:在Vulkan中,NDC坐标系采用了右手坐标系(Y轴向下),比较特别,且取值范围为【0-1】http://xiaopengyou.fun/public/2019/07/28/2_Triangle/。

图形API坐标系对比以及坐标系之间变换_API_03

4.FrameBuffer坐标系

FrameBuffer坐标系,是一个2D坐标系。主要从它的Y的不同来考虑。 在DirectX、Metal、Vulkan中,FrameBuffer的坐标系都是非常传统的。左上角是原点,Y轴朝下。 在OpenGL中,该坐标系的左下角是原点。 这也是很多纹理在OpenGL 模式下要Flip Y过来的原因了(openGL的FrameBuffer坐标系Y轴与纹理坐标系Y轴方向xiang'fan。

图形API坐标系对比以及坐标系之间变换_API_04

5.纹理坐标系

其实纹理没有坐标系,这里说的是 纹理的第一个像素的 sampler(0, 0)表示这张纹理的左上角还是左下角。 这个因为图片格式都是定死了,每个平台都是一样的——表示的都是图片的左上角。

图形API坐标系对比以及坐标系之间变换_API_05  

 

视图变换和投影变换

我们知道我们在屏幕上看见的画面其实都是二维的,那么是怎么做到把三维空间中所展示的内容显示到一个二维空间上呢?这里就需要我们的视图变换以及投影变换来实现了。

整个过程我们可以理解为生活中拍照片,例如我们想去一个摄影棚拍全家福,我们可以分如下三步:

  1. 首先我们可以先决定好站位问题,谁在前谁在后等等,这步操作就是模型变换(Model Transformation)。
  2. 然后我们得全家移步到摄影棚拍(即把摄影棚的相机当做坐标原点),然后还要摆正相机(相机自身的旋转),把相机镜头朝向我们(相机的朝向)以及相机要和我们保持一定的距离(与相机的距离)才能拍出好的照片。这步操作就是我们本章要学习的视图变换(View/Camera Transformation)
  3. 最后就是按下相机的快门,拍出照片(照片就是二维的),这步操作也是我们本章要学习的投影变换(Projection Transformation)

上诉这些变换操作就是我们常说的MVP变换,它们对应的矩阵即MVP矩阵。若对变换和矩阵不熟悉的,建议看下前文。

视图变换(View/Camera transformation)

平时大家一定也都拍过照片,想要拍出想要的照片,我们肯定要选好拍照的位置,对准要拍摄的物体,以及拿好相机(因为可以横着拍,竖着拍或者斜着拍)。在摄像机成像中,也是一样的道理,我们首先需要定义摄像机如下几个属性:

  • 位置(Position):即Camera的坐标,我们可以标记为 图形API坐标系对比以及坐标系之间变换_API_06
  • 朝向(LookAt):即Camera往哪看,其实也就是相机的-z轴向量,我们可以标记为 图形API坐标系对比以及坐标系之间变换_API_06 (单位向量)
  • Y轴的朝向(UpDirection):即Camera的y轴向量,我们可以标记为 图形API坐标系对比以及坐标系之间变换_API_06 (单位向量)。例如若Camera的y轴和世界坐标系的y轴平行,则看见的画面是正的,若Camera的y轴绕z轴旋转,则看见的画面也会跟着旋转。

图形API坐标系对比以及坐标系之间变换_API_09

该图为3DMAX中的摄像机

注:我们约定在右手坐标系中,Camera的LookAt方向为自身的-z轴方向(例如3DMax中的Camera),而在左手坐标系中LookAt方向为自身的z轴方向(例如Unity中的Camera)。

通过相对运动我们知道,如果摄像机所观察的物体和摄像机一起运动,那么摄像机所观察到的画面是保持不变的,例如下图中,两个摄像机的位置朝向都不相同,看见的画面确是一样的。

图形API坐标系对比以及坐标系之间变换_API_10

图形API坐标系对比以及坐标系之间变换_API_11

那么假如有个变换矩阵 图形API坐标系对比以及坐标系之间变换_API_06 ,使我们的摄像机变换为位置在世界坐标原点,看向-z方向,摄像机的y轴即为世界坐标y轴。那么摄像机所观察的物体同样使用矩阵 图形API坐标系对比以及坐标系之间变换_API_06 进行变换,即可保持所观察到的画面不变。这样的操作就是我们所谓的视图变换,变换矩阵 图形API坐标系对比以及坐标系之间变换_API_06 即为视图变换矩阵。

至于我们为什么要执行视图变换,是为了使后续在执行投影变换时,简化计算,看到后面你就知道了。

接下来我们来看看矩阵 图形API坐标系对比以及坐标系之间变换_API_06 的值应该如何计算,根据前面的描述我们可以把视图变换拆分为如下几步:

  1. 通过平移变换,将Camera移到原点位置,即使 图形API坐标系对比以及坐标系之间变换_API_06 变为 (0,0,0)
  2. 通过旋转变换,使Camera看向-z方向且自身的y轴与世界坐标的y轴重叠,即使 图形API坐标系对比以及坐标系之间变换_API_06 变为 (0,0,-1), 图形API坐标系对比以及坐标系之间变换_API_06 变为 (0,1,0)。

通过复合变换我们可知,只需要把上面两部所对应的矩阵相乘得到的结果及时我们的视图变化矩阵 图形API坐标系对比以及坐标系之间变换_API_06 的值。

首先是平移变换的矩阵,这个很简单,设 图形API坐标系对比以及坐标系之间变换_API_06 ,我们只需要移动 图形API坐标系对比以及坐标系之间变换_API_06 即可将摄像机移动到原点,对应矩阵(设为 图形API坐标系对比以及坐标系之间变换_API_06 )即为:

图形API坐标系对比以及坐标系之间变换_API_06

移动到原点后,我们要通过旋转,使 图形API坐标系对比以及坐标系之间变换_API_06 变为 (0,0,-1) , 图形API坐标系对比以及坐标系之间变换_API_06 变为 (0,1,0)。这一步我们有点无从下手,因为我们之前讨论旋转矩阵时都是绕什么什么轴旋转多少多少度,而在这里我们并不知道应该绕什么轴旋转多少度。

那么应该如何解决呢?这里有一个逆向思维,即:我们不知道如何将 图形API坐标系对比以及坐标系之间变换_API_06 变为 (0,0,-1) , 图形API坐标系对比以及坐标系之间变换_API_06 变为 (0,1,0),但是我们能够知道如何将 (0,0,-1) 变为 图形API坐标系对比以及坐标系之间变换_API_06 ,(0,1,0) 变为 图形API坐标系对比以及坐标系之间变换_API_06 ,也就是上诉旋转的逆变换,我们假设我们原先要求的矩阵为 图形API坐标系对比以及坐标系之间变换_API_06 ,那么它的逆变换矩阵即为 图形API坐标系对比以及坐标系之间变换_API_06 。

因为通过前面的知识我们知道,旋转矩阵中的值即为x,y,z三个轴的单位向量旋转后的值。在这里 (0,1,0) 变为 图形API坐标系对比以及坐标系之间变换_API_06 ,(0,0,1) 变为了 图形API坐标系对比以及坐标系之间变换_API_06 ,因为 图形API坐标系对比以及坐标系之间变换_API_06 代表的是摄像机自身的-z轴方向, 图形API坐标系对比以及坐标系之间变换_API_06 代表的是摄像机自身的y轴方向,根据叉乘的定义可得摄像机自身的x轴方向即为 图形API坐标系对比以及坐标系之间变换_API_06 ,即 (1,0,0) 变为 图形API坐标系对比以及坐标系之间变换_API_06 。

那么我们就可得:

图形API坐标系对比以及坐标系之间变换_API_06

同时又因为我们的旋转矩阵为正交矩阵,其逆矩阵即等于转置矩阵(这一点在之前的文章着重介绍和证明过了), 图形API坐标系对比以及坐标系之间变换_API_06 ,因此 图形API坐标系对比以及坐标系之间变换_API_06 的值即为 图形API坐标系对比以及坐标系之间变换_API_06 的转置,可得:

图形API坐标系对比以及坐标系之间变换_API_06

所以即可求得视图变化矩阵 图形API坐标系对比以及坐标系之间变换_API_06 的值,即为上面两个矩阵相乘:

图形API坐标系对比以及坐标系之间变换_API_06

投影变换(Projection transformation)

摄像机对好要拍摄的物体后,就差最后按下快门变成照片这一步了,而这一步也就是我们的投影变换,即从三维变成二维。在图形学中,投影变换分为如下两种,分别为正交投影和透视投影。

正交投影(Orthographic projection)

正交投影示意图如下:

图形API坐标系对比以及坐标系之间变换_API_45

可以发现,正交投影没有近大远小的现象,视线是互相平行的,类似于平行光照,这种投影更多的用于工程图。图中我们还可发现有一个Near clip plane和一个Far clip plane,它们分别代表该摄像机能看见的最近的距离以及最远距离,即摄像机只能看见两个平面之间的物体。在正交投影中,Near clip plane的大小等于Far clip plane,能被摄像机拍摄到的空间即为一个长方体。

由于已经进行过了视图变换,即摄像机在原点,看向-z轴方向,摄像机的y轴与世界坐标y轴重叠。因此我们的所拍摄的空间也能够确定下来,如下图:

图形API坐标系对比以及坐标系之间变换_API_46

该空间的宽度即为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。

整个变换过程就是我们的正交投影变换,其对应矩阵即为正交投影变换矩阵。该变换我们可以分为如下两步(这也体现了视图变换的重要性,使得我们做投影变换变得很简单):

  1. 平移变换,将长方体S平移到原点。
  2. 缩放变换,将长方体S的长宽高缩放到2

同样的,我们来看看这两个变换的矩阵:

首先是移到原点的平移变换,即把长方体S的中心点移动到原点,那么我们就要求出长方体中心点的坐标。根据前面的数据,我们可以知道:

中心点x的坐标即为 图形API坐标系对比以及坐标系之间变换_API_06 ,中心点y的坐标即为 图形API坐标系对比以及坐标系之间变换_API_06 ,中心点x的坐标即为 图形API坐标系对比以及坐标系之间变换_API_06

因此平移矩阵(设为 图形API坐标系对比以及坐标系之间变换_API_06 )即为:

图形API坐标系对比以及坐标系之间变换_API_06

接着是将长方体S变为边长为2的立方体的缩放变换,那么我们只需要知道长方体的长宽高即可。我们设x轴方向的为长度,即为 r-l ,y轴方向的为高度,即为 t-b ,z轴方向的为宽度,即为 n-f 。

所以缩放矩阵(设为 图形API坐标系对比以及坐标系之间变换_API_06 )即为:

图形API坐标系对比以及坐标系之间变换_API_06

因此可以得出最终的正交投影变换矩阵(设为 图形API坐标系对比以及坐标系之间变换_API_06 )为:

图形API坐标系对比以及坐标系之间变换_API_06

注:该变化会导致物体的被拉伸(因为长方体变成了立方体),在后续的视口变换过程中会再对此进行处理。

透视投影(Perspective projection)

图形API坐标系对比以及坐标系之间变换_API_56

与正交投影不同的是,透视投影会有近大远小的现象,类似于我们的人眼或者照相机拍照,这种投影更具有真实感,被广泛使用。从图中我们可以看出Far clip plane的面积要大于Near clip plane,因此我们摄像机所观测的区域不再是一个长方体,而是变成了一个四棱台(即四棱锥去掉顶部),也就是我们常说的视锥体(Frustum)。

我们来看下在视图变换后,透视投影在坐标系中的样子,如下图:

图形API坐标系对比以及坐标系之间变换_API_57

通过前面我们知道,投影变换是把相机观测的空间压缩成一个标准立方体,对于这个视锥体我们应该如何压缩呢?有个思路是这样的:

  1. 我们先通过某种变换把这个视锥体压缩成一个长方体
  2. 再把长方体移到原点上压缩成标准立方体,即等于执行正交投影变换

那么我们只需要计算出第一步的变换矩阵(设为 图形API坐标系对比以及坐标系之间变换_API_06 ),然后将它乘以正交投影变换矩阵,即可得到我们的透视投影变换矩阵(设为 图形API坐标系对比以及坐标系之间变换_API_06 )了。

要使视锥体变为长方体,首先我们要使 图形API坐标系对比以及坐标系之间变换_API_06 变为 图形API坐标系对比以及坐标系之间变换_API_06 的直线( 图形API坐标系对比以及坐标系之间变换_API_06 表示 图形API坐标系对比以及坐标系之间变换_API_06 点到N点的距离,后面同理), 图形API坐标系对比以及坐标系之间变换_API_06 变为 图形API坐标系对比以及坐标系之间变换_API_06 的直线, 图形API坐标系对比以及坐标系之间变换_API_06 变为 图形API坐标系对比以及坐标系之间变换_API_06 的直线, 图形API坐标系对比以及坐标系之间变换_API_06 变为 图形API坐标系对比以及坐标系之间变换_API_06 的直线。

首先我们来看看如何使图形API坐标系对比以及坐标系之间变换_API_06 变为 图形API坐标系对比以及坐标系之间变换_API_06的直线,做这一步前我们可以从x轴方向看向yz平面,来观察这个视锥体,方便理解。

图形API坐标系对比以及坐标系之间变换_API_72

设N点的z值为 n,F点的z值为 f ,(f < n < 0)。

通过图中我们可以发现, 图形API坐标系对比以及坐标系之间变换_API_06 和 图形API坐标系对比以及坐标系之间变换_API_06 是相似三角形,可得: 图形API坐标系对比以及坐标系之间变换_API_06 ,即 图形API坐标系对比以及坐标系之间变换_API_06 。

扩展开来可得在 图形API坐标系对比以及坐标系之间变换_API_06 直线上的任意一点,设(x,y,z),其y值只需乘以 图形API坐标系对比以及坐标系之间变换_API_06 即可与T1点的Y值相等,这样直线 图形API坐标系对比以及坐标系之间变换_API_06 即变为一条 图形API坐标系对比以及坐标系之间变换_API_06 的水平线, 图形API坐标系对比以及坐标系之间变换_API_06 也是同理。

对于 图形API坐标系对比以及坐标系之间变换_API_06 和 图形API坐标系对比以及坐标系之间变换_API_06 ,我们只需要从y轴方向观察xz平面即可,同样是乘以 图形API坐标系对比以及坐标系之间变换_API_06 即可。

因此对应视锥体里的任意一点 (x,y,z),我们只需要将其x和y的值乘以 图形API坐标系对比以及坐标系之间变换_API_06 即可使该视锥体变为长方体。

但是此时z值是否会发生变化我们暂时还不知道,先当做未知来处理(看着似乎不会变换,但实际上并不是这样,后面会在解释)。

关于上面的结论,我们可以得到如下一个变换矩阵:

图形API坐标系对比以及坐标系之间变换_API_06

这个矩阵有个问题,那就是矩阵中的z是一个变量,导致该矩阵并不是一个常量。

因此我们这里需要引入齐次坐标的概念,设我们有个常量k,通过其次坐标我们可以知道,(x,y,z,1) 是等价于 (kx,ky,kz,k) 的,那么可以得知:

图形API坐标系对比以及坐标系之间变换_API_06

因此我们可以把上面的矩阵修改为:

图形API坐标系对比以及坐标系之间变换_API_06

这样我们矩阵中的变量z就被消灭掉了。

接下来我们要看看z值的变换,从图中我们可得知的信息为变为长方体后:

  1. Near clip plane上的任意点,设为(i,j,n),它的z值不变依旧为 n。
  2. Far clip plane上的任意点,设为(k,l,f),它的z值不变依旧为 f。

可得如下两个方程:

图形API坐标系对比以及坐标系之间变换_API_06

设我们矩阵中的四个未知数分别为 A,B,C,D,可得:

图形API坐标系对比以及坐标系之间变换_API_06

从中可知 A=B=0,简化得:

图形API坐标系对比以及坐标系之间变换_API_06

即可算出,C=n+f,D=-nf,因此视锥体压缩为长方体的矩阵为:

图形API坐标系对比以及坐标系之间变换_API_06

因此也可以知道在该变换过程中,任意点的z的值可能是会发生变化的。举个例子,视锥体的中心点 图形API坐标系对比以及坐标系之间变换_API_06 变换后为:

图形API坐标系对比以及坐标系之间变换_API_06

我们来比下大小:

图形API坐标系对比以及坐标系之间变换_API_06

由于f < n < 0,所以 图形API坐标系对比以及坐标系之间变换_API_06 ,2(n+f) < 0,可得(负数除以负数为一个正数)

图形API坐标系对比以及坐标系之间变换_API_06

即视锥体的中心点在变换后离摄像机更远了。

变成长方体后,只需在执行一次正交投影变换即可,因此我们的透视投影变换矩阵为:

图形API坐标系对比以及坐标系之间变换_API_06

注:在有些文章里可以看见 图形API坐标系对比以及坐标系之间变换_API_06 的第三列元素和本文的正好是取负的关系,那是因为他们在计算时,把 n 和 f 当做正数使用,即 N 点和 F 点的z轴坐标为 -n 和 -f 。

 

对称视锥体

对称视锥体,即为N为Near clip plane的中心点,F为Far clip plane的中心点,因此我们可得:

图形API坐标系对比以及坐标系之间变换_API_06

那么正交投影矩阵就可以简化为:

图形API坐标系对比以及坐标系之间变换_API_06

同样的透视投影矩阵也可以简化为:

图形API坐标系对比以及坐标系之间变换_API_06

 

使用FOV和Aspect ratio表示透视投影矩阵

依旧看之前的图片,如下(重新贴一下,方便看):

图形API坐标系对比以及坐标系之间变换_API_103

在图中 图形API坐标系对比以及坐标系之间变换_API_06 我们称之为 Field of View (for Y / for Vertical),也就是常说的FOV。自然而然 图形API坐标系对比以及坐标系之间变换_API_06 是x轴或者是水平方向的FOV,我们只需要知道其中一个即可,通常用y轴的。我们设 图形API坐标系对比以及坐标系之间变换_API_06 。

图形API坐标系对比以及坐标系之间变换_API_06 的长度(即为宽,设值为w)与 图形API坐标系对比以及坐标系之间变换_API_06 的长度(即为高,设值为h)比例我们称之为宽高比(Aspect ratio),我们假设为该值为 a,即 图形API坐标系对比以及坐标系之间变换_API_06 。

同时可得:

图形API坐标系对比以及坐标系之间变换_API_06

因为这种表示方式需要我们的视锥体为前面介绍的对称视锥体,因此我们直接将上面的式子带入对称视锥体对应的透视投影矩阵 图形API坐标系对比以及坐标系之间变换_API_06 中,得:

图形API坐标系对比以及坐标系之间变换_API_06

通过上面yz平面的侧面图:

图形API坐标系对比以及坐标系之间变换_API_72

我们可以发现: 图形API坐标系对比以及坐标系之间变换_API_06 ,即 图形API坐标系对比以及坐标系之间变换_API_06 。

又因为 w=ah,所以 图形API坐标系对比以及坐标系之间变换_API_06 。

所以可得,FOV为 θ ,宽高比为 a 时,透视投影矩阵为:

图形API坐标系对比以及坐标系之间变换_API_06

同时知道了FOV和宽高比,我们就可以计算出之前的 r,l,t,b 这些值。