第三章 学习Shader所需的数学基础(1)
1. 笛卡尔坐标系
在游戏中,我们使用的数学大部分都是为了计算位置、距离和角度等变量。而这些就算大部分是在笛卡尔坐标系下进行的。
1.1 二维笛卡尔坐标系
一个二维笛卡尔坐标系包含了两个部分的信息
1.一个特殊的位置,即原点,他是整个坐标系的中心
2.两条过原点的互相垂直的矢量,即x轴和y轴。这些坐标轴也被称为是该坐标系的基矢量。
虽然在图中x轴和y轴分别是水平方向的和垂直方向的。但这并不是必须的。而且虽然图中的x轴指向右、y轴指向上,但这也不是必须的。如下图所示
1.2 三维笛卡尔坐标系
在三维笛卡尔坐标系中,我们需要定义三个坐标轴和一个原点。下图展示了一个三维笛卡尔坐标系。
这三个坐标轴也被称为是该坐标系的基矢量(basis vector)。通常情况下,这三个坐标轴之间是互相垂直的,且长度为1,这样的基矢量被称为标准正交基(orthonormal basis),但这并不是必须的。例如,在一些坐标系中坐标轴之间相互垂直但长度不为1,这样的基矢量被称为正交基(orthogonal basis)。
与二维笛卡尔坐标系类似,三维笛卡尔坐标系中的坐标轴方向也不是固定的。这种不同导致了两种不同类型的坐标系:左手坐标系(left-handed coordinate space)和右手坐标系(right-handed coordinate space)
大拇指、食指、中指分别对应+x、+y和+z轴方向。
一个判断左手还是右手坐标系的方法是,判断前向(Forward)的方向。请读者坐直,向右伸直你的右手,此时右手就是x轴的正方向,而你头顶向上的方向就是y轴的正方向,那么你本身所在的坐标系就是一个左手坐标系;如果你的正前方方向是z轴的负向。那么这就是一个右手坐标系。
在左手坐标系中,旋转的正方向是顺时针的,而在右手坐标系中,旋转的正方向是逆时针的。
1.3 Unity使用的坐标系
对于模型空间和世界空间,Unity使用的是左手坐标系。但对于观察空间来说,Unity使用的是右手坐标系。观察空间,通俗来讲就是以摄像机为原点的坐标系。在这个坐标系中,摄像机的前向的z轴的负方向,这与模型空间和世界空间中定义相反。也就是说z坐标的减少意味着场景深度的增加。
2. 点和矢量
点(point)是n维空间(游戏中主要用二维和三维空间)中的一个位置,它没有大小、宽度这类概念。在笛卡尔坐标系中。我们可以用两个或3个实数表示一个点的坐标。
矢量(vector,也被称为向量)。在数学家看来矢量就是一串数字。你可能要问点的表示不也是一串数字吗?没错,但矢量存在的意义更多是为了和标量(scalar)区分开来。通常来将,矢量是n维空间中一种包含了模(magnitude)和方向(direction)的有向线段。
具体来讲:
1.矢量的模指的是这个矢量的长度。一个矢量的长度可以是任意非负数
2.矢量的方向描述了这个矢量在空间中的指向。
从矢量的定义来看,它只有方向和模两个属性,并没有位置信息。通常矢量被用于表示相对于某个点的偏移(displacement),也就是说它是一个相对量。只要矢量的模和方向保持不变,无论在哪里,都是同一个矢量。
2.1 矢量的点积
点积(dot product ,也被称为內积,inner product)。
点积的几何意义很重要,因为点积几乎用到了图形学的各个方面,其中一个几何意义就是投影(projection)。假设有一个单位向量a和另一个不限长的矢量b。现在我们希望得到b在平行于a的一条直线上的投影。那么我们可以使用点积a·b来得到b在a方向上的有符号的投影。
那么投影到底是什么意思呢?这里给出一个通俗解释,我们可以认为现在有一个光源,它发出的光是垂直于a方向的,那么b在a方向上的投影就是b在a方向上的影子,如图所示。
需要注意的是,投影的值可能是负数。投影结果的正负号与a和b的方向有关:当它们的反向相反(夹角大于90度时),结果小于0;当它们的方向互相垂直(夹角为90度时),结果等于0;当它们的方向相同(夹角小于90度),结果大于0,下图给出了这三种情况的图示。
那么如果a不是一个单位矢量会如何呢?这很容易想到,任意两个矢量的点积a·b等同于b在a方向上的投影值,再乘以a的长度。
2.2 矢量的叉积
叉积(cross product)也被称为外积(outer product)。与点积不同的是,矢量叉积的结果仍是一个矢量,而非标量。
矢量叉积的规律图:
叉积到底有什么用呢?最常见的一个应用就是计算垂直于一个平面、三角形的矢量。另外,还可以用于判断三角面片的朝向。
3. 矩阵的几何意义:变换
3.1 什么是变换
变换(transform),是指把我们的一些数据,如点、方向矢量甚至是颜色等,通过某种方式进行转换的过程。
我们先来看一个非常常见的变换类型——线性变换(Linear transform)。线性变换指的是那些可以保留矢量加和标量乘的变换,用数学公式来表示这两个条件就是:
f(x)+f(y)=f(x+y)
kf(x)=f(kx)
上面的例子看起来很抽象。缩放(scale)就是一种线性变换。例如f(x)= 2x,可以表示一个大小为2的统一缩放,即经过变换后矢量x的模被放大2倍。可以发现f(x)= 2x是满足上面2个条件的。同样,旋转(rotation)也是一种线性变换。对于线性变换来说,如果我们需要对一个三维矢量进行变换,那么仅仅使用3×3矩阵就可以表示所有的线性变换。
线性变换除了旋转和缩放外,还包括错切(shear)、镜像(mirroring,也被称为reflection)、正交投影(orthographic projection)等。
但是,仅有线性变换是不够的。我们来考虑平移交换,例如f(x)= x+(1,2,3)。这个变换就不是一个线性变换,它既不满足标量乘法,也不满足矢量加法,如果我们令x=(1,1,1)那么:
f(x)+f(x)=(4,6,8)
f(x)+f(x)= (3,4,5)
可见,两个运算得到的结果是不一样的。因此我们不能用一个3×3矩阵来表示一个平移变换。这不是我们所希望看到的,毕竟平移变换是非常常见的一种变换。
这样,就有了仿射变换(affine transform)。仿射变换就是合并线性变换和平移变换的变换类型。仿射变换可以使用一个4×4矩阵来表示,为此我们需要把矢量扩展到思维空间下,这就是其次坐标空间(homogeneous space)。
下表给出了图形学中常见的变换矩阵名称及他们的特性。(N表示不满足该特性,Y表示满足该特性)
变换名称 | 是线性变换吗 | 是仿射变换吗 | 是可逆矩阵吗 | 是正交矩阵吗 |
平移矩阵 | N | Y | Y | N |
绕坐标轴旋转的旋转矩阵 | Y | Y | Y | Y |
绕任意轴旋转的旋转矩阵 | Y | Y | Y | Y |
按坐标轴缩放的缩放矩阵 | Y | Y | Y | N |
错切矩阵 | Y | Y | Y | N |
镜像矩阵 | Y | Y | Y | Y |
正交投影矩阵 | Y | Y | N | N |
透视透视投影矩阵 | N | N | N | N |
其中左上角M用于表示旋转和缩放,t用于表示平移,0是0矩阵,右下角的元素就是标量1。
下面,我们具体来学习如何用这样一个矩阵来表示平移、旋转和缩放。
3.4 平移矩阵
我们可以用矩阵乘法来表示对一个点进行平移变换。
从结果来看我们很容易看出为什么这个矩阵有平移效果:点的x、y、z分量分别增加了一个位置偏移。在3D可视化的效果是,把x、y、z在空间中平移(tx,ty,tz)个单位。
有趣的是,如果我们对一个方向矢量进行平移变换,结果如下:
可以发现,平移变换不会对方向矢量产生任何影响。这点很容易理解,我们在学习矢量的时候就说过了,矢量没有位置属性,也就是说它可以位于空间中的任意一点,因此位置的改变(即平移)不应该对矢量产生影响。
那么,现在我们应该明白当给定一个平移操作时如何构建一个平移矩阵:基础变换矩阵中的t矢量对应了平移矢量,左上角的M矩阵既是单位矩阵为单位矩阵I。
平移矩阵的逆矩阵就是反向平移得到的矩阵,即
可以看出,平移矩阵并不是一个正交矩阵。
3.5 缩放矩阵
我们可以对一个模型空间的x轴、y轴和z轴进行缩放。同样,我们可以使用矩阵乘法表示一个缩放变换:
对方向矢量同样可以使用同样的矩阵进行缩放:
如果缩放系数kx=ky=kz,那么我们把这样的缩放称为统一缩放(uniform scale),否则称为非统一缩放(nonuniform scale)。从外观上看,统一缩放是扩大整个模型,而非统一缩放会拉伸或挤压模型。更重要的是。统一缩放不会改变角度和比例信息,而非统一缩放会改变与模型相关的角度和比例。例如在对法线进行变换时,如果存在非统一缩放,直接使用用于变换顶点的变换矩阵的话,就会得到错误的结果。
缩放矩阵的逆矩阵是使用原缩放系数的倒数来对点或方向矢量进行缩放,即
缩放矩阵一般不是正交矩阵。
上面矩阵只适用于沿坐标轴方向进行缩放。如果我们希望在任一方向上进行缩放,就需要使用一个复合变换。其中一种主要的思想就是,先将缩放坐标轴变成标准轴,然后沿坐标轴的缩放,然后使用逆变换得到原来的缩放轴方向。
3.6 旋转矩阵
旋转是三种常见的变换矩阵中最复杂的一种。我们知道,旋转操作需要指定一个旋转轴,这个旋转轴不一定是空间中的坐标轴,但本节所讲的旋转就是指绕着空间的x轴、y轴或z轴进行旋转。
如果我们要把点绕着x轴旋转θ度可以使用下面的矩阵:
绕y轴旋转也是类似的
最后,绕Z轴的旋转:
旋转矩阵的逆矩阵是旋转相反角度得到的变换矩阵。旋转矩阵是正交矩阵,而且多个旋转矩阵之间的串联同样是正交的。
3.7 复合变换
我们可以把平移、旋转和缩放组合起来,来形成一个复杂的变换过程。例如,可以对一个模型先进行大小为(2,2,2)的缩放,在绕y轴旋转30度,最后向z轴平移4个单位。复合变换可以通过矩阵的串联来实现,上面的变换可以使用下面的公式来计算:
由于上面我们使用的是列矩阵,因此阅读顺序是从右往左,即先进行缩放变换,在进行旋转变换,最后进行平移变换。需要注意的是,变幻的结果是依赖于变换顺序的,由于矩阵乘法不满足交换律,因此矩阵的乘法顺序很重要。也就是说,不同的变换顺序得到的结果可能是不一样的。
在绝大多数情况下,我们约定的变换顺序就是先缩放,再旋转,最后在平移。
除了需要注意不同类型的变换顺序外,我们有时还需要小心旋转的变换顺序。在上面我们给出了分别绕x轴、y轴和z轴的变换矩阵,一个问题是,如果我们需要同时绕着3个轴进行旋转,是先绕x轴、再绕y轴最后绕z轴旋转还是按其他的旋转顺序呢?
当我们直接给出(θx,θy,θz)这样的旋转角度时,需要定义一个旋转顺序。在unity中,这个旋转顺序是zxy,这在旋转的API文档中都有说明。这意味着,当给定(θx,θy,θz)这样的旋转角度时,得到的组合旋转变换矩阵是:
一些读者会有疑问,上面的公式书写是不是反了?不是说列矩阵要从右往左读吗?这样一来顺序不是颠倒了吗?实际上,有一个重要的东西我们没有说明白,那就是旋转时所用的坐标系。给定一个旋转顺序(例如这里的zxy)以及它们对应的旋转角度(θx,θy,θz),有两种坐标系可以选择。
(1)绕坐标系E下的z轴旋转θz,绕坐标系E下的y轴旋转θy,绕坐标系E下的x轴旋转θx,即进行一次旋转时不一起旋转当前坐标系。
(2)绕坐标系E下的z轴旋转θz,绕坐标系E下的z轴旋转θz后的新坐标系E‘下的y轴旋转θy,在坐标系E’下绕y轴旋转θy后的新坐标系E”的x轴旋转θx,即在旋转时,把坐标系一起转动。
很容易知道这两种选择的结果是不一样的。但如果把他们的旋转顺序颠倒一下,它们得到的结果就会是一样的。说明白点,在第一种情况下,按zxy顺序旋转和在第二种情况下,按yxz顺序旋转是一样的。而在unity文档中说明的旋转顺序指的是在第一种情况下旋转。