渲染游戏的过程能够理解成是把一个个顶点通过层层处理最终转化到屏幕上的过程,本文就旨在说明,顶点是通过了哪些坐标空间后,最终被画在了咱们的屏幕上。spa
首先,咱们来看一个简单的问题:当给定一个坐标空间以及其中一点(a, b, c)时,咱们是如何知道该点的位置的呢?3d
坐标空间的变换就蕴含在上面的4个步骤中。如今,咱们已知坐标空间C的3个坐标轴在坐标空间P下的表示Xc, Yc, Zc,以及其原点位置Oc。当给定坐标空间C中的一点Ac = (a, b, c),咱们一样能够依照上面4个步骤来肯定其在坐标空间P下的位置Aporm
对获得的表达式作以下变换,其中“|”符号表示按列展开
继续对其中的加法表达式作变换,即扩展到齐次坐标空间作平移变换
如今,咱们获得了坐标空间C到坐标空间P的变换矩阵Mc->p
能够看出Mc->p其实是经过坐标空间C在坐标空间P中的原点和坐标轴的矢量表示构建出来的:把3个坐标轴依次放入矩阵的前3列,把原点矢量放到最后一列,再用0和1填充最后一行便可。对象
咱们能够利用反向思惟,从这个变换矩阵中提取出坐标空间C的原点和坐标轴在坐标空间P的表示。例如,当咱们已知从模型空间到世界空间的4×4变换矩阵,咱们能够提取出它的第一列,再进行归一化(为了消除缩放的影响)来获得模型空间的x轴在世界空间下的单位矢量表示。一样的方法能够提取y轴和z轴。blog
当对方向矢量进行坐标空间变换时,因为矢量是没有位置的,所以坐标空间的原点变换是能够忽略的。那么对方向矢量的坐标空间变换就可使用3×3的矩阵来表示,即
在Shader中,咱们经常看到截取变换矩阵的前3行前3列来对法线方向,光照方向进行空间变化,这正是缘由所在。游戏
一旦求出来Mc->p,Mp->c就能够经过求逆矩阵的方式求出来,由于从坐标空间C变换到坐标空间P与从坐标空间P变换到坐标空间C是互逆的两个过程。当Mc->p是一个正交矩阵时,Mc->p的逆矩阵就等于它的转置矩阵,即
此时,咱们还能够经过Mc->p反推出坐标空间P的坐标轴在坐标空间C中的表示Xp, Yp, Zp,这些坐标轴对应的就是Mc->p的每一行。ip
模型空间,是和某个模型或者说是对象有关的,模型空间也被称为对象空间或局部空间。
每一个模型都有本身独立的坐标空间,当它移动或旋转的时候,模型空间也会跟着移动和旋转。
Unity在模型空间中使用的是左手坐标系,所以在模型空间中,+x轴,+y轴,+z轴分别对应的是模型的右,上,前向。
模型空间的原点和坐标轴一般是由美术人员在建模软件里肯定好的。当导入到Unity中后,咱们能够在顶点着色器中访问到模型的顶点信息,其中就包含了每一个顶点的坐标。这些坐标都是相对于模型空间中的原点(一般位于模型的重心)定义的。数学
世界空间是一个特殊的坐标系,由于它创建了咱们所关心的最大空间,即整个游戏空间
在Unity中,世界空间一样使用了左手坐标系。它的x轴,y轴,z轴是固定不变的。
顶点变换的第一步,就是将顶点坐标从模型空间转换到世界空间中,这个变换一般叫作模型变换
在Unity中,咱们能够经过Transform组件中的值得知模型作了哪些变换。这个值是根据Transform的父节点的模型坐标空间中的原点定义的,若是这个Transform没有任何父节点,那么这个值就是相对于世界坐标空间定义的。it
要将模型空间中的一点转换到其父空间中,须要获取M子->父,这个矩阵能够经过模型的Transform值获得。Transform中包含了旋转,缩放和平移值,则M子->父 = Mtranslation Mrotate Mscale。而从模型空间转换到世界空间的变换矩阵M模型->世界能够经过子空间到父空间变换矩阵,父空间到爷爷空间变换矩阵,连乘,直到世界空间为止获得。io
观察空间也被称为摄像机空间,能够认为是模型空间的一个特例,即摄像机的模型空间。
在Unity中,观察空间使用的是右手坐标系,即+x轴指向右方,+y轴指向上方,+z轴指向摄像机后方
顶点变换的第二步就是将顶点坐标从世界空间变换到观察空间中。这个变换一般叫作观察变换。
从观察空间到世界空间的变换矩阵咱们一样能够经过Transform中的值获得,再对该矩阵求逆获得从世界空间到观察空间的变换矩阵。咱们还可使用另外一种方法,对Transform组件中的值直接取反(作逆向变换),而后获得从世界空间到观察空间的变换矩阵。注意,因为观察空间使用的是右手坐标系,所以还须要对变换矩阵的z份量进行取反操做。
顶点接下来要从观察空间转换到裁剪空间中,这个变换能够被称为投影变换。这个用于变换的矩阵叫作裁剪矩阵或是投影矩阵
裁剪空间的目的是可以方便地对渲染图元进行裁剪:彻底位于这块空间内部的图元将会被保留,彻底位于这块空间外部的图元将会被剔除,与这块空间边界相交的图元就会被裁剪。而这块空间就是由视椎体来决定的。
视椎体有两种类型,分别对应两种投影类型:透视投影(下图左)和正交投影(下图右)。透视投影模拟了人眼看世界的方式,而正交投影则彻底保留了物体的距离和角度。
投影矩阵虽然叫作投影矩阵,但并无真正进行投影,而是为投影作准备。目的是对x,y,z份量进行缩放,通过投影矩阵的缩放后,咱们能够直接使用w份量做为范围值,只有x,y,z份量都位于这个范围内的顶点才认为是在裁剪空间内。而且w份量在真正的投影时也会用到。
透视投影和正交投影分别对应了不一样的投影矩阵。还须要注意的是投影矩阵会改变空间的旋向性:空间从右手坐标系变换到了左手坐标系
其中FOV表示视椎体垂直方向的张开角度,而Near和Far分别控制了近裁剪平面和远裁剪平面距离摄像机的远近。这样咱们能够求出近裁剪平面的高度,以下所示。远裁剪平面相似。
而根据摄像机的横纵比信息,我么就能够获得近裁剪平面的宽度
一个顶点和透视投影的投影矩阵相乘后获得的结果以下
视椎体的变化以下所示
此时咱们就能够按以下不等式来判断一个变换后的顶点是否位于视椎体内
其中Size表示视椎体竖直方向上高度的一半,而Near和Far一样分别控制了近裁剪平面和远裁剪距离摄像机的远近。则近裁剪平面的高度以下所示。远裁剪平面相似。
近裁剪平面的高度一样能够经过摄像机的纵横比获得
一个顶点和正交投影的投影投影矩阵相乘后获得的结果以下
视椎体的变化以下所示
判断一个变换后的顶点是否位于视椎体内使用的不等式和透视投影中的同样,这种通用性也是为何要使用投影矩阵的缘由之一。
当完成了全部的裁剪工做后,就须要进行真正的投影了,即把视椎体投影到屏幕空间中,这个过程能够被称为屏幕映射。通过这一步变换,咱们会获得真正的像素位置,对应的2D坐标,而不是虚拟的三维坐标。这个过程能够理解成有两步:
在Unity中,屏幕空间左下角的像素是(0, 0),右上角的像素坐标是(pixelWidth, pixelHeight)。齐次除法和屏幕映射的过程可使用下面的公式来表示
$$screen_x = \frac{clip_x * pixelWidth}{2 * clip_w} + \frac{pixelWidth}{2}$$
$$screen_y = \frac{clip_y * pixelHeight}{2 * clip_w} + \frac{pixelHeight}{2}$$
在Unity中,从裁剪空间到屏幕空间的转换是由底层帮咱们完成的。咱们的顶点着色器只须要把顶点转换到裁剪空间便可(模型空间-世界空间-观察空间-裁剪空间,对应的矩阵一般会串联成一个MVP矩阵)。
最后,咱们再来看一种特殊的变换:法线变换。在游戏中,模型的一个顶点每每会携带额外的信息,而顶点法线和切线就是其中的两种信息,切线和法线是互相垂直的。
因为切线是由两个顶点之间的差值计算获得的,所以咱们能够直接使用变换顶点的矩阵MA->B来变换切线。但若是直接使用MA->B来变换法线,获得的新法线可能就不会和切线垂直了。例以下图所示.
那么应该使用哪一个矩阵来变换法线呢?咱们能够经过数学约束条件推出这个矩阵。因为顶点法线NA和切线TA垂直,则TANA = 0。给定变换矩阵MA->B,咱们已知TB = MA->BTA。如今咱们要找到一个矩阵G来变换法线NA,使得变换后的法线仍然与切线垂直,即
经过一些推导可得
因为TANA = 0,所以可得
即
这说明使用原变换矩阵的逆转置矩阵来变换法线就能够获得正确的结果。
值得注意的是,若是矩阵MA->B是正交矩阵,则咱们能够直接使用原变换矩阵做为法线的变换矩阵。若是变换只包含旋转和统一缩放,咱们能够利用统一缩放系数k来获得变换矩阵MA->B的逆转置矩阵,这样能够避免计算逆矩阵的过程。
视口空间中的坐标被称为视口坐标,就是把屏幕归一化,这样屏幕左下角就是(0, 0),右上角就是(1, 1)。若是已知屏幕坐标的话,咱们只须要把x,y份量除以屏幕分辨率便可获得视口坐标。若是已知裁剪空间中的坐标,能够经过如下公式获得视口坐标