在上一篇学习了矩阵的简单知识点,但是单凭那点知识是不够的,在本篇中我们将继续深化学习矩阵的知识,基本概念上篇已经说过,大家可以翻看学习。
在后面会学习坐标空间的相关知识,方便大家对之后学习的理解。
矩阵变换
首先需要清楚的是,变换实际上并没有对物体进行真的变换,只是把物体放在了另外一个坐标系中来描述。
在变换中,包括旋转、缩放、错切、镜像、正交投影,仿射(在前面的变换中加入平移变换)等。
先交代一个概念:线性变换
线性变换指可以保留矢量加和标量乘的变换。
![][0]
[0]:http://latex.codecogs.com/png.latex?f(x)+f(y)=f(x+y)
![][1]
[1]:http://latex.codecogs.com/png.latex?kf(x)=f(kx)
例如:f(x) = 2x,表示矢量x的模放大2倍
而平移不是线性变换
例如:
平移变换函数为:
![][2]
[2]:http://latex.codecogs.com/png.latex?f(x)=x+a
根据线性变换关系f(x)+f(y)=f(x+y),我们设:
![][3]
[3]:http://latex.codecogs.com/png.latex?f(x_1)=x_1+a,f(x_2)=x_2+a
于是有:
![][4]
[4]:http://latex.codecogs.com/png.latex?f(x_1)+f(x_2)=x_1+x_2+2a
而由于是平移变换,f(x1+x2)应为:
![][5]
[5]:http://latex.codecogs.com/png.latex?f(x_1+x_2)=x_1+x_2+a
即
![][6]
[6]:http://latex.codecogs.com/png.latex?f(x_1)+f(x_2)\neq、f(x_1+x_2)
所以平移不是线性变换。
旋转
物体绕着某个点或轴旋转。我们先考虑2D的情况,坐标基向量绕原点旋转一定角度,得到新的基向量p',q'。
旋转矩阵如下:
![][7]
[7]:http://latex.codecogs.com/png.latex?R(\theta)=\begin{bmatrix}p'\q'\end{bmatrix}=\begin{bmatrix}cos\theta&sin\theta\-sin\theta&cos\theta\end{bmatrix}
在3D坐标系中:
对比2D,3D需要区分左手系与右手系,判定方法依然为左手法则和右手法则。
绕X轴旋转时:
旋转矩阵如下:
![][8]
[8]:http://latex.codecogs.com/png.latex?R_x(\theta)=\begin{bmatrix}p'\q'\r'\end{bmatrix}=\begin{bmatrix}1&0&0\0&cos\theta&-sin\theta\0&sin\theta&cos\theta\end{bmatrix}
绕Y轴旋转时:
旋转矩阵如下:
![][9]
[9]: http://latex.codecogs.com/png.latex?R_y(\theta)=\begin{bmatrix}p'\q'\r'\end{bmatrix}=\begin{bmatrix}cos\theta&0&sin\theta\0&1&0\-sin\theta&0&cos\theta\end{bmatrix}
绕Z轴旋转时:
旋转矩阵如下:
![][10]
[10]: http://latex.codecogs.com/png.latex?R_z(\theta)=\begin{bmatrix}p'\q'\r'\end{bmatrix}=\begin{bmatrix}cos\theta&-sin\theta&0\sin\theta&cos\theta&0\0&0&1\end{bmatrix}
复合旋转:
对于复合旋转,我们可以通过简单的旋转相乘得到。
我们定义一组角度
![][11]
[11]: http://latex.codecogs.com/png.latex?(\theta_x,\theta_y,\theta_z)
那么对应的选择矩阵即为:
![][12]
[12]: http://latex.codecogs.com/png.latex?M_{rotate_z}M_{rotate_x}M_{rotate_y}=\begin{bmatrix}cos\theta_z&-sin\theta_z&0\sin\theta_z&cos\theta_z&0\0&0&1\end{bmatrix}\begin{bmatrix}1&0&0\0&cos\theta_x&-sin\theta_x\0&sin\theta_x&cos\theta_x\end{bmatrix}\begin{bmatrix}cos\theta_y&0&sin\theta_y\0&1&0\-sin\theta_y&0&cos\theta_y\end{bmatrix}
缩放
缩放比较简单,对基向量进行标量线性变换即可:
假设X,Y,Z缩放比例如下:
![][13]
[13]:http://latex.codecogs.com/png.latex?(k_x,k_y,k_z)
则缩放矩阵为:
![][14]
[14]:http://latex.codecogs.com/png.latex?\begin{bmatrix}k_x&0&0\0&k_y&0\0&0&k_z\end{bmatrix}
平移
对于平移矩阵,因为不是线性变换,所以无法通过3x3的矩阵来表示,所以提出了齐次坐标的概念
- 齐次坐标
齐次坐标在3X3的基础上多出了第四维w,变成一个4x4的矩阵
(Ps:对于点,从三维坐标转换成齐次坐标是把其w分量设为1,而对于方向向量则把其w分量设为0。想象一下,平移对点是有影响的,所以w分量不能为0,而方向向量没有位置的概念,所以可以把w分量置为0。)
有了齐次坐标,我们就可以表示平移变换了。
设一次变换中的平移量为
![][15]
[15]:http://latex.codecogs.com/png.latex?(t_x,t_y,t_z)
则其对应的变换矩阵为:
![][16]
[16]:http://latex.codecogs.com/png.latex?\begin{bmatrix}1&0&0&t_x\0&1&0&t_y\0&0&1&t_z\0&0&0&1\end{bmatrix}
(Ps:从上面的矩阵可以看出,平移变换矩阵不是正交矩阵)
复合变换
复合变换其实就是上述的各种变换矩阵的乘积结果,即:
![][17]
[17]:http://latex.codecogs.com/png.latex?P_{new}=M_{translation}M_{rotate}M_{scale}P_{old}
坐标空间
在计算机图形学中,为了方便定位每个物体的位置,定义了一系列的坐标空间,在渲染的过程中,计算机根据顺序一步一步地将物体从模型自身的坐标空间转换到显示器的窗口空间中。
转换顺序如下:
模型空间->世界空间->摄像机空间->裁剪空间->屏幕空间
下面对这些坐标空间进行一一讲解。
模型空间
模型空间是一个物体最基本的坐标空间,每个模型都有自己独立的坐标空间,当物体进行选择或者移动时,该模型坐标空间会随着物体一起移动。
当我们在Unity的Scene中进行物体的旋转时,选中Local所进行的就是基于模型空间的操作,而Global则是进行基于世界空间的操作。
世界空间
世界空间构建了我们整个游戏场景的空间坐标。在这个坐标系下,我们可以知道具体物体的位置,还可以知道一个物体相对与另外一个物体的位置。
在Unity中,一个物体的Transfrom属性的position对应的就是该物体在世界空间中的坐标位置(针对没有父物体的对象)。
在坐标变换中,第一步就是将顶点坐标从模型空间转换到世界空间。
那Unity中具体是怎么做的呢?
例如我们要对一个物体进行转换,先看物体的Transform信息:
我们从上图中可以获得一些列转换信息:
- 进行(2,2,2)的缩放:放大2倍
- 进行(0,30,0)的旋转:绕Y轴旋转30°
- 进行(3,0,10)的平移:水平平移3个单位,往里平移10个单位
由此我们可以得到其变换矩阵(从右往左计算):
![][18]
[18]:http://latex.codecogs.com/png.latex?M_{Model2World}=\begin{bmatrix}1&0&0&t_x\0&1&0&t_y\0&0&1&t_z\0&0&0&1\end{bmatrix}\begin{bmatrix}cos\theta&0&sin\theta&0\0&1&0&0\-sin\theta&0&cos\theta&0\0&0&0&1\end{bmatrix}\begin{bmatrix}k_x&0&0&0\0&k_y&0&0\0&0&k_z&0\0&0&0&1\end{bmatrix}\\\~~~~~~~~~~~~~~~~~~~~~=\begin{bmatrix}1&0&0&3\0&1&0&0\0&0&1&10\0&0&0&1\end{bmatrix}\begin{bmatrix}\frac{\sqrt{3}}{2}&0&\frac{1}{2}&0\0&1&0&0\-\frac{1}{2}&0&\frac{\sqrt{3}}{2}&0\0&0&0&1\end{bmatrix}\begin{bmatrix}2&0&0&0\0&2&0&0\0&0&2&0\0&0&0&1\end{bmatrix}\\\~~~~~~~~~~~~~~~~~~~~~=\begin{bmatrix}\sqrt{3}&0&1&3\0&2&0&0\-1&0&\sqrt{3}&10\0&0&0&1\end{bmatrix}
我们有了变换矩阵,就可以对目标进行变换了。
例如我们对点(2,3,4)进行从模型空间到世界空间的变换
![][19]
[19]:http://latex.codecogs.com/png.latex?P_{world}=M_{Model2World}P_{model}\\~~~~~~~~~~~~~=\begin{bmatrix}\sqrt{3}&0&1&3\0&2&0&0\-1&0&\sqrt{3}&10\0&0&0&1\end{bmatrix}\begin{bmatrix}2\3\4\1\end{bmatrix}=\begin{bmatrix}2\sqrt{3}+7\6\4\sqrt{3}+8\1\end{bmatrix}
摄像机空间
摄像机空间又叫观察空间。在摄像机空间中,摄像机位于原点,需要注意的是,在Unity中摄像机空间选择的是右手坐标系,即摄像机的正方向指向的是-Z轴。
下面我们对上述的点进行摄像机空间的转换:
首先我们看看当前摄像机的Transform信息
从上图可以得知,该摄像机在世界空间中的变换是先按(30,0,0)进行旋转,再按(0,3,-8)进行平移。
一般的思路是,我们要求世界坐标系在摄像机坐标系中的表示,求法是先构建摄像机空间变换到世界坐标系的变换矩阵,再对该矩阵求逆。
根据上图的数据中,我们可以类似模型空间到世界空间的变化一样,得到摄像机在世界空间上的变换矩阵;而其逆矩阵,其实就是对各组数据求反所构建出来的变换矩阵。
我们可以这样想,把摄像机进行逆变换,还原到与世界坐标重合的位置,先进行(0,-3,8)平移,再进行(-30,0,0)旋转,即平移整个摄像机空间。
由此可以得到变换矩阵为:
![][20]
[20]:http://latex.codecogs.com/png.latex?M_{view}=\begin{bmatrix}1&0&0&0\0&cos\theta&-sin\theta&0\0&sin\theta&cos\theta&0\0&0&0&1\end{bmatrix}\begin{bmatrix}1&0&0&t_x\0&1&0&t_y\0&0&1&t_z\0&0&0&1\end{bmatrix}\\~~~~~~~~~~~~=\begin{bmatrix}1&0&0&0\0&\frac{\sqrt{3}}{2}&\frac{1}{2}&0\0&-\frac{1}{2}&\frac{\sqrt{3}}{2}&0\0&0&0&1\end{bmatrix}\begin{bmatrix}1&0&0&0\0&1&0&-3\0&0&1&8\0&0&0&1\end{bmatrix}\\~~~~~~~~~~~~=\begin{bmatrix}1&0&0&0\0&\frac{\sqrt{3}}{2}&\frac{1}{2}&-\frac{3\sqrt{3}}{2}+4\0&-\frac{1}{2}&\frac{\sqrt{3}}{2}&\frac{3}{2}+4\sqrt{3}\0&0&0&1\end{bmatrix}
由于摄像机坐标是右手坐标系,所以要进行Z分量的取反操作:
![][21]
[21]:http://latex.codecogs.com/png.latex?M_{view}=M_{negate_z}M_{view}\\~~~~~~~~~~~~=\begin{bmatrix}1&0&0&0\0&1&0&0\0&0&-1&0\0&0&0&1\end{bmatrix}\begin{bmatrix}1&0&0&0\0&\frac{\sqrt{3}}{2}&\frac{1}{2}&-\frac{3\sqrt{3}}{2}+4\0&-\frac{1}{2}&\frac{\sqrt{3}}{2}&\frac{3}{2}+4\sqrt{3}\0&0&0&1\end{bmatrix}\\~~~~~~~~~~~~=\begin{bmatrix}1&0&0&0\0&\frac{\sqrt{3}}{2}&\frac{1}{2}&-\frac{3\sqrt{3}}{2}+4\0&\frac{1}{2}&-\frac{\sqrt{3}}{2}&-\frac{3}{2}-4\sqrt{3}\0&0&0&1\end{bmatrix}
所以最终的顶点变换为:
![][22]
[22]:http://latex.codecogs.com/png.latex?P_{view}=M_{view}P_{world}\\~~~~~~~~~~~=\begin{bmatrix}1&0&0&0\0&\frac{\sqrt{3}}{2}&\frac{1}{2}&-\frac{3\sqrt{3}}{2}+4\0&\frac{1}{2}&-\frac{\sqrt{3}}{2}&-\frac{3}{2}-4\sqrt{3}\0&0&0&1\end{bmatrix}\begin{bmatrix}2\sqrt{3}+7\6\4\sqrt{3}+8\1\end{bmatrix}\\~~~~~~~~~~~=\begin{bmatrix}2\sqrt{3}+7\\frac{7\sqrt{3}}{2}+8\-8\sqrt{3}-\frac{9}{2}\1\end{bmatrix}
裁剪空间
用于裁剪空间变换的矩阵成为裁剪矩阵,也成为投影矩阵。裁剪空间由一个称为视锥体的空间决定,主要是为了确定哪些图元将会被渲染,完全位于视锥体内的图元会被保留,完全在视锥体外的图元将被剔除,而部分位于视锥体内的图元将会被被裁剪。
而视锥体有两种类型,决定两种投影方式:透视投影、正交投影。
透视投影:这种投影的视锥体是一个台体。靠近摄像机的平面较小,称为近裁剪平面,远离摄像机的平面较大,称为远裁剪平面,可以达到一种人眼观看的效果,即近大远小。
平行投影:平行投影的视锥体是一个长方体,一般在2D游戏或者小地图时会选择平行投影。
透视投影
在Unity中,裁剪平面由Camera和Game视图的横纵比决定
在透视投影中,我们可以看到3个参数:
Field of View(FOV)、Near、Far
FOV决定视锥体的直径,也就是视锥体是“胖”的还是“瘦”的;
Near和Far决定视锥体近平面和远平面距离摄像机的远近
由此我们可以确定近平面与远平面的高度
![][23]
[23]:http://latex.codecogs.com/png.latex?nearClipPlaneHeight=2\cdot、Near\cdot、tan\frac{FOV}{2}
![][24]
[24]:http://latex.codecogs.com/png.latex?farClipPlaneHeight=2\cdot、Far\cdot、tan\frac{FOV}{2}
而横纵比(Aspect)决定着显示的宽高比
Aspect的计算方式为:
![][25]
[25]: http://latex.codecogs.com/png.latex?Aspect=\frac{nearClipPlaneWidth}{nearClipPlaneHeight}=\frac{farClipPlaneWidth}{farClipPlaneHeight}
有了Near、Far、FOV、Aspect,我们就可以确定透视投影的变换矩阵(这里只给出求解结果,具体求解过程请参考Twinsen前辈的 这篇博客):
![][26]
[26]: http://latex.codecogs.com/png.latex?M_{frustum}=\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{2Far\cdot、Near}{Far-Near}\0&0&-1&0\end{bmatrix}
通过上述变换矩阵,我们就可以对顶点进行透视投影的摄像机空间变换
![][27]
[27]: http://latex.codecogs.com/png.latex?P_{Clip}=M_{frustum}P_{view}\\~~~~~~~~~~~ =\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{2Far\cdot、Near}{Far-Near}\0&0&-1&0\end{bmatrix}\begin{bmatrix}x\y\z\1\end{bmatrix}\\~~~~~~~~~~~=\begin{bmatrix}x\frac{cot\frac{FOV}{2}}{Aspect}\ycot\frac{FOV}{2}\-z\frac{Far+Near}{Far-Near}-\frac{2Far\cdot、Near}{Far-Near}\-z\end{bmatrix}
由上述结果可以看出,投影矩阵的本质是对x、y、z分量进行不同程度的缩放,z分量还额外进行了一定的平移,而w分量由1变成了-z,而这个w量将决定哪些点该保留,哪些点该裁剪,哪些点该剔除。
在视锥体内的点必须满足(针对OpenGL):
![][28]
[28]: http://latex.codecogs.com/png.latex?-w\leq、x\leq、w\~~~~ -w\leq、y\leq、w\~~~~-w\leq、z\leq、w
而该变换还达到了一个目的,把摄像机空间的右手系变回了左手系。
正交投影
正交投影比透视投影简单,Size为改变视锥体高度的一半,Near和Far跟透视投影一样决定近平面与远平面。
![][29]
[29]: http://latex.codecogs.com/png.latex?nearClipPlaneHeight=farClipPlaneHeight=2\cdot、Size
如果我们知道Aspect与平面高度,平面宽度我们就可以算出
![][30]
[30]: http://latex.codecogs.com/png.latex?nearClipPlaneWidth=Aspect\cdot、farClipPlaneHeight
至此,我们就可以确定正交投影的裁剪矩阵(这里只给出求解结果,跟透视投影大同小异)以及顶点变换结果:
![][31]
[31]: http://latex.codecogs.com/png.latex?M_{ortho}=\begin{bmatrix}\frac{1}{Aspect\cdot、Size}&0&0&0\0&\frac{1}{Size}&0&0\0&0&-\frac{2}{Far-Near}&-\frac{Far+Near}{Far-Near}\0&0&0&1\end{bmatrix}
![][32]
[32]: http://latex.codecogs.com/png.latex?P_{clip}=M_{ortho}P_{view}\\~~~~~~~~~~ =\begin{bmatrix}\frac{1}{Aspect\cdot、Size}&0&0&0\0&\frac{1}{Size}&0&0\0&0&-\frac{2}{Far-Near}&-\frac{Far+Near}{Far-Near}\0&0&0&1\end{bmatrix}\begin{bmatrix}x\y\z\1\end{bmatrix}\\~~~~~~~~~~=\begin{bmatrix}\frac{x}{Aspect\cdot、Size}\\frac{y}{Size}\-\frac{2z}{Far-Near}-\frac{Far+Near}{Far-Near}\1\end{bmatrix}
最终的裁剪规则跟透视投影一致
屏幕空间
屏幕空间要做的就是把裁剪空间中视锥体的内容投影到二维的显示器上,也就是得到目标的像素位置。
上面裁剪空间中得到的齐次变换结果包含一个w分量,在屏幕空间的转换中,我们需要对该结果进行透视除法(用w分量除以x、y、z分量,把w分量归一化),经过透视除法后,视锥体内的点将转换到一个正方体中,这个正方体的x、y、z分量范围都是[-1,1](OpenGL中)。
经透视除法后就可以根据变换后的x、y坐标来算出对应屏幕像素的位置,屏幕左下角的坐标为(0,0),屏幕右上角的坐标为(pixelWidth,pixelHeight)。
根据数学关系可得:
![][33]
[33]: http://latex.codecogs.com/png.latex?screen_x=\frac{clip_x\cdot、pixelWidth}{2\cdot、clip_w}+\frac{pixelWidth}{2}\\~~~~~screen_y=\frac{clip_y\cdot、pixelHeight}{2\cdot、clip_w}+\frac{pixelHeight}{2}
其中clip为裁剪结果的x、y、w分量
而z分量在这里无需进行投影运算,通常作为深度缓冲进行另外的处理。
至此,我们就可以把一个点从模型空间一步一步转换到屏幕空间中了!
另外,法线的变换会比较特殊,如果我们使用顶点变换的变换矩阵来进行法线的变换,会发现结果很有可能跟平面不再垂直。
为解决这个问题,我们需要使用切线。
而切线在变换后总与平面相切,与法线垂直,这样我们可以通过切线来求取变换后的法线。
因为我们无法用原变换矩阵来变换法线,所以我们设定法线变换矩阵为G,于是有:
![][34]
[34]: http://latex.codecogs.com/png.latex?T'\cdot、N'=(M_{t\rightarrow、t'}T)\cdot、(GN)=0
经推导:
![][35]
[35]: http://latex.codecogs.com/png.latex?(M_{t\rightarrow、t'}T)\cdot、(GN)=T T(MT_{t\rightarrow、t'}G)N=0
因为T·N=0,所以:
![][36]
[36]: http://latex.codecogs.com/png.latex?M^T_{t\rightarrow、t'}G=I
即有:
![][37]
[37]: http://latex.codecogs.com/png.latex?G=(M T_{t\rightarrow、t'}){-1}=(M {-1}_{t\rightarrow、t'}){T}
如果M是正交矩阵,那就有:
![][38]
[38]: http://latex.codecogs.com/png.latex?M T_{t\rightarrow、t'}=M{-1} {t\rightarrow、t'}\\(M^T{t\rightarrow、t'})^{-1}=M_{t\rightarrow、t'}
所以我们得出变换法线的结论:
- 当变换只包含旋转,也就是变换矩阵为正交矩阵,我们就可以用原来的矩阵进行变换。
- 当变换包含选择与统一缩放,我们只需要原来的矩阵上乘以1/k。
- 当变换不是线性变换,那我们就要乖乖地求取转置和逆矩阵了。
当然,坐标空间不止这么几个,例如:切线空间,这个在以后用到的时候再做讲述。
最后给出Unity中内置的变换矩阵:
变量名 | 描述 |
---|---|
UNITY_MATRIX_MVP | 将顶点/方向向量从模型空间变换到裁剪空间 |
UNITY_MATRIX_MV | 将顶点/方向向量从模型空间变换到摄像机(观察)空间 |
UNITY_MATRIX_V | 将顶点/方向向量从世界空间变换到摄像机(观察)空间 |
UNITY_MATRIX_P | 将顶点/方向向量从摄像机(观察)空间变换到裁剪空间 |
UNITY_MATRIX_VP | 将顶点/方向向量从世界空间变换到裁剪空间 |
UNITY_MATRIX_T_MV | UNITY_MATRIX_MV的转置矩阵 |
UNITY_MATRIX_IT_MV | UNITY_MATRIX_MV的转置逆矩阵,用于将法线从模型空间转换到摄像机(观察)空间 |
【Ps:UNITY_MATRIX_T_MV这个矩阵是个神奇的矩阵,如果我们的变换是线性变换,就可以直接把这个矩阵当成逆矩阵来使用!
对于法线变换这种可以不考虑平移的情况,我们可以直接截取该矩阵的3*3阵列进行运算!
而对于UNITY_MATRIX_IT_MV,我们可以通过该矩阵来求解法线从模型空间到观察空间的变换;
我们也可以通过求转置的方法得到UNITY_MATRIX_MV的逆矩阵,例如,我们要把一个点从观察空间转换到模型空间:
mul(transpose(UNITY_MATRIX_IT_MV), viewPos);
或
mul(viewPos, UNITY_MATRIX_IT_MV);
(在矩阵与向量的乘法中,右乘矩阵=左乘矩阵的转置)】
终于写完了。。本篇信息量有点大,需要我们去慢慢理解。