UnityShader数学基础

左手右手坐标系
二维坐标系尽管x和y指向虽然可能不同,但总可以通过一些旋转操作来使他们的坐标轴指向相同,从这种意义上来说,所有的二维笛卡尔坐标系都是等价的。但对于三维笛卡尔坐标系,靠旋转并不能让两个不同朝向的坐标系重合,也就是说,三维笛卡尔坐标系并不都是等价的。因此,出现了两种不同的三维坐标系:左手坐标系和右手坐标系。我们可以用双手来判断一个坐标系的旋向性,举起左手,食指和大拇指摆出一个L手势,并且让食指指向上,大拇指向右,然后伸出中指,应该是指向前方,这样就是一个左手
坐标系,大拇指食指以及中指分别对应了+x +y +z的方向,同样也可以用右手得到一个右手坐标系。
UnityShader数学基础
UnityShader数学基础
除了坐标轴朝向不同,左右手坐标系对于正向旋转的定义也不同,两个坐标系的旋转方向分别是由左手右手定则确定的,左手坐标系旋转方向是顺时针,右手坐标系旋转正方向是逆时针。
UnityShader数学基础
左右手坐标系之间是可以转换的,最简单方法就是其中一个轴反转,其他两个轴不变。

Unity模型和世界空间使用的是左手坐标系,模型空间中一个物体的右侧,上侧,和前侧分别对应了x,y,z轴的正方向。
UnityShader数学基础

对于观察空间来说,Unity使用的是右手坐标系。观察空间,以摄像机为原点的坐标系。在这个坐标系中,摄像机的前向方向是z轴的负方向,这与模型空间和世界空间中的定义相反,z轴坐标的减少意味场景深度的增加。
UnityShader数学基础
向量叉乘
叉乘的公式
UnityShader数学基础
叉乘满足反交换律,对两个矢量进行叉积的结果会得到一个同时垂直于这两个矢量的新矢量,新矢量的模等于两个矢量的模的乘积再乘以之间夹角的正弦值。公式如下
UnityShader数学基础
新矢量的方向根据左手右手坐标系来确定

矩阵
一个矩阵可以把一个矢量从一个坐标空间转换到另一个坐标空间。
正交矩阵
三维变换中经常需要使用逆矩阵来求解反向的变换,求解逆矩阵往往计算很大,但转置矩阵非常容易得到。如何判断一个矩阵是否是正交矩阵呢,直接根据公式定义计算量也很大。可以根据正交矩阵的定义:
UnityShader数学基础
得到结论:
矩阵的每一行,都是单位矢量,因为只有这样他们与自己的点积才是1
矩阵的每一行,之间相互垂直,只有这样他们的点积才能是0
同样每一列也满足上述条件,因为M是正交矩阵,转置矩阵也是正交矩阵

行矩阵还是列矩阵
矩阵相乘时候选择行矩阵还是列矩阵来表示矢量是非常重要的,因为这决定了矩阵乘法的书写顺序和结果。Unity中,把矢量放在矩阵右侧,就是把矢量转换成列矩阵来运算。这意味着,我们的矩阵乘法通常都是右乘。使用列向量的结果是,我们的阅读顺序是从右向左。

矩阵的几何意义 变换
线性变换包括,旋转缩放,错切,镜像,正交投影,除了线性变换还有平移变换,可以使用一个3x3的矩阵表示所有的线性变换。仿射变换就是合并线性变换和平移变换的变换类型,平移变换需要使用一个4x4的矩阵来表示,因此需要把矢量扩展到四维空间,就是齐次坐标空间。下图给出了图形学常见的变换矩阵名称以及他们的特性:
UnityShader数学基础
UnityShader数学基础

齐次坐标
齐次坐标是一个四维矢量。如何把三维矢量转变齐次坐标,对于一个点,从三维坐标转换成齐次坐标是把w分量设为1,对于方向矢量来说,需要把w设为0.这样的设置导致,当用一个4x4的矩阵对一个点进行变换时候,平移旋转缩放都会施加在这点,但是用一个变换一个方向矢量,平移效果就会忽略。

分解基础变换矩阵
4x4矩阵可以表示平移旋转缩放。可以把纯平移,纯旋转和纯缩放的变换矩阵叫做基础变换矩阵。这些矩阵有一个共同点,可以把一个基础变换矩阵分解成4各组成部分
UnityShader数学基础
左上角的矩阵用于表示旋转和缩放,t3x1用于表示平移,01x3是零矩阵,右下角就是标量1

平移矩阵
可以使用矩阵乘法来表示对一个点的平移变换
UnityShader数学基础
从结果可以看出为什么这矩阵有平移效果,点xyZ的分量分别增加了一个位置偏移。在3D中的可视化效果是,把点(x,y,z)在空间中平移了(tx,ty,tz)个单位。有趣的是,对一个方向矢量做平移变换,结果如下
UnityShader数学基础
平移变换对方向矢量不会产生影响,矢量没有位置属性。
构建一个平移矩阵:基础变换矩阵中的t3x1矢量对应了 平移矢量,左上角的矩阵M3x3位单位矩阵I3
平移矩阵的逆矩阵就是反向平移得到的矩阵,
UnityShader数学基础

可以看出平移矩阵并不是一个正交矩阵。

缩放矩阵
可以对一个模型沿着空间xyz轴进行缩放,可以用矩阵乘法来表示一个缩放变换:
UnityShader数学基础
对方向矢量可以使用同样的矩阵进行缩放
UnityShader数学基础
如果缩放系数kx=ky=kz,这样的缩放称为统一缩放,否则为非统一。在对发现进行变换时,如果存在非统一缩放,直接使用用于变换顶点的变换矩阵的话,就会得到错误的结果。缩放矩阵的逆矩阵是使用原缩放系数的倒数来对点或者方向矢量进行缩放:
UnityShader数学基础
缩放矩阵一般不是正交矩阵,上面的矩阵只适合沿着坐标轴方向的缩放,如果在任意方向缩放,需要使用一个复合变换,先将缩放轴变换为标准坐标轴,然后沿着坐标轴缩放,再使用逆变换得到原来的缩放轴朝向。

旋转矩阵
旋转操作需要制定一个轴,这个旋转轴不一定是空间中的坐标轴,这里只涉及沿xyz轴进行旋转
绕x轴旋转可以使用下面矩阵
UnityShader数学基础
绕y轴旋转可以使用下面矩阵
UnityShader数学基础

绕z轴旋转可以使用下面矩阵
UnityShader数学基础
旋转矩阵的逆矩阵是旋转相反角度得到的变换矩阵。旋转矩阵是正交矩阵,而且多个旋转矩阵之间的串联同样是正交的。

复合变换
可以把平移旋转缩放组合起来,形成一个复杂的变换过程。可以对一个模型先进性缩放,再绕y轴旋转,之后向z轴平移,复合变换可以通过矩阵的串联来实现,如下
UnityShader数学基础

使用列矩阵,阅读顺序需要从右向左,先缩放,再旋转最后平移。乘法顺序很重要,因为矩阵不满足交换律。在绝大数情况下,我们约定的变换顺序就是先缩放再旋转,最后平移。除了需要注意不同类型的变换顺序外,还需要小心旋转的变换顺序,一个问题是我们需要同时绕着3个轴旋转,Unity中旋转顺序是zxy,这在旋转相关的API文档中有说明。意味着,
UnityShader数学基础
给定一个旋转顺序假如上面的zxy以及对应的旋转角度(θx,θy,θz),有两种坐标系可以选择:
绕坐标系E下的z轴旋转θz,绕坐标系E下的y轴旋转θy,绕坐标系E下的x轴旋转θx,进行一次旋转时不一起旋转当前坐标系。
绕坐标系E下的z轴旋转θz,在坐标系E下绕z轴旋转θz后的新坐标系下的y轴转θy,在新的坐标系下绕y轴旋转θy后的新坐标系下的x轴θx,就是说在旋转时候,把坐标系一起转动。
这两种结果是不一样的,如果把他们的旋转顺序颠倒一下,结果就会一样,在第一种情况下,按zxy顺序和在第二种按yxz顺序是一样的,unity文档中说明的旋转顺序就是第一种情况下的顺序。

模型空间
模型空间也成称为对象空间或者局部空间,每个模型都有自己独立的坐标空间,当移动或者旋转的时候,模型空间会跟着移动旋转。

世界空间
是我们所关心的最大空间。Unity中可以通过调整Transform组件中的Position来改变模型位置,这里位置是相对于这个transform的父节点的模型坐标空间中原点定义的。如果一个Transform没有任何父节点,那么这个位置就是世界坐标系的位置。
顶点变换第一步就是将顶点坐标从模型坐标转换到世界空间,这个变换通常叫做模型变换。转换过程中可能涉及到缩放旋转以及平移。
UnityShader数学基础
观察空间
也被称为摄像机空间。摄像机决定了我们渲染游戏所使用的视角,unity观察空间实用的是右手坐标系,摄像机正前方指向-z轴方向。为了得到顶点在观察空间中的位置,有两种方法。
一种是计算观察空间的三个坐标轴在世界空间下的表示,构造出从观察空间到世界空间的变换矩阵,再对该矩阵求逆得出变换矩阵。
另外是还可以想象平移整个观察空间,让摄像机原点位于世界坐标的原点,坐标轴与世界空间的坐标轴重合。

裁剪空间
伟裁剪空间,用于从观察空间转换到裁剪空间的矩阵程伟投影矩阵。
裁剪空间的目标是能够方便的对渲染图元进行裁剪,完全位于这块空间内部的图元将会被保留,完全位于这块空间外的图元将会被剔除,而在这块空间边界相交的图元就会被裁剪。这块空间是由视椎体决定的。视锥体是空间的一块区域,这块区域决定了摄像机可以看到的空间,视锥体由六个平面包围,这些平面也被称为裁剪平面。视锥体有两种一种是正交投影,一种是透视投影。

透视投影中,地板上的平行线并不会保持平行,离摄像机越近网格越大,越远越小,而在正交投影,所有的网格大小一样,而且平行线会一直保持平行。透视投影模拟了人眼看世界发放时,而正交投影则完全保留了物体的距离和角度。在视锥体的六块裁剪平面中,有两块裁剪平面比较特殊,分别称为近裁剪平面和远裁剪平面,他们决定了摄像机可以看到的深度范围。
UnityShader数学基础

透视投影的视锥体是一个金字塔形,侧面的四个裁剪平面将会在摄像机处相交,更符合视锥体这个词,正交投影的视锥体是一个长方体。我们希望根据视锥体围城的区域对图元进行裁剪,但是如果直接使用视锥体定义的空间来裁剪,那么不同的视锥体就需要不同的处理过程,而对于透视投影视锥体来说,判断一个顶点是否处于金字塔内部比较麻烦。所以使用一种更加通用方便和整洁的方式来进行裁剪工作,这种方式就是通过一个投影矩阵把顶点转换到一个裁剪空间。

投影矩阵两个目的:
为投影做准备,投影矩阵并没有做投影工作,真正的投影是后面的齐次除法。经过投影矩阵的变换后,顶点w的分量将会有特殊的意义。齐次是对xyz分量进行缩放,经过投影矩阵的缩放后,可以直接使用w分量作为一个范围值,如果xyz分量都位于这个范围,就说明顶点位于剪裁空间内。在裁剪空间之前,虽然我们使用了齐次坐标来表示点和矢量,但他们的第四个分量都是固定的,点的w是1,方向是0,经过投影矩阵的变换后,会赋予齐次坐标第四个坐标更加丰富的意义。

两种矩阵投影类型使用的矩阵:
透视投影
视锥体意义在于定义了场景中一块三维空间。所有位于这块空间的物体将被渲染,否则会被剔除,这块区域由6个裁剪平面定义,这6个裁剪平面又是如何决定,在Unity他们是由Camera组件中的参数和Game视图的纵横比共同决定
UnityShader数学基础
通过Camera组件的FOV属性来改变锥体竖直方向的张开角度,Clipping Planes中的Near和Far参数可以控制视锥体的近远裁剪平面距离相机的远近。这样我们可以求出视锥体近裁剪平面和远裁剪平面的高度:
UnityShader数学基础
横向的信息通过摄像机的横纵比可以得到,在Unity中,一个摄像机的横纵比由Game视图的横纵比和Viewport Rect中的W和H共同决定,假设当前摄像机的横纵比为Aspect
UnityShader数学基础
现在可以根据Near Far FOV和Aspect的值来确定透视矩阵的投影矩阵
UnityShader数学基础
而一个顶点和上述矩阵投影相乘后,可以由观察空间转换到裁剪空间

UnityShader数学基础
从结果可以看出,投影矩阵本质是对xyz进行了不同程度的缩放,缩放目的为了方便裁剪。此时顶点的w不再是1,而是原先z分量的取反结果。我们可以按如下不等式来判断一个变换后的顶点是否位于视锥体内。如果一个顶点在视锥体内,那么变换后的坐标必须满足
UnityShader数学基础
任何不满足上述条件的图元需要被剔除
UnityShader数学基础
裁剪矩阵会改变矩阵的旋向性,空间从右手坐标系变换到了左手坐标系,这意味着离摄像机越远z值越大

正交投影
和透视投影类似,在Unity中视锥体的六个面也是由Camera组件中的参数和Game师徒的横纵比共同决定
UnityShader数学基础
正交投影的视锥体是一个长方体,计算比较简单,由图可以看出通过Camera组件的Size属性来改变视锥体竖直方向上高度的一半,而Clipping Planes中的Near和Far参数可以控制视锥体的近裁剪平面和远裁剪平面距离摄像机的远近。这样我们可以求出视锥体近裁剪平面和远裁剪平面的高度:
UnityShader数学基础
现在缺乏横向的信息,可以通过摄像机的横纵比得到,假设当前摄像机的横纵比是Aspect
UnityShader数学基础
现根据已知的Near Far Size和Aspect来确定正交矩阵的裁剪矩阵
UnityShader数学基础
一个顶点和上述投影矩阵相乘后结果如下
UnityShader数学基础
和透视投影不同的是,使用正交投影矩阵对顶点进行变换之后,w仍然为1.本质是因为投影矩阵最后一行的不同,投食投影最后一行是(0 0 -1 0)而正交投影的投影矩阵最后一行是(0 0 0 1)这是为了齐次除法做准备
判断一个变换厚的顶点是否位于视锥体内使用的不等式和透视投影中的一样,下图显示了经过上述投影矩阵后,正交投影的视锥体变化。
UnityShader数学基础
屏幕空间
经过投影矩阵的变换后,可以进行裁剪操作,之后需要把视锥体投影到屏幕空间,经过这一步就能得到真正像素的位置。屏幕空间是一个二维空间,因此必须把顶点从裁剪空间投影到屏幕空间,来生成2D坐标,过程有两个步骤
1.进行标准齐次除法,也称为透视除法,用齐次坐标系的w分量去除xyz分量。Opengl中,这一步的得到的坐标叫做归一化设备坐标,经过这一步可以把坐标从齐次裁剪空间转换到NDC。经过透视投影变换的裁剪空间,经过齐次除法后会变换到一个立方体内,按照Opengl的传统这立方体xyz分量的范围都是[-1,1]但在Dx中,z分量范围在[0,1],Unity选择了Opengl的空间,如图
UnityShader数学基础
对于正交投影,剪裁空间实际上已经是一个立方体,而且经过正交投影矩阵后的顶点w分量是1,因此其次书法不会对xyz产生影响 如图
UnityShader数学基础
经过齐次除法后,透视投影和正交投影的视锥体都变换到一个相同的立方体内,接下来可以根据变换后的xy坐标来映射输出窗口对应像素坐标。在Unity中屏幕空间左下角像素坐标是(0,0),右上角是(pixelWidth,pixelHeight)。由于当前xy是在[-1,1]因此映射过程就是一个缩放的过程。齐次除法和屏幕映射的过程可以使用下面公式来总结:
UnityShader数学基础
上面公式对xy分量都做了处理,通常z会被用于深度缓冲,一个传统的方式是把clipz/clipw的值直接存进深度缓冲,但这不是必须的。
在Unity中,从裁剪空间到屏幕空间的转换是由底层帮我们完成的,我们的顶点着色器只需要把顶点转换到裁剪空间即可。

总结
以上就是一个顶点如何从模型空间到屏幕坐标的过程,下图总结了这些空间和用于变换的矩阵。
UnityShader数学基础
顶点着色器最基本的任务就是把顶点坐标从模型空间转换到裁剪空间,对应了上图前三个顶点变换过程,在片元着色器,也可以得到该片元在屏幕空间的像素位置。
在Unity中,坐标系的旋向性也随着变换发生了改变,下图总结了各个空间使用的坐标系旋向性
UnityShader数学基础

上图可以发现,只有在观察空间Unity使用了右手坐标系
这里仅仅给出了一些最重要的坐标空间,还有一些空间实际开发中也会遇到比如切线空间

法线变换
之前已经看到如何使用变换矩阵来变换一个顶点或者一个方向,但法线是我们需要特殊处理的一种方向矢量。在游戏中,模型顶点往往会携带额外信息,顶点发现就是其中一种。当我们变换一个模型的时候,不仅要变换顶点,还需要变换法线,以便于后续处理例如计算光照。一般来说绝大部分的方向矢量都可以使用同一个4x4或3x3的变换矩阵把其从坐标空间A转换到空间B,但是在变换法线的时候,如果使用同一个变换矩阵,可能无法确保维持法线的垂直性。
切线也称为切矢量,与法线类似,切线也是模型顶点携带的一种信息,与纹理空间对齐,与法线垂直
UnityShader数学基础
由于切线是由两个顶点之间的差值计算得到,因此我们可以直接使用用于变换的顶点矩阵来变换切线,假设用3x3的变换矩阵来变换顶点,不考虑平移,可以用下面等式得到变换后的切线
UnityShader数学基础
Ta Tb表示空间A和空间B的切线方向,如果直接用上面的矩阵来变换法线,得到的新法线方向可能就不会与表面垂直,
UnityShader数学基础
可以由数学约束条件推导出矩阵,同一个顶点的切线Ta和法线Na必须满足垂直条件,Ta*Na = 0.给定变换矩阵Ma->b,Tb=Ma->bTa,现在 想要找到一个矩阵G来变换法线
UnityShader数学基础

对上面等式推导后可以得到
UnityShader数学基础
UnityShader内置变量
Unity提供了很多内置参数,使得我们不再需要自己手动计算一些值,这些内置变量可以在UnityShaderVarias.cginc文件找到定义说明。
变换矩阵
下面是unity5.2版本的
UnityShader数学基础
UNITY_MATRIX_MV 什么时候是一个正交矩阵,如果只考虑旋转平移和缩放这三种变换情况,一个模型的变换只包括旋转,那么就是一个正交矩阵。如果只包括旋转和统一缩放,那么几乎是一个正交矩阵,统一缩放可能导致每一行或者每一列的矢量长度不为1而是缩放系数k,可以通过除以这个系数,来变为正交矩阵
这种情况下 UNITY_MATRIX_MV的逆矩阵就是 UNITY_MATRIX_T_MV/k。如果只是对方向矢量变换的话,条件可以更宽,不需要考虑平移变换,我们可以截取UNITY_MATRIX_T_MV的前三行三列把方向矢量从观察空间变换到模型空间。对于方向矢量,我们可以在使用它们前进行统一的归一化处理,消除缩放影响。还有一个矩阵 UNITY_MATRIX_IT_MV矩阵,发现的变换需要使用原变换矩阵的逆转矩阵,因此这个矩阵可以把法线从模型空间转换到观察空间,对他进行转置就可以得到UNITY_MATRIX_MV的逆矩阵。
因此为了把顶点或者方向矢量从观察空间转换到模型空间可以使用类似代码:
UnityShader数学基础

摄像机和屏幕参数
UnityShader数学基础

上一篇:wpf 的style


下一篇:二进制枚举子集