顶点着色器最基本的功能就是把模型的顶点坐标从模型坐标空间转换到齐次裁剪坐标空间中。
1.坐标空间的变换
我们常用Unity里的模型,有基于世界坐标系的位置,也有基于父节点的位置,那么他们之间是如何转换的呢。
假设现在有父坐标空间P与一个子坐标空间C。我们用Mc→p来表示子坐标空间变换到父坐标空间的变换矩阵,而Mp→c是其逆矩阵。
下面我们将演示如何求出从子坐标空间到父坐标空间的变换矩阵Mc→p。
假设我们在坐标空间中有一个点P,我们要如何确定它的位置呢。首先我们从坐标原点出发Oc,向x轴方向移动a个单位axc,再向y轴方向移动b个单位byc,最后向z轴方向移动c个单位cxc,这样我们就得到了我们的点P。
Ap = Oc + axc + byc + czc
这里我们根据得到的公式进行转换就得到了我们的变换矩阵(其中用到的“|”符号表示是按列展开的)。
对于方向矢量的空间变换,因为矢量是没有位置的,因此我们可以将坐标空间的原点忽略。
2.顶点坐标空间变换过程
在渲染流水线中,一个顶点要经过多个坐标空间的变换才能最终在屏幕上显示。接下来,我们将讲解顶点在各种空间的变换过程。
3.模型空间
也称为对象空间或局部空间,每个模型都有自己独立的坐标空间,当他移动或者旋转时,模型空间也会跟着它移动旋转。
模型空间的原点和坐标轴通常是由美术人员在建模软件里确定好。当我们导入到Unity后,我们可以再顶点着色器中访问到模型的顶点信息,其中包含了每个顶点的坐标。这些坐标都是相对于模型空间中的原点定义的(通常位于模型重心)。
假设妞妞的鼻子顶点位置是(0,2,4),由于顶点变换中往往包含了平移变换,因此需要将其扩展到齐次坐标系下(0,2,4,1).
4.世界空间
在世界空间中我们可以表述物体顶点的绝对位置。
在Unity中我们可以挑中Trans from组件中的Position属性来改变模型的位置,这个位置是相对于这个物体的父节点的模型空间中的原点定义的,如果这个物体没有任何父节点,那么这个位置就是在是世界坐标系中的位置,也是绝对位置。
顶点变换的第一步,就是将顶点坐标从模型空间变换到世界空间中,这个变换通常叫做模型变换。
根据Trans from组件上的信息,我们可以知道在世界空间中,妞妞进行了(2,2,2)的缩放,又进行了(0,150,0)的旋转以及(5,0,25)的平移。注意:这里的变换顺序是不能互换的。据此我们可以构建出模型变换的变换矩阵:
由此我们可以对妞妞的鼻子进行模型变换了
5.观察空间
观察空间也被称为摄像机空间。摄像机决定了我们渲染游戏所使用的视角。在观察空间中,摄像机位于原点。
Unity在模型空间和世界空间中选用的都是左手坐标系,而在观察空间中使用的是右手坐标系。
顶点变换的第二步就是将顶点坐标从世界变换到观察空间中,这个变换通常叫观察变换。
为了得到顶戴在观察空间中的位置,我们想象平移整个观察空间,让摄像机的原点位于世界坐标的原点,坐标轴与世界空间中的坐标轴重合即可。
由Transform组件我们可以知道,摄像机在世界空间中的变换是先按(30,0,0)进行旋转,然后按(0,10,-10)进行平移。为了让摄像机重新回到初始状态,我们先按(0,-10,10)平移,以便摄像机回到原点,然后再按(-30,0,0)进行旋转,以便让坐标轴重合。
变换矩阵获取方式如下:
由于观察空间使用的是右手坐标系,因此我们需要对Z分量进行取反操作。
现在我们可以用它来对妞妞的鼻子进行顶点变换了。
这样我们就得到了观察空间中妞妞鼻子的位置——(9,8.84,-27.31)。
6.裁剪空间
顶点接下来要从观察空间转换裁剪空间(也称为齐次裁剪空间),这个用于变换的矩阵叫做裁剪矩阵(也成为投影矩阵)。
这里我们需要了解下什么叫视锥体,视锥体由六个裁剪平面包围而成。视锥体有两种类型;一种是正交投影,一种是透视投影。
在追求真实感的3D游戏中我们会使用透视投影,而在一些2D游戏或者渲染小地图等我们会使用到正交投影。
在是最提的6块裁剪平面中,有两块裁剪平面比较特殊,分别是近裁剪平面和远裁剪平面。特们决定了摄像机可以看到的深度范围。
接下俩,我们可以通过一个投影矩阵将顶点转换到一个裁剪空间中。根据视锥体的不同,接下来的转换又分为透视投影和正交投影。
投影是什么意思呢?我们可以理解成是一个空间的将为,列如从四维空间投影到三维空概念中。
-
透视投影
视锥体的意义在于定义了场景中的一块三维空间。所有位于这块空间中的物体将会被渲染,否则就会被剔除或裁剪。那么组成这块区域的6个裁剪平面又是如何定义的呢?在Unity中,他们由Camera组件中的参数和Game视图中的纵横比共同决定。
由图可以看出,我们可以通过Camera组件中的Field of View属性来改变视锥体竖直方向张开的角度,而Clippping Planes中的Near和Far参数可以控制视锥体的近裁剪平面和远裁剪平面距离的摄像机远近。这样我们可以求出视锥体近裁剪平面和远裁剪平面的高度。
求出高度信息后我们还需要横向信息。在Unity中,一个摄像机的纵横比由Game试图的纵横比和Viewport Rect中的W和H属性共同决定。,假设点当前摄像机的纵横比为Aspect。
现在我们可以根据已知的Near、Far、FOV和Aspect的值来确定透视投影的投影矩阵。
当一个顶点和上述投影矩阵相乘后,可以由观察空间变换到裁剪空间中
从结果来看,这个投影的本质就是对x,y和z分量进行了不同程度的缩放,缩放的目的是为了方便裁剪。此时顶点的w分量不再是1,而是原来z分量的取反结果。现在我们可以根据如下不等式来判断一个变换后的顶点是否位于视锥体内。
从图中我们还可以看出,裁剪据则会那个会改变空间的旋向性,空间从右手坐标系变换到了左手坐标系,这意味着摄像机离得越远,z值将越大。
- 正交投影
在Unity中正交投影也是由Camera组件中的参数和Game视图中的纵横比共同决定的。正交投影的视锥体是一个长方体,因此计算上比透视投影来说更加简单。由图中我们可以看出,我们可以通过Camera组件的Size属性来改变视锥体竖直方向高度的一半,而Clipping Planes中的Near和Far参数可以控制视锥体的近裁剪平面和远裁剪平面距离摄像机的远近。这样我们可以求出是最提的近裁剪平面和远裁剪平面的高度:
现在我们还缺少横向信息,我们可以通过摄像机的纵横比得到,假设当前摄像机的横纵比为Aspect,那么我们可以根据已知的Near、Far、Size和Aspect的值来确定正交投影的裁剪矩阵如下:
一个顶点和上述投影矩阵相乘后的结果如下:
注意和透视投影不同的是,使用正交投影的投影举证对顶点进行变换后,其w 分量仍然为1。
现在我们回到农场游戏,前面我们已经确定了妞妞的鼻子在观察空间中的位置为(9,8.84,-27.31)现在,我们要计算它在裁剪空间中的位置。
首先农场游戏是一个3D游戏,所以我们采用透视摄像机。摄相机参数和Game视图的纵横比如图所示:
据此,我们可以知道透视投影的参数:FOV为60°,Near为5,Far为40,Aspect为4/3=1.333。那么它们所对应的投影矩阵就是:
接下来我们用这个投影来把妞妞的鼻子从观察空间转换到裁剪空间中。
这样我们就求出了妞妞鼻子在裁剪空间的位置(11.691,15.311,23.692,27.31)。接下来Unity会判断妞妞的鼻子是否需要裁剪。
7.屏幕空间
当完成所有的裁剪工作后,就需要进行真正的投影了。也就是说,我们要把视锥体投影到屏幕空间中。经过这一步我们会得到真正的像素位置,而不是虚拟三维坐标。
首先我们要进行标准齐次除法(透视除法),就是用齐次坐标系的w分量去除以x、y、z分量。在OpenGL中我们把这一步得到的坐标教做归一化设备坐标。经过这一步,我们可以把坐标从齐次裁剪坐标空间转换到NDC中。经过齐次除法后会变换到一个立方体内。
而对于正交投影来说,他的裁剪空间已经是一个立方体了,而且由于经过正交投影矩阵变换后的顶点的w分量是1,因此齐次除法并不会对顶点x、y、z坐标产生影响。
齐次除法和屏幕映射的过程可以使用下面的公式来总结:
在Unity中,从裁剪空间到屏幕空间的转化是由Unity帮我们完成的。我们的顶点着色器只用把顶点转换到裁剪空间即可。
现在我们要妞妞鼻子的位置(11.691,15.311,23.692,27.31)转换到屏幕上像素得的位置。假设当前屏幕的像素宽为400,高为300。首先我们需要进行齐次除法,把裁剪空间的坐标投影到NDC中,然后再映射到屏幕空间中。过程如下:
8.总结
以上就是一个顶点如何从模型空间变换到屏幕坐标的过程。
参考书籍《Unity Shader入门精要》