基本数学与理论工具
计算机图形学(CG,computer graphics)是一门高度数学化的学科,包含了非常多的理论概念,下面会以坐标系为切入点,讨论一些常用的数学和理论工具,这对于理解文中内容来说足够了。
坐标系
如果想要描述某个三维空间中,所有物体的空间位置,最简单的做法就是以空间中的某个点作为原点(origin),为这个空间建立起一个三维的笛卡尔坐标系(Cartesian coordinate system),这是一种正交坐标系统,存在
根据正轴方向的组合方式,有两种不同的约定,即左手坐标系(left-handed coordinate system)和右手坐标系(right-handed coordinate system),它们的差异主要在于如何选择 Z 轴的正方向,并没有优劣之分,只是习惯的不同。
可以摆出图中左手坐标系的手势,然后转动手腕使中指指向屏幕,此时 Z 轴的正方向是我们的前方,如果切换为右手坐标系的话,需要保持右手食指和拇指的方向与左手的一致,即 X、Y 轴的正方向保持不变,此时右手中指就指向了我们的后方,这使得 Z 轴的正方向反了过来,因此左手坐标系更符合人眼在现实世界的观察习惯,而右手坐标系则更适合于纸面上的一些数学描述,可从下图理解这一点。
根据惯例,DirectX 使用的是左手坐标系,而 OpenGL 使用右手坐标系,不过 OpenGL 的归一化设备坐标系(NDC)会采用左手坐标系
坐标系分层
上面仅定义了一个全局坐标系(global coordinate),或者说是绝对坐标系(absolute coordinate),这实际上并不足够灵活。
假设空间中存在一个物体,物体中的所有点可看作是一个整体,即使物体在空间中平移或旋转,它们之间的相对距离都保持不变,如果仅使用全局坐标系来描述这个物体的话,不能直观地从各个点的绝对坐标值来理解它们之间所保持的相对关系,当物体发生了平移或旋转,这些点的绝对坐标也会随之变化。
一种灵活的做法是,选取物体局部空间中的某个位置作为原点(比如物体的中心),来为物体定义一个局部坐标系(local coordinate),也可称为物体坐标系(object coordinate),物体的所有点将使用相对坐标(relative coordinate)来描述,即相对物体原点的坐标值。
这种做法不仅可以直观地体现出物体的内部联系,当物体整体发生平移或旋转时,即使点的绝对坐标值发生了变化,相对坐标值也还是保持不变。
将整个物体映射到全局坐标系的方法非常简单,只需确定物体原点的绝对坐标以及物体在全局坐标系的旋转状态,就可以将所有点的相对坐标映射为绝对坐标。
这在为三维物体进行建模时非常有用,可以在物体的局部坐标系(模型坐标系)中进行建模,并把物体的原点信息以及所有顶点的相对坐标导出,在使用的时候再映射到三维空间中,即使物体在空间中移动或旋转,只需更新原点位置和旋转状态即可,不用直接更新顶点所存储的相对坐标,这可以节省大量存储写入开销,并且兼容性非常好,能轻易地迁移到别的应用场景中。
这种坐标系的局部化,是可以无限制地继续分层(level)的,特别是对于像人物模型之类的复杂三维模型来说,分层所带来的灵活性,使问题被大大简化。
在三维渲染中,按照惯例也定义多种不同的坐标系,比如下面这些:
- 模型坐标系(model space)或称物体坐标系(object space)
- 世界坐标系(world space)
- 相机坐标系(camera space)或观察者坐标系(view space)
- etc.
这里简要说一下,我们的每个物体都处于世界坐标系下, 但物体存在局部空间,因此物体内的其它对象想要渲染,就需要先变换到世界坐标系下,这属于从局部空间到全局空间的映射。但我们在渲染之前,还需要根据相机在世界坐标系下的视野范围,将范围内的对象从世界坐标系变换到相机坐标系下,这属于从全局空间到局部空间的映射,映射到相机坐标系之后就可以进行后续的变换了,这里不继续讨论。
向量与矩阵
有了坐标系之后,就可以用向量(vector)来描述物体的空间坐标,由于前面我们定义的是一个三维坐标系,所以实际使用的应该是三维向量
我们还可以通过矩阵(matrix)来为向量进行诸如平移(translate)、旋转(rotate)、缩放(scale)、投影(projection)之类的变换(transformation)。
单位矩阵(identity matrix)是一种特殊的矩阵,其主对角线上的元素全部为
有的矩阵变换是可逆的,如果计算出原本矩阵的逆矩阵(inverse matrix),就可通过逆矩阵来执行与原本矩阵相反的变换,这样就可以在不同的空间之间来回映射,比如一个 TRS 变换的逆矩阵为以下形式:
- 先求出原本矩阵的余子式矩阵(cofactor matrix)
- 把余子式矩阵转换为代数余子式(余因子)矩阵后,为其执行矩阵转置(transpose),从而得到伴随矩阵(adjugate matrix)
- 计算原本矩阵的行列式(determinant),把伴随矩阵与行列式的倒数相乘,从而得到原本矩阵的逆矩阵
矩阵变换与齐次坐标系
本篇文章主要关注平移(translate)、旋转(rotate)、缩放(scale)以及投影(projection)四种变换,而要想把这些变换组合到一起,去给一个三维向量执行变换的话,仅采用一个
因此,我们还需要将原本欧氏坐标系中的三维向量(3D vector)转换到齐次坐标系(homogeneous coordinates)中,变为一个四维向量(4D vector),即给原本的三维向量增加一个维度
投影矩阵会放到后面讨论,而旋转矩阵由于较为复杂,绕不同轴进行旋转的构造方式都不一样,在文章中其实也不需要考虑其具体构造方法,所以这里不讨论旋转矩阵的构造,以下给出平移和缩放变换的
矩阵的拼接顺序
这里先抛开投影变换不谈,来看下平移(Translate)、旋转(Rotate)以及缩放(Scale)的组合顺序问题,一般有 TRS 和 SRT 两种,不过这指的仅是矩阵的相乘顺序,并不是与向量的结合顺序,由于矩阵与向量相乘时,我们一般采用列向量,形式为
TRS:先缩放再旋转后平移
SRT:先平移再旋转后缩放
这个顺序的先后非常重要,主要原因是像旋转和缩放这样的变换通常都是相对于坐标系“原点”进行的,组合顺序不一样会导致变换的结果不同。
这个顺序其实可以换一个角度来看,比如说
,如果以物体自身坐标系的角度来看的话,就是先将自身坐标系缩放、旋转,而在平移的时候,也是根据自身坐标系进行,那么结果跟以父坐标系的“先平移再旋转后缩放”是一样的
有时候也可以直接以变换的顺序来称呼这些组合,到底怎么称呼其实是习惯问题,不过很容易混淆,比如:
不过,在执行视变换(viewing transformation)时,即将世界坐标系的对象映射到相机坐标系(camera space)时,有些不一样,首先,视变换并不用考虑缩放的问题,只需要考虑平移和旋转。
在为相机构造视变换矩阵(view matrix)的时候,我们为其提供的是相机的世界坐标,如果只是把相机自身映射到世界坐标的话,那视变换矩阵与模型矩阵(model matrix)的构造方式是一样的,只是不用插入缩放矩阵。
但视变换的目标是要把世界坐标系中的对象映射到相机的局部空间中,因此视变换其实是相机执行世界变换的逆变换,那视变换矩阵的构造方法就是,先求出相机的世界变换矩阵,再求这个矩阵的逆矩阵,如下:
此外还需要注意一点,那就是我们在执行 MVP 组合变换的时候,实际的拼接顺序应该是:
而执行变换的顺序为,先执行世界变换,再到视变换,最后投影变换
总的来说,矩阵的拼接顺序需要注意以下几点:
- 把希望优先执行的变换放到右边,即越早与向量结合的变换,越早执行
- 如果希望从子坐标系映射到父坐标系,通常采用 TRS 的矩阵拼接顺序、SRT 的变换顺序
- 如果希望从父坐标系映射到子坐标系,比如视变换,可以先拼接一个从子到父的 TRS 矩阵,然后求逆矩阵,得到一个 SRT 拼接顺序的矩阵
由于后续会用到 ST(scale,translate)的矩阵拼接,这里先给出 ST 和 TS 两种拼接结果的对比:
欧拉角与四元数
有些时候,我们需要将平移、旋转、缩放这些变换持久化存储起来,使用一个
from: Sensors to detect motion in three dimensions
在欧拉角中,通常会把 X、Y、Z 轴分别称为俯仰轴(pitch axis)、偏航轴(yaw axis)以及翻滚轴(roll axis),而绕这些轴旋转的角度分别称为俯仰角(pitch angle)、偏航角(yaw angle)以及翻滚角(roll angle),不过,这些叫法并非是固定的,使实际使用情况而定。
欧拉角的描述方法非常简单,即绕各个轴的旋转角度,在执行旋转的时候,会根据一定的顺序,每次绕一个轴进行旋转,如上图。
在右手坐标系中,欧拉角逆时针旋转为正角度值,而在左手坐标系中,则是顺时针旋转为正角度值。
欧拉角的旋转方式(或组合方式)主要以下两类:
- 泰特布莱恩角(Tait–Bryan angles):(x-y-z,y-z-x,z-x-y,x-z-y,z-y-x,y-x-z)
- 经典欧拉角(Poroper/classic Euler angles):(z-x-z,x-y-x,y-z-y,z-y-z,x-z-x,y-x-y)
from: Gimbal lock - Wiki
欧拉角可以很直观地描述旋转,可读性非常强,但会受到万向锁(gimbal lock)的影响,当依次施加三个旋转时,第一个或第二个旋转可能导致第三个轴的方向与先前两个轴之一相同,这意味丢失了一个轴上的自由度(degree of freedom),因为不能围绕唯一轴应用第三个旋转值。
除了欧拉角和矩阵外,还可以使用四元数来描述旋转,这本质上是一个四维度的超复数(hypercomplex number),拥有三个虚部和一个实部,这实际是相当于用三个虚部描述了一个三维向量,即旋转轴(rotation axis),而实部描述了绕这个旋转轴发生旋转的角度(angle)。
四元数的定义如下:
四元数描述的是绕轴旋转一次,因此不会导致欧拉角那样的万向锁问题,不过其不能表示在任何方向上超过
欧拉角、四元数以及旋转矩阵之间可以互相转换,各有优缺点,由于欧拉角更为直观,所以在一些游戏引擎的 inspector 上通常会采用欧拉角来表示,不过在内部将使用四元数描述,其实多数的实现在为旋转变换做持久化的时候,基本也都是采用四元数,而在实际执行变换的时候,视情况可能会直接使用四元数执行,但多数情况应该会转换为矩阵,与其它变换组合在一起。
三维虚拟相机
一个三维场景中所放置的相机(camera)可用来模拟人类的眼睛,其可视范围内所能见到的景象,就是渲染到屏幕上的画面,像游戏这类应用场景,都是采用实时渲染(real-time rendering)的工作方式,相机会跟随着角色一起平移(translation)或旋转(rotation)以切换视角,并以锁定帧率(limited FPS,locked frames per second)或不锁帧(unlimited FPS)的方式,定期或不断地重新渲染,以更新屏幕上的可视图像。
这些虚拟相机通常都是基于理想化的针孔相机模型(pinhole camera model)或合成相机模型(synthetic camera model),不过,除非需要模拟真实物理相机,否则在传统三维渲染中,一般情况下并不会使用诸如焦距、成像平面或投影平面之类的概念,这是由于传统三维渲染的机制与真实物理相机的成像机制是有区别的,因此它们在概念的使用上存在一些差异。
物理相机主要考虑的是怎样有效的捕捉光信号并将其转化为电子或其它类型的信号,而传统三维渲染中的虚拟相机更多的是一个数学模型,其考虑的是如何根据透视(perspective)或正交(orthographic)的投影关系,把三维场景中的对象投影(projection)到二维平面上。
实际上,在给三维场景中的某一个顶点执行投影变换之前,需要先将其变换到相机的相机坐标系(camera space)或称观察者坐标系(view space)下。
一般来说,一个物体有其自身的物体坐标系(object space)或称局部坐标系(local space),而局部空间内的那些顶点想要进行渲染的话,得先通过世界变换(world transformation)或称模型变换(modeling transformation),映射到世界坐标系(world space)下,涉及的变换有 缩放、旋转、平移(SRT),而矩阵的拼接顺序为 TRS。
处于世界坐标系下的顶点,可直接进行取景变换(view space transformation)或称视变换(viewing transformation),通过平移和旋转,映射到相机坐标系,该变换矩阵实际是为相机世界变换矩阵的逆矩阵。
当顶点处于相机坐标系后就可以为其执行投影变换了,不过这需要把以下两个操作考虑进去:
- 根据相机的可视空间体(view volume)对图元进行剔除(culling)或裁剪(clipping)
- 处理各图元之间的遮挡(occlusion)关系,比如说某个物体被另一个物体完全遮挡住了,那么该物体就不应该被渲染出来
由于相机所提供的数学模型本质只是一种几何变换(geometry transformation),所以这两个操作并不是由相机的投影变换所完成的,排除某些优化外,剔除和裁剪通常在投影变换之后立刻进行,而遮挡问题会交给后续的渲染流程来处理,如深度测试(depth testing),不难发现,这些处理其实都依赖顶点坐标
实际上,相机的投影变换(projection transformation)并不会直接将对象投影在一个二维平面上,而是将顶点映射到一个裁剪空间(clip space)中,这使得顶点的深度信息得以保留并方便后续的处理,因此相机的投影变换也称为裁剪变换(clip transformation)。
裁剪空间也称为齐次裁剪空间(homogeneous clip space),这是一个齐次坐标系(homogeneous coordinates),坐标点使用四维向量(4D vector)来描述,并使用一个
对于一个正交相机(orthographic camera)来说,其投影变换为正交投影(orthographic projection),所做的变换非常简单,为平移和缩放的组合。
- 平移操作可理解为将整个空间平移,使相机可视体原点(如可视体的中心)与相机坐标系原点重合,这相当于将顶点映射到了可视体的坐标系中
- 缩放操作可理解为将整个空间缩放,使相机可视体的长、宽、高被缩放成某个单位长度(如
或半长为 ),这相当于将正交相机原本的可视体,从长方体缩放成了单位大小的立方体
之所以结合可视体并以其为主体来描述变换,仅是为了便于理解,实际执行变换的其实是顶点,可结合下图来理解这种变换,现实的实现可能会采用不同的原点位置或立方体大小,图中选择以相机可视体的中心为其原点,缩放后的边长为
透视相机(perspective camera)比正交相机多了一个透视的变换,因此透视投影(perspective projection)为透视、平移和缩放的组合。
透视相机的可视体是一个平截头体,而透视所做的,就像是将平截头体“挤压”成了跟正交相机可视体一样的长方体,如图所示:
这实际是根据相似三角形的原理,将每个顶点的 X、Y 分量进行了映射,处理了每个对象之间的透视关系,另外,从上图可发现,在透视之后,绿色顶点的 Z 分量稍微往右边挪了一下,这是因为对深度的映射通常有些特殊,一般会采用非线性(non-linear)的映射方式,这主要是为了避免浮点数精度问题引起的深度冲突(z-fighting),详细原因会在之后重新提到。
当透视相机的可视体被压成了跟正交相机一样的长方体后,可看作是将透视相机变成了一个正交相机,不过视体内的顶点已经根据透视处理好了,满足透视关系,而接下来的平移和缩放,就是使其进一步变成一个规范化的正方体,目的跟正交相机的正交投影是一样的,因此,透视相机的透视投影相当于是透视与正交投影的组合。
当对顶点执行完投影变换之后,相机所提供的数学模型其实已经算是完成了其自身的任务,此时顶点已经被映射到了一个齐次裁剪空间中。
但要注意的是,由于以上两种投影变换都是在齐次坐标系下进行的,一个
假设有一个齐次坐标
这个特性非常有用,由于投影变换最终是将相机的可视体变成一个边长为单位长度的立方体,并且所有顶点随着可视体一起变换,这意味着,变回到欧氏坐标系后,位于可视体内的所有顶点,它们在
也就是说,将齐次坐标的
这就是为什么对图元的剔除和裁剪是在裁剪空间中进行的原因,也表明了这个空间为啥叫裁剪空间,判断顶点是否位于可视体内的依据非常简单:
- 对于透视投影来说,这个过程称为透视除法(perspective divide),此时才实际完成了透视投影
- 对于正交相机所做的正交投影来说,这一步通常是没必要的,直接把第四维分量去掉就行了,因为顶点经过正交相机的投影变换后,在裁剪空间的
分量会是
如果先变换到归一化设备坐标系中,然后再执行剔除和裁剪的话,由于前者需要执行透视除法,这样就还得对那些应该被剔除掉的顶点执行除法,从而导致不必要的开销,所以剔除和裁剪还是应该放在裁剪空间中做
归一化设备坐标空间是一个标准立方体(canonical cube),或称规范视体积(canonical view volume),其三个轴的取值范围都相等,通常为
假设场景中有两个顶点被映射到该空间中,如果它们映射后的
不同的实现(如 DirectX 和 OpenGL)所采用的投影矩阵构造方式可能会存在差异,这使得它们的规范视体积的取值范围会不同,比如 DirectX 通常是
当顶点被映射到归一化设备坐标系(NDC)后,就可以通过视口变换(viewport transformation,viewport mapping)进一步映射到视口坐标系(viewport coordinate)上了,视口(viewport)是一个对应了窗口某块区域的二维平面,顶点 NDC 坐标的
至此,就可以对图元进行光栅化(rasterization)了,图元基本都是采用三角形,并由顶点和索引(index)来描述,光栅化会对每个三角形进行绘制,为了处理遮挡关系,会配合深度缓冲(z-buffer,depth buffer),对每个三角形着色出的每个像素进行深度测试,只有通过测试,才会将其像素值写入帧缓冲区(frame buffer)中,并最终在屏幕上显示出来。
以上提到的各种操作中,除了剔除、裁剪以及光栅化等操作外,其它操作的本质都属于几何变换,这些变换使得顶点的坐标在各种坐标系之间过渡,进而变换到窗口的某块区域上,可将这部分变换流程总结为下图:
投影
简单来说,投影(projection)就是降维的过程,比如从三维降到二维
三维渲染主要有透视投影(perspective projection)和正交投影(orthographic projection)两种投影方式,它们的可视空间体(view volume)分别为视锥体(view frustum)和正交视体(orthographic view volume),前者为一个平载头体(frustum),后者为一个长方体(cuboid),对比可参考下图:
from: 《Fundamentals of Computer Graphics》 - 7.3. Perspective Projection
透视投影采用的是中心投影法(central projection),所有投影线可会汇聚到一个点上,而正交投影所有投影线相互平行,这造成了它们投影效果的差异,这主要有以下两点:
透视投影不会维持原本的平行关系,平行线在投影后可汇聚到消失点(vanishing point),而正交投影可以在投影后保持原来的平行关系
《平行线经过透视投影后汇聚而成的消失点》
from: PERSPECTIVE透视投影可以产生近大远小、近高远低、近实远虚的视觉效果,具有很强的立体感和真实感,而正交投影,无论物体间的距离如何,它们投影后的大小比列关系,都保持与真实的大小比列关系一致
from: Orthographic
可从下图来理解它们的成像方式差异,也可从中体会为什么透视投影会出现近大远小、近高远低、近实远虚这样的效果,这就像是将物体给挤压到了一个平面上,距离平面越远的物体,被挤压的越严重。
从几何学上看,透视投影利用了相似三角形(similar triangle)的原理,这对于理解透视投影来说,是一个非常重要的基础。
上图演示了在一个空间中,将一个点通过透视投影映射到平面上的过程,根据相似三角形原理,可得到投影前后的点坐标之间的关系:
在现实世界中,驱动投影成像的本质是光线及其与物质之间的相互作用,物体与物体之间的遮挡关系其实已经被处理好了,对于成像面来说,只需要考虑其所接收到的光线即可。
但对于传统的三维渲染来说,其并不是由光线来驱动成像(除非是光追渲染),透视投影所做的,更多是为场景中的几何点进行一种几何变换,这期间不需要考虑对象的着色问题,不过,为了在后续渲染流程计算像素值时能够正确处理对象之间的遮挡关系,需要将几何点的深度信息保留下来。
因此,不能单纯的用一个成像(或投影)平面来建模投影,或者说,几何点在经过透视投影后,不能直接映射在一个平面上,而是应该变换到另一个三维空间上,这样才能将深度( Z 轴)保留下来。
在此逐步来看这个所谓的三维空间是怎样演化出来的,实际应用中的数学建模可能并不如此,一些细节还需要考虑,不过基本思路是差不多的,所以以下过程只是为了能从中理解基本思路和一些要点。
首先,我们并不希望得到的是一个无限大的空间,因此可将上面投影面定义为一个近裁剪面,并在它后面的适当位置上再定义一个远裁剪面,通过这两个面定义一个可视空间体,其从形状上看是一个平截头体,以下图为例,远裁剪面上所有点都能够投影到近裁剪面上,所有超出这个可视空间体的物体,对于眼睛来说不可见。
然后,我们可以在上述透视关系的基础上,让几何点坐标的
虽然让
分量保持为投影前的值不变,可使后续计算的顶点深度信息与其原始 Z 轴距离呈线性关系,也能更真实地还原顶点之间原始的深度差异,但由于计算机浮点数操作存在舍入误差,从而降低深度信息的精度,因此通常还需要对 分量进行非线性调整,不过这个问题我们留到后续拼接透视投影矩阵时再讨论,这里先让 分量继续保持不变
透视以及两个裁剪面相当于划出了一个新的空间,空间的高度和宽度由近裁剪面的大小决定,而深度( Z 轴)范围则由两个裁剪面的距离决定,如下图所示:
这个空间是一个长方体,可看作是由原本的视锥体经过透视所挤压而成,这类似于正交投影的可视体,当几何点被挤压到此空间后,其实它们的透视关系已经被处理好了,假设再对这个空间使用正交投影进行成像,无论投影面放在哪里,平行性和物体之间的大小关系都不会再变了,即保持经过透视之后的关系。
对于透视投影来说,完成透视这一步之后,后续的处理就跟正交投影是一样的,可以认为透视投影只不过是比正交投影多了一步“透视”处理。
接下来只要将这个长方体空间整体进行平移、缩放,映射到一个归一化的立方体后,投影就完成了。
平移实际上是为了让长方体空间中的所有点的坐标,映射到长方体空间坐标系上,以该空间的原点为坐标系原点。
至此,可把透视投影的变换总结为以下流程:
- 【透视】透视:根据透视关系,将顶点的
分量映射在近裁剪平面上,虽然对于、 分量,我们这里仅是保持原始值,但在实际处理中会将其非线性化。这使得透视投影的可视空间体从一个视锥体被挤压成了一个长方体,这个长方体与正交投影的可视空间体基本是一样的 - 【正交】平移:其实就是将顶点的坐标重新映射在长方体空间坐标系中
- 【正交】缩放:把长方体空间缩放为一个立方体,顶点的坐标被归一化
就像【标记】所描述的那样,以上流程其实蕴含了正交投影的变换过程,透视投影只是比正交投影多了一步透视,以上流程实际也并不算是真实的情况,比如裁剪的问题就没有考虑进去,也没有考虑怎样调整
如果从矩阵的组合变换上来描述的话,正交投影和透视投影可表达为以下形式:
透视相机
对于一个采用透视投影(perspective projection)的相机来说,可呈现出近大远小的透视效果,其可视空间体(view volume)为视锥体(view frustum)或称视景体,这决定了相机的可视范围,在渲染管线中,场景中的对象需要与视锥体进行相交测试(intersection test),处于视锥体外的图元将会被剔除(culling),即不参与渲染,而与视锥体相交但部分位于体外的图元将会被裁剪(clipping)。
视锥体从几何形状上看为一个平截头体(frustum),这由两个裁剪平面(clipping planes)所决定,即近裁剪面(near clip plane)和远裁剪面(far clip plane),平截头体四条侧边往近裁剪面方向的延长线可汇聚到一个点上,该点通常也是相机的空间坐标点,称为透视中心(center of perspective)或投影中心(center of projection)。
视锥体的主要参数有纵横比(aspect ratio)、近和远裁剪面到相机的 Z 轴距离以及视场角(FOV,field of view - theta),两个裁剪平面的宽高可以通过这些参数间接得到。
其中纵横比指的是裁剪面的宽高之比,而两个裁剪平面到相机的距离决定了视野范围的深度区域,具体来说,近裁剪面和远裁剪面分别决定了可见的最小深度和最大深度,即能看得到多近和多远。
视场角可分为水平视场角(HFOV,horizontal field of view)、垂直视场角(VFOV,vertical field of view)和对角视场角(DFOV,diagonal field of view),在已知其中一个角的情况下,可结合裁剪面的距离以及纵横比来得到其它两个角的大小,因此,只需指定其中某个视场角即可,一般都会选择指定垂直视场角(VFOV)的大小,所以通常视场角都是指垂直视场角,该角为视锥体顶部中心和底部中心分别与透视中心连线所形成的夹角,即 Y 轴视野范围的角度大小。
参考上图,可根据视场角的大小、裁剪面的距离以及三角函数来计算出裁剪面的高,再根据纵横比来得到宽,同时也可得到水平和对角视场角,它们之间的关系如下(对近裁剪面和远裁剪面都适用):
实际上,视场角决定了视野范围的广度,而两个裁剪面形成的深度区域决定了视野范围的深度。
视场角越大,视野范围越广,意味着能够看到的内容越多,如果同一个相机,分别使用较广和较窄的视场角来捕捉画面,并映射到同一个视口上(可理解为映射到大小相同的平面上),对比之下可发现,相机使用较窄视角所得到的画面,就像是将场景给拉近了,或者说画面被放大了,因此,通过调整视场角可以实现缩放/变焦(zoom)的效果。
可从上图对真实相机的简化模型来理解视场角是怎样导致画面缩放的,此图仅是理想化的情况,真实相机比这要复杂的多。图中的传感器(sensor)为相机的成像传感器(image sensor),其具有固定的物理尺寸,可将其抽象为成像平面(image plane)或投影平面(projection plane),面的尺寸大小与传感器相等且固定不变。
对比左右两图采用不同的视场角大小可发现,由于传感器尺寸固定,为了使视野范围内的光线都能落到传感器上,并铺满整个成像面,需要调整传感器到投影中心的距离,即焦距(focal length),这体现了视场角与焦距之间的关系(
这两种情况都需要将视野范围内的景象全部映射到尺寸固定的传感器上,相较于左图,右图的视场角更小,因此视野更窄,传递给传感器的“内容”显然会更少,被“缩放”到大小相同的成像面后,画面中的景象就会显得更大。
在三维渲染中,通常都没必要对成像平面或投影平面进行建模,不过在进行一些讨论或分析时,尽管不常见,有时也会用到投影平面的概念进行辅助。针孔相机模型中的投影平面位于相机的后面,并且成“倒立”的图像,与此不同的是,为了简化分析,通常会将投影平面放到相机的前面,那么成像面所得到的就是“正立”的图像。
将投影平面放置在距离相机多远的位置(焦距),视情况而定,理论上可放在任何位置,不过一般会限制在近裁剪面和远裁剪面之间,一种做法是让投影平面和近裁剪面重合,另一种常见做法则是调整投影平面的焦距使其半高等于单位长度(
比如说,在为视场角量化一个规范化的焦距,或反过来通过规范化焦距确认视场角大小时,就可采用使投影平面半高为单位长度的做法,从而将计算进行简化和规范化,如下面两个式子:
也可根据视口的大小,将
from: WebGL Visualizing the Camera
透视投影变换
当世界坐标系中的顶点映射到相机坐标系后,就可根据正交关系或透视关系为其执行投影变换(projection transformation),从而将其映射到一个裁剪空间(clip space)中进行后续处理了,我们在前面已经讨论过正交投影与透视投影的关联性,所以这里只考虑透视相机的投影变换,可称为透视投影变换(perspective projection transformation)。
这个变换将在齐次坐标系中完成,因此需要将顶点的坐标转换为齐次坐标:
以下将根据变换的顺序来看,首先是透视,在前面已经得出了以下透视关系(其中
from: Z-fighting - Wiki
为了减少这种现象,我们可以采用非线性深度缓存(non-linear depth buffer),即每个顶点的深度信息与其原始 Z 轴距离呈非线性关系(non-linear),这么做并不会影响顶点原先的深度关系(即遮挡关系)。
这种非线性关系中的深度值精度,会在越靠近相机时表现得越高,越远离相机则表现得越低,不过,这符合我们的期望。
此外,有时还可以通过稍微缩短近裁剪面与远裁剪面的距离,来减少这种 z-fighting 现象。
在正交相机中,深度值还是采用线性变化的,透视相机之所以需要非线性化,主要还是因为经历过透视后,导致了顶点之间在
分量的关系发生了变化,那么此时深度值的精度就很重要了 、
下图展示了线性深度和非线性深度的对比:
from: The Math behind (most) 3D games - Perspective Projection
为了构造
- 近裁剪面上的所有坐标值不变
- 远裁剪面上的所有坐标的
分量不变,保持为 ,即相机到远裁剪面的距离 - 远裁剪面的中心点的坐标值不变,固定为
由于
透视矩阵相当于把透视相机的视锥体压成了如上图这样的的长方体,此时远裁剪面的大小可看作与近裁剪面一样。
from: Projection Matrix
而我们最终要做的就是将透视得到的长方体再变为如上图右边这样的规范视体积(canonical view volume),也称为标准立方体(canonical cube),顶点的坐标将被映射为归一化设备坐标(normalized device coordinate),每个轴的取值范围为
接下来,将长方体平移、缩放就可以了,为此,我们还需要知道裁剪面的大小(近裁剪面的大小即可),这可通过相机的视锥体参数求出来,前面有讨论过,这里不重复,参考上图中的参数即可。
平移要做的是将长方体的中心与相机坐标系原点对齐,需要注意的是,虽然在上图中,
接下来就是把长方体缩放为规范视体积了,由于标准立方体每个轴的取值范围为
正交矩阵,如下:
另外,由于我们是在齐次坐标系中执行的变换,因此处于相机坐标系的顶点,在执行透视投影后,实际还未映射到 NDC 坐标系上,而是处于裁剪空间中,在此完成裁剪和剔除,再执行透视除法(perspective divide),才真正映射为 NDC 坐标。