Unity Shader:相关数学基础

本文同时发布在我的个人博客上:https://dragon_boy.gitee.io

坐标系

有左手坐标系和右手坐标系两种,Unity使用的是右手坐标系。

点和矢量

我们使用两个或三个以上的实数来表示一个点的坐标,如。

矢量是指n维空间中一种包含了模和方向的有向线段。矢量的表示方法和点类似,如。

矢量通常由一个箭头表示,由起点指向终点。矢量常被用于表示相对于某个点的偏移,只要矢量的模和方向保持不变,无论在哪里,都是同一个矢量。

矢量运算

矢量和标量乘除

对乘法,将矢量的每个分量和标量相乘即可:

对除法,同理:,标量需非零。

矢量加减法

两个矢量加减法,把对应的分量进行相加或相减即可:

几何意义上,矢量相加即前者起点连接至后者终点,矢量相减即后者终点指向前者终点。

矢量的模

矢量的模即矢量的长度:

单位矢量

单位矢量即模为1的矢量。将某个非零矢量转化为单位矢量的过程称为归一化。

矢量乘法

矢量之间的乘法有两种,点积和叉积。

点积公式有两个:
1:

2(为两个矢量的夹角):

点积的结果在几何意义上是获得矢量在上的投影与的模的乘积,如果二者都是单位矢量,那么点积结果就是前者在后者上的投影。

叉积公式:

下面是另一种表示:

上述表明矢量叉积的结果的模是两矢量构成平行四边形的面积。

在几何意义的上,叉积的结果是一个新的矢量,分别垂直于和,且沿、和叉积的方向可构成一个右手坐标系。

矩阵

矩阵有行列之分,以矩阵为例:

矢量的矩阵表示法

矢量可以看作时的列矩阵或的行矩阵。

矩阵运算

矩阵和标量的乘法

将矩阵的每个元素和标量相乘即可。

矩阵和矩阵的乘法

两个矩阵相乘,要求前一个矩阵的列数等于后一个矩阵的行数,相乘得到的矩阵的行数是第一个矩阵的行数,列数是第二个矩阵的列数。如的维度是,的维度是,那么的维度是。

矩阵的乘法即前一个矩阵的每一行(行)与后一个矩阵的每一列(列)相乘(可看作行构成的矢量和列构成的矢量点积),每次相乘的结果填写在对应的行列上。

注意,矩阵乘法不满足交换律,但满足结合律。

特殊矩阵

方阵

方阵即行列数相等的矩阵。

同时,如果除对角线的元素外全是0的方阵称为对角矩阵。

单位矩阵

针对上述的对角矩阵,如果对角线的元素全是0的话,那么就称为单位矩阵。

转置矩阵

对于一个的矩阵,它的转置为的矩阵。转置运算即将原矩阵的行列翻转。原矩阵的行变为列,列变为行。

注意,矩阵的转置的转置等于原矩阵。

矩阵的串接的转置等于反向串接各个矩阵的转置:

逆矩阵

只有方阵才有逆矩阵。

一个矩阵和它的逆矩阵的乘积为单位矩阵:

零矩阵没有逆矩阵。如果一个矩阵有逆矩阵,那么这个矩阵是可逆的,或非奇异的,相反则是不可逆的或奇异的。

如果一个矩阵的行列式为不为0,那么该矩阵就是可逆的(具体不解释)。

注意,逆矩阵的逆矩阵为原矩阵。

单位矩阵的逆矩阵是它本身。

转置矩阵的逆矩阵是逆矩阵的转置:

矩阵串接相乘后的逆矩阵等于反向串接各个矩阵的逆矩阵:

注意,如果某个矩阵或矢量进行了一个矩阵变化,那么使用逆矩阵可以还原这个变换:

正交矩阵

如果一个方阵和它的转置矩阵的乘积是单位矩阵,那么这个方阵就是正交矩阵:

将正交矩阵与逆矩阵的概念结合起来,就可以得到,如果一个矩阵正交,那么它的转置矩阵和逆矩阵相等:

上述式子在实际计算中非常有用,因为逆矩阵的计算量很大(涉及伴随矩阵),所以如果能判断某个变换矩阵是正交的话,可以很简单地用转置矩阵代替它的逆矩阵进行计算。

那么如何判断一个矩阵是否正交,我们把一个矩阵的列看作是一个矢量,称为基矢量,那么根据正交矩阵定义:

M^TM = \left[ \begin{matrix} {-} & c_1& {-}\\ {-} & c_2 &{-}\\ {-} & c_3 &{-} \end{matrix} \right] \left[ \begin{matrix} {|} & {|} &{|}\\ c_1 & c_2 & c_3\\ {|} & {|} &{|} \end{matrix} \right] = \left[ \begin{matrix} c_1 \cdot c_1 & c_1 \cdot c_2 & c_1 \cdot c_3\\ c_2 \cdot c_1 & c_2 \cdot c_2 & c_2 \cdot c_3\\ c_3 \cdot c_1 & c_3 \cdot c_2 & c_3 \cdot c_3 \end{matrix} \right] = \left[ \begin{matrix} 1&0&0\\ 0&1&0\\ 0&0&1 \end{matrix} \right]

根据上述等式,得:

,,都是单位矢量,且两两垂直。那么满足这样条件的矩阵的就是正交矩阵。

矢量的矩阵表示

在Unity中,常将矢量当做是列矩阵来使用,即放到矩阵的右边来进行乘法。这种情况下,矩阵乘法通常是右乘,即:

变换

概念

变换是将一些数据通过某种方式进行转换的过程。

一个非常常见的变换是线性变换,指的是可以保留矢量加和标量乘的变换。数学公式为:

缩放是一种线性变换,旋转也是一种线性变换,我们可以使用一个的矩阵就可以对一个三维向量进行线性变换。除此之外还有错切,镜像,正交投影。

但平移变换不是线性变换,我们不能使用一个的矩阵对一个三维向量进行变换。

由此出现仿射变换,即合并线性变换和平移变换的变换类型。仿射变换使用一个的矩阵来表示,这样我们需要将矢量扩展到四维空间下,即齐次坐标空间。

齐次坐标

对于一个三维向量,我们扩展一个维度,分量称为。对于坐标,常设为1,而对于方向,常设为0。因为我们只想对位置进行平移变换,方向不行。

基础变换矩阵

我们使用一个的矩阵来表示平移、旋转和缩放。一个基础的变换矩阵可以分解为4个部分:

表示旋转缩放的矩阵,表示平移。

平移矩阵

下面是一个平移矩阵(针对点)的例子:
\left[ \begin{matrix} 1&0&0&t_x\\ 0&1&0&t_y\\ 0&0&1&t_z\\ 0&0&0&1 \end{matrix} \right] \left[ \begin{matrix} x\\y\\z\\1 \end{matrix} \right] = \left[ \begin{matrix} x+t_x\\ y+t_y\\ z+t_z\\ 1 \end{matrix} \right]

如果是方向矢量的话,由于分量为0,平移变换不会有影响:

\left[ \begin{matrix} 1&0&0&t_x\\ 0&1&0&t_y\\ 0&0&1&t_z\\ 0&0&0&1 \end{matrix} \right] \left[ \begin{matrix} x\\y\\z\\0 \end{matrix} \right] = \left[ \begin{matrix} x\\ y\\ z\\ 0 \end{matrix} \right]

平移变换的逆矩阵就是反向平移得到的矩阵:

平移矩阵并不是正交矩阵。

缩放矩阵

下面是一个缩放矩阵(针对点)的例子:

\left[ \begin{matrix} k_x&0&0&0\\ 0&k_y&0&0\\ 0&0&k_z&0\\ 0&0&0&1 \end{matrix} \right] \left[ \begin{matrix} x\\y\\z\\1 \end{matrix} \right] = \left[ \begin{matrix} k_xx\\ k_yy\\ k_zz\\ 1 \end{matrix} \right]

对方向矢量缩放:

\left[ \begin{matrix} k_x&0&0&0\\ 0&k_y&0&0\\ 0&0&k_z&0\\ 0&0&0&1 \end{matrix} \right] \left[ \begin{matrix} x\\y\\z\\0 \end{matrix} \right] = \left[ \begin{matrix} k_xx\\ k_yy\\ k_zz\\ 0 \end{matrix} \right]

如果,那么这样的缩放为统一缩放,否则为非统一缩放。由于非统一缩放会改变角度和比例信息,所以针对方向向量的缩放不能使用上述的方式。

缩放矩阵的逆矩阵如下:

缩放矩阵不是正交矩阵。

上面的矩阵只适合沿坐标轴方向缩放,如果要在任意方向缩放,就要使用一个复合变换,先缩放轴,再沿坐标轴缩放。

旋转矩阵

绕x轴旋转:

绕y轴旋转:

绕z轴旋转:

旋转矩阵是正交矩阵。

复合变换

大多数情况下,将平移、旋转、缩放结合起来的顺序往往是:先缩放,后旋转,再平移,也就是:

注意,针对旋转的顺序,Unity的顺序是zxy,即旋转变换矩阵是:

旋转时的坐标系有两种可以选择:

  • 绕坐标系E的z轴旋转,绕y轴旋转,再绕x轴旋转。即进行一次旋转时不一起旋转当前坐标系。
  • 绕E的z轴旋转,E绕z轴旋转相同角度的E',绕E‘的y轴旋转,E’绕y轴旋转相同角度的E'',最后绕E''的x轴旋转。即再旋转时,把坐标系一起转动。

在上述两种方式中,旋转的结果不一样,Unity是第一种。

坐标空间

模型空间

模型空间即模型的局部坐标系,在Unity中是左手坐标系。

世界空间

世界空间被用来描述物体在场景中的位置,在Unity中,世界空间是左手坐标系。

将顶点从模型空间变换到世界空间的变换称为模型变换(Model)。这一变换通常由组成。

观察空间

也被称为摄像机空间。

摄像机决定了我们渲染游戏所使用的视角。在观察空间中,摄像机位于原点,在Unity中,+x指向摄像机的右方,+y指向摄像机的上方,+z指向摄像机的后方,即摄像机指向-z轴。

将顶点从世界空间转换到观察空间的变换为观察变换(View)。

为得到顶点在观察空间中的位置,有两种方法:一是计算观察空间的三个坐标轴在世界空间下的表示,然后构建出从观察空间变换到世界空间的变换矩阵,再对矩阵求逆来得到从世界空间变换到世界空间的矩阵。二是想象平移整个观察空间,让摄像机的原点位于世界坐标的原点,坐标轴与世界空间的坐标轴重合即可。

这里使用第二种方法,即想办法让摄像机变换到世界空间的原点即可(注意,由于观察空间使用右手坐标系,因此需要对z分量取反)。

裁剪空间

将顶点从观察空间转换到裁剪空间的变换矩阵称为裁剪矩阵,也被称为投影矩阵(Projection)。裁剪空间由视锥体决定。

视锥体是空间中的一块区域,视锥体内的区域是摄像机可以看到的空间。视锥体由六个平面构成,这些平面称为裁剪平面。视锥体有两种类型,透视投影和正交投影。

在视锥体的裁剪平面中有两块很特殊,分别称为近裁剪平面和远裁剪平面,它们决定了摄像机可以看到的深度范围。


投影矩阵有两个目的:

  • 为投影做准备
  • 对x、y、z分量进行缩放
透视投影

我们利用上图的参数可以计算出近裁剪平面和远裁剪平面的高度:

裁剪平面的宽度我们可以通过横纵比得到。假设摄像机的横纵比为:

根据上述信息,投影矩阵为:

使用上述投影矩阵后:

可以看到,投影矩阵本质上是对x、y、z坐标进行一些缩放,同时z分量也进行了平移,缩放的目的是为了方便裁剪。此时w分量不再是1,而是-z,通过这个w,我们可以判断一个顶点是否在视锥体内,在的话必须满足下面条件:

透视投影后,空间从右手坐标系变为左手坐标系。

正交投影

假设横纵比为,那么:

正交投影矩阵:

使用正交投影矩阵后:

使用正交投影后,w分量仍是1。

屏幕空间

这一步的变换,我们会得到真正的像素位置。

将顶点从裁剪空间投影到屏幕空间有两个步骤:首先进行透视除法,用x、y、z分量除以w分量。这一步得到的坐标被称为NDC,经过透视除法的裁剪空间变换到一个立方体内:


对于正交投影没什么区别。

在Unity中,屏幕空间左下角的像素坐标是(0,0)。接下来就是将NDC缩放值屏幕坐标系。

透视除法和屏幕映射的过程总结如下:


法线变换

法线是一个方向矢量,垂直于表面,与其垂直的一个矢量被称为切线。

切线由两个顶点之间的差值计算得来,因此可以使用变换顶点的变换矩阵来变换切线。假设不考虑平移变换,变换后的切线为:

不过,如何直接这么变换的话,法线就有可能不垂直于表面了:


下面我们可以来推导一下正确的法线变换矩阵。

首先,切线与法线垂直,即,假设变换法线的矩阵为,变换后法线仍与切线垂直,那么:

经推导得:

由于,如果,则上式成立,即。

如果变换矩阵是正交矩阵,那么G = M_{A->B},当然,这限于只包含旋转变换。如果只包含旋转和统一缩放,那么。

Unity Shader的内置相关数学变量

内置变量:Built-in shader variables
内置方法:Built-in shader helper functions

你可能感兴趣的:(Unity Shader:相关数学基础)