《Unity3D高级编程之进阶主程》第七章,渲染管线与图形学(一) - 图形学基础2
理解矩阵表达缩放的几何意义
缩放矩阵与旋转矩阵在几何意义上有着类似的解释,我们同样用二维坐标系来表达缩放矩阵的两个 a,b 向量。举例二维标准轴的旋转矩阵:
[1.5, 0] 等 [a]
[0, 0.75] 于 [b]
我们把 a和b 表现在二维坐标系中,即如图:
缺少图片
图中a,b两个向量可以形成了一个矩形,我们就假设这个矩形图像表达了a,b向量的缩放关系。当a,b被拉长时,这个矩形图像被也同样被拉长。其原理为向量与矩阵的乘法,我们知道:
二维向量与二维矩阵相乘 = (x * m11 + y * m21, x * m12 + y * m22)
上图中标准坐标系上的缩放矩阵来说,我们带入向量得到:
(x*m11 + y*m21, x*m12 + y*m22) = (x*1.5 + y*0, x*0 + y*0.75) = (1.5*x, 0.75*y)
这个结果比较非常直观的表达了缩放矩阵对向量的缩放,矩阵除了对角线上的数字,其他位置的数字都是0。这种简单的旋转矩阵,也仅限于标准坐标系中对标准轴的缩放,我们在使用缩放矩阵时更多时候需要对任意轴上进行缩放。那么怎么计算对任意轴方向上的缩放呢?我们来看如下图所示:
缺少图片
沿 N 方向缩放 v 向量,其实和前一节中绕 N 轴旋转 v 向量有相似的原理。我们根据所知道的推导这个公式,即我们已知v,已知 N,已知缩放因子k (k是一个单纯的数字) ,要求得v’。可以通过 N 和 v 求得投影 v1,通过 v 和 v1 求得他们的垂直向量 v2,再通过缩放因子 k 求得v1和v2缩放后的向量 v1‘ 和 v2’,最后通过 v1‘ 和 v2’ 求得缩放后的结果向量 v‘,即如下图公式。
缺少图片
同样的,三维空间中,如果是标准坐标系上缩放,则缩放矩阵可以是简单的对角线矩阵,即。
[scale_x, 0, 0]
[0, scale_y, 0]
[0, 0, scale_z]
也同样的,这种标准坐标系上的缩放满足不了我们的计算需求,我们需要计算沿任意向量上的缩放矩阵公式。这里不再重复叙述推导的过程,我们只要理解缩放矩阵的由来和推导的方式就可以了,我们最终的目的通过是解释原理来理解缩放矩阵的几何意义。
缺少图片
与二维缩放矩阵的推导类似,同样的向量同样的变化同样的推导,最终我们获得了,沿任意方向缩放的矩阵。
平行投影,镜像,切变的理解和几何意义
平行投影
上面我们了解到了可以用矩阵去缩放任意点和向量,假设如果在缩放时某个轴上的缩放因子为零会变成是什么样的呢,如图所示:
缺少图片
图中模型所有的点都被’挤‘到了一个平面上,这种所有点都被拉平至垂直轴或平面上的做法就叫,平行投影,或者也可以叫正交投影。在标准坐标轴上的平行投影可以用一个简单的矩阵来表示,它只要把那个投影轴上的缩放因子置零就可以,即:
二维上x轴平行投影
[1, 0]
[0, 0]
二维上y轴平行投影
[0, 0]
[0, 1]
三维上xy平面上的平行投影
[1, 0, 0]
[0, 1, 0]
[0, 0, 0]
三维上xz平面上的平行投影
[1, 0, 0]
[0, 0, 0]
[0, 0, 1]
三维上yz平面上的平行投影
[0, 0, 0]
[0, 1, 0]
[0, 0, 1]
当然只是标准坐标轴上的平行投影还不够用,我们也需要任意直线或任意平面的投影矩阵,不过这次我们不需要再次计算任意轴上的平行投影,只需要通过前面计算过的缩放矩阵在任意方向或任意轴上的缩放矩阵就可以得到平行投影的矩阵,即如下图:
缺少图片
通过使缩放方向上的缩放因子变为零的方式,来获得平行投影矩阵。三维空间中也是一样,只是我们要注意的是,在对任意轴三维缩放矩阵中要让 N 方向为垂直平面的方向而不是平行于平面的方向。
镜像
镜像其实挺容易理解的,平行投影是将某个轴上的缩放因子变为零,镜像就是在某个轴上将缩放因子为负1,这样在缩放时就形成了‘翻转’的局面。如图所示:
缺少图片
坐标轴的四个象限,右上角的图为正常的图案,左边为对x轴翻转后的图案,下边为y轴翻转后的图案,右下角为x轴和y轴同时翻转的图案。镜像矩阵也和平行投影矩阵一样,可以用任意缩放矩阵来得出对任意方向和轴的镜像矩阵,如图所示:
缺少图片
图中为任意方向的缩放矩阵在当缩放因子为负1时的公式,不难理解镜像不过是缩放的一种特殊形式。
切变
切变非常特殊和有趣,它其实是一种坐标系‘扭曲’变换。这种坐标系的‘扭曲’变换将会被运用到对次级空间的坐标平移上。切变的形式如图:
缺少图片
图中y轴方向的向量中的x坐标被平移了。这是切变在x轴上的变化,我们也可以让切变在y轴上发生变化,如图:
缺少图片
这两种形式的切变都是通过位移x轴方向上的坐标或y轴方向上的坐标来达到的,其实我们可以理解为对次级维度坐标系的‘扭曲’。为什么要这么理解呢,因为它在仿射变化中起到了非常关键的作用,我们将在下面几节的内容中介绍。
切变的矩阵为:
二维x轴方向的‘扭曲’
[1, 0]
[s, 1]
二维y轴方向的‘扭曲’
[1, s]
[0, 1]
三维x,y轴方向的的切变
[1, 0, 0]
[0, 1, 0]
[s, t, 1]
三维x,z轴方向的切变
[1, 0, 0]
[s, 1, t]
[0, 0, 1]
三维y,z轴方向的切变
[1, s, t]
[0, 1, 0]
[0, 0, 1]
我们用向量与矩阵相乘来看看它们的结果会是如何:
向量p与3x3矩阵M的乘法 p * M = [x * M11 + y * M21 + z * M31, x * M12 + y * M22 + z * M32, x * M13 + y * M23 + z * M33]
当p向量与上面5个切变矩阵相乘时:
第一个切变矩阵为向量x轴方向的偏移,(x + y * s, y)
第二个切变矩阵为向量y轴方向的偏移,(x, x * s + y)
第三个切变矩阵为向量x、y轴方向的起偏移,(x + s * z, y + t * z, z)
第四个切变矩阵为向量x、z轴方向的起偏移,(x + s * y, y, t * y + z)
第五个切变矩阵为向量y、z轴方向的起偏移,(x, s * x + y, t * x + z)
上述经过矩阵乘法后得到的切变的结果可知,切变中坐标的变化都是在当前轴中向以其他轴坐标方向为标准进行偏移。下面一节平移中就会应用到切变的变化。
齐次坐标的平移矩阵
我们前面介绍的3x3矩阵的线性变换中并不包括平移这个操作,因为它无法在三维空间中做平移操作,零向量就能很好证明这一点,由于矩阵乘法的性质,任何矩阵乘以零向量都是零,因此零向量无法平移。矩阵乘法其实很强大,经过我们上面的介绍我们已经知道了矩阵乘法可以表达旋转,缩放,投影,镜像,切变,可惜的是无法表达平移,怎么办?齐次矩阵就恰好满足了我们的需求,齐次矩阵它在原来的维度上增加了一个维度,用多出来的那个维度来表达了平移操作。即如下图:
[1, 0, 0, 0]
[0, 1, 0, 0]
[0, 0, 1, 0]
[x, y, z, 1]
上图中用增加一个维度的方式用x,y,z分别表示了在x,y,z轴上的偏移。
为什么这种方式就能表达平移操作?我们回忆下上一节说的切变的原理和过程,在某个轴不变的情况下对其他轴进行偏移,这就是切变。与此同时,在同一维度下我们无法对当前维度空间的矩阵进行偏移,于是可以通过增加一个维度,用新增的维度切变方式来偏移次级维度,齐次矩阵正好用切变解决了次级维度的平移问题。
在上述的图中描述了4维矩阵,我们可以认为它在第四维用到了切变,平移了次级维度的空间。为了能顺利让向量与齐次矩阵相乘,我们的坐标也必须是齐次的,即在坐标末尾补上1
(x,y,z,1) x [1, 0, 0, 0] = (x + x`, y + y`, z + z`, 1)
[0, 1, 0, 0]
[0, 0, 1, 0]
[x`, y`, z`, 1]
现在我们有了平移矩阵,我们可以通过用3x3矩阵增加一个维度的方式把原来无法表达的操作只用一个矩阵全都表达出来。假设我们先旋转后平移,我们先将向量 v 乘以旋转矩阵,旋转后得到的结果再乘以平移矩阵,平移得到 v‘ ,假设旋转矩阵为R,平移矩阵为T,就可以用向量与矩阵的乘法,以及矩阵的结合律来表达:
v' = vRT
R为:
[r11, r12, r13, 0]
[r21, r22, r23, 0]
[r31, r32, r33, 0]
[0, 0, 0, 1]
T为:
[1, 0, 0, 0]
[0, 1, 0, 0]
[0, 0, 1, 0]
[x, y, z, 1]
通过结合律 v' = vRT = v(RT)
R与T相乘的结果为
[r11, r12, r13, 0]
[r21, r22, r23, 0]
[r31, r32, r33, 0]
[x, y, z, 1]
我们可以通过先乘以旋转矩阵再乘平移矩阵得到结果,也可以使用结合律先将矩阵相乘。在实际计算过程中,如果矩阵的操作比较频繁,我们可以先用结合律计算一个固定的变化矩阵,后面所有的变化计算都通过变化矩阵来计算得到结果,这样省去了重复计算矩阵的算力。我们常用的顶点着色器中的顶点坐标空间转换就是这样做的,它在整个程序开始执行前就已经计算好了一个变化的矩阵,这个变化的矩阵就叫做 MVP,即Model,View,Projection,它是由模型空间矩阵、观察者空间矩阵、投影空间矩阵相乘得到的结果,乘以此矩阵就会从模型坐标系变换为世界坐标系,再变换为观察者坐标系,最后变化为视锥体的裁剪空间供渲染绘制所用。
我们通过齐次矩阵,用增加一个维度的方式来表达当前维度无法表达的计算,用高一个维度的切变去操作次级维度的平移。
理解 Quaternion 四元数
为什么不是欧拉角
欧拉角的定义是,x,y,z 分别表达了x轴上的旋转角度,y轴上的旋转角度,z轴上的旋转角度,即(20,40,50) 表达了在x轴上旋转20度,y轴上旋转40度,z轴上旋转50度。看起来简单易懂就能定义坐标系上的旋转角度,为什么就不能使用它来表达所有的旋转角度,却还要使用四元数呢?其实是有原因的。
欧拉角存在别名,100度的旋转角度可以用-260度来表示,370度角与10度角以及-350度角是相同的旋转角度,这种表达方式在计算上特别难统一。欧拉角在插值上存在些问题,一个-260度的角度和一个50度的角度进行插值是需要先进行转换的,先将-260度转换成100度,再进行插值才可以得到正确的结果。简单的别名问题虽然讨厌,但是可以转换角度的方式解决,只是转换角度这种方式并不是一个靠谱的方式,在计算过程中会遇到相当多的麻烦。
欧拉角这种周期性和旋转之间的不独立性造成了欧拉角在线性变化的计算中比较困难,因此我们需要寻找在计算过程中更加便捷的方法,不需要转换,没有别名,统一规格。四元数恰好就满足了这些需求,它在线性变换中能统一且灵活,它有个致命缺陷就是在表现上让人比较难理解,一般人一眼看不出一个四元数所表达的旋转的方向和角度。
四元数的由来
四元数可以说是图形数学中的“异次元”,这也是为什么普通人难以理解它的原因,为什么说它是一个“异次元”呢?我们所有的向量,坐标,矩阵,旋转,缩放,平移,都是建立在使用坐标系和空间矩阵计算的这一套计算体系上的,而四元数则不是,它特立独行,它计算所用的公式,并不是建立在传统的坐标系矩阵上,而是拥有自己一套“自有”的公式体系,即复数体系。
四元数有两种记法即:
向量v 加 w分量 (v, w)
四个分量都分开 (x, y, z, w)
某些情况下v分量更方便,而在另一些情况下分开记会更清楚。但其实这个w分量和x,y,z的相关度有但不是很大,因此我们不要被迷惑了,以为他们的值越大或者越小会怎样怎样的,其实和我们肉眼看到的是有所差别的。
最早数学家们是用复数系统来表达二维中的旋转的。我们来回忆下什么是复数?
我们把形如 z=a+bi(a,b均为实数)的数称为复数,其中a称为实部,b称为虚部,i称为虚数单位,其中虚数 i * i = -1。
当z的虚部等于零时,常称z为实数;
当z的虚部不等于零时,实部等于零时,常称z为纯虚数。
数学家使用复数对(a, b)来表达二维平面中的旋转,他们把向量也定义为复数对,如果(a,b)为向量的话,那么就定义了 a + b * i,i为虚数,满足 i * i = -1,a为实轴的坐标,b为虚轴坐标,我们可以理解为x,y轴上的坐标。
对于定义一个旋转复数,为 (cos(β), sin(β)) 表达式为 cos(β) + sin(β) * i,i为虚数,β为旋转的角度。他们发现从平面上求得某个向量旋转 β 的结果,可以用向量复数对乘以旋转复数对的方式来获得,即
缺少图片
v = x + y * i
r = cos(β) + sin(β) * i
v' = vr = (x + y * i)(cos(β) + sin(β) * i)
= (x * cos(β) - y * sin(β)) + (x * sin(β) + y * cos(β)) * i
上述图中的公式推导,乘法的运算跟传统的计算有点差异,因为我们表达式中有复数运算,其中复数i满足 i * i = -1。
这是最早发明的二维向量旋转运算法则,由十六世纪被意大利米兰学者卡当提出复数后,经过达朗贝尔、棣莫弗、欧拉、高斯等人的工作,最后成形的数学体系。不过二维的上的旋转公式很快就不够用,无法对三维的旋转操作,旋转复数局限性太大。爱尔兰数学家 William Hamilton 终于在1843年找到了一种表达三维旋转的复数表达方式,即四元数就此诞生。
四元数的几何意义
四元数扩展了复数系统,它使用了三个虚部,即i,j,k,因此四元数的表达复数表达方式为
q = w + i * x + j * y + k * z
其中i,j,k为虚数,即满足
i*i = j*j = k*k = -1
i*j=k, ji=-k
j*k=i, k*j=-i
k*i=j, i*k=-j
与复数能用来旋转二维中的向量类似,四元数也能用来旋转三维中的向量。
四元数被解释为角位移的轴一角方式。什么是轴一角?就是绕某个单一轴旋转一个角位移就能表达旋转的方式就叫轴一角,这个角位移其实就是一个和向量类似的表达方式,即(x,y,z),只不过四元组用4个元素来表达罢了。说白了,四元组可以理解为绕 某个轴N 旋转的角位移,和欧拉角用x,y,z表达绕标准坐标轴旋转是同样的道理,只不过这个轴不再是标准轴,而是任意轴。
这也就说明了,四元组不再受到标准轴的限制,它可以表达绕任意轴旋转的角位移。四元组表示为对任意轴 N 的角位移即:
q = [cos(β/2), sin(β/2) * N]
= [cos(β/2), sin(β/2) * Nx, sin(β/2) * Ny, sin(β/2) * Nz]
利用上图的四元组表达方式,假设我们绕的是某个单轴旋转,也就是在x轴上旋转 A 度,或者在y轴上旋转 B 度,或者在z轴上旋转 C 度,根据上述的公式,我们可以得到三个供旋转的四元数,即:
A' = [cos(-A/2), sin(-A/2) * 1, 0, 0]
B' = [cos(-B/2), 0, sin(-B/2) * 1, 0]
C' = [cos(-C/2), 0, 0, sin(-C/2) * 1]
上图中的角度为什么是负的呢,因为我们在旋转点时的角度,和在旋转坐标系时的角度恰恰是相反的,因此操作旋转点的角度其实就是旋转坐标系反方向的角度,即负的角度。
现在我们要计算绕x轴上旋转A角度,并且绕y轴上旋转B角度,并且绕z轴上旋转C角度的四元组时,可以把 A‘,B’,C‘,这三个四元组乘起来,即:
A'B'C' = (A'B'C') 最终计算得到
[x,]
[y,]
[z,]
[w]
等于
[cos(B/2)sin(A/2)cos(C/2) + sin(B/2)cos(A/2)sin(C/2),]
[sin(B/2)cos(A/2)cos(C/2) - cos(B/2)sin(A/2)sin(C/2),]
[cos(B/2)cos(A/2)sin(C/2) - sin(B/2)sin(A/2)cos(C/2),]
[cos(B/2)cos(A/2)cos(C/2) + sin(B/2)sin(A/2)sin(C/2)]
上图中得到的最后公式就是从欧拉角转换到四元数的计算公式。
四元数的虽然在计算上很方便且通用,但在辨识度上却存在着严重的缺陷,人肉眼很难分辨某个四元数的旋转情况。但是没关系,我们只要理解它的原理,并且已经知道了四元数的几何意义,在运用四元数过程中便能更加自如。
参考文献:
《3D数学基础:图形与游戏开发》
感谢您的耐心阅读
Thanks for your reading
版权申明
本文为博主原创文章,未经允许不得转载:
《Unity3D高级编程之进阶主程》第七章,渲染管线与图形学(一) - 图形学基础2
Copyright attention
Please don't reprint without authorize.
微信公众号,文章同步推送,致力于分享一个资深程序员在北上广深拼搏中对世界的理解
QQ交流群: 777859752 (高级程序书友会)