《Unity3D高级编程之进阶主程》第七章,渲染管线与图形学(一) - 图形学基础1

游戏项目除了逻辑,模块,框架,架构,算法,还需要图形学的支撑,很多人在业务层面打拼了很多年也始终无法突破的原因就是对图形学研究还不够深。

最终我们还是要面对图形的绘制和计算的,所以这块也是非常重要的部分,只是在使用了现代图形引擎之后,特别是使用了Unity3D之后,它为我们包装了很多工具和接口,用起来很方便,但就因为方便往往忘了图形学知识的重大意义。

===

其实缺哪块都不行,没有逻辑,不懂得各个模块的编写手法,不懂得框架的搭建,不懂架构,或者不懂算法,不懂图形学,都是在无法完整的体现一个优秀程序员的知识面的,特别是对于那些需要在一个项目中当起顶梁柱的主程同学,对于每个部分的都需要了如指掌,特别是图形学部分,因为它很容易被忽视,也特别需要我们静下心来学习、理解、掌握。

非常想从最最基本的讲起,但我们毕竟是一本面向中高级程序员的书,所以不在将更多篇幅用在对点,线,面,向量,坐标系等的文字说明和介绍上了。

我们特别挑出了比较重要部分与Unity3D结合的来细致讲解一下。

Vector3 的意义

Vector3 有,x,y,z三个变量,我们一眼就识别它是个代表坐标的数据。

不仅如此,其实它还可以代表 距离,速度,位移,加速度,还有方向。

两个Vector3变量a,b的相减,就能得到一个从b点到a点的向量c。

这个c是一个长度为a到b的距离并且具备b到a的方向的向量,c也同样是一个Vector3,那么为什么a,b就是坐标,c就是向量呢。

其实任何一个Vector3变量,确定它是坐标还是向量,全靠我们如何定义它。

我们可以定义a是坐标,也可以定义a是速度,至于如何定义怎么计算,全靠我们在后面的程序中怎么对待它。

速度,位移,加速度,也是同样的道理。我们再举个例子。

还是a,b定义为Vector3的坐标点,a减去b,除以1,可以认为我们向量除以了一个时间1秒得到了我们需要的有方向的速度。

再比如,a点减去b点,得到c向量,那么任意坐标加上c就会得到与a和b同样的相对位置的d点,那么c就成了位移。

如果现在是四个坐标a,b,c,d,当a减去b除以1秒得到了a到b的时的速度,那么b减去c除以1秒得到也是b到c时的速度,如果这两个速度再相减,依然是Vector3,而此时这个Vector3已经不再代表坐标和速度,而是一个由两个Vector3的速度得到的加速度了。

赋予给Vector3什么样的意义全靠我们用它们来计算什么。

我们挑选了几个Vector3比较重要的几何意义。

Vector3点乘的几何意义

向量a,与,向量b点乘的计算公式为

    a·b = (x1,y1,z1)·(x2,y2,z2) = x1*x2 + y1*y2 + z1*z2

而点乘又有另外一个计算公式即为

    a·b = ||a||*||b||*cos(β)

即用更直观的如图所示:

    缺少图片

图中a向量与b向量的夹角为β,a向量和b向量长度不变的情况下,计算出来的值越大,也就是β值越小,说明a和b的夹角越小。

相反,当计算出来的值越小,也就是β值越小,说明a和b的夹角越大。

当β大于90度时,计算出来的是一个负数,也就是b指向的方向其实与a指向的方向是相反的。

我们用这种方式可以用点乘得到一个判断依据,即当a和b点乘得到的数为正数时,两者的方向比较一致,并且在长度相同的情况下结果越大越一致,而当点乘结果为负数时,a和b两者的方向则是相反的,在长度相同的情况下,数值负得越厉害,相反的程度越一致。

除了方向判断外,我们还可以用点乘来计算出β的角度,即:

    β = arcos( (a·b) / (|a|*|b|) )

// todo Unity3D里的Vector3点乘API

Vector3叉乘的几何意义

与Vector3点乘一样,Vector3的叉乘也是类似的向量与向量之间的计算公式,不同的是,叉乘的结果不再是一个数值,而是一个同样维度的向量。即:

    a x b = (a1, a2, a3) x (b1, b2, b3) = (a2*b3 - a3*b3, a3*b1 - a1*b3, a1*b2 - a2*b1)

两个向量a,b的叉乘,得到的是与a,b向量形成的平面垂直的向量c。

那么c的长度多少呢,其实a x b的长度等于向量的大小与向量的夹角sin值的积,即

    |a x b| = |a|*|b|*sin(β)

即c的长度大小与a和b向量的夹角有关。这样看来,如果β为0时,即a和b是平行的,c就是0长度,当β为90度时,即a和b互相垂直时,a和b的叉乘长度就是a的模乘以b的模。

此外,|a x b|得到的|a||b|sin(β)这个公式,就是a和b形成的四边形的面积值,即如图所示

    缺少图片

图中四边形的体积公式其实是,|b| * h,那么h怎么得到呢,我们可以用|a| * sin(β) 得到,因此就有了这个公式的演变,即:

    四边形面积 = |b| * h

    由于 h = |a| * sin(β) 代入后 => 四边形面积 = |b| * |a| * sin(β)

    => 四边形面积 = |b| * |a| * sin(β) = |a x b|

经过几个公式的转换,我们得到向量叉乘后的模就是四边形的面积。在3D中也是同样适用,因为两个向量确定一个平面,所以两个向量可以确定给一个对等边四边形的平面,从而可以用叉乘可以算出他们的面积。

// todo Unity3D里的Vector3叉乘API

向量之间的投影

在几何计算过程中,我们经常用到‘投影’这种方式,在向量中投影也是常用的技巧。如图:

    缺少图片

图中向量b 往 向量a投影得到 c,其实c就是向量a乘以某个系数得到的。这个系数可以认为是c的模除以a的模得到的。即

    c = a * (|c|/|a|)

我们不知道|c|的值,但是c和b的夹角β又能得到计算cos的公式即:

    cos(β) = |c|/|b|

于是

    |c| = |b| * cos(β)

再套入到计算投影向量c的公式上去时,就变成了:

    c = a * (|b| * cos(β)/|a|)

    进一步 => c = a * (|b| * cos(β) * |a|/ |a| * |a|)

    由于 |b| * cos(β) * |a| 等于 a与b的点乘公式,于是就有了

    投影向量c => c = a * (a · b) / |a|^2

至此经历了几个公式的转换,得到了b向量向a向量投影,得到c向量的公式。

// todo Unity3D里的Vector3投影API,以及其他API

矩阵的意义

矩阵看起来比较玄乎,很多人看到矩阵这个词就头疼,其实耐下心来研究,会发现矩阵是很可爱的。

矩阵五花八门,这些五花八门的矩阵使用频率非常低,我们常用矩阵其实没几种。

在图形学计算中,我们常用的矩阵大小是2x2,3x3,4x4 矩阵,这种行与列的数量相同的矩阵,我们称为‘方阵’矩阵。

在众多方阵矩阵中,常用到也是一些比较特殊的矩阵。如对角矩阵,即只有行列号相同的位置有数字,其他位置都是0的方阵矩阵。

以及单位矩阵,即行列号相同的对角线上的数字都为1,其他位置都为0的方阵矩阵。

这两种特殊的矩阵因它们简单易懂在图形学计算过程中也是非常常用的矩阵。

矩阵间的计算我们可以罗列一下其实并不多。

转置矩阵,就是把矩阵沿着对角线翻转一下,由于我们常用的是‘方阵’矩阵,所以转置矩阵后方阵矩阵还是同样的大小,只不过对角线两侧的数字对调了一下。

矩阵乘法,如果是数字和矩阵相乘则,直接带入矩阵中的所有变量即可,这种标量的乘法其实就是扩大矩阵中所有的数值。

如果矩阵与矩阵相乘则,A矩阵 x B矩阵,则需要一些附加条件,即矩阵A的列数必须与矩阵B的行数相等,否则无法相乘或者说相乘无意义。

矩阵相乘后得到的矩阵,里面每个位置Cij(即C矩阵的第i行第j列)都是A矩阵的第i行向量与B矩阵的第j列向量点乘的计算结果,如下我们拿2x2方阵相乘做示意:

    A x B = [a11, a12]  x  [b11, b12]
            [a21, a22]     [b21, b22]

    = [a11*b11 + a12*b21, a11*b12 + a12*b22]
      [a21*b11 + a22*b21, a21*b12 + a22*b22]

上图对矩阵乘法公式做出了很简单易懂的描述,即Cij = Ai1 * B1j + Ai2 * B2j + Ai3 * B3j ... 即A的i行向量与B的j列向量点乘的值。

逆矩阵,逆矩阵由运算矩阵相乘而来,由于矩阵与矩阵相乘也会得到标准的单位矩阵,即对角线都是1其余都是0的方阵矩阵,于是就有了一个矩阵与某个矩阵相乘等于单位矩阵时,这‘某个’矩阵就是该矩阵的‘逆矩阵’。

然而不是每个矩阵都有逆矩阵的,一个明显的例子是若矩阵的某一行或列上的元素都是0,用任何矩阵乘以该矩阵,结果都是一个零矩阵。因此我们通常称一个有逆矩阵的矩阵为这个矩阵可逆,相反如果这个矩阵没有逆矩阵,那么就称这个矩阵不可逆。

最后我们了解一下齐次矩阵,齐次矩阵也并没有什么神秘的,只不过是从我们认知的角度上划分了矩阵分量和w分量。

齐次坐标就是将一个原本是n维的向量用一个n+1维向量来表示,齐次矩阵也是同样的道理,n维表达不了的事情用n+1维来表达,我们由浅入深来理解齐次矩阵会更加容易。

在二维空间中,(x,y)只能代表平面上的一个点,或平面上的向量,无法表达不同平面的点和向量,当如果需要表达不同平面的点就需要扩容维度,齐次向量就起到了这个作用。

齐次向量中有3个分量,即(x, y, w),x,y分量和第三个神秘的w。

为了能更加容易的理解w分量存在的意义,我们可以把w分量想象是不同平面的代表,即当w = 1的平面是标准平面,即(x, y, 1)为标准平面中的x,y坐标点,这个点在w = 1的这个平面中。

当w不是1时,就是不同平面的上的点,如果想要将坐标投影到w = 1的平面上去,就需要除以w分量,即齐次坐标(x, y, w)映射在二维平面中标准坐标系中时为(x/w, y/w)。

同样的道理,在三维空间中(x, y, z)只能代表标准坐标系中的点和向量,无法表达非标准坐标系,齐次向量补充了它的缺陷,齐次向量坐标为(x, y, z, w),当w = 1时,我们可以认为xyz坐标是在标准三维空间中,当w != 1时,则认为xyz是在其他空间当中的点,如果想要将它们坐标映射到标准三维空间中去,则只要除以w,即(x/w, y/w, z/w),就是实际在标准空间上的点。那么当 w = 0时怎么办,我们则认为它是‘无限远的点’,它描述的是一个方向而不是一个坐标位置。

向量在空间中的运用还不够广泛,矩阵可以表达空间中的缩放、旋转、切变,但无法表达偏移,因此我们需要增加一个维度的矩阵叫做齐次矩阵来表达当下维度的偏移,即齐次矩阵。

由矩阵带来的旋转,缩放,投影,镜像,和仿射。

很多人都难以理解,矩阵为什么能做到旋转和缩放,或者怎么理解矩阵的旋转和缩放,这节我们就来用最通俗易懂的方式讲讲如何理解矩阵的旋转和缩放。

我们先来了解下,向量与矩阵的乘法。

首先我们要明白,我们用的都是方阵矩阵,即2x2,3x3,4x4的矩阵。由于矩阵与矩阵相乘必须是前置的列与后置的行数要相等才有意义,向量与矩阵相乘也是一样,如果向量不是前置的那个即左乘矩阵,或者向量是以竖列表达方式右乘矩阵,对于向量与矩阵的乘法来说都是无意义的,即如下图所示:

    缺少图片

图中,第一个例子,当向量左乘矩阵,是无定义的结果,第二个例子,向量以竖列的表达方式右乘矩阵,也是无定义的结果,只有第三个例子和第四个例子,向量横向表达右乘矩阵,或竖向表达左乘矩阵,才有意义。

最终我们得出了向量与矩阵的乘法公式结果为:

    (x*m11 + y*m21 + z*m31, x*m12 + y*m22 + z*m32, x*m13 + y*m23 + z*m33)

至于向量的行向量的表达方式,和列向量的表达方式,其实都是可以行得通的,那么我们为什么要选择行向量呢,因为行向量表达更方便,无论是书写还是计算,行向量更加适合我们人类的习惯,因此我们选择了行向量来表达向量。

如何理解旋转矩阵

我们从 2x2 矩阵讲起,因为比起3x3矩阵2x2矩阵更加容易理解,而它们的原理一样。假设一个 2x2 的矩阵即

    [2, 1]
    [-1,2]

我们可以认为它是由两个行向量a,b构成,a为(2,1),b为(-1,2),即

    [2, 1]  等  [a]
    [-1,2]  于  [b]

在平面坐标系中,a和b的表达如图:

    缺少图片

图中a,b向量表达在2D坐标系中,是两个互相垂直的线段向量。

我们把这两个a,b向量可以看做是,从两个标准向量(1,0)和(0,1)旋转并且放大过来的向量,即如图下所示:

    缺少图片

图中,(1,0)和(0,1)向量,缓缓的旋转,并且放大,逐步变成了(2,1)和(-1,2),即从原来的(1,0)和(0,1)向量上,旋转了β度并放大了2.236倍。

这就是矩阵的几何解释,其实从形象简单,对于标准矩阵来说,旋转多少缩放多少,最终形成了另一个矩阵,这个结果矩阵就是我们需要的‘变换矩阵’。

对于任何一个向量来说,乘以‘变换矩阵’就能得到‘变换矩阵’所要表达的旋转和缩放值,即

    [2, 1]
    [-1,2]

如果这个矩阵表达了,向量旋转β度和2.236倍的缩放,那么任何二维向量乘以这个矩阵,就会得到在标准坐标系中以标准轴为基准旋转β度,并且以标准轴为基准放大2.236倍。

这样一个看起来无关紧要的矩阵还是不够直观,我们来看看更直观的表达方式,如果一个矩阵要表达旋转β度,那么它的a,b的向量该是如下图:

    缺少图片

a就是 [cosβ, sinβ],b就是 [-sinβ, cosβ],它们分辨表达了标准向量(1,0)和(0,1)旋转β度后的向量。

    [cosβ,  sinβ]
    [-sinβ, cosβ]

因此任何向量乘以这个这个旋转矩阵都会在标准坐标系中以标准轴为基准旋转β度。

理解了二维空间的矩阵旋转原理,我们延伸到三维空间就容易多了。

三维空间的矩阵也可以像二维空间一样理解,用三个向量来表示空间中的矩阵,即

    [a]   [Ax, Ay, Az]
    [b] = [Bx, By, Bz]
    [c]   [Cx, Cy, Cz]

一个绕x轴旋转β度的旋转矩阵为:

    [1, 0,     0]
    [0, cosβ,  sinβ]
    [0, -sinβ, cosβ]

那么一个绕y轴旋转β度的旋转矩阵为:

    [cosβ, 0, -sinβ]
    [0, 1, 0]
    [sinβ, 0, cosβ]

于是一个绕z轴旋转β度的旋转矩阵为:

    [cosβ, sinβ, 0]
    [-sinβ, cosβ, 0]
    [0, 0, 1]

用β度所形成的向量来表达坐标空间中的旋转矩阵,这样的表达可以帮助我们更加清晰的理解旋转矩阵的几何表达意义。

上述只是对某一个轴进行旋转β度,如果我们要对某个向量做各个方向轴上的旋转,比如在x轴上旋转20度,再在y轴上旋转30度,最后在z轴上旋转15度,相当于这个向量在坐标系中旋转了20°,30°,15°,这时我们该怎么办?

有了上述对各轴的旋转矩阵,我们就能很容易计算出各方向上的矩阵,就如上面提的问题,在各轴上都有旋转角度,我们就可以用先旋转x轴,再旋转y轴,最后旋转z轴的方式,来计算最后的结果,如图:

    缺少图片

图中,c向量乘以x轴旋转矩阵,再乘以y轴旋转矩阵,最后乘以z轴旋转矩阵,即:

c * Mx * My * Mz = c`(旋转后的结果)

其中Mx为以x轴为基准旋转20度的旋转矩阵,My为以y轴为基准旋转30度的旋转矩阵,Mz为以z轴为基准旋转15度的旋转矩阵。c乘以Mx得到旋转x轴后的向量,再乘以My得到旋转y轴后的向量,最后乘以Mz得到旋转z轴后的向量,最终得到结果。

绕任意轴旋转

绕标准轴即x轴,y轴,z轴旋转,得到的矩阵还远远不够我们需要的计算,很多时候我们需要计算绕任意向量或者说绕任意轴的计算公式。

让我们导出绕轴 N 旋转角度 β 的矩阵,即这个公式为 F(N, β),也就是说这个公式满足:

    v * F(N, β) = v' (v向量绕 N 轴,旋转β度后的结果v‘)

怎么解这个公式呢,其中v已知,N已知,β已知,还需要知道哪些变量,如图所示:

    缺少图片

图中,v 到 N 的投影分量可以计算得到 v1,进而可以计算得到 v 到 v1 的垂直向量 v2,最后用垂直向量 v2 与角度 β 可以计算得到 v’ 与 v 到 N 的投影分量的垂直向量 v‘’,最后由 v‘’ 和 v1 计算得到 v‘。

其中用到的计算方式有,向量投影计算公式,向量旋转计算公式,垂直向量计算公式,最后结果为:

    缺少图片

一个看起来很复杂的,只要一步步推导就能得到的结果矩阵。

其实结果并不重要,我们也记不住,但推导的过程是比较重要的,因为推导的过程运用到了很多其他方面的知识,加深了理解,虽然’用进废退‘的我们也很容易忘记这些公式,但只要心里知道有这个公式的存在,只要模糊记忆依然存在,在需要用到时搜索一下很快就能进入状态了。

参考资料:

《3D数学基础:图形与游戏开发》

感谢您的耐心阅读

Thanks for your reading

  • 版权申明

    本文为博主原创文章,未经允许不得转载:

    《Unity3D高级编程之进阶主程》第七章,渲染管线与图形学(一) - 图形学基础1

    Copyright attention

    Please don't reprint without authorize.

  • 微信公众号