在制作游戏的时候我们经常会遇到坐标变换,比如模型空间变换到世界空间,这里用到的数学知识就是线性代数中的矩阵变换。矩阵变换算上大学来来回回也学了好几次了,可是过一段时间就忘记了,这次更多的是从几何意义去理解矩阵变换,希望能够更深入的理解其中的原理。我自己也是个数学小白,这篇文章都是通过在网上学习,最后转换成自己的理解,不够严谨的地方,还请大家指出错误。
在讲矩阵变换之前,先提一些基本概念。
1.点和向量
我们用一组数据表示点和向量比如(x,y,z)这组数据就是三维空间中的一个点或者向量。在计算机中我们是无法分别这组数据是点还是向量。我们可以增加一个维度比如(x,y,z,w)令w=1表示点,令w=0表示向量。向量表达的信息是方向和长度,点是位置信息,平移这个变换对点是有意义的,但是对于向量却没有意义的。所以我们要在逻辑上区分点和向量这两个概念。
2.坐标系的标准基
我们都知道一个三维空间是由x,y,z轴组成的,我们经常使用的坐标轴就是x轴(1,0,0),y轴(0,1,0) ,z轴(0,0,1)。这三个向量就是坐标系的标准基。我们一般都是先将坐标轴进行线性变换,生成一个变换矩阵,然后让所有的点乘以这个矩阵,从而实现模型的坐标系变换。
3.左右手坐标系
x,y,z的排列方式有多种,每一种组合定义的坐标系构造的空间都是不一样的。因此定义了左右手坐标系来帮助我们确定游戏中到底使用了哪个坐标系。
左手坐标系和右手坐标系唯一的区别就是左手坐标系z轴的正方向指向屏幕里边,而右手坐标系z轴的正方向指向的是屏幕外面。
我们平时学数学在纸上画的坐标系是右手坐标系。注意当我们把左手或右手按照这个姿势旋转的话它依然是左右手坐标系。换句话说左右手坐标系代表的是三个轴的相对位置,因此无论怎么旋转都不会改变这两个坐标系。在计算机中我们可以通过x(1,0,0),
y(0,1,0)进行叉乘判断z轴的方向,来判断该系统使用的是哪个坐标系。如果z是负数那么是左手坐标系,反之是右手坐标系。
4.向量的叉乘
向量n和向量b进行叉乘得到一个新的向量t,这个向量t即垂直n也垂直b。(向量的叉乘结果是一个矢量,向量的点积结果是一个标量)满足这个条件的向量其实有两个,一个是t另一个是-t。那么在计算机中我们是如何确定t的方向呢?这里就需要根据左右手坐标系确定了。首先确定当前系统用的是左手还是右手,四指由n向b弯曲,那么大拇指的方向就是叉乘的方向。
5.逆时针还是顺时针
我们在说旋转的时候都会说按照逆时针或者顺时针旋转多少度。但是在计算机中这里又会产生歧义,因为逆时针和顺时针是会随着观察者的位置改变的,比如正面看表是顺时针旋转,表不动,人跑到后面去看就变成逆时针了。这里我们给出正旋转和负旋转来准确定义旋转的方向。为什么要定义正负旋转?给定一个参数30°,请问这个30°它应该怎么旋转。给定任意一个轴,眼睛观察轴的朝向(轴往眼睛里面插),左手坐标系下顺时针为正,右手坐标系下逆时针为正。
6.空间的基向量
空间的基向量就是一组互相垂直的单位向量,比如二维空间的i = (1,0),j =(0,1),如下图:
模型空间(局部空间)中的点转换到世界空间的变换矩阵,实际上是根据基向量求得的。因为模型上的每个点和基向量是绑定的,基向量的移动轨迹和点的移动轨迹是一致的。
另外如果一个向量绕基向量顺时针旋转θ,那么相当于基向量逆时针旋转θ。
7.线性代数
大学学线性代数的时候真的很枯燥,感觉就是一堆矩阵乘来乘去。现在开始认真学习数学以后,发现其实数学和编程很相似。不同的数学知识解决不同的实际问题,不同的函数处理不同的功能,其实都是一个化繁为简的过程。比如微积分就是把复杂问题简单化,把复杂的曲线,曲面,变成直线和平面。而线性代数则是处理这些基本的线性运算。其实游戏中遇到的许多问题都可以转化为严谨的数学模型,这是一个即有趣又复杂的事情。相信只要认真学习数学,丰富自己的数学思维,将来对游戏开发一定会有巨大的帮助。
8.线性变换
我们先给出线性变换的定义,已知u和v为两个向量,k为标量,如果变换满足以下两个条件,那么变换就是线性变换:
1.T(u+v) = T(u) + T(v)
2.T(k*v) = k * F(v)
我们从最简单的缩放矩阵开始
我们把缩放变换定义为:S(i,j) = (si * i, sj * j)则有
S(u+v) = (si*(ui + vi),sj*(uj + vj))
= (si*ui +si*vi,sj*uj + sj*vj)
= (si*ui,sj*uj) + (si*vi,sj*vj)
= S(u) + S(v)
S(k*v) = (si*ui*k,sj*uj*k)
=k*S(v)
通过上面的证明可知缩放变换是一个线性变换。
我们在使用一个变换矩阵之前,先要证明这个变换是不是线性变换,只有是线性变换才能应用到矩阵乘法当中。
9.线性变换的几何意义
这里说一下推移操作,我们在游戏中常见的变换是旋转,缩放,平移。推移其实也是旋转,只是每个轴旋转的角度不同。比如上面的图,x轴没有旋转,y轴进行了旋转。
9. 仿射变换
如果我们要在缩放变换的基础上加上平移变换那么矩阵该怎么给出?
i' = si * i + a
j' = sj * j + b
显然矩阵乘法无法表达加法,我们需要进行升维(齐次坐标)来实现加法变换。
仿射变换就是一个线性变换加上一个平移变换组成的新的变换,当然仿射变换也是线性变换。
如果w=1则进行平移变换,如果w=0则不进行平移变换。看上去有没有点眼熟,我们之前说过w=1为点,w=0为向量,这样我们可以通过w的值来区别点和向量的变换,如果是向量的话则没有平移变换。
10.矩阵的行列式
首先矩阵的行列式是一个数,它的几何意义是矩阵变换的伸缩因子。听上去很抽象,这里直接推荐马老师的文章写的非常好。
https://www.matongxue.com/madocs/247.html
矩阵的行列式最主要的作用就是判断矩阵是否可逆,如果行列式不等于零,那么矩阵可逆,否则不可逆。
行列式等于零,相当于降维,也就是三维变成了二维,二维空间因为缺失信息是无法变回三维空间的。
11.余子式和代数余子式以及伴随矩阵以及逆矩阵
我们在写shader的时候经常会遇到逆矩阵,比如计算光照的时候,我们需要先把光源从世界坐标变换到顶点的模型坐标(然后再变换到顶点的切空间),这里就用到了世界坐标的逆矩阵,下面给出逆矩阵的公式。
上面是求逆矩阵的公式,其中A*为伴随矩阵,|A|为矩阵的行列式。可以看到矩阵的行列式不能为零,否则分母就为零了。
伴随矩阵是通过代数余子式求得的,代数余子式是带符号的余子式。余子式是矩阵中的某一个元素去掉该元素的行和列得到的子矩阵。具体内容可以复习一下线性代数。
这篇文章主要了罗列了顶点变换需要用到的数学知识,如果自己写数学库的话需要掌握这些知识,当然现在的数学库已经很成熟了,但是不理解原理使用的时候也容易出错,所以还是花时间总结了一下。