游戏研发中的线性代数


在现代游戏研发管线中,绝大多数项目都会基于一款游戏引擎进行业务开发,常常是不懂其数学原理也能够把游戏做得很好。
但了解数学原理还是有用武之地的——
1、懂原理可以更好的使用,也就是把引擎用得更好。
2、角色等实体在3D世界下的运动控制。
3、图形学之shader编写需要懂一些数学,尤其是矩阵的应用。

问:谈数学,数学是什么?数学难,难在哪?
答:数学是工具,难在造工具与理解工具的运行原理。
但是~ 使用工具还是比较简单,这篇文章就讨论怎么用的问题。
在3D世界(2D也一样)空间的坐标表达与变换是借助线性代数这个数学工具来实现的,具体就是向量和矩阵,其中矩阵多用来操作和变换向量。
可以粗略地理解为向量是空间中的点,而矩阵是将这些点变换到其他地方的工具。
本文怎么读?
多关心几何意义的部分,它是讲怎么用的。

那么先讨论向量
从代数上讲,向量是一个数字序列;从几何上讲,它是 1>有大小和方向的有向线段 2>表达了向量在每个维度上的有向位移。
按照维度来划分,有2维向量对应2维世界,3维向量对应3维世界,更高维也可以有,但已经缺乏实际意义了。
按照长度划分,有零向量,代数上表示长度为0,几何上表示没有位移;有单位向量,代数表示长度为1,几何意义是单位向量可以在保留方向信息的情况下,将长度标准化为1;其他则是长度非0非1的向量喽。
按书写方式分,列向量,横着写的向量,OpenGL使用列向量;有行向量,竖着写的向量,DirectX使用行向量。
列向量和行向量在代数与几何意义上都没有差别,只是会与矩阵相乘时影响到左乘或是右乘的合法性而已。

向量运算
向量的长度,被称为向量的“模”,记做||a||,几何意义是求两个点的距离。运算法则是通过各元素的平方相加再开方,如[1 2]的模 = (1*1+2*2)的平方根。
标量与向量乘法,运算法则是将向量的每个分量与标量相乘,如 2[1 2 3]=[2 4 6]。几何意义是获得与原向量平行,但长度不同或方向相反的新向量。
向量的标准化,就是将其长度变为1,运算符法则是将向量除以它的模,如[2 2 2]/2=[1 1 1]。几何意义是将向量的单位变为1,对于许多向量,我们只关心它的方向而不关心其大小,此时用单位向量会非常方便。
向量加法,的运算法则是将对应分量相加,如[1 2 3] + [4 5 6] = [1+4 2+5 3+6] = [5 7 9],几何上是平移向量,使得向量a的头连接向量b的尾,接着从a的尾向b的头画一个向量。几何意义是得到位移的组合。
向量减法,运算法则是将对应分量相减,如[5 7 9] - [1 2 3] = [5-1 7-2 9-3] = [4 5 6],几何上是先将向量b求反,然后平移向量,使得向量a的头连接向量b的尾,接着从a的尾向b的头画一个向量。几何意义是求两点距离。

点积的运算法则是对应分量乘积的和,如a·b = [1 2 3]·[4 5 6] = 1*4 + 2*5 + 3*6 = 32。几何意义有两个,一是求两向量的夹角;二是求向量投影。
两向量夹角:
点积等于向量大小与向量夹角的cos的积
也就是 a·b = ||a||||b||cos(θ)
所以夹角θ = arccos(a·b / ||a||||b||)
当只需要θ范围,而不需要具体数值时可以通过点积的符号来判定
a·b > 0 时,0=< θ <90
a·b = 0 时,θ = 90
a·b < 0 时,90=< θ <100
向量投影:
给定两个向量v和n,能将v分解成两个分量:v∥和v⊥。它们分别平行和垂直于n,并满足v=v∥+v⊥。一般称平行分量v∥为v在n上的投影。
v∥ = n * (||v∥||/||n||) = n * (v·n/||n||²)
v⊥ = v - v∥

叉积的运算法则是[x1 y1 z1]x[x2 y2 z2]=[y1z2-z1y2 z1x2-x1z2 x1y2-y1x2]。几何意义仅可应用于3D向量。
1>向量a和b差积的值就是垂直于向量a和b的向量c,多用于求垂直于平面的向量。
2>叉积的模 = a和b为两边的平行四边形的面积。

从代数上讲,矩阵就是多维数组;从几何上讲,矩阵的本质就是变换,我们观察矩阵的时候,是在观察变换。
矩阵的分类

方阵,行和列相同的矩阵
对角矩阵,所有非对角线元素都为0的矩阵
单位矩阵,对角线元素为1,其他元素为0的矩阵
将向量作为矩阵使用,列向量可以看作n*1矩阵,列向量可以看作1*n矩阵。
矩阵即是变换的证明

根据变换的需要建立一个矩阵

如图,我们所要做的一切就是计算基向量的变换,然后将变换后的基向量填入矩阵。

矩阵的运算
矩阵乘法,运算法则是矩阵的每个元素分别和标量相乘,得到一个与原矩阵相同维数的矩阵,如下图

矩阵乘法的合法性检测,核心是A矩阵的行要等于B矩阵的列

向量和矩阵乘法,运算法则与矩阵乘法一致,注意行向量放左边,列向量放右边才合法。几何意义是变换向量(/坐标)。

转置,运算法则是把矩阵Mij转为Mji,即沿着矩阵的对角线翻折

线性变换,可以简单地理解为 对平行线变换后仍然是平行线的变换就是线性变换,也就是说不会“弯折”。比如旋转变换,缩放变换,正交投影变换这些。
具体的变换矩阵,缩放的就很简单,例如写一个xyz轴都放大3倍的矩阵只需要如图

但是更复杂的就没必要记了,也记不住,直接调用游戏引擎提供的生成矩阵的接口就好了。
我们也只需要记住创建矩阵的核心原理就好,即“计算基向量的变换,然后将变换后的基向量填入矩阵”
将变换种类列举如下:
旋转,绕x轴,绕y轴,绕z轴,绕任意轴
缩放,沿坐标轴,沿任意轴
正交投影,向坐标轴或平面上投影,向任意直线或平面投影,应用在正交摄像机
透视投影,向坐标轴或平面上投影,向任意直线或平面投影,多应用在正交摄像机
镜像,沿任意轴镜像
切变/扭曲。
变换的组合
多个变换矩阵按照次序组合(或说“连接”)成一个矩阵。这个新矩阵代表依次执行原变换的累加效果,这样可以减少(对顶点的)变换次数,能提高非常多的效率 。
变换的分类
线性变换,可以简单地理解为 对平行线变换后仍然是平行线的变换就是线性变换,也就是说不会“弯折”。比如旋转变换,缩放变换,投影变换等。
仿射变换,指线性变换后接着平移。因此,仿射变换的集合是线性变换的超集,任何线性变换都是仿射变换,但不是所有仿射变换都是线性变换。一句话理解,仿射变换是带位移的线性变换
可逆变换,如果存在一个逆变换可以“撤销”原变换,那么该变换就是可逆的。除了投影外,其他仿射变换都是可逆的
等角变换,如果变换前后两向量夹角的大小和方向都不改变,该变换是等角的,只有平移,旋转,缩放是等角变换。
正交变换,轴保持互相垂直,并且不进行缩放变换,只有平移,旋转,镜像是正交变换
刚体变换,只改变物体的位置和方向,不包括形状,一句话就是,物体就是钢块,别想敲打它。平移和旋转是仅有的刚体变换。

重点来了!
行列式,在任意方阵中都存在一个标量,称作该矩阵的行列式。
求行列式:
2维,主对角线上的元素的积减去反对角线元素的积

3维,把矩阵M写两遍,然后主对角线上的元素的积减去反对角线元素的积


余子式
假设矩阵M有r行,c列,记做m{ij},减去i行j列后剩下的部分,称为矩阵M的余子式,记做M{ij},结果为一个矩阵。
代数余子式,给定行、列元素的代数余子式等与相应余子式的有符号行列式,结果为一个标量。
代数意义
矩阵积的行列式等于矩阵行列式的积
|AB|=|A||B|
以上可以扩展到多个矩阵的情况
|M1M2..Mn-1Mn|=|M1||M2|...|Mn-1||Mn|
矩阵转置的行列式等与原矩阵的行列式
|mT|=|M|
如果矩阵的任意行或列全为零,那么它的行列式等与0
交换矩阵的任务两行或两列,行列式变负
任意行列式的非零积加到另一行或列上不会改变行列式的值
几何意义
2维,行列式等与以基向量为两边的平行四边形的有符号面积
3维,行列式等与以变换后的基向量为三边的平行六面体的有符号体积
行列式的符号说明了矩阵是否包含镜像或投影
如果行列式为零,那么该矩阵包含投影
如果行列式为负,那么该矩阵包含镜像

矩阵的逆,方阵M的逆,记作M-1,也是一个矩阵,当M与M-1相乘时,结果是单位矩阵。即M(M-1) = M-1M = I。
计算法则:如果矩阵是正交的,则它的转置就是它的逆,这个非常有用,可以用来快速求逆。(平移,旋转和镜像是仅有的正交变换)。
只有方阵才有逆。
若矩阵的某一行或列上的元素都为零,用任何矩阵乘以该矩阵,结果都是一个零矩阵,所以它没有逆。
没有逆的矩阵称为 奇异 矩阵;有逆的矩阵称为 非奇异 矩阵。
代数意义
如果M是非奇异矩阵,则该矩阵的逆的逆等与原矩阵: (M-1)-1 = M
单位矩阵的逆是它本身:I-1 = I
矩阵转置的逆等与它的逆的转置: (MT)-1 = (M-1)T
矩阵乘积的逆等与矩阵的逆的相反顺序的乘积: (AB)-1 = B-1A-1。这可扩展到多个矩阵的情况:
(M1M2...Mn-1Mn)-1 = Mn-1Mn-1-1...M2-1M1-1
几何意义
它使得我们可以计算变换的“反向”或“相反”变换——能“撤销”原变换的变换。

4*4齐次矩阵
齐次坐标,就是将一个原本是n维的向量用一个n+1维向量来表示,是指一个用于投影几何里的坐标系统,如同用于欧氏几何里的笛卡儿坐标一般。
一句话理解,就是在+1维度坐标空间,可以用来执行原维度空间的平移运算

CocosCreator游戏引擎提供的api参考
可以看出,引擎都有提供计算接口,比如求转置,求逆,求行列式。
所以我们更应该关注以上概念的用途,而不用太过于记忆计算法方法。
——矩阵——
都在 cc.Mat4类里面,该有的都有,简单列举一下
矩阵标量乘法
static
multiplyScalar<Out extends IMat3Like>(out: Out, a: Out, b: number): Out;
矩阵加/减法
static add<Out
extends IMat3Like>(out: Out, a: Out, b: Out): Out;
static
subtract<Out extends IMat3Like>(out: Out, a: Out, b: Out): Out;
矩阵乘法
static
multiply<Out extends IMat4Like>(out: Out, a: Out, b: Out): Out;
——变换矩阵——
转置
static
transpose<Out extends IMat4Like>(out: Out, a: Out): Out;
求逆
static invert<Out
extends IMat4Like>(out: Out, a: Out): Out;
求行列式
static
determinant<Out extends IMat4Like>(a: Out): number;
矩阵乘法
static
multiply<Out extends IMat4Like>(out: Out, a: Out, b: Out): Out;
在给定矩阵变换基础上加入变换(矩阵和向量乘法)
static
transform<Out extends IMat4Like, VecLike extends IVec3Like>(out: Out, a:
Out, v: VecLike): Out;
矩阵位移
static
translate<Out extends IMat4Like, VecLike extends IVec3Like>(out: Out, a:
Out, v: VecLike): Out;
矩阵缩放
static scale<Out
extends IMat4Like, VecLike extends IVec3Like>(out: Out, a: Out, v: VecLike):
Out;
矩阵旋转
static rotate<Out
extends IMat4Like, VecLike extends IVec3Like>(out: Out, a: Out, rad: number,
axis: VecLike): Out | null;
static
rotateX<Out extends IMat4Like>(out: Out, a: Out, rad: number): Out;
static
rotateY<Out extends IMat4Like>(out: Out, a: Out, rad: number): Out;
static
rotateZ<Out extends IMat4Like>(out: Out, a: Out, rad: number): Out;
——求新矩阵——
计算位移矩阵
static
fromTranslation<Out extends IMat4Like, VecLike extends IVec3Like>(out:
Out, v: VecLike): Out;
计算缩放矩阵
static
fromScaling<Out extends IMat4Like, VecLike extends IVec3Like>(out: Out,
v: VecLike): Out;
计算旋转矩阵
static
fromRotation<Out extends IMat4Like, VecLike extends IVec3Like>(out: Out,
rad: number, axis: VecLike): Out | null;
矩阵等价判断
static
strictEquals<Out extends IMat3Like>(a: Out, b: Out): boolean;
本文配套脑图下载地址:
https://pan.baidu.com/share/init?surl=v4HHHLvW80uXdBe5KaeCow&pwd=e5g8

可爱的伙伴,都看到这了~ 右下角点个“赞”和“在看”呗
到顶部