本系列为作者学习UnityShader入门精要而作的笔记,内容将包括:
- 书本中句子照抄 + 个人批注
- 项目源码
- 一堆新手会犯的错误
- 潜在的太监断更,有始无终
总之适用于同样开始学习Shader的同学们进行有取舍的参考。
文章目录
(该系列笔记中大多数都会复习前文的知识,特别是前文知识非常重要的时候,这是为了巩固记忆,诸位可以直接通过目录跳转)
复习
知识点复习
上节我们介绍了矩阵的一些基本性质,矩阵可以被视为向量的集合,也可以当作对基底的线性变换。
矩阵加减
矩阵加减很简单,由于矩阵是向量的集合,因此我们可以将其视为多个向量的加减,仅需要保证两个矩阵的行列数一致即可:
A = [ a 11 a 12 a 13 a 14 a 21 a 22 a 23 a 24 a 31 a 32 a 33 a 34 ] B = [ b 11 b 12 b 13 b 14 b 21 b 22 b 23 b 24 b 31 b 32 b 33 b 34 ] A + B = [ a 11 + b 11 a 12 + b 12 a 13 + b 13 a 14 + b 14 a 21 + b 21 a 22 + b 22 a 23 + b 23 a 24 + b 24 a 31 + b 31 a 32 + b 32 a 33 + b 33 a 34 + b 34 ] A=\begin{bmatrix}a_{11} &a_{12} &a_{13}& a_{14}\\ a_{21}&a_{22}&a_{23}&a_{24}\\ a_{31}&a_{32}&a_{33}&a_{34} \end{bmatrix} \newline B=\begin{bmatrix}b_{11} &b_{12} &b_{13}& b_{14}\\ b_{21}&b_{22}&b_{23}&b_{24}\\ b_{31}&b_{32}&b_{33}&b_{34} \end{bmatrix} \newline A+B=\begin{bmatrix}a_{11}+b_{11} &a_{12}+b_{12} &a_{13}+b_{13}& a_{14}+b_{14}\\ a_{21}+b_{21}&a_{22}+b_{22}&a_{23}+b_{23}&a_{24}+b_{24}\\ a_{31}+b_{31}&a_{32}+b_{32}&a_{33}+b_{33}&a_{34}+b_{34} \end{bmatrix} A=a11a21a31a12a22a32a13a23a33a14a24a34B=b11b21b31b12b22b32b13b23b33b14b24b34A+B=a11+b11a21+b21a31+b31a12+b12a22+b22a32+b32a13+b13a23+b23a33+b33a14+b14a24+b24a34+b34
减法同理
矩阵数乘
矩阵的数乘也很简单,也可以视为对向量集合的数乘:
k M = M k = k [ m 11 m 12 m 13 m 14 m 21 m 22 m 23 m 24 m 31 m 32 m 33 m 34 ] = [ k m 11 k m 12 k m 13 k m 14 k m 21 k m 22 k m 23 k m 24 k m 31 k m 32 k m 33 k m 34 ] kM=Mk=k\begin{bmatrix}m_{11} &m_{12} &m_{13}& m_{14}\\ m_{21}&m_{22}&m_{23}&m_{24}\\ m_{31}&m_{32}&m_{33}&m_{34} \end{bmatrix}= \begin{bmatrix}km_{11} &km_{12} &km_{13}& km_{14}\\ km_{21}&km_{22}&km_{23}&km_{24}\\ km_{31}&km_{32}&km_{33}&km_{34} \end{bmatrix} kM=Mk=km11m21m31m12m22m32m13m23m33m14m24m34=km11km21km31km12km22km32km13km23km33km14km24km34
矩阵乘法
A 为 r × n 的矩阵, B 为 n × m 的矩阵,则 A B 结果为 r × m 的矩阵 A为r×n的矩阵,B为n×m的矩阵,则AB结果为r×m的矩阵 A为r×n的矩阵,B为n×m的矩阵,则AB结果为r×m的矩阵
矩阵AB的结果计算出矩阵C,其中 c 11 = a 11 ∗ b 11 + a 12 ∗ b 21 c_{11}=a_{11}*b_{11}+a_{12}*b_{21} c11=a11∗b11+a12∗b21, c 12 = a 11 ∗ b 12 + a 12 ∗ b 22 c_{12}=a_{11}*b_{12}+a_{12}*b_{22} c12=a11∗b12+a12∗b22…以此类推,就是使用被乘数的行向量与乘数的列向量相乘。
矩阵乘法有以下性质:
- 矩阵乘法不满足交换律: A B ≠ B A AB \ne BA AB=BA
- 矩阵乘法满足结合律: ( A B ) C = A ( B C ) (AB)C=A(BC) (AB)C=A(BC)
矩阵相乘在几何上的含义就是:
对一个矩阵集合中的所有向量(乘数B)应用一种线性变换(被乘数A)之后得到的新的向量集合(AB)
方阵
方阵指的是行列数相等的矩阵。在三维渲染里最常用的方阵是3×3和4×4的
行列式
行列式的值代表了应用某线性变换的矩阵其基向量构成的面积大小是单位面积的多少倍(三维下同理面积替换为体积)。
如果行列式计算的值为0的话,说明矩阵中的列向量线性相关,因为行列式变为0代表了面积/体积消失了。意味着原来的张成空间发生了降维,只有在矩阵的列向量线性相关时才会发生张成空间降维的现象。
若行列式为负则说明变换后的空间相对于原空间发生了翻转。
对角矩阵
M = [ 1 0 0 0 0 2 0 0 0 0 3 0 0 0 0 4 ] M=\begin{bmatrix}1 &0 &0& 0\\ 0&2&0&0\\ 0&0&3&0\\ 0&0&0&4 \end{bmatrix} M=1000020000300004
像这样,对角元素非零,其余元素都为0的矩阵被我们称为对角矩阵,对角矩阵的行列式等于对角元素之积。
单位矩阵
单位矩阵我们通常用 E E E来表示,一个4×4的单位矩阵如下:
E = [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] E=\begin{bmatrix}1 &0 &0& 0\\ 0&1&0&0\\ 0&0&1&0\\ 0&0&0&1 \end{bmatrix} E=1000010000100001
单位矩阵是一种特殊的对角矩阵,其对角元都为1。任何矩阵乘以单位矩阵结果都为它自身。
即: E M = M E = M EM=ME=M EM=ME=M
转置矩阵
转置矩阵就是对矩阵进行翻转,行变列,列变行:
M i j T = M j i M^T_{ij} = M_{ji} MijT=Mji
[ 6 2 1 3 7 4 8 1 ] T = [ 6 7 2 4 1 2 3 1 ] \begin{bmatrix}6 &2 &1& 3\\ 7&4&8&1\end{bmatrix}^T = \begin{bmatrix}6 &7\\ 2&4\\ 1 &2\\ 3 &1\\ \end{bmatrix} [67241831]T=62137421
性质:
- ( M T ) T = M (M^T)^T=M (MT)T=M
- ( A B ) T = B T A T (AB)^T=B^TA^T (AB)T=BTAT
逆矩阵
若有 A x = v Ax=v Ax=v,应用线性变换A后,我们可以从x变换到v,那么同样的也可以应用另一种线性变换从v变换到x,这个线性变换在几何上直观的来看就是A的逆变换。我们将这个变换记作 A − 1 A^{-1} A−1,也就是A的逆矩阵。
如果应用了线性变换 A A A之后,再应用线性变换 A − 1 A^{-1} A−1,其实相当于将向量变换过去,再变换回来,本质上和我们什么都没做一样,就相当于乘以了一个单位矩阵,因此有:
A A − 1 = A − 1 A = E AA^{-1}=A^{-1}A=E AA−1=A−1A=E
方程 A x = v Ax=v Ax=v就可以表示为 x = A − 1 v x=A^{-1}v x=A−1v
如果一个方阵的行列式=0,那么这个方阵就没有逆矩阵,我们将其称为“奇异矩阵”。
所以如果一个方阵的行列式不为0,则代表它存在逆矩阵。
性质:
- ( M − 1 ) − 1 = M (M^{-1})^{-1}=M (M−1)−1=M
- E − 1 = E E^{-1}=E E−1=E
- ( M T ) − 1 = ( M − 1 ) T (M^T)^{-1}=(M^{-1})^T (MT)−1=(M−1)T
- ( A B ) − 1 = B − 1 A − 1 (AB)^{-1}=B^{-1}A^{-1} (AB)−1=B−1A−1,同样可以推广到多矩阵: ( A B C D ) − 1 = D − 1 C − 1 B − 1 A − 1 (ABCD)^{-1}=D^{-1}C^{-1}B^{-1}A^{-1} (ABCD)−1=D−1C−1B−1A−1
正交矩阵
如果一个方阵 M M M和它的转置矩阵 M T M^{T} MT的乘积是单位矩阵,也就是 M M T = M T M = E MM^T=M^TM=E MMT=MTM=E。则我们称该方阵M为正交矩阵。
如果该矩阵M还是可逆的,那么就有 M M T = M M − 1 = E MM^T=MM^{-1}=E MMT=MM−1=E,所以 M T = M − 1 M^T=M^{-1} MT=M−1
这个性质很重要,这样我们就不必求矩阵的逆,直接使用转置矩阵就得到矩阵的逆了。
一个矩阵想要是正交矩阵,那么必须满足以下性质:
- 方阵中的每个列向量的模长为1
- 所有列向量两两正交(内积为0)
若同时满足上述两种条件,则称基向量构成了标准正交基。
练习题答案
(1) [ − 1 11 − 2 18 ] \begin{bmatrix}-1 &11\\ -2&18\\ \end{bmatrix} [−1−21118]
(2)算不了
(3) [ 11 11 − 6 ] \begin{bmatrix}11\\ 11\\ -6 \end{bmatrix} 1111−6
(1)不是,显然不是所有列向量模长为1
(2)是,单位矩阵当然是正交矩阵
(3)是,满足行列向量模长都为1,且两两正交内积为0
该题主要考虑到,行向量是1x3,列向量是3x1,因此矩阵乘法的结果是不一样的。
被乘数相当于线性变换,而乘数是向量或者向量的集合。
(1)相同,乘以单位矩阵相当于没变
(2)不相同,行向量答案: [ 3 2 18 ] \begin{bmatrix}3&2&18\\ \end{bmatrix} [3218],列向量答案 [ 15 − 16 18 ] \begin{bmatrix}15\\ -16\\ 18 \end{bmatrix} 15−1618
(3)相同,行向量答案: [ 22 − 11 27 ] \begin{bmatrix}22&-11&27\\ \end{bmatrix} [22−1127],列向量答案 [ 22 − 11 27 ] \begin{bmatrix}22\\ -11\\ 27 \end{bmatrix} 22−1127
第二题中,我们想要行列向量相同,则需要对其中一个矩阵进行转置,例如使得行向量乘以矩阵的转置:
在第三题中,行列向量计算结果相同,是因为该矩阵的转置矩阵等于它自身,这个矩阵是一个对称阵。
矩阵的几何意义
在线性代数的学习中我认为很重要的一点就是数形结合,在最初学习线性代数的时候,我只是在计算上记住公式,而不理解为什么这样做。通过对线性代数的本质系列课程的学习,我们在几何上理解线性代数才能更加理解“为什么”这个问题。
作者抛出了一些疑问:
- 点和向量可以画出来,矩阵呢?
- 我听说矩阵和线性变换,仿射变换相关,这些变换是什么意思?
- 齐次坐标是什么意思?
- 变换和矩阵的关系是什么?或者说给定一个变换我们如何得到相应的矩阵呢?
在学习完本节后,我们再来回答这些问题。
什么是变换
变换(transform)(突然一惊,transform不是UnityObject的基本属性之一吗?),指的是我们将一些数据,如点、方向向量甚至是颜色等,通过某种方式进行转换的过程。
什么是线性变换(Linear transform),线性变换指的是那些可以保留向量加和标量乘的变换,用公式来表示就是:
f ( x ) + f ( y ) = f ( x + y ) k f ( x ) = f ( k x ) f(x)+f(y)=f(x+y)\newline kf(x)=f(kx) f(x)+f(y)=f(x+y)kf(x)=f(kx)
所以变换本质上是对函数花里胡哨的另一种表示,我们可以简单的理解为一种函数,也就是:
x → f ( x ) → y x\to f(x) \to y x→f(x)→y,向量x经过变换 f ( x ) f(x) f(x)变为向量y。
在线性代数的本质一课中我们学习到,在几何上的线性空间中,可以根据各个轴的单位长度画出单位网格,这些单位网格的大小是一致的。而所谓线性变换,就是对基向量应用一系列变换操作后,原点保持不变,所有的变换后的单位网格大小依然保持相等,且网格线仍然是直线。
(单元格大小不一致,也不是线性变换)
以上三种都不是线性变换
原点不变,网格平行,单元格大小一致,这样的变换才叫做线性变换。
实质上,线性变换可以视为我们对基向量的变换,对基向量的变换会应用到整个张成空间上。
线性变换一般有旋转和缩放,除此之外还包括错切、镜像、正交投影等等。
在n维空间中的线性变换,用n阶矩阵可以完全描述。因此在三维中的线性变换可以完全由一个三阶方阵来描述。
那么,如果在线性变换之后,我们再添加一个平移变换,例如 f ( x ) = x + ( 1 , 2 , 3 ) f(x)=x+(1,2,3) f(x)=x+(1,2,3),这个变换就不是一个线性变换,因为既没有标量的乘除,也不满足向量的加减( f ( x ) + f ( x ) ≠ f ( x + x ) f(x)+f(x) \neq f(x+x) f(x)+f(x)=f(x+x))
在几何上的表现就是整个向量空间进行了平移。那么原点位置改变就不是线性变换,我们将这种变换称为仿射变换(affine transfrom)(仿射变换是线性变换的父集,它包含了平移变换和线性变换),那么一个三维空间的仿射变换就无法用3阶矩阵来进行描述(因为非线性),我们需要将向量扩展到四维空间下,这个四维空间被称为齐次坐标空间(homogeneous space)(英文homogeneous是同质化的意思,所以应该也称为同质化空间?)
上面表格表示了不同变换和它们的特性,这些特性不能光去记忆,要根据几何性质分析,例如平移矩阵是仿射变换,是非线性的。可以平移过去,自然能平移回来,所以是可逆的。而正交矩阵的前提就是必须是线性变换。(正交矩阵和正交变换互为充分必要条件,而正交变换的前提是必须是线性变换),因此平移矩阵不是正交矩阵。
又比如绕坐标轴旋转的旋转矩阵,首先旋转是线性变换,且线性变换被包含在仿射变换中。能旋转过去自然也能逆向旋转回来,所以是可逆矩阵,而旋转之后基底依然是标准正交基,因此是正交矩阵
缩放矩阵同理,而之所以不是正交矩阵,是由于缩放后基底模长改变,就不是标准正交基了
又例如正交投影矩阵,对向量进行投影变换之后会导致张成空间的降维,我们说高维到低维可以唯一确定,但是低维到高维的原空间向量是无法唯一确定的,因此不可逆,而正交基也被投影到了低维空间上,因此也不构成标准正交基,所以不是正交矩阵。
如果能在几何性质上联系变换,可以更加深入地理解变换的含义。
齐次坐标
由于3x3的矩阵不能描述仿射变换,因此我们将其拓展为了4x4的矩阵
为什么仿射变换需要拓展一个维度呢?这就涉及到线性代数的本质,所谓线性代数,我们的计算都应当是线性的,如果想要用矩阵描述非线性的仿射变换,我们能不能找到一种方法将其描述为线性的呢?答案是可以的。
假设一个二维空间上的仿射变换,其线性矩阵需要从2x2拓展到3x3,从几何意义上来看就是由二维空间拓展到了三维空间。也就是说对于原向量的变换被我们放在了三维空间中进行处理:
那么一个低维的仿射变换,放到高一维度的空间就成为了线性变换,我们就可以进行线性代数的计算了。最后我们只需降维投影到原来的低维空间,就得到了最终的变换结果。
这就是为什么仿射变换需要拓展出一个维度。本质上就是在高维空间进行了线性变换。
我们将仿射变换产生的高维矩阵称为齐次坐标(homogeneous coordinate),在三维软件中这个齐次坐标通常是四维齐次坐标。我们将第四维上的分量称为 w 分量 w分量 w分量,因此实际上任意变换我们都可以通过乘以四维齐次坐标来实现,当仅仅是线性变换的时候我们可以使得w分量为0,使其依然保持在二维平面上(仅表示缩放,旋转)。
分解基础变换矩阵
我们用一个4x4的矩阵来表示平移、旋转和缩放。我们将表示纯平移、纯旋转和纯缩放的变换矩阵称为基础变换矩阵,我们可以将一个基础变换矩阵分解成四个组成部分:
y = A x + b → [ y 1 ] = [ A b 0 1 ] [ x 1 ] y=Ax+b \to \begin{bmatrix}y\\ 1 \end{bmatrix}=\textcolor{blue}{\begin{bmatrix}A&b\\ 0&1 \end{bmatrix}}\begin{bmatrix}x\\ 1 \end{bmatrix} y=Ax+b→[y1]=[A0b1][x1]
上图中蓝色矩阵就是基础变换矩阵,其中:
[ M 3 × 3 t 3 × 1 0 1 × 3 1 ] \begin{bmatrix}M_{3×3}&t_{3×1}\\ 0_{1×3}&1 \end{bmatrix} [M3×301×3t3×11]
左上角的三阶矩阵A表示旋转和缩放,右上角b表示平移,左下角0是零矩阵,右下角元素是标量1
(实质上我认为应当以列向量的角度看这个基础变换矩阵,旋转和缩放的列向量在第四维都是0,代表了在三维空间的线性变换,最后一个列向量b的第四维为1,代表了在四维空间的线性变换)
平移矩阵
当令缩放大小等于1时,也就是不旋转或缩放,仅仅进行平移变换:
说实话我并不同意作者上述的表达,我认为是因为变换的方向向量并没有拓展到第四维(第四维为0),因此我们无法对其应用平移变换。
那么平移的逆矩阵自然就是反方向平移了:
明显,平移矩阵并不是正交矩阵
缩放矩阵
同样的,我们可以用四维矩阵描述一个缩放变换:
如果 k x = k y = k z k_x=k_y=k_z kx=ky=kz,则说明在各个分量上缩放了相同的倍率,我们将其称之为统一缩放,否则为非统一缩放 。统一缩放是对空间的整体缩放,因此不会改变角度和比例信息,而非统一缩放会。
例如在对法线进行变换的时候,如果存在非统一缩放,直接对顶点进行变换时就会得到错误的结果。
缩放矩阵的逆矩阵就是除以缩放倍率。当且仅当 k x = k y = k z = 1 k_x=k_y=k_z=1 kx=ky=kz=1时,缩放矩阵为正交矩阵。
旋转矩阵
初看旋转矩阵可能觉得很难理解,举个例子:
可能有些读者并不理解为什么绕x轴的旋转矩阵要这样写,但如果从二维推广到三维大家就会清晰许多,让我们看看二维的旋转矩阵:
看上图的二维旋转,我们以基向量 X A → X B X_A\to X_B XA→XB的旋转为例, X A X_A XA向量旋转 θ \theta θ度成为 X B X_B XB向量,假设 X A = [ 1 0 ] T , X_A=[1 \space 0]^T, XA=[1 0]T,旋转角度 θ = 30 ° , \theta =30°, θ=30°,则易证旋转后的 X B = [ 3 2 1 2 ] T X_B=[\frac{\sqrt3}{2} \space \frac{1}{2}]^T XB=[23 21]T
对于组成 X A X_A XA的x分量和y分量而言,在x分量上相当于乘以了 c o s θ cos\theta cosθ,y分量上相当于乘以了 s i n θ sin\theta sinθ。
同理可知,基向量 Y A Y_A YA旋转到 Y B Y_B YB位置后,x轴变为负数,因此在x分量上相当于乘以了 − s i n θ -sin\theta −sinθ,在y分量上相当于乘以 c o s θ cos\theta cosθ。
由此,我们就对整个二维平面实现了旋转,那么对于向量 O P A = [ x a y a ] T = A OP_A=[x_a \space y_a]^T=A OPA=[xa ya]T=A,假设其旋转后变为向量 O P B = [ x b y b ] T = B OP_B=[x_b \space y_b]^T=B OPB=[xb yb]T=B,设旋转矩阵为 M M M,不难得出下式:
M A = [ c o s θ − s i n θ s i n θ c o s θ ] [ x a y a ] = B MA=\begin{bmatrix}cos\theta & -sin\theta\\ sin\theta &cos\theta \end{bmatrix}\begin{bmatrix}x_a\\ y_a \end{bmatrix}=B MA=[cosθsinθ−sinθcosθ][xaya]=B
那么二维旋转和三维旋转有什么关系呢?实际上三维的旋转我们可以描述为二维平面的绕轴旋转,例如xy平面绕z轴旋转,或者xz平面绕y轴旋转。
现在我们再看旋转矩阵,是不是就清楚了呢?
(绕x轴旋转的旋转矩阵的含义实际上就是:x轴不变,yz平面旋转 θ \theta θ度)
其余同理,由于旋转矩阵是正交的,因此注意保持左上方的三阶矩阵的正交性质。
那么三维旋转其实本质上可以描述为多个二维旋转的组合。旋转是不是很简单呢?如果你觉得是那你要失望了,在旋转矩阵中我们还没考虑万向死锁现象。不过我会在练习题中加入计算证明欧拉角中万向死锁现象的习题。
复合变化
我们可以将平移,旋转和缩放组合起来,形成一个复杂的变换过程:
P n e w = M t r a n s l a t i o n M r o t a t i o n M s c a l e P o l d P_{new}=M_{translation}M_{rotation}M_{scale}P_{old} Pnew=MtranslationMrotationMscalePold
运用一下矩阵计算的结合律,实际上我们的计算顺序是先缩放,再旋转,在平移,记住这个约定俗成的计算顺序。
当然我们也可以将三种变换合并为一个复合矩阵:
(如果矩阵的运算顺序不同,得到的复合矩阵也不同)
除了需要注意不同类型变换的计算顺序之外,我们还应当注意,在旋转变换中的计算顺序也是很重要的。
在三维空间中的旋转可以分别描述为绕x轴、y轴、z轴的旋转。在Unity中,旋转的顺序是zxy,这意味着,当给定 ( θ x , θ y , θ z ) (\theta_x,\theta_y,\theta_z) (θx,θy,θz)这样的旋转角度时,得到的组合旋转变换矩阵是:
而上述矩阵运算下实际的旋转顺序是YXZ而非我们刚才讲的ZXY,原因是Unity中实际上有两种旋转坐标系:
- 绕原坐标系E下的z轴旋转 θ z \theta_z θz,绕原坐标系E的y轴旋转 θ y \theta_y θy,绕原坐标系E下的x轴旋转 θ x \theta_x θx,即进行一次旋转时不一起旋转当前坐标系。
- 绕坐标系E下的z轴旋转 θ z \theta_z θz,得到心坐标系 E ′ E' E′,绕 E ′ E' E′的y轴旋转 θ y \theta_y θy,得到新坐标系 E ′ ′ E'' E′′,再绕 E ′ ′ E'' E′′的x轴旋转 θ \theta θ,即在旋转式将坐标轴一起转动。
显而易见的,这两种旋转顺序计算的结果不同,但如果将它们的旋转顺序颠倒一下,其结果就是相同的!也就是Unity中虽然旋转顺序是zxy,属于第一种。但如果我们进行复合旋转矩阵计算,那么每次应用一种旋转矩阵后其坐标轴都会变换为新的坐标轴,属于第二种。
因此,矩阵运算顺序YXZ实际和Unity中的ZXY是相同的。
而如果采用第二种方式,就会发生一种特殊的现象,当中间的轴x旋转90°后,z轴和y轴会重合。此时物体就丢失了一条坐标轴,这种现象被我们称为欧拉角的万向死锁现象。
练习题
1.用复合旋转矩阵 M x M y M z M_xM_yM_z MxMyMz证明为什么当y轴旋转 π 2 \frac{\pi}{2} 2π后会产生万向死锁现象?