本系列为UnityShader入门精要读书笔记总结,
原作者博客链接:http://blog.csdn.net/candycat1992/article/
书籍链接:http://product.dangdang.com/23972910.html
为了让读者更加理解数学计算的几何意义, 我们先来假定一个场景。 现在, 假设我们正在开
发一款卡通风格的农场游戏。 在这个游戏里, 玩家可以在农场里养很多可爱的奶牛。 与普通农场
游戏不同的是, 我们的主角不是玩家, 而是一头牛—妞妞
在游戏制作中, 我们使用数学绝大部分都是为了计算位置、 距离和角度等变量。 而这些计算
大部分都是在笛卡儿坐标系 下进行的。
1)二维笛卡尔坐标系
一个二维的笛卡尔坐标系包含了两个部分的信息:
一个特殊的位置,即原点,它是整个坐标系的中心。
两条过原点的互相垂直的矢量,即X轴和Y轴。这些坐标轴也被称为是该坐标的矢量。
OpenGL 和 DirectX 使用了不同的二维笛卡尔坐标系。如下图所示:
2)三维笛卡尔坐标系
在三维笛卡尔坐标系中,我们需要定义三个坐标轴和一个原点。如下图所示:
这三个坐标轴也被称为是该坐标轴的基矢量。通常情况下,这个三个坐标轴之间是相互垂直的,且长度为1,这样的的基矢量被称为标准正交基,但这并不是必须的。例如,在一些坐标系中坐标轴之间相互垂直但长度不为1,这样的基矢量被称为正交基。如非特殊说明,后续默认使用的都是标准正交基。
正交的意思是相互垂直。
三维坐标系可以大致分为左手坐标系和右手坐标系。
三维坐标系并不都是等价的。因为就出现了不同的三维坐标系:左手坐标系和右手坐标系。如果两个坐标系具有相同的旋向性,那么我们就可以通过旋转的方法来让它们的坐标轴指向重合。但是如果它们具有不同的旋向性,那么就无法达到重合的目的。下图分别为左手坐标系和右手坐标系。拇指X,食指Y,中指Z,都保持中指指向前方的姿态。
Unity使用的是左手坐标系。
但对于观察空间来说,Unity使用的是右手坐标系。观察空间,通俗来讲就是以摄像机为原点的坐标系。在这个坐标系中,摄像机的前向是z轴的负方向,这与在模型空间和世界空间中的定义相反。也就是说,z轴坐标的减少意味着场景深度的增加。
点是n维空间中的一个位置,它没有大小和宽度这类概念。在笛卡尔坐标系中,我们可以使用2个或3个实数来表示一个点的坐标。
矢量的定义则复杂一些。矢量存在的意义更多是为了和标量区分开来。通常的讲,矢量是指n维空间中一种包含了模和方向的有向线段,我们通常讲到的速度就是一种典型的矢量。
具体来讲。
矢量的模指的是这个矢量的长度,一个矢量的长度可以是任意的非负数。
矢量的方向则描述了这个矢量在空间中的指向。
1)矢量和标量的乘法/除法
只能是矢量被标量除,而不能是标量被矢量除。
2)
矢量的加减法
需要注意的是,一个矢量不能和一个标量相加或相减。矢量的加减法遵守三角法则。
3)矢量的模
4)单位矢量
单位矢量指的是那些模为1 的矢量。也被称为归一化矢量。
5)矢量的点积
矢量的乘法有两种最常用的种类:点积和叉积
公式一
点积满足交换律。
性质一:点积可结合标量乘法
性质二:点积可结合矢量加法和减法,和性质一类似。
性质三:一个矢量和本身进行点积的结果,是该矢量的模的平方。
公式二
6)矢量的叉积
叉积不满足交换律,也不满足结合律。
叉积的方向
叉积的结果是得到一个同时垂直于两个向量的新的向量,但是这个方向其实是有两个?如何确定需要涉及到我们之前的坐标系。
公式记忆办法
每次都是从a开始向b画斜线,被减数是右斜,减数是左斜 从ay az ax依次进行。
1)基础概念
矩阵是由m*n个标量组成的长方形数组。
矩阵由行列之分。如下是一个3*4矩阵。
M(i,j) 表明了这个元素在矩阵M的第i行,第j列。
矢量可以看成n*1的列矩阵或1*n的行矩阵
2)基础运算
矩阵与标量的乘法。
矩阵与矩阵的乘法,它们的结果会是一个新的矩阵,并且这个矩阵的维度和两个原矩阵的维度都有关系。
一个 r*n 的矩阵A和一个n*c 的矩阵B相乘,它们的结果AB将会是一个 r*c 大小的矩阵。
两个矩阵可以进行运算的前提是A的列数等于B的行数。
最终的运算结果是A的行数XB的列数的一个新矩阵。
对于每个元素我们C_ij,我们找到A中的i行和B中的j列,然后将他们对应的乘积加起来。
如果不满足规定,就不能相乘。
性质一:矩阵乘法并不满足交换律。
性质二:矩阵乘法满足结合律
3)特殊矩阵。
方块矩阵(方阵),是指行和列数目相等的矩阵。
如果一个方阵除了对角元素之外的所有元素都为0,那么这个矩阵就叫做对角矩阵。如下图所示:
单位矩阵:对角矩阵中对角元素值都为1。任何矩阵和它相乘结果还是原来的矩阵。
转置矩阵:实际上是对原矩阵的一种运算,即转置运算。转置矩阵的计算非常简单,只需要将原矩阵翻转一下即可。原矩阵的第i行变成了第i列。而第j列变成了第j行。
如下所示。
性质一:矩阵转置的转置等于原矩阵。
性质二:矩阵串联的转置,等于反向串接各个矩阵的转置。
逆矩阵
并不是所有的矩阵都有逆矩阵,第一个前提改矩阵是一个方阵。
逆矩阵的性质:该矩阵和逆矩阵相乘,得到一个单位矩阵。
变换指的是我们把一些数据,如点,方向矢量甚至是颜色等,通过某种方式进行转换的过程。
最常见的是线性变换。线性变换指的是那些可以保留矢量加和标量乘的变换。如下:
类似缩放和旋转都是一个线性变换。还有错切,镜像,正交投影等,都是线性变换。
平移变换满足标量相乘,但是不满足矢量加法。
仿射变换是合并线性变换和平移变换的变换类型。仿射变换可以使用一个4*4的矩阵来表示,这就是齐次坐标空间。
我们知道,由于3*3矩阵不能表示平移操作,我们就把其扩展到了4*4的矩阵。为此,我们还需要把原来的三维矢量转换成四维矢量,也就是齐次坐标。
对于一个点,转换为齐次坐标就是把其w分量设为1.
对于方向矢量来说,需要把其w分量设为0.
这样的设置就会导致,当用一个4*4矩阵对一点进行变换时,平移、旋转、缩放都会施加于该点。但是如果是用于变换一个方向矢量,平移的效果就会被忽略。
我们已经知道,可以使用一个4*4的矩阵来表示平移、旋转和缩放。我们把表示纯平移、纯旋转、纯缩放的变换矩阵叫做基础变换矩阵、这些矩阵具有一些共同点,我们可以把一个基础变换矩阵分解成4个组成部分:
其中左上角的矩阵M(3*3)用于表示旋转和缩放,右上角的t(3*1)表示平移,左下角的 0(1*3) 是零矩阵,右下角的元素是标量1。
我们可以使用矩阵乘法来表示对一个点进行平移变换:
从结果来看我们可以很容易看出为什么这个矩阵有平移效果,点的x,y,z分量分别增加了一个位置平移。
有趣的是,如果我们队一个方向矢量进行平移变换,结果如下:
可以发现,平移变换不会对方向矢量产生任何影响。
平移矩阵的逆矩阵就是反向平移得到的矩阵,即
可以看出,平移矩阵并不是一个正交矩阵。
我们可以对一个模型沿空间的x轴、y轴和z轴进行缩放。同样,我们可以使用矩阵乘法来表示一个缩放变换。
如果三个缩放系数相等,我们把这样的缩放称为统一缩放,否则为非统一缩放。
缩放矩阵的逆矩阵是使用原缩放系数的倒数来对点或方向矢量进行缩放。即
缩放矩阵一般不是正交矩阵。
旋转是三种常见的变换矩阵中最复杂的一种。
如果我们需要把点绕着x轴旋转θ度,可以使用下面的矩阵:
旋转矩阵的逆矩阵是旋转相反角度得到的交换矩阵。旋转矩阵是正交矩阵,而且多个旋转矩阵之间的串联同样是正交的。
我们可以把平移、旋转和缩放组合起来,来形成一个复杂的变换过程。例如,可以对一个模型先进行大小为(2,2,2)的缩放,再绕y轴旋转30度,最后向z轴平移4个单位,复合变换可以通过矩阵的串联来实现。上面的变换可以使用下面的公式进行计算。
在绝大多数情况下,我们约定的变换的顺序就是先缩放,再旋转,最后平移。
渲染游戏的过程可以理解成是把一个个顶点经过层层处理最终转化到屏幕上的过程, 接下来我们就将学习这个转换的过程是如何实现的。
我们需要在不同的情况下使用不同的坐标空间, 因为一些概念只有在特定的坐标
空间下才有意义, 才更容易理解。
我们知道,要想定义一个坐标空间,必须指明其原点位置和3个坐标轴的方向。而这些数值实际上是相对于另一个坐标空间的。也就是说,坐标空间会形成一个层次结构——每个坐标空间都是另一个坐标空间的子空间,反过来说,每个空间都有一个父坐标空间。对坐标空间的变换实际上就是在父空间和子空间之间对点和矢量进行变换。
具体的我感觉只需了解即可,不需要深究,在此不做过多说明了。
除了观察空间是右手坐标系,其他都是左手坐标系。
法线,也被称为法矢量。在游戏中,模型的一个顶点往往会携带额外的信息,而顶点法线就是其中的一种信息。当我们变换一个模型的时候,不仅需要变换它的顶点,还需要变换顶点法线,以便在后续处理中计算光照。
一般来说,点和绝大部分方向矢量都可以使用同一个4*4或3*3的变换矩阵把其从坐标空间A变换到坐标空间B中。但在变换发现的时候,如果使用同一个变换矩阵,可能就无法确保维持法线的垂直线。
切线,也被称为切矢量与法线类似,切线往往也是模型顶点携带的一种信息,它通常与纹理空间对其,而且与法线方向垂直。如下图:
具体推倒略过,我们可以先记住结论。
法线变换使用原变换矩阵的逆转置矩阵来变换法线就可以得到正确的结果。
使用Unity 写 Shader 的一个好处在于,它提供了很多内置的参数,这使得我们不再需要自己手动计算一些值。本节将给出Unity内置的用于空间变换和摄像机以及屏幕参数的内置变量。这些内置变量可以在UnityShaderVariables.chnic文件中找到定义和说明。
下表给出了Unity5.2 版本提供的所有内置变换矩阵,下面所有的矩阵都是float4×4类型的。
其中有一个矩阵比较特殊,即UNITY_MATRIX_T_MV矩阵。
下表给出了Unity5.2版本提供的摄像机和屏幕参数信息
对于线性变换来说(例如旋转和缩放),仅适用3×3的矩阵就足够表示所有的变换了。但如果存在平移变换,我们就需要使用4×4的矩阵,因此,在对顶点的变换中个,我们通常使用4×4的变换矩阵。当然,在变换前我们需要把点坐标转换成齐次坐标的表示会,即把顶点的w分量设为1。而在对方向矢量的变换中,我们通常使用3×3的矩阵就足够了,这是因为平移变换对方向矢量是没有影响的。
在进行矩阵相乘时,参数的位置将决定是按列矩阵还是行矩阵进行乘法。在CG中,矩阵乘法是通过mul函数实现的。
因此,参数的位置会直接影响结果值。通常在变换顶点时,我们都是使用右乘的方式来按列矩阵进行乘法。这是因为,Unity提供的内置矩阵(如UNITY_MATRIX_MVP等)都是按列存储的。但有时,我们也会使用左乘的方式,这是因为可以省去对矩阵的转置的操作。
需要注意的一点是,CG对矩阵类型中元素的初始化和访问顺序。在CG中,对float4×4等类型的变量是按行优先进行填充的。
(2) 在左手坐标系中, 有一点的坐标是( 0, 0, 1 ), 如果把该点绕 y 轴正方向旋转+90。 旋转
后的坐标是什么? 如果是在右手坐标系中, 同样有一点坐标为( 0,0,1 ), 把它绕 y 轴正方向旋转
+90°, 旋转后的坐标是什么?
正方向的判定使用握拳法。
(1,0,0)
(1,0,0)
(3 ) 在 Unity 中, 新建的场景中主摄像机的位置位于世界空间中的(0, 1,-10) 位置。 在不改
变摄像机的任何设置( 如保持 Rotation 为(0,0,0), Scale 为( 1, 1, 1 )) 的情况下, 在世界空间中
的(0, 1, 0)位置新建一个球体,在摄像机的观察空间下, 该球体的 z 值是多少? 在摄像机的模型空间下, 该球体的 z 值又是多少?
先算出之前的空间位置偏移,球-摄像机=(0,0,10)
观察空间 原点摄像机(0,0,0) 球 原点+偏移 因为Z轴是反的,所以Z取反 球=(0,0,-10)
模型空间 原点摄像机(0,0,0) 球 原点+偏移 Z轴不变,无需进行变化,所以Z取反 球=(0,0,10)
这个只是位置的变化,如果有了旋转缩放,还是得重新求出对应的转换矩阵才行。