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