Unity_Shader基础篇_4.1_Unity Shader入门精要

4.6坐标空间
4.6.1 为什么要使用这么多不同的坐标空间
因为一些概念只有在特定的坐标空间下才有意义,才更容易理解。
4.6.2 坐标空间的变换
关于定义一个坐标空间,必须指明其原点位置和3个坐标轴的方向。而这些数值实际上是相对于另一个坐标空间的(所有的都是相对的)。也就是说,坐标空间会形成一个层次结构——每个坐标空间都是另一个坐标空间的子空间,反过来说,每个空间都有一个父(parent)坐标空间。对坐标空间的变换实际上就是在父空间和子空间之间对点和矢量进行变换。
4.6.3 顶点的坐标空间变换过程
一个顶点要经过多个坐标空间的变换才能最终被画在屏幕上。一个顶点最开始是在模型空间(4.6.4)中定义的,最后它将会变换到屏幕空间中,得到真正的屏幕像素坐标。
4.6.4 模型空间
模型空间(model space),是和某个模型或者说是对象有关的。有时模型空间也被称为对象空间(object space)局部空间(local space)。每个模型都有自己独立的坐标空间,当它移动或旋转到时候,模型空间也会跟着它移动和旋转。
在模型空间中,我们经常使用一些方向概念,例如“前(forward)”“后(back)”“左(left)”“右(right)”“上(up)”“下(down)”。在本书中,我们把这些方向称为自然方向。模型空间中的坐标轴通常会使用这些自然方向。在4.2.4节中我们讲过,Unity在模型空间中使用的左手坐标系。
4.6.5 世界空间
世界空间(world space)是一个特殊的坐标系,因为它建立了我们所关心的最大的空间。
顶点变换的第一步,就是将顶点坐标从模型空间变换到世界空间中。这个变换通常叫做模型变换(model transform)
根据Transform组件上的信息,用缩放,旋转,平移的数据构建出模型变换的变换矩阵:
这里写图片描述
其中(tx,ty,tz)对应平移信息,θ对应旋转角度,(kc,ky,kz)对应缩放大小。
接下来就是模型变化换公式:
P(world) = M(modsl)P(modsl)
4.6.6 观察空间
观察空间(view sapce)也被称为摄像机空间(camera space)。Unity在模型空间和世界空间中选用的都是左手坐标系,而在观察空间中使用的是右手坐标系。这是符合OpenGL传统的,在这样的观察空间中,摄像机的正前方指向的是-z方向。
这种左右手坐标系之间的改变很少会对我们在Unity中的编程产生影响,因为Unity为我们做了很多渲染的底层工作,包括很多坐标空间的转换。但是,如果读者需要调用类似Camera.cameraToWorldMatrix、Camera.cameraToCameraMatrix等接口自行计算某模型在观察空间中的位置,就要小心这样的差异。
最后需要注意一下,观察空间和屏幕空间(4.6.8)是不同的。观察空间是一个三维空间,而屏幕空间是一个二维空间。从观察空间到屏幕空间的转换需要经过一个操作,那就是投影(projection)
顶点变换的第二步,就是将顶点坐标从世界空间变换到观察空间中。这个变换通常叫做观察变换(view transform)
为了得到顶点子啊观察空间中的位置,我们可以有两种方法。一种方法是计算观察空间的3个坐标轴在世界空间下的表示,然后根据4.6.2中提到的方法,构建出从观察空间变换到世界空间的变换矩阵,再对该矩阵求逆来得到从世界空间变换到观察空间的变换矩阵。我们还可以使用另一种方法,即想象平移整个观察空间,让摄像机原点位于世界坐标的原点,坐标轴与世界空间中的坐标轴重合即可。这两种方法得到的变换矩阵都是一样的,不同的只是我们思考的方式。
由Transform组件可以知道,摄相机在世界空间中进行了什么变换,那么,为了把摄像机重新移回到初始状态(这里指摄像机原点位于世界坐标的原点、坐标轴与世界空间中的坐标轴重合),我们需要进行逆向变换,即先按平移,在旋转,以便让坐标轴重合。因此,变换矩阵是:
这里写图片描述
但是,由于观察空间使用的是右手坐标系,因此需要对z分量进行取反操作。我们可以通过乘以另一特殊的矩阵来得到最终的观察变换矩阵:
这里写图片描述
现在我们可以用他来对对象顶点变换了:
这里写图片描述
4.6.7 裁剪空间
顶点接下来要从观察空间转换到裁剪空间(clip space,也被称为齐次裁剪空间)中,这个用于变换的矩阵叫做裁剪矩阵(clip matrix),也被称为投影矩阵(projection matrix)。
裁剪空间的目的是能够方便地对渲染图元进行裁剪:完全位于这块空间内部的图元将会被保留,完全位于这块空间外部的图元将会被剔除,而与这块空间边界相交的图元就会被裁剪。那么,这块空间是如何决定的呢?答案是由视锥体(view frustum)来决定。
视锥体指的是空间中的一块区域,这块区域决定了摄像机可以看到的空间。视锥体由六个平面包围而成,这些平面也被称为裁剪平面(clip planes)。视锥体有两种类型,这涉及两种投影类型:一种是正交投影(orthographic projection),一种是透视投影(perspective projection)
这里写图片描述
在视锥体的6块裁剪平面中,有两块裁剪平面比较特殊,他们分别被称为近裁剪平面(near clip plane)远剪裁平面(far clip place)。他们决定了摄像机可以看到的深度范围。
这里写图片描述
投影矩阵有两个目的。
·首先是为投影做准备。这是个迷惑点,虽然投影矩阵的名称包含了投影二字,但是它并没有进行真正的投影工作,而是在为投影做准备。真正的投影发生在后面的齐次除法(homogeneous division)过程中。而经过投影矩阵的变换后,顶点的w分量将会具有特殊的意义。
投影可以理解成是一个空间的降维,而投影矩阵实际上并不会真的进行这个步骤,它会为真正的投影做准备工作。真正的投影会在屏幕映射时发生,通过齐次除法来得到二维坐标。
·其次似乎对x、y、z分量进行缩放。我们上面讲过直接使用视锥体的6个裁剪平面来进行裁剪会比较麻烦。而经过投影矩阵的缩放后,我们可以直接使用w分量作为一个范围值,如果x、y、z分量都位于这个范围内,就说面该顶点位于裁剪空间内。
在裁剪空间之前,虽然我们使用了齐次坐标来表示点和矢量,但他们的第四个分量都给固定的:点的w分量是1,方向矢量的w分量是0。经过投影矩阵的变换后,我们就会赋予齐次坐标的第四个坐标更加丰富的含义。
透视投影
这里写图片描述
调节属性我们可以求出视锥体近裁剪平面好远裁剪平面的高度,也就是:
nearClipPlaneHeight = 2·Near·tan(FOV/2)
farClipPlaneHeight = 2·Far·tan(FOV/2)
现在我们还缺乏横向的信息。这可以通过摄像机的横纵比得到。在Unity中,一个摄像机的横纵比由Game视图的横纵比和Viewport Rect中的W和H属性共同决定(实际上,Unity允许我们在脚本里通过Camera.aspect进行更改)。假设,当前摄像机的纵横比为Aspect,我们定义:
Aspect = nearClipPlaneWidth/nearClipPlaneHeight
Aspect =farClipPlaneWidth/farClipPlaneHeight
现在,我们由已知的Near、Far、FOV和Aspect的值来确定透视投影的投影矩阵。如下:
这里写图片描述
需要注意的是,这里的投影矩阵是建立在Unity对坐标系的假定上面的,也就是说,我们针对的是观察空间为右手坐标系,使用列矩阵在矩阵右侧进行相乘,且变换后z分量范围将在[-w,w]之间的情况。而在类似DirectX这样的图形接口中,它们希望变换后z分量范围将在[0,w]之间,因此就需要对上面的透视矩阵进行更改。
而一个顶点和上述投影矩阵相乘后,可以由观察空间变换到裁剪空间中,结果如下:
这里写图片描述
从结果可以看出,这个投影矩阵本质就是对x,y和z分量进行了不同程度的缩放(当然,z分量还做了一个平移),缩放的目的是为了方便剪裁。我们可以注意到,此时顶点的w分量不再是1,而是原先z分量的取反结果。现在,我们就可以按如下不等式来判断一个变换后的顶点是否位于视锥体内。如果一个顶点在视锥体内,那么它变换后的坐标必须满足:
-w<=x<=w -w<=y<=w -w<=z<=w
任何不满足上述条件的图元都需要被剔除或者裁剪。
这里写图片描述
从图4.39还可以注意到,裁剪矩阵会改变空间的旋向性:空间从右手坐标系变换到了左手坐标系。这意味着,离摄像机越远,z值越大。
正交投影
这里写图片描述
依旧是调整好属性后,我们可以求出视锥体近裁剪平面和远裁剪平面的高度,也就是:
nearClipPlaneHeight = 2·Size
farClipPlaneHeight = nearClipPlaneHeight
现在我们还缺乏横向的信息。同样,我们可以通过摄像机的横纵比得到。依旧假设,当前摄像机的横纵比为Aspect,那么:
nearClipPlaneWidth = Aspect·nearClipPlaneHeight
farClipPlaneWidth = nearClipPlaneWidth
现在,我们可以根据已知的Near、Far、Size和Aspect的值来确定正交投影的裁剪矩阵。如下:
这里写图片描述
同样,这里的投影矩阵是建立在Unity对坐标系的假定上面的。
一个顶点和上述投影矩阵相乘后的结果如下:
这里写图片描述
和透视投影不用的是,使用正交投影的投影矩阵对顶点进行变换后,其w分量仍然为1。本质是因为投影矩阵最后一行的不同,透视投影的投影矩阵的最后一行是[0 0 -1 0],而正交投影的投影矩阵的最后一行是[0 0 0 1]。这样的选择是有原因的,是为了为齐次除法做准备。
判断一个变换后的顶点是否位于视锥体内使用的不等式和透视投影中的一样,这种通用性也是为什么要使用投影矩阵的原因之一。图4.41显示了经过上述投影矩阵后,正交投影的视锥体的变化。
这里写图片描述
同样,裁剪矩阵改变了空间的旋向性。可以注意到,经过正交投影变换后的顶点实际已经位于一个立方体内了。

4.6.8 屏幕空间
经过投影矩阵的变换后,我们可以进行裁剪操作。当完成了所有的裁剪工作后,就需要进行真正的投影了,也就是说,我们需要把视锥体投影到屏幕空间(screen space)中。经过这一步变化,我们会得到真正的像素位置,而不是虚拟的三维坐标。
屏幕空间是一个二维空间,因此,我们必须把顶点从裁剪空间投影到屏幕空间中,来生成对应的2D的坐标。这个过程可以理解的成有两个步骤。
首先,我们需要进行标准齐次除法(homogeneous division),也被称为透视除法(perspective division)。虽然这个步骤听起来很陌生,但是它实际上非常简单,就是用齐次坐标系的w分量去除以x、y、z分量。在OpenGL中,我们把这一步得到的坐标叫做归一化的设备坐标(Normalized Device Coordinates,NDC)。经过这一步,我们可以把坐标从齐次裁剪坐标空间转换到NDC中。经过透视投影变换后的裁剪空间,经过齐次除法后会变换到一个立方体内。按照OpenGL的传统,这个立方体的x、y、z分量的范围都是[-1,1]。但是DirectX这样的API中,z分量的范围会是[0,1]。而Unity选择了OpenGL这样的齐次裁剪空间,如图4.43所示:
这里写图片描述
而对于正交投影来说,它裁剪空间实际已经是一个立方体了,而且由于经过正交投影矩阵变换后的顶点的w分量是1,因此齐次除法并不会对顶点的x、y、z坐标产生影响,如图4.44所示。
这里写图片描述
经过齐次除法后,透视投影和正交投影的视锥体都变换到一个相同的立方体内。现在,我们可以根据变换后的x和y坐标来映射输出窗口的对应像素坐标。
在Unity中,屏幕空间左下角的像素坐标似是(0,0),右上角的像素坐标是(poxelWidth,pixelHeight)。由于当前x和y坐标都是[-1,1],因此这个映射的过程就是一个缩放的过程。
齐次除法和屏幕映射的过程可以使用下面的公式来总结:
screen(x)= clip(x)·pixelWidth/2·clip(w) + pixelWidth/2
screen(y)= clip(y)·pixelWidth/2·clip(w) + pixelWidth/2
上面的式子对x和y分量都进行了处理,z分量通常会被用于深度缓冲。一个传统的方式是把clip(z)/clip(w)的值直接存进深度缓冲中,但这并不是必须的。通常驱动生产商会根据硬件来选择最好的存储格式。此时,clipw也并不会被抛弃,虽然它已经完成了它的主要工作——在齐次除法中作为分母来得到NDC,但它仍然会在后续的一些工作中起到重要的作用,例如进行透视校正插值。
在Unity中,从裁剪空间到屏幕空间的转换是有Unity帮我们完成的。我们的顶点着色器只需要把顶点转换到裁剪空间即可。
4.6.9总结
这里写图片描述
这里写图片描述
4.7 法线变换
法线(normal),也别称为法矢量(normal vector)。上面我们已经看到如何使用变换矩阵来变换一个顶点或一个方向矢量,但法线是需要我们特殊处理的一种方向矢量。在游戏中,模型的一个顶点往往会携带额外的信息,而顶点法线就是其中一种信息。当我们变换一个模型的时候,不仅需要变换它的顶点,还需要变换顶点法线,以便在后续处理(如片元着色器)中就算光照等。
一般来说,点和绝大多数方向矢量都可以使用同一个4×4或3×3的变换矩阵M(A->B)把其从坐标空间A变换到坐标空间B中。但是变换法线的时候,如果使用同一个变换矩阵,可能就无法确保维持法线的垂直性。
我们先来了解一下另一种方向矢量——切线(tangent),也被称为切矢量(tangent vector)。与法线类似,切线往往也是模型顶点携带的一种信息。他通常与纹理空间对齐,而且与法线方向垂直。
由于切线是由两个顶点之间的差值计算得到的,因此我们可以直接使用用于变换顶点的变换矩阵来变换切线。假设,我们使用3×3的变换矩阵M(A-B)来变换顶点(注意,这里涉及的变换矩阵都是3×3,不考虑平移变换。这是因为切线和法线都是方向矢量,不会受平移的影响),可以由下面的式子直接得到变换后的切线:这里写图片描述
其中T(A)T(B)分别表示在坐标空间A下和坐标空间B下的切线方向。但如果直接使用M(A-B)来变换法线,得到的新的法线方向可能就不会与表面垂直了。图4.48给出了这样的一个例子。
这里写图片描述
我们由数学约束条件来推出变换法线的矩阵。我们知道同一个顶点的切线T(A)和法线N(A)必须满足垂直条件,即T(A)·N(A) = 0。给定变换矩阵M(A->B),我们已经知道T(B) = M(A->B)T(A)。我们现在想要找到一个矩阵G来变换法线N(A),使得变换后的法线仍然与切线垂直。即这里写图片描述
对上式进行一些推导后可得:
这里写图片描述
由于T(A)*N(A) =0,因此如果M(A->B)^TG = 1,那么上式即可成立。也就是说,如果G = ((M(A->B)^T)^-1 = (M(A->B)^-1)^T),即使用原变换矩阵的逆转置矩阵来变换法线就可以得到正确的结果。
值得注意的是,如果变换矩阵M(A->B)是正交矩阵,那么M(A->B)^-1 = M(A->B)^T,因此(M(A->B)^T)^-1 = M(A->B),也就是说我们可以使用用于变换顶点的变换矩阵来直接变换法线。如果变换只包括旋转变换,那么这个变换矩阵就是正交矩阵。而如果变换只包含旋转和统一缩放,而不包含非统一缩放,我们利用统一缩放系数k来得到变换矩阵M(A->B)的逆转置矩阵(M(A->B)^T)^-1 = 1/K(M(A->B))。这样就可以避免计算逆矩阵的过程。如果变换中包含了非统一变换,那么我们就必须要求解逆矩阵来得到变换法线的规矩。

猜你喜欢

转载自blog.csdn.net/qq_39710961/article/details/79994454