学习目标
了解空间变换时获得新坐标的思路,尽量不深究矩阵的运算
线性代数的本质 - 03 - 矩阵与线性变换(真的没有偷懒)
作用
这是个很有诱惑力的矩阵,因为如果一个矩阵被证明是正交矩阵,那就等价于:
M T M^T MT M M M = I I I, M M M M T M^T MT = I I I, M − 1 M^{-1} M−1 = M T M^T MT
判断
主要有两种方法判断:
条件太苛刻了,妥协一下:若矩阵是只进行了旋转、统一缩放,那么它需要在等式中乘上 k k k或 1 k \frac1k k1
平移矩阵
T T_{} T = [ 1 0 0 T x 0 1 0 T y 0 0 1 T z 0 0 0 1 ] \begin{bmatrix} 1&0&0&Tx\\0&1&0&Ty\\ 0&0&1&Tz\\ 0&0&0&1\end{bmatrix} ⎣⎢⎢⎡100001000010TxTyTz1⎦⎥⎥⎤
缩放矩阵
S S_{} S = [ S x 0 0 0 0 S y 0 0 0 0 S z 0 0 0 0 1 ] \begin{bmatrix} Sx&0&0&0\\0&Sy&0&0\\ 0&0&Sz&0\\ 0&0&0&1\end{bmatrix} ⎣⎢⎢⎡Sx0000Sy0000Sz00001⎦⎥⎥⎤
旋转矩阵
绕某一轴旋转 = 在其他两轴所组成的平面进行旋转
R x ( y o z ) R_{x(yoz)} Rx(yoz) = [ 1 0 0 0 0 c o s θ − s i n θ 0 0 s i n θ c o s θ 0 0 0 0 1 ] \begin{bmatrix} 1&0&0&0\\0&cosθ&-sinθ&0\\ 0&sinθ&cosθ&0\\ 0&0&0&1\end{bmatrix} ⎣⎢⎢⎡10000cosθsinθ00−sinθcosθ00001⎦⎥⎥⎤
R y ( z o x ) R_{y(zox)} Ry(zox) = [ c o s θ 0 s i n θ 0 0 1 0 0 − s i n θ 0 c o s θ 0 0 0 0 1 ] \begin{bmatrix} cosθ&0&sinθ&0\\ 0&1&0&0\\ -sinθ&0&cosθ&0\\ 0&0&0&1\end{bmatrix} ⎣⎢⎢⎡cosθ0−sinθ00100sinθ0cosθ00001⎦⎥⎥⎤
R z ( x o y ) R_{z(xoy)} Rz(xoy) = [ c o s θ − s i n θ 0 0 s i n θ c o s θ 0 0 0 0 1 0 0 0 0 1 ] \begin{bmatrix} cosθ&-sinθ&0&0\\ sinθ&cosθ&0&0\\ 0&0&1&0\\ 0&0&0&1\end{bmatrix} ⎣⎢⎢⎡cosθsinθ00−sinθcosθ0000100001⎦⎥⎥⎤
R x R_{x} Rx和 R z R_{z} Rz直接在二维旋转矩阵的基础上插入所绕轴,并用0和1替代
R y R_{y} Ry表现出来的 s i n θ sinθ sinθ的正负和 R x R_{x} Rx和 R z R_{z} Rz相反,可以理解为:
在 z o x zox zox平面中,并没有按照 x y z xyz xyz的顺序相乘(如 x o y xoy xoy、 x o z xoz xoz、 y o z yoz yoz),所以要在 θ θ θ前加一个负号,化简后得到了 s i n θ sinθ sinθ与 R x R_{x} Rx、 R z R_{z} Rz相反的 R y R_{y} Ry
至于为啥 R y R_{y} Ry是 z o x zox zox不是 x o z xoz xoz,可以画一个右手系的坐标系,然后用向量叉乘的右手定则比划一下就了解啦
进行下一步前先看一下Unity提供的内置变换矩阵:
既然这里又有MVP变换矩阵,还有各自分开的其他变换矩阵,就说明在Shader它们肯定是比较常用的,也就是说还需要对每一个变换之前和之后的状态有一个基本的理解,而不是把整个过程在脑中打包成一个MVP变换小黑盒。如此才能在之后的学习实践中如鱼得水
书中先提出了一个获取父坐标系或子坐标系的思路,这个思路线性代数的本质 - 03 - 矩阵与线性变换不谋而合,如果哪天又转不过弯了,记得回去看看
经常会看到说:将点A从一个空间变换到空间
但是看过上面的视频后肯定会更想把这句话替换为:将点A所在的空间变换成另一个空间
从坐标轴到 M c → p M_{c → p}{} Mc→p
当一个坐标系的三个坐标轴 x x x, y y y, z z z以及原点 O O O已知时,就可以得到将它变换到父坐标系的变换矩阵 M c → p M_{c → p}{} Mc→p: [ x x y x z x o x x y y y z y o y x z y z z z o z 0 0 0 1 ] \begin{bmatrix} x_x&y_x&z_x&o_x\\x_y&y_y&z_y&o_y\\x_z&y_z&z_z&o_z\\0&0&0&1\end{bmatrix} ⎣⎢⎢⎡xxxyxz0yxyyyz0zxzyzz0oxoyoz1⎦⎥⎥⎤
一列一列看就会发现这个矩阵其实完完全全就是一个从左至右摆放着3个坐标轴向量、一个原点坐标的透明小盒子罢了
更简化的 M c → p M_{c → p}{} Mc→p
因为对于没有“位置”概念的向量(即平移向量不会做出任何改变)来说,原点 O O O的坐标(平移变换)知不知道无所谓,所以上面的变换矩阵可以简化为:
M c → p M_{c → p}{} Mc→p = [ x x y x z x x y y y z y x z y z z z ] \begin{bmatrix} x_x&y_x&z_x\\x_y&y_y&z_y\\x_z&y_z&z_z\end{bmatrix} ⎣⎡xxxyxzyxyyyzzxzyzz⎦⎤
这也是为什么Shader中会有很多截取变换矩阵的前3行前3列来对法线方向、光照方向来进行空间变换
的操作
从 M c → p M_{c → p}{} Mc→p到 M p → c M_{p → c}{} Mp→c
根据逆矩阵的定义以及 M M_{} M M − 1 M_{}^{-1} M−1 = I I_{} I, M p → c M_{p → c}{} Mp→c已经可以求出了
这里再看 M c → p M_{c → p}{} Mc→p其实符合正交矩阵判断中的由标准正交基组成
一条,因此可以直接使用 M p → c M_{p → c}{} Mp→c = M c → p T M_{c → p}^{T}{} Mc→pT轻松得到 M p → c M_{p → c}{} Mp→c = [ x x x y x z y x y y y z z x z y z z ] \begin{bmatrix} x_x&x_y&x_z\\y_x&y_y&y_z\\z_x&z_y&z_z\end{bmatrix} ⎣⎡xxyxzxxyyyzyxzyzzz⎦⎤
从 M p → c M_{p → c}{} Mp→c到坐标轴
根据第一步的原理,可由 M p → c M_{p → c}{} Mp→c获得父坐标系在子坐标系中的表示: x x x = [ x x y x z x ] \begin{bmatrix} x_x\\y_x\\z_x\end{bmatrix} ⎣⎡xxyxzx⎦⎤, y y y = [ x y y y z y ] \begin{bmatrix} x_y\\y_y\\z_y\end{bmatrix} ⎣⎡xyyyzy⎦⎤, z z z = [ x z y z z z ] \begin{bmatrix} x_z\\y_z\\z_z\end{bmatrix} ⎣⎡xzyzzz⎦⎤
任务:将经过旋转、缩放、平移等变换的模型的顶点坐标转换到世界坐标系
根据先缩放,再旋转,后平移
的原则以及矩阵乘法的结合性,可以轻松获得模型变换矩阵
M m o d e l M_{model}{} Mmodel = M t r a n s l a t e M_{translate}{} Mtranslate M r o t a t e M_{rotate}{} Mrotate M s c a l e M_{scale}{} Mscale
首先需要注意一点:Unity在观察空间中使用的是右手系,在其他空间(模型、世界、裁剪、屏幕)使用的是左手系
,这也是摄像机的观察方向是-z的原因
这个从左手系到右手系的变换在一般情况下不会对Unity中的shader变成造成影响,因为Unity会做很多渲染的底层工作,其中就包括了很多坐标空间的转换
但是,如果需要调用类似Camera.cameraToWorldMatrix
、Camera.worldToCameraMatrix
等接口自行计算某模型在观察空间中的位置,就要注意这个差异
计算观察变换矩阵有两种思路:
最后要记得乘个 S z S_z Sz = -1的矩阵缩放矩阵来给 z z z分量取个反
投影变换这个名字非常有迷惑属性,它并没有真正意义上进行投影,而是通过缩放xyz并改变w的值来到齐次裁剪空间,为投影做准备
投影分为两种:透视投影(左图)和正交投影(右图)
- 透视投影
由上图可知,决定透视投影变换矩阵的有4个变量:Near,Far,Field of View,Aspect(相机宽高比,与FOV联动间接决定了左右视角范围)
经过一系列的推导(具体可回顾GAMES101-现代计算机图形学入门-闫令琪-Lecture 04 Transformation Cont.)得到:
M p e r s p M_{persp}{} Mpersp: [ c o t F O V 2 A s p e c t 0 0 0 0 c o t F O V 2 0 0 0 0 − F a r + N e a r F a r − N e a r − 2 ⋅ F a r ⋅ N e a r F a r − N e a r 0 0 − 1 0 ] \begin{bmatrix} \frac{{cot{\frac{FOV}{2}}}}{Aspect}&0&0&0\\0&cot{\frac{FOV}{2}}&0&0\\0&0&-\frac{Far + Near}{Far - Near}&-\frac{2·Far·Near}{Far - Near}\\0&0&-1&0\end{bmatrix} ⎣⎢⎢⎢⎡Aspectcot2FOV0000cot2FOV0000−Far−NearFar+Near−100−Far−Near2⋅Far⋅Near0⎦⎥⎥⎥⎤
注意:该矩阵仅针对Unity(右手系的观察空间,矩阵左乘,变换后z分量范围在[-w,w]之间)
从变换矩阵可以看出,透视投影矩阵本质上是对 x x x, y y y, z z z分量做了不同程度的缩放,并且给 z z z分量做了平移
同时也可以看出,变换后的顶点 w w w分量变成了- z z z
接下来就可以判断变换后的顶点是否在视锥体内了:
若在,则必须同时满足:- w w w≤- x x x≤ w w w,- w w w≤- y y y≤ w w w,- w w w≤- z z z≤ w w w
- 正交投影
由上图可知,决定正交投影矩阵的有3个变量:Near,Far,Size,Aspect(与Size联动,间接决定横向视野范围)
经过一系列的推导(具体可回顾GAMES101-现代计算机图形学入门-闫令琪-Lecture 04 Transformation Cont.)得到:
M o r t h o M_{ortho}{} Mortho: [ 1 A s p e c t ⋅ S i z e 0 0 0 0 1 S i z e 0 0 0 0 − 2 F a r − N e a r − F a r + N e a r F a r − N e a r 0 0 0 1 ] \begin{bmatrix} {\frac1{Aspect·Size}}&0&0&0\\0&1\over{Size}&0&0\\0&0&-\frac{2}{Far - Near}&-\frac{Far + Near}{Far - Near}\\0&0&0&1\end{bmatrix} ⎣⎢⎢⎡Aspect⋅Size10000Size10000−Far−Near2000−Far−NearFar+Near1⎦⎥⎥⎤
从变换矩阵可以看出,正交矩阵本质上也是对 x x x, y y y, z z z分量做了不同程度的缩放,给 z z z分量做了平移
接下来就可以判断变换后的顶点是否在长方体的视锥体内了:
若在,则必须同时满足:- w w w≤- x x x≤ w w w,- w w w≤- y y y≤ w w w,- w w w≤- z z z≤ w w w
任务:从裁剪空间变换到屏幕空间(这一步其实是Unity来做的)
第一步:齐次除法(homogeneous division,也被称为透视除法)
简单来讲就是用齐次坐标的 w w w分量除以 x x x, y y y, z z z分量
这一步的处理会将坐标变换到归一化的设备坐标(Normalized Device Coordinate)
,空间会变成一个立方体
经过透视投影和齐次除法后进入NDC:
齐次除法将透视投影得到的空间再次缩放,在这里只有近平面上的点和远平面上的中心点的 x x x, y y y, z z z不会被影响
经过正交投影和齐次除法后进入NDC:
由于进行齐次除法前,正交投影产生的空间已经是一个立方体空间了,所以齐次除法并不会对顶点的 x x x, y y y, z z z坐标产生影响
Unity在这里采用的是OPenGL的方式,将 x x x, y y y, z z z分量的范围限定在[-1,1]
在DirectX中, z z z分量的范围是[0,1],如果用到需要注意
第二步:获取屏幕空间坐标
这一步就是真正的“投影”了:将三维空间中的顶点转换到二维屏幕窗口中
Unity的屏幕坐标采用左边的标准,左下角坐标为(0,0),右上角坐标为(pixelWidth,pixelHeight)
接下来就是将当前的2 × 2方形平面缩放成pixelWidth × pixelHeight屏幕大小的矩形空间
PS:关于像素Pixel
“像素”像素可以理解为组成图像的基本元素
,是一个“不可细分”方形的区域(颜色上不可细分,在反走样的采样上可以细分),它需要与“图素”区分开
一个像素的坐标就是这个像素的中点所在位置
最终的屏幕坐标
将上面两步总结一下,就可以得出经过齐次除法和屏幕映射得到的屏幕坐标:
s c r e e n x screen_x screenx = c l i p x ⋅ p i x e l W i d t h 2 ⋅ c l i p w \frac{clip_x·pixelWidth}{2·clip_w} 2⋅clipwclipx⋅pixelWidth + p i x e l W i d t h 2 \frac{pixelWidth}{2} 2pixelWidth
s c r e e n y screen_y screeny = c l i p y ⋅ p i x e l H e i g h t 2 ⋅ c l i p w \frac{clip_y·pixelHeight}{2·clip_w} 2⋅clipwclipy⋅pixelHeight + p i x e l H e i g h t 2 \frac{pixelHeight}{2} 2pixelHeight
上面的式子对xy分量做了缩放平移处理,z和w分量也会被以其他方式存储起来,以便将来使用
如z分量将会被用于深度缓冲,w分量将会被用于透视矫正插值等
一个传统的做法(非必须,还有其他方法)是将它们使用 c l i p z c l i p w \frac{clip_z}{clip_w} clipwclipz存储起来
看着图写一遍还记着的要点咯,看看还记得多少
法线与切线
法线Normal是垂直于当前点、线、面的向量,它可以在片元着色器中帮助计算光照等操作
切线Tangent是两顶点坐标相减获得的向量,它可以和BiTangent、Normal一起构成切线空间
切线空间中,Tangent轴和BiTangent轴分别于纹理空间中的u、v对齐,Normal轴垂直于它们组成的平面
与变换矩阵相乘后的法线与切线
由于切线本身就是两顶点相减得到的,可以直接将切线与变换矩阵相乘,来得到变换后的切线
但法线是由垂直约束得到的,直接将法线与变换矩阵相乘就有可能无法得到正确的变换后的法线,如下图
这个可以在线性代数的本质 - 03 - 矩阵与线性变换这系列视频中清楚的理解,在一些非统一缩放(视频中的两坐标轴不同比例拉伸)中,相互垂直的网格线全部变成了非垂直
法线变换矩阵的获取
切线的变换矩阵 M A → B M_{A→B} MA→B已知,可以得到:① 变换后的切线 T B T_B TB = M A → B M_{A→B} MA→B T A T_A TA
设法线变换矩阵为 G A → B G_{A→B} GA→B,则有: ② N B N_B NB = G A → B G_{A→B} GA→B N A N_A NA
变换后的切线和法线依然满足互相垂直:③ T B T_B TB· N B N_B NB = 0 0 0
下面的推导理解上可能有点绕,需要记得 T B T_B TB和 N B N_B NB既可以当做向量,又可以当做列矩阵
将③从向量点积
转换成矩阵乘法
,这就需要通过转置来让两个矩阵的行列数符合矩阵乘法的前提条件:将 T B T_B TB进行转置后得到行矩阵 T B T T_B^T TBT,再用它乘以列矩阵 N B N_B NB,得到:
④ T B T T_B^T TBT N B N_B NB = 0 0 0
接着将①②代入④中,得到:
⑤ ( ( ( M A → B M_{A→B} MA→B T A T_A TA) T ^T T ( G A → B G_{A→B} GA→B N A N_A NA) = 0 0 0
将括号打开,得到:
⑥ T A T T_A^T TAT M A → B T M_{A→B}^T MA→BT G A → B G_{A→B} GA→B N A N_A NA = 0 0 0
这时想起来还有一个⑦: T A T_A TA· N A N_A NA = 0 0 0,同样转换成矩阵乘法后获得⑧ T A T T_A^T TAT· N A N_A NA = 0 0 0
于是⑧和⑥相视一笑,⑥终于找到了藏在自家内部伪装成 M A → B T M_{A→B}^T MA→BT G A → B G_{A→B} GA→B的单位矩阵 I I I:
⑩ M A → B T M_{A→B}^T MA→BT G A → B G_{A→B} GA→B = I I I
要想等式成立,让 M A → B T M_{A→B}^T MA→BT和 G A → B G_{A→B} GA→B互为逆矩阵就好了
即: G A → B G_{A→B} GA→B = ( M A → B T ) − 1 M_{A→B}^T)^{-1} MA→BT)−1 = ( M A → B − 1 ) T M_{A→B}^{-1})^{T} MA→B−1)T,也就是原变换矩阵的逆转置矩阵
如果… M A → B M_{A→B} MA→B是一个正交矩阵?
那么 M A → B − 1 M_{A→B}^{-1} MA→B−1 = M A → B T M_{A→B}^T MA→BT,直接 ( M A → B T ) − 1 M_{A→B}^T)^{-1} MA→BT)−1 = M A → B M_{A→B} MA→B
也就是说,如果变换质保函旋转和统一缩放,就可以利用统一缩放系数k来得到变换矩阵 M A → B M_{A→B} MA→B的逆转置矩阵 ( M A → B T ) − 1 M_{A→B}^T)^{-1} MA→BT)−1 = 1 k \frac{1}{k} k1 M A → B M_{A→B} MA→B
否则,就要老老实实去求解逆转置矩阵了
参考资料
Unity Shader入门精要-冯乐乐
GAMES101-现代计算机图形学入门-闫令琪
切线空间(Tangent Space)完全解析