【Unity Shader入门】Shader数学基础:矩阵变换

【Unity Shader入门】Shader基础概念:渲染流水线
【Unity Shader入门】Shader编程基础:ShaderLab语法
【Unity Shader入门】Shader数学基础:向量(矢量)
【Unity Shader入门】Shader数学基础:矩阵
【Unity Shader入门】Shader数学基础:矩阵变换
【Unity Shader入门】Shader编程初级:Shader结构

矩阵变换

变换:是把一些数据(点、矢量、颜色)通过某种方式进行转换的过程。
线性变换:是指可以保留矢量加和标量乘的变换。其数学公式是:
f(x)+f(y)=f(x+y) f(x) + f(y) = f(x+y)
kf(x)=f(kx) kf(x) = f(kx)

线性变换都包括:旋转、缩放、错切(Shear)、镜像(Mirroring)、正交投影(orthographic projection)。
仿射变换(affine transform)可以合并线性变换平移变换,将仿射变换扩展到4维空间下用4x4的矩阵来表示,它就齐次座标空间

齐次座标空间

由于3×3矩阵不能表示一个平移操作,就将其扩展到了4×4的矩阵。为此,还需要把原来的三维矢量转换成四维座标,也就是齐次座标(齐次座标的维度可以超过四维,但本文泛指四维齐次座标)。
如何把一个三维矢量转换成四维矢量呢:
1、对于一个点,从三维座标转换成齐次座标就是把w分量设置为1
2、对于方向矢量,需要把w分量设置为0。这样当用4×4矩阵进行变换时,平移的效果会被忽略(因为方向矢量没有位置)。

基础变换矩阵

可以使用一个4x4的矩阵来表示平移、旋转和缩放。把表示纯平移、纯旋转和纯缩放的变换矩阵叫做基础变换矩阵。这些矩阵具有一些共同点,可以把一个基础变换矩阵分解成4 个组成部分:
[M3×3t3×101×31] \begin{bmatrix} M_{3×3} & t_{3×1} \\ 0_{1×3} & 1 \\ \end{bmatrix}

平移矩阵

我们可以使用矩阵乘法来表示对一个点进行平移变换,如下所示,从结果很容易看出这个矩阵为什么有平移效果:点的x, y, z分量分别增加了一个位置偏移,即把点(x, y, z)在空间中平移了(tx, ty, tz)个单位。
[100tx010ty001tz0001][xyz1]=[x+txy+tyz+tz1] \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} x \\ y \\ z \\ 1 \\ \end{bmatrix} = \begin{bmatrix} x + t_x \\ y + t_y \\ z + t_z \\ 1 \\ \end{bmatrix}

对一个方向矢量进行平移变换,不会对其产生任何影响:
[100tx010ty001tz0001][xyz0]=[xyz0] \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} x \\ y \\ z \\ 0 \\ \end{bmatrix} = \begin{bmatrix} x \\ y \\ z \\ 0 \\ \end{bmatrix}

平移矩阵的逆矩阵就是反向平移得到的矩阵。可以看出平移矩阵并不是一个正交矩阵。
[100tx010ty001tz0001] \begin{bmatrix} 1 & 0 & 0 & -t_x \\ 0 & 1 & 0 & -t_y \\ 0 & 0 & 1 & -t_z \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}

缩放矩阵

我们可以使用矩阵乘法来表示一个缩放变换:
[kx0000ky0000kz00001][xyz1]=[kxxkyykzz1] \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} x \\ y \\ z \\ 1 \\ \end{bmatrix} = \begin{bmatrix} k_xx \\ k_yy \\ k_zz \\ 1 \\ \end{bmatrix}
对方向矢量同样可以进行缩放:
[kx0000ky0000kz00001][xyz0]=[kxxkyykzz0] \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} x \\ y \\ z \\ 0 \\ \end{bmatrix} = \begin{bmatrix} k_xx \\ k_yy \\ k_zz \\ 0 \\ \end{bmatrix}
如果缩放系数Kx = Ky = Kz,我们把这样的缩放称为统一缩放,否则称为非统一缩放。从外观上看,统一缩放是扩大整个模型,而非统一缩放会拉伸或挤压模型。更重要的是,统一缩放不会改变角度和比例信息,而非统一缩放会改变与模型相关的角度和比例。例如在对法线进行变换时,如果存在非统一缩放,直接使用用于变换顶点的变换矩阵的话,就会得到错误的结果。

缩放矩阵的逆矩阵是使用原缩放矩阵系数的倒数来进行缩放,缩放矩阵一般也不是正交矩阵。

[1kx00001ky00001kz00001] \begin{bmatrix} \frac 1 { k_x} & 0 & 0 & 0 \\ 0 & \frac 1 { k_y} & 0 & 0 \\ 0 & 0 & \frac 1 { k_z} & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}
上面的矩阵只适用于沿座标轴方向进行缩放。如果我们希望在任意方向上进行缩放,就需要使用一个复合变换。其中一种方法的主要思想就是,先将缩放轴变换成标准座标轴,然后进行沿座标轴的缩放,再使用逆变换得到原来的缩放轴朝向。

旋转矩阵

旋转是三种常见的变换矩阵中最复杂的一种。我们知道,旋转操作需要指定一个旋转轴,这个旋转轴不一定是空间中的座标轴,但这里旋转就是指绕着空间中的x 轴, y 轴或z 轴进行旋转。
如果我们需要把点绕着x 轴旋转θ度,可以使用下面的矩阵:
Rx(θ)=[10000cosθsinθ00sinθcosθ00001] R_{x}(\theta)= \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos \theta & -\sin \theta & 0 \\ 0 & \sin \theta & \cos \theta & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}
同理,绕y 轴的旋转如下所示:
Ry(θ)=[cosθ0sinθ00100sinθ0cosθ00001] R_{y}(\theta)= \begin{bmatrix} \cos \theta & 0 & \sin \theta & 0 \\ 0 & 1 & 0 & 0 \\ -\sin \theta & 0 & \cos \theta & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}
绕z 轴的旋转如下所示:
Rz(θ)=[cosθsinθ00sinθcosθ0000100001] R_{z}(\theta)= \begin{bmatrix} \cos \theta & \sin \theta & 0 & 0 \\ \sin \theta & \cos \theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}

旋转矩阵的逆矩阵是旋转相反角度得到的变换矩阵。旋转矩阵是正交矩阵,而且多个旋转矩阵之间的串联同样是正交的。

复合变换

我们可以把平移、旋转和缩放组合起来,来形成一个复杂的变换过程。
Pnew=MtranslationMrotationMscalθPold 复合变换公式:P_{new} = M_{translation}M_{rotation}M_{scal\theta}P_{old}
由于上面我们使用的是列矩阵,因此阅读顺序是从右到左,即先进行缩放变换,再进行旋转变换,最后进行平移变换。需要注意的是,变换的结果是依赖于变换顺序的,由于矩阵乘法不满足交换律,因此矩阵的乘法顺序很重要。想象一下,如果让读者向前一步然后左转,记住此时的位置。然后回到原位,这次先左转再向前走一步,得到的位置和上一次是不一样的。究其本质,是因为矩阵的乘法不满足交换律,因此不同的乘法顺序得到的结果是不一样的
在绝大多数情况下,我们约定变换的顺序就是先缩放,再旋转,最后平移。

对比不同变换顺序产生的变换矩阵的表达式。如果只考虑对y 轴的旋转的话,按先缩放、再旋转、最后平移这样的顺序组合3 种变换得到的变换矩阵是:
MrotationMscalθMtranslation=[cosθ0sinθ00100sinθ0cosθ00001][kx0000ky0000kz00001][100tx010ty001tz0001] M_{rotation}M_{scal\theta}M_{translation}= \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 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}
=[kxcosθ0kxsinθtxkxcosθ+tzkzsinθ0ky0txkxkxsinθ0kxcosθtxkxsinθ+tzkzcosθ0001] =\begin{bmatrix} k_x\cos \theta & 0 & k_x\sin \theta & t_xk_x\cos \theta + t_zk_z\sin \theta \\ 0 & k_y & 0 & t_xk_x \\ -k_x\sin \theta & 0 & k_x\cos \theta & -t_xk_x\sin \theta + t_zk_z\cos \theta \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}
而如果使用了其他变换顺序,例如先平移,再缩放,最后旋转,那么得到的变换矩阵是:
MtranslationMrotationMscalθ=[100tx010ty001tz0001][cosθ0sinθ00100sinθ0cosθ00001][kx0000ky0000kz00001] M_{translation}M_{rotation}M_{scal\theta}= \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}
=[kxcosθ0kxsinθtx0ky0tykxsinθ0kzcosθtz0001] =\begin{bmatrix} k_x\cos \theta & 0 & k_x\sin \theta & t_x \\ 0 & k_y & 0 & t_y \\ -k_x\sin \theta & 0 & k_z\cos \theta & -t_z \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}
从两个结果可以看出,得到的变换矩阵是不一样的。
除了需要注意不同类型的变换顺序外,有时还需要小心旋转的变换顺序。如果我们需要同时绕着3个轴进行旋转,那么应该按什么样的旋转顺序呢?
当直接给出( θx, θy, θz)这样的旋转角度时,需要定义一个旋转顺序。在Unity 中,这个旋转顺序是zxy,这在旋转相关的API 文档中都有说明。这意味着,当给定(θx, θy, θz)这样的旋转角度时,得到的组合旋转变换矩阵是:
MrotatθzMrotatθxMrotatθy=[cosθsinθ00sinθcosθ0000100001][10000cosθsinθ00sinθcosθ00001][cosθ0sinθ00100sinθ0cosθ00001] M_{rotat\theta z}M_{rotat\theta x}M_{rotat\theta y}= \begin{bmatrix} \cos \theta & \sin \theta & 0 & 0 \\ \sin \theta & \cos \theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix} \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} \cos \theta & 0 & \sin \theta & 0 \\ 0 & 1 & 0 & 0 \\ -\sin \theta & 0 & \cos \theta & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}
旋转时使用的座标系也有以下两种选择:
1、绕座标系E下的z 轴旋转θz,绕座标系E 下的y 轴旋转θy,绕座标系E 下的x 轴旋转θx,即进行一次旋转时不一起旋转当前座标系。
2、绕座标系E下的z 轴旋转θz,在座标系E 下在绕z 轴旋转θz 后的新座标系E’下的y 轴旋转θy , 在座标系E’下再绕y 轴旋转θy 后的新座标系E”下的x 轴旋转θx,即在旋转时,把座标系一起转动。

很容易知道,这两种选择的结果是不一样的。但如果把它们的旋转顺序颠倒一下,它们得到的结果就会是一样的!说得明白点,在第一种情况下,按zxy 顺序旋转和在第二种情况下,按yxz顺序旋转是一样的。而Unity 文档中说明的旋转顺序指的是在第一种情况下的顺序。
和上面不同类型的变换顺序导致的问题类似,不同的旋转顺序得到的结果也可能是不一样的。同样可以通过对比不同旋转顺序得到的变换矩阵来理解为什么会出现这样的不同。

错切矩阵

切变是座标系的变换,非均匀的拉伸。切变时候,角度变化,但是面积或体积不变。也可以理解为座标轴间的角度变化,造成的扭曲。

错切矩阵的逆矩阵可以通过取负来取得
(Hij)1(s)=Hij(s) (H_{ij})-1(s)= Hij(-s)

如下图,这是x座标根据y座标的切变,机器人的y座标没有变化,只有x座标变化了,变化后的座标x1理解为将y座标乘以切变因子s与原座标x的和:x1 = x + sy。如果是3D则增加z座标的切变因子t: x1 = x + sy,y1 = y + tz

在这里插入图片描述

2D中切变矩阵为:
Hx(s)=[10s1] H_x(s)= \begin{bmatrix} 1 & 0\\ s & 1\\ \end{bmatrix}
Hy(s)=[1s01] H_y(s)= \begin{bmatrix} 1 & s\\ 0 & 1\\ \end{bmatrix}
在3D中,同样的道理,如下三个矩阵,分别是随着z增大,x和y发生切变。随着y增大,x和z发生切变。随着z增大,x和y发生切变。
Hxy(s,t)=[100010st01] H_{xy}(s,t)= \begin{bmatrix} 1 & 0 &0 \\ 0 & 1 &0 \\ s & t &01 \\ \end{bmatrix}
Hxz(s,t)=[100s1t0001] H_{xz}(s,t)= \begin{bmatrix} 1 & 0 &0 \\ s & 1 &t\\ 0 & 0&01 \\ \end{bmatrix}
Hyz(s,t)=[1st0100001] H_{yz}(s,t)= \begin{bmatrix} 1 & s &t \\ 0 & 1 &0\\ 0 & 0&01 \\ \end{bmatrix}

镜像矩阵

镜像矩阵也叫做反射,与正交投影相似,正交投影将缩放值设为0,而镜像则设为-1.

在这里插入图片描述

R是reflect(反射)的缩写。2D:
R(n^)=S(n^,1)=[1+(11)nx2(11)nxny(11)nxny1+(11)ny2]=[1nx22nxny2nxny12ny2] R(\hat n)= S(\hat n,-1)= \begin{bmatrix} 1+(-1-1)n_x^2&(-1-1)n_xn_y \\ (-1-1)n_xn_y&1+(-1-1)n_y^2 \\ \end{bmatrix}= \begin{bmatrix} 1-n_x^2&-2n_xn_y \\ -2n_xn_y&1-2n_y^2 \\ \end{bmatrix}

3D:
R(n^)=S(n^,1)=[1+(11)nx2(11)nxny(11)nxnz(11)nxny1+(11)ny2(11)nynz(11)nxnz(11)nynz1+(11)nz2]=[12nx22nxny2nxnz2nxny12ny22nynz2nxnz2nynz12nz2] R(\hat n)= S(\hat n,-1)= \begin{bmatrix} 1+(-1-1)n_x^2&(-1-1)n_xn_y &(-1-1)n_xn_z \\ (-1-1)n_xn_y&1+(-1-1)n_y^2 &(-1-1)n_yn_z\\ (-1-1)n_xn_z& (-1-1)n_yn_z &1+(-1-1)n_z^2\\ \end{bmatrix}\\= \begin{bmatrix} 1-2n_x^2& -2n_xn_y & -2n_xn_z \\ -2n_xn_y&1-2n_y^2 & -2n_yn_z\\ -2n_xn_z& -2n_yn_z &1-2n_z^2\\ \end{bmatrix}

正交投影矩阵

投影意味和降维操作,将所有的点拉平到要投影的直线或平面上,从原来的点到投影点的直线相互平行,这就是正交投影。透视投影是另一种投影。

在这里插入图片描述

向座标轴或平面上投影

通过将垂直方向上缩放因子设为0来实现,如将3D点投影到xy平面,则抛弃z分量,通过将z方向上的缩放因子设为0实现。

P是projection(投影)的缩写,2D中,Px表示向x轴投影,Py同理:
Px=S([01],0)=[1000] P_x= S (\begin{bmatrix} 0&1 \end{bmatrix},0)= \begin{bmatrix} 1&0 \\ 0&0 \\ \end{bmatrix}
Py=S([10],0)=[0001] P_y= S (\begin{bmatrix} 1&0 \end{bmatrix},0)= \begin{bmatrix} 0&0 \\ 0&1 \\ \end{bmatrix}\\

3D中,Pxy表示向xy平面投影,其余同理:
Pxy=S([001],0)=[100010000] P_{xy}= S (\begin{bmatrix} 0&0&1 \end{bmatrix},0)= \begin{bmatrix} 1&0&0 \\ 0&1&0 \\ 0&0&0 \\ \end{bmatrix}
Pxz=S([010],0)=[100000001] P_{xz}= S (\begin{bmatrix} 0&1&0 \end{bmatrix},0)= \begin{bmatrix} 1&0&0 \\ 0&0&0 \\ 0&0&1 \\ \end{bmatrix}
Pyz=S([100],0)=[000010001] P_{yz}= S (\begin{bmatrix} 1&0&0 \end{bmatrix},0)= \begin{bmatrix} 0&0&0 \\ 0&1&0 \\ 0&0&1 \\ \end{bmatrix}

向任意指向或平面投影

投影有垂直于直线或平面的向量n定义,通过使n方向上的缩放因为0就能导出任意方向的投影矩阵。P(n)表示向垂直于向量n的轴或平面投影矩阵,S(n,0)表示在n方向上的缩放因子为0的缩放矩阵.

2D:
P(n^)=[1+(01)nx2(01)nxny(01)nxny1+(01)ny2]=[1nx2nxnynxny1ny2] P(\hat n)=\begin{bmatrix} 1+(0-1)n_x^2 & (0-1) n_xn_y \\ (0-1) n_xn_y & 1+(0-1)n_y^2 \\ \end{bmatrix}= \begin{bmatrix} 1-n_x^2 & -n_xn_y \\ -n_xn_y & 1-n_y^2 \\ \end{bmatrix}
3D:

P(n^)=[1+(01)nx2(01)nxny(01)nxnz(01)nxny1+(01)ny2(01)nynz(01)nxnz(01)nynz1+(01)nz2]=[1nx2nxnynxnznxny1ny2nynznxnznynz1nz2] P(\hat n)=\begin{bmatrix} 1+(0-1)n_x^2 & (0-1) n_xn_y & (0-1) n_xn_z \\ (0-1) n_xn_y & 1+(0-1)n_y^2 & (0-1) n_yn_z \\ (0-1) n_xn_z & (0-1) n_yn_z & 1+(0-1)n_z^2 \\ \end{bmatrix}= \begin{bmatrix} 1-n_x^2 & -n_xn_y & -n_xn_z \\ -n_xn_y & 1-n_y^2 & -n_yn_z \\ -n_xn_z & -n_yn_z & 1-n_z^2 \\ \end{bmatrix}

透视投影矩阵

Unity中投射投影矩阵与正交投影

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章