一、基础数学
(一)基础数学计算
Mathf.Abs(value); // 绝对值
Mathf.CeilToInt(value); // 向上取整
Mathf.FloorToInt(value); // 向下取整
Mathf.Clamp(value, min, max); // 钳制函数,限制数的范围
Mathf.Max(a, b); // 最大值
Mathf.Min(a, b); // 最小值
Mathf.Pow(value,pow); // 幂函数
Mathf.RoundToInt(value); // 四舍五入
Mathf.Sqrt(value); // 平方根
Mathf.IsPowerOfTwo(value); // 判断一个数是否是2的n次方
Mathf.Sign(value); // 判断正负数(正数1,负数-1)
(二)三角函数
- 角度:常用的角度单位,定义一周为360^\circ。
- 弧度(rad):国际标准角度单位,定义一周为2\pi弧度。
- 基础关系:\pi\ rad=180°
- 弧度转角度:1\ rad = (\frac{180}{\pi})^\circ \approx 57.3^\circ,即 弧度 × 57.3^\circ = 对应角度。
- 角度转弧度:1^\circ = (\frac{\pi}{180})\ rad \approx 0.01745\ rad,即 角度 × 0.01745\ rad = 对应弧度。
Mathf.PI; // PI的值
Mathf.Deg2Rad; // 角度转弧度(角度 * Mathf.Deg2Rad = 对应弧度)
Mathf.Rad2Deg; // 弧度转角度(弧度 * Mathf.Rad2Deg = 对应角度)
Mathf.Sin(value); // 正弦
Mathf.Cos(value); // 余弦
Mathf.ASin(value); // 反正弦
Mathf.ACos(value); // 反余弦
(三)插值运算
t 为插值系数,在[0, 1]的区间内,计算的结果为:start + (end - start) * t
Mathf.Lerp(start, end, t); // 插值运算
(四)坐标系
Unity3D的坐标系是左手坐标系。
1、世界坐标系
三个轴向固定,不受物体旋转影响,坐标原点为世界的中心点。

2、物体坐标系
即本地坐标系,轴向会随物体旋转而变化。

3、屏幕坐标系
原点为屏幕左下角,向右为X轴正方向,向上为Y轴正方向。宽高可通过Screen.width、Screen.height获取。
4、视口坐标系
原点为屏幕左下角,向右为X轴正方向,向上为Y轴正方向。坐标中左下角为(0,0),右上角为(1,1)。

5、坐标系间的相互转换
- 世界坐标系与本地坐标系
this.transform.InverseTransformPoint(Vector); // 世界坐标点转本地坐标(受缩放影响)
this.transform.InverseTransformDirection(Vector); // 世界坐标向量转本地坐标(不受缩放影响)
this.transform.InverseTransformVector(Vector); // 世界坐标向量转本地坐标(受缩放影响)
this.transform.TransformPoint(Vector); // 本地坐标点转世界坐标(受缩放影响)
this.transform.TransformDirection(Vector); // 本地坐标向量转世界坐标(不受缩放影响)
this.transform.TransformVector(Vector); // 本地坐标向量转世界坐标(受缩放影响)
- 世界坐标系与屏幕坐标系
Camera.main.WorldToScreenPoint(position) // 世界坐标转屏幕坐标(转换后z轴表示离摄像机多远)
Camera.main.ScreenToWorldPoint(position) // 屏幕坐标转世界坐标(参数的z轴表示离摄像机多远)
- 世界坐标系与视口坐标系
Camera.main.WorldToViewportPoint(position); // 世界坐标转视口坐标
Camera.main.ViewportToWorldPoint(position); // 视口坐标转世界坐标
- 屏幕坐标系与视口坐标系
Camera.main.ViewportToScreenPoint(position); // 视口坐标转屏幕坐标
Camera.main.ScreenToViewportPoint(position); // 屏幕坐标转视口坐标
(五)向量
1、向量概念
- 标量:只有数值大小,没有方向。
- 向量:有数值大小,有方向的矢量。
- 两点决定一个向量:
终点 - 起点,可以获取从起点指向终点的向量。 - 零向量:大小为零的向量,如
(0,0,0)。 - 负向量:与原向量大小相同,方向相反的向量,如
(x,y,z)的负向量为(-x,-y,-z)。 - 向量的模长:向量的长度,即向量中两端点的距离。
(x,y,z)的模长为:\sqrt{x^2 + y^2 + z^2} - 单位向量:模长为1的向量,只需要方向信息的向量。归一化公式:单位向量 = (x/模长, y/模长, z/模长)。
Vector3 vec = Vector3.one; // 向量声明
vec.magnitude; // 向量模长
vec.normalized; // 单位向量
2、向量运算
(1)加减乘除
- 位置+位置:无意义。
- 向量+向量:得到一个新向量。向量相加,结果是首尾相连(三角形法则),或者用平行四边形法则。
- 位置+向量:得到一个新位置,相当于原位置偏移了向量的方向和距离。
- 位置-位置:得到一个新向量,两点决定一个向量。
- 向量-向量:得到一个新向量,A - B的结果为:B头部指向A头部的一个向量。
- 位置-向量:得到一个新位置,和位置+向量一样。只不过偏移的是反方向。
- 向量*标量 or 向量/标量:得到一个新向量,相当于向量对模长的一个缩放。
(2)向量点乘
向量点乘的几何意义是一个向量到另一个向量的投影的长度。可以判断对象的方位,计算两个向量之间的夹角。
点乘公式为:\vec{a} \cdot \vec{b} = x_a x_b + y_a y_b = \|\vec{a}\| \|\vec{b}\| \cos\theta
- 计算方位:点乘结果大于0则为锐角,等于0为直角,小于0为钝角。
- 计算夹角:通过公式可以推到计算出夹角余弦值,然后通过反三角函数可以获取对应角度值。
Vector3.Dot(a, b); // 点乘
(3)向量叉乘
向量叉乘的结果是一个垂直于原来两个向量所在平面的向量,其长度等于由这两个向量构成的平行四边形的面积。可以获取一个平面的法向量,判断两个向量间的左右关系。
叉乘公式为:\vec{a} \times \vec{b} = \begin{pmatrix} y_a z_b - y_b z_a \\ z_a x_b - x_a z_b \\ x_a y_b - y_a x_b \end{pmatrix}
叉乘的反交换律:\vec{a} \times \vec{b} = -\vec{b} \times \vec{a},即交换位置后,叉乘的结果向量方向是相反的。
- 获取法向量:两个向量叉乘,结果即为两向量所在平面的法向量。
- 判断左右关系:Unity中是左手坐标系,若A和B都在XZ平面上,A叉乘B的结果向量的Y值大于0则B在A右边,Y值小于0则B在A左边。
Vector3.Cross(a, b); // 叉乘
二、线性代数概念
1、线性代数
线性代数是数学的一个分支学科,主要研究向量、矩阵、线性方程组、线性映射、行列式、特征值与特征向量等对象。可以简单的理解为,线性代数是一门研究向量和线性变换的数学学科,线性代数主要研究如何使用这些向量进行运算,以及它们之间的变换规则。
2、矩阵
矩阵(Matrix)是线性代数中的一个核心概念,指由若干元素按行、列排列形成的矩形阵列。
矩阵是由 m x n 个标量组成的,通过方括号内的数值表格来表示:
Matrix = \begin{pmatrix}
M_{11} & \dots & M_{1n} \\
\vdots & \ddots & \vdots \\
M_{m1} & \dots & M_{mn}
\end{pmatrix}
在程序中,我们用于存储矩阵结构的容器类型有很多,比如数组、嵌套列表、Unity中的Matrix4x4、Matrix3x2结构体等。
3、为什么学习矩阵
矩阵是线性代数中的核心工具,我们在进行Shader开发时,很多数学计算都需要利用矩阵来完成,如坐标系转换、投影计算、光照计算、纹理映射等等。因此学习矩阵是为了能在 Shader 开发中利用其进行相关数学计算。
三、矩阵
(一)矩阵乘法
1、标量×矩阵:标量 k 乘以矩阵里每一个元素,比如下面的标量 k 乘以三行三列矩阵的计算公式为:
kM_{33} =
\begin{pmatrix}
k m_{11} & k m_{12} & k m_{13} \\
k m_{21} & k m_{22} & k m_{23} \\
k m_{31} & k m_{32} & k m_{33}
\end{pmatrix}
2、矩阵×矩阵:左列右行要相等才可以相乘,相乘的结果为左行右列大小的矩阵。这里举个例子好理解些,矩阵相乘结果如下:
A_{m\times n}B_{n\times p}=C_{m\times p}
矩阵每个元素的结果是左行乘右列再相加,比如下面AB两矩阵相乘,结果第一行第二列元素计算公式为:
A=\begin{pmatrix}
a_{11} & a_{12} & a_{13} \\
a_{21} & a_{22} & a_{23}
\end{pmatrix},
B=\begin{pmatrix}
b_{11} & b_{12} \\
b_{21} & b_{22} \\
b_{31} & b_{32}
\end{pmatrix},
C=AB=\begin{pmatrix}
c_{11} & c_{12} \\
c_{21} & c_{22}
\end{pmatrix}
c_{12}=a_{11}b_{12}+a_{12}b_{22}+a_{13}b_{32}
3、矩阵乘法性质
- 不满足交换律:AB \neq BA
- 满足结合律:(AB)C = A(BC)
(二)特殊矩阵
1、方块矩阵
方块矩阵简称方阵,它的特点是行列数相等,比如 3×3、4×4,n×n 的矩阵都可以称为方阵。
2、对角矩阵
对角矩阵是一种特殊的方阵,它是只有主对角线有值,其余元素全为零的方阵。矩阵中的主对角线就是从左上角到右下角的对角线。
\begin{bmatrix}
1 & 0 & 0 \\
0 & 5 & 0 \\
0 & 0 & 9
\end{bmatrix}
3、单位矩阵
单位矩阵是一种特殊的对角矩阵,它是主对角线上的元素均为1 的对角矩阵。
\begin{bmatrix}
1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{bmatrix}
单位矩阵的性质:满足乘法规则前提下,任何矩阵(M)和单位矩阵(I)相乘,结果不变。
MI = IM = M
4、数量矩阵
数量矩阵是一种特殊的对角矩阵,它是主对角线上的元素为同一值的对角矩阵,可以看作是单位矩阵 I 乘以了一个标量 k。
\begin{bmatrix}
9.5 & 0 & 0 \\
0 & 9.5 & 0 \\
0 & 0 & 9.5
\end{bmatrix}
5、转置矩阵
转置矩阵是将原始矩阵的行和列互换得到的新矩阵,记作 M^T。
\begin{bmatrix}
1 & 2 \\
3 & 4 \\
5 & 6 \end{bmatrix}^T =
\begin{bmatrix}
1 & 3 & 5 \\
2 & 4 & 6
\end{bmatrix}
转置矩阵的性质:
- 转置两次为原来的矩阵:(M^T)^T = M
- 两个矩阵乘积的转置为顺序颠倒,各自转置再相乘:(AB)^T = B^T A^T
6、逆矩阵
设A为n阶方阵,如果存在矩阵B满足 AB = BA = I,则称A为可逆矩阵,B为A的逆矩阵,记作A^{-1}。
逆矩阵篇幅较多,这里不详细阐述,可以在下两节中学习。
7、正交矩阵
设A为n阶方阵,若存在它的转置矩阵,满足 AA^T=A^TA=I,则A为正交矩阵。
正交矩阵篇幅较多,因此可以后面章节中学习。
8、行矩阵与列矩阵
行矩阵表示只有一行的矩阵,列矩阵表示只有一列的矩阵。
行矩阵和列矩阵篇幅较多,因此可以在后面章节中学习。
(三)行列式
1、行列式概念
行列式仅对方阵有定义,是一个把方阵映射成一个数值的运算,记作det(A)或者|A|。
在几何上,行列式可以看作矩阵列向量所围成的平行多边形或多面体的有向面积或体积,比如二阶行列式的值,表示二维矩阵 A 的列向量所围成的平行四边形面积。
2、行列式计算
对于 2 阶行列式:
|A|= \begin{vmatrix} a_{11} & a_{12}\\ a_{21} & a_{22} \end{vmatrix} =a_{11}a_{22}-a_{12}a_{21}
对于 3 阶行列式:
|A|=
\begin{vmatrix}
a_{11} & a_{12} & a_{13}\\
a_{21} & a_{22} & a_{23}\\
a_{31} & a_{32} & a_{33}
\end{vmatrix}
=a_{11}a_{22}a_{33}+a_{12}a_{23}a_{31}+a_{13}a_{21}a_{32}-a_{13}a_{22}a_{31}-a_{12}a_{21}a_{33}-a_{11}a_{23}a_{32}
看公式可能很复杂,因此一般会结合图来理解,即对角线法则,不过只对2、3阶行列式适用,高阶这里就不提了。


3、余子式与代数余子式
- 余子式:设n阶方阵A,划去元素 a_{ij} 所在的第i行、第j列,剩余元素构成的 n-1 阶行列式,称为元素 a_{ij} 的余子式,记作M_{ij}。
- 代数余子式:在余子式基础上增加符号项,其中i是行号,j是列号,计算公式如下:
A_{ij}=(-1)^{i+j}M_{ij}
这里举个例子说明一下,比如下面这个方阵,求a_{11}的余子式和代数余子式。
A= \begin{pmatrix} 1 & 2 & 3\\ 4 & 5 & 6\\ 7 & 8 & 9 \end{pmatrix}
余子式则为划去第一行第一列剩下的部分,代数余子式则加上对应符号,结果为:
M_{11}= \begin{vmatrix} 5 & 6\\ 8 & 9 \end{vmatrix} = -3,
A_{11}=(-1)^{1+1}M_{11}=-3
(四)逆矩阵
1、定义
逆矩阵必须是一个方阵,并且不是所有矩阵都有逆矩阵。方阵 M 的逆矩阵记作 M^{-1}。逆矩阵必须满足一个性质:MM^{-1}=M^{-1}M=I,可以理解为:原矩阵是一个线性变换,而逆矩阵就是这个线性变换的”逆变换“。
如果一个矩阵存在对应的逆矩阵,我们就说该矩阵是可逆的,如果不存在,那么该矩阵为不可逆的。并且如果矩阵可逆,它的逆矩阵是唯一的,并且它的行列式不为0,即|A| \neq 0。而行列式为0的矩阵我们称为奇异矩阵,否则为非奇异矩阵,因此可以说可逆矩阵是非奇异矩阵,不可逆矩阵是奇异矩阵。
2、伴随矩阵
为了方便理解为什么逆矩阵这么算,我先提一下这个伴随矩阵,不懂地方的可以去相应的学习。伴随矩阵指由原矩阵所有元素的代数余子式构成的矩阵,再进行转置之后得到的矩阵,记作 A^*。
A^* =
\begin{pmatrix}
A_{11} & A_{12} & \dots & A_{1n} \\
A_{21} & A_{22} & \dots & A_{2n} \\
\vdots & \vdots & \ddots & \vdots \\
A_{n1} & A_{n2} & \dots & A_{nn}
\end{pmatrix}^T =
\begin{pmatrix}
A_{11} & A_{21} & \dots & A_{n1} \\
A_{12} & A_{22} & \dots & A_{n2} \\
\vdots & \vdots & \ddots & \vdots \\
A_{1n} & A_{2n} & \dots & A_{nn}
\end{pmatrix}
伴随矩阵有个很重要的性质:
AA^* = A^*A = |A|I
这和逆矩阵的性质非常像,因此求出矩阵对应的伴随矩阵就可以求出逆矩阵了。
3、逆矩阵计算
从上面的描述中,我们得知逆矩阵计算步骤如下:
- 计算矩阵的行列式:确定矩阵必须是方阵,然后才能计算,若行列式不为0,则进入下一步。
- 计算矩阵的伴随矩阵:先求出矩阵的代数余子式矩阵,然后转置即可。
- 计算逆矩阵:从上面的公式可以推出,逆矩阵就是伴随矩阵除以行列式的值,即A^{-1}=\frac{1}{|A|}A^*。`
4、逆矩阵的性质
设矩阵 M,A,B 均为可逆方阵,I 为单位矩阵,\boldsymbol{v} 为向量,则一些重要逆矩阵的性质如下:
- 逆矩阵的逆矩阵是原矩阵本身:(M^{-1})^{-1} = M
- 矩阵乘以自己的逆矩阵为单位矩阵:MM^{-1} = M^{-1}M = I
- 单位矩阵的逆矩阵是它本身:I^{-1} = I
- 转置矩阵的逆矩阵等于逆矩阵的转置:(M^T)^{-1} = (M^{-1})^T
- 矩阵乘积的逆矩阵,等于逆矩阵反向相乘:(AB)^{-1} = B^{-1}A^{-1}
- 逆矩阵实现线性变换的反向还原:M^{-1}(M\boldsymbol{v}) = (M^{-1}M)\boldsymbol{v} = I\boldsymbol{v} = \boldsymbol{v}
(五)正交矩阵
1、定义
正交矩阵是一种特殊的方阵,正交矩阵 M 满足如下性质:MM^T=M^TM=I。
正交的意思是垂直,以我们熟悉的点乘来说,a \cdot b 就是两个向量的各分量相乘再相加,当点乘结果为0时表示两向量垂直,即两向量正交。
而正交矩阵也可以这么理解,我们拆解一下计算过程,以3行3列矩阵为例:矩阵每一列可以看作是一个列向量,而它的转置矩阵每一行可以看作一个行向量。
M = \begin{bmatrix}
| & | & | \\
v & u & w \\
| & | & |
\end{bmatrix} =
\begin{bmatrix}
v_x & u_x & w_x \\
v_y & u_y & w_y \\
v_z & u_z & w_z
\end{bmatrix} \\
M^T = \begin{bmatrix}
— & v & — \\
— & u & — \\
— & w & —
\end{bmatrix} =
\begin{bmatrix}
v_x & v_y & v_z \\
u_x & u_y & u_z \\
w_x & w_y & w_z
\end{bmatrix}
两个矩阵相乘为单位矩阵,意味着向量间两两点乘,除自身点乘自身向量为1外,其余都是0。在几何意义上,表示所有向量都是单位向量,且向量间两两相互垂直。
M^T M = \begin{bmatrix}
v\cdot v & v\cdot u & v\cdot w \\
u\cdot v & u\cdot u & u\cdot w \\
w\cdot v & w\cdot u & w\cdot w
\end{bmatrix} =
\begin{bmatrix}
1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{bmatrix}
2、如何判断正交矩阵
判断正交矩阵有两种方法:
- 判断 MM^T=M^TM=I,若满足则为正交矩阵。
- 判断矩阵每一行(列)是否都为单位矩阵,同时每一行(列)向量间是否都彼此正交,若都满足则为正交矩阵。
3、正交矩阵的性质
- 若 M 为正交矩阵,M^T 也为正交矩阵。
- 因为逆矩阵定义 MM^{-1}=M^{-1}M=I,因此正交矩阵的转置就是逆矩阵,即 M^T = M^{-1}。
- 正交矩阵的行列式只能为 1 或 -1。
- 正交矩阵的各行(列)都是单位向量且彼此正交(垂直)。
(六)行矩阵与列矩阵
1、定义
在 Unity Shader 开发中我们经常会对向量进行矩阵运算,Unity 中向量一般都是三维向量 (x,y,z),由于要进行矩阵计算,我们就需要把向量用矩阵表示。这里有两种表示方式:
- 行矩阵:只有一行的矩阵。
- 列矩阵:只有一列的矩阵。
在进行向量的矩阵运算时,把它作为列矩阵和行矩阵得到的运算结果通常是不同的,列矩阵放在矩阵右边计算,而行矩阵放在矩阵左边计算,举个例子就懂了:
列矩阵计算:
\begin{pmatrix}
m_{11} & m_{12} & m_{13} \\
m_{21} & m_{22} & m_{23} \\
m_{31} & m_{32} & m_{33}
\end{pmatrix}
\begin{pmatrix}
x \\ y \\ z
\end{pmatrix}
行矩阵计算:
\begin{pmatrix}
x & y & z
\end{pmatrix}
\begin{pmatrix}
m_{11} & m_{12} & m_{13} \\
m_{21} & m_{22} & m_{23} \\
m_{31} & m_{32} & m_{33}
\end{pmatrix}
2、Unity中的使用
在标准的线性代数中,矩阵和向量的乘法是按列进行的。 为了和标准的数学和线性代数概念保持一致,便于理解和应用,Unity 也遵循这种矩阵乘法的定义,也就是将向量表示为列矩阵进行计算。
使用列矩阵时,我们的阅读顺序是从右到左的。假设向量为列矩阵 v,ABC 分别为3个变换矩阵,则 CBAv = C(B(Av)) 表示先对向量 v 使用 A 变换,再依次使用 B、C 进行变换。
如果想把向量作为行矩阵处理,也是可以的,此时是从左往右看。为了让行矩阵计算结果和列矩阵相同,我们可以采用以下规则:行矩阵必须乘以变换矩阵的转置矩阵。也就是 v A^T B^T C^T = ((v A^T) B^T) C^T。
这两者关系也很简单,其实就是转置矩阵的一个性质:(CBAv)^T = v^T A^T B^T C^T。
(七)矩阵的几何意义
1、几何意义
在数学中,几何意义指数学概念、对象或运算在几何学中的直观解释或视觉表达。可以将抽象的数学概念与我们对空间和形状的感知联系起来,这样我们可以用直观的方式理解和描述数学概念,使其更具实际意义。
2、矩阵的几何意义
矩阵的几何意义就是变换(Transform),在游戏开发中,如果你看到了一个矩阵,那么基本上你可以认为你看到的是一个变换,这些变换一般包含平移、旋转、缩放等等。
比如我们想要将一个点、一个向量进行一种变换,那么我们可以利用矩阵来进行数学计算,从而达到变换的目的。
3、变换的种类
在数学中常用的几何变换有两种:线性变换和仿射变换,他们的主要区别在于是否保持直线的平行性和原点位置。
- 线性变换:保持直线的平行性和原点位置不变,比如缩放、旋转等操作。
- 仿射变换:指线性变换与平移的组合,比如缩放旋转后再平移,会改变原点位置。
4、对于我们的意义
我们可以利用我们之前学习的矩阵相关知识,对三维空间中的向量进行平移、旋转、缩放、坐标变换、投影等等计算,这样我们就可以对 Shader 中的数据进行处理,让其最终在屏幕上的效果是按照我们的需求来呈现的。
四、变换矩阵
(一)齐次坐标
1、齐次坐标是什么
齐次坐标是一种在计算机图形学中常用的表示坐标的方式,它是通过引入一个额外的维度来扩展传统的笛卡尔坐标系, 就是将一个原本是 n 维的向量或矩阵用 n + 1 维来表示,让我们可以更方便的进行几何变换和矩阵运算。
2、为什么用齐次坐标
-
主要原因1:通过齐次坐标,我们可以明确的区分向量和点。比如三维空间中的 ( x , y , z) ,它既可以表示点,也可以表示向量。那么我们用齐次坐标给它加一维,变成 (x , y , z , w),其中 w = 1 时表示一个点,w = 0 时表示一个向量。 这样我们就可以明确它是点还是向量了。
-
主要原因2:3x3矩阵不能直接表示平移变换(后面就知道为什么了),它只能表示线性变换,比如对象的旋转、缩放等。平移涉及到改变对象在空间中的位置,包括移动对象的原点。因此需要引入一个额外的维度来表示平移操作,所以我们使用齐次坐标来将 3x3 矩阵加一个维度变为 4x4 的矩阵。这样既可以表示线性变换,又可以表示平移变换。
注:3x3 矩阵一般称为线性矩阵,主要处理线性变换,而4x4矩阵一般称为仿射矩阵,主要处理仿射变换。
3、齐次坐标的性质
这是后来补充的一些性质,如果目前看不懂没关系,可以在后面遇到对应知识时回来看。
假设三维空间的一个点坐标为 (x, y, z),则对应齐次坐标为 (x, y, z, 1),这个 1 对应 w 分量。
- 若 w 不为 0,则该点在三维笛卡尔坐标系中坐标为 (X,Y,Z)=(\frac{x}{w},\frac{y}{w},\frac{z}{w}) 。
- 若 w 为 0,则该点表示一个无穷远的点,也可表示一个向量。
- 齐次坐标表示的点,该坐标的分量全部乘以一个非零实数(标量),仍表示该点。即 (x, y, z, 1) 和 (kx, ky, kz, k) 对应三维空间的坐标是一样的,根据前面的性质也能推出来。
(二)基础变换矩阵
我们已经知道,通过 4×4 的矩阵,就可以表示平移、旋转和缩放。而表示纯平移、纯旋转和纯缩放的变换矩阵叫做基础变换矩阵。这些矩阵都有一些共同点,我们可以把一个基础变换矩阵拆成 4 部分:
左上角是 3×3 的 M 矩阵,表示旋转、缩放等;右上角的 3×1 的 t 矩阵表示平移,下面两个部分是固定的,一个是零矩阵,另一个是标量 1。
\begin{bmatrix}
M^{3\times 3} & t^{3\times 1} \\
0^{1\times 3} & 1
\end{bmatrix} =>
\begin{bmatrix}
M_{11} & M_{12} & M_{13} & t_1 \\
M_{21} & M_{22} & M_{23} & t_2 \\
M_{31} & M_{32} & M_{33} & t_3 \\
0 & 0 & 0 & 1
\end{bmatrix}
这个基础变换矩阵我们主要就是使用上面的两部分,即 M 矩阵和 t 矩阵。为什么要用齐次空间才能表示平移现在也很明显了,就是需要多一个维度来表示平移。
我们前面还提到了三维向量如何转换为齐次坐标,就是添加一个 w 分量:
- w = 1 时表示点:此时应用基础变换时,平移、旋转、缩放都会施加于该点。
- w = 0 时表示向量:向量的平移效果会被忽略。
当然,如果还不理解也没关系,后面讲解几个变换矩阵就很容易理解了。
(三)平移矩阵
1、平移矩阵构成
平移矩阵是仿射变换的一种特例,它不旋转、不缩放,只挪位置,它的构成如下:
\begin{bmatrix}
1 & 0 & 0 & t_x \\
0 & 1 & 0 & t_y \\
0 & 0 & 1 & t_z \\
0 & 0 & 0 & 1
\end{bmatrix}
2、平移矩阵的计算
- 平移矩阵 × 点:此时 w = 1,表示点位置偏移了一段距离。
\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}
- 平移矩阵 × 向量:此时 w = 0,向量不受位移影响。
\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}
3、平移矩阵的逆矩阵
平移矩阵不是正交矩阵,因此逆矩阵需要通过计算,不过最终的逆矩阵也很简单,也很容易理解,其实就是坐标往回移动了而已。
\begin{bmatrix}
1 & 0 & 0 & -t_x \\
0 & 1 & 0 & -t_y \\
0 & 0 & 1 & -t_z \\
0 & 0 & 0 & 1
\end{bmatrix}
(四)旋转矩阵
1、旋转矩阵的构成
旋转需要指定一个旋转轴(不一定是空间坐标轴),这里讲的是绕 x、y、z 三个坐标轴旋转 θ 角的矩阵。
绕 x 轴旋转 θ:
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 轴旋转 θ:
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 轴旋转 θ:
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}
2、旋转矩阵的计算
旋转矩阵由基础变换矩阵的构成规则中 3x3 的 M 矩阵决定,平移部分的 3x1 矩阵都为 0,因此点(w = 1)和向量(w = 0)与旋转矩阵进行计算都会发生改变,即绕某一个轴进行旋转。
3、旋转矩阵的逆矩阵
旋转矩阵是正交矩阵,因此x、y、z 轴的旋转矩阵的逆矩阵是它们的转置矩阵,我们可以利用旋转矩阵的逆矩阵来还原旋转。
4、关于旋转矩阵推导与记忆
这里先提一下二维的旋转矩阵推导,三维也是类似的,这里我在B站视频中找到一张不错的图,可以说明二维的旋转矩阵如何推导:

三维的很类似,因此这里就不详细讲三维的推导了,只说下如何记忆,个人表达能力有限,因此若说的不清楚请见谅。这里一般都是用右手坐标系,所以我也用右手坐标系来讲,首先旋转默认情况下是逆时针旋转,对于三维来说,就是让旋转轴指向你,然后逆时针旋转。

对于绕 z 轴和 x 轴旋转,可以发现矩阵都是类似的,但是绕 y 轴旋转却是“相反”的,-\sin\theta 那行在下面,而不是在上面,这是什么原因呢?
其实这就涉及到循环对称的一个性质,这个循环怎么理解呢?如果你推导过就比较容易理解,但还是直接来看后面一步吧,这里其实就是将二维旋转矩阵四个值填入对应位置。
那么如何填呢?可以看下面这张图,蓝色框就代表左上角,那就从左上角开始依次填,需要注意绕 y 轴旋转这里,由于开始位置在右下角,因此填入其他元素就需要“循环”来填充(循环简单理解就比如从下边出去,上边进来)。

(五)缩放矩阵
1、缩放矩阵的构成
缩放矩阵用于改变点或向量在各个坐标轴上的尺度,使其在每个方向上变大或变小。k_x, k_y, k_z 分别代表 x、y、z 轴的缩放因子:
\begin{bmatrix} k_x & 0 & 0 & 0 \\ 0 & k_y & 0 & 0 \\ 0 & 0 & k_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}
当 k_x = k_y = k_z 时,称为统一缩放,否则称为非统一缩放。
2、缩放矩阵的计算
缩放矩阵由基础变换矩阵的构成规则中 3x3 的 M 矩阵决定,平移部分的 3x1 矩阵都为 0,因此点(w = 1)和向量(w = 0)与缩放矩阵进行计算都会发生改变。
- 对点缩放:改变模型大小,比如顶点位置被拉伸/压缩。
- 对向量缩放:统一缩放时,只改变向量长度,不改变方向;非统一缩放时,即改变大小,还可能改变方向。(因为各轴缩放比例不同,向量被拉歪了)
3、缩放矩阵的逆矩阵
只有当三个k都为1,即单位矩阵时,缩放矩阵才为正交矩阵,否则缩放矩阵都不是正交矩阵。而缩放矩阵的逆矩阵也很简单:
\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}
(六)复合运算
1、概念
复合运算就是我们在计算矩阵变换时,可以把平移、旋转、缩放等计算组合起来,通过结合多矩阵的乘法,来形成一个复杂的变换过程。比如我们可以将一个模型,先缩放到2倍大小,再绕y轴旋转60°,最后再向x轴平移5个单位。
这种复合变换的过程可通过矩阵串联来实现,即通过矩阵进行计算时,利用乘法结合律从右往左去计算:先缩放、后旋转、再平移。
P_{new} = M_{平移}M_{旋转}M_{缩放}P_{old}
2、计算顺序对结果的影响
在进行复合运算时,变换的结果依赖于变换的顺序,因为矩阵乘法不满足交换律。这个很好理解,比如一个人先往前一步再左转,和一个人先左转再往前一步的结果是不一样的。
3、Unity 中需要遵守的规则
- 在进行平移、旋转、缩放的复合运算时,我们约定的变换顺序为:先缩放、再旋转、后平移。
- 在进行 x、y、z 轴旋转的复合运算时,我们约定的变换顺序为:z -> x -> y。
之后我们在 Unity 中进行 Shader 开发时,遵守这两个规则即可。
五、坐标变换
(一)坐标空间的变换
1、坐标空间
坐标空间是一个用于描述和定位物体位置的数学概念。它一般由一个基础参照物和轴线组成。常见的坐标空间包括二维平面坐标空间和三维空间,比如在三维空间中,通常有一个原点 (0, 0, 0) 并使用三个坐标轴来描述点的位置。
2、为什么需要不同的坐标空间
我们在 Unity 基础中讲解的坐标系相关知识中已经提到了不同的坐标空间:世界坐标系、物体坐标系、屏幕坐标系等等。之所以存在这么多不同的坐标空间,主要是因为不同的问题需要不同的坐标系来描述和解决,帮助我们更方便的完成需求。而不同坐标空间的存在,也就需要用到坐标空间的变换。
在渲染管线中,顶点、法线等相关模型数据会经过以下的空间变换:模型空间 → 世界空间 → 观察空间 → 裁剪空间 → 屏幕空间。
3、坐标空间的变换
想要定义一个坐标空间,我们必须指明:坐标原点位置和 3 个坐标轴的方向。而坐标空间会形成一个层次结构——每个坐标空间都是另一个坐标空间的子空间(所有都是相对的)。在 Unity 中,世界坐标空间相当于我们的基础坐标空间,Unity 中其他的大部分坐标空间,都是世界坐标空间的子空间。即这些子坐标空间的原点和轴向的相关表示数据,都是基于世界坐标空间的。坐标空间的变换实际上就是父空间和子空间之间对点或向量进行变换。
4、空间坐标的变换规则
假设现在有父坐标空间 P,子坐标空间 C,我们知道在 P 中 C 的原点位置和 3 个单位坐标轴,我们一般会有两种需求:将子空间下的点或向量 A_{c} 转换到父空间中的 A_{p};或反过来,将父空间下的点或向量 B_{p} 转换到子空间中的 B_{c}。
A_p = M_{c→p}A_c \\
B_c = M_{p→c}B_p
而 M_{p→c} 是 M_{c→p} 的逆矩阵,因此我们只需要求出 M_{c→p} 即可。
假设我们已知子坐标空间 C 的 3 个坐标轴在父坐标空间 P 下分别表示:x_c、y_c、z_c,原点为 O_c。点 A_c = (a, b, c) 为子坐标空间中的一点,则该点转换为父坐标空间 P 中的 A_p 的公式我们很容易得出:
A_p = O_c + ax_c + by_c + cz_c
为了将这个公式转换为矩阵,我们可以进行如下步骤:
- 将向量部分转换成矩阵:
\begin{align*} A_p &= O_c + ax_c + by_c + c{z}_c \\ &= (x_{O_c}, y_{O_c}, z_{O_c}) + a(x_{x_c}, y_{x_c}, z_{x_c}) + b(x_{y_c}, y_{y_c}, z_{y_c}) + c(x_{z_c}, y_{z_c}, z_{z_c}) \\ &= (x_{O_c}, y_{O_c}, z_{O_c}) + \begin{bmatrix} x_{x_c} & x_{y_c} & x_{z_c} \\ y_{x_c} & y_{y_c} & y_{z_c} \\ z_{x_c} & z_{y_c} & z_{z_c} \end{bmatrix} \begin{bmatrix} a \\ b \\ c \end{bmatrix} \\ &= (x_{O_c}, y_{O_c}, z_{O_c}) + \begin{bmatrix} | & | & | \\ x_c & y_c & z_c \\ | & | & | \end{bmatrix} \begin{bmatrix} a \\ b \\ c \end{bmatrix} \end{align*}
- 将原点部分转换成平移矩阵,需要拓展成齐次坐标空间:
\begin{align*} A_p &= (x_{O_c}, y_{O_c}, z_{O_c}, 1) + \begin{bmatrix} | & | & | & 0 \\ x_c & y_c & z_c & 0 \\ | & | & | & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} a \\ b \\ c \\ 1 \end{bmatrix} \\ &= \begin{bmatrix} 1 & 0 & 0 & x_{O_c} \\ 0 & 1 & 0 & y_{O_c} \\ 0 & 0 & 1 & z_{O_c} \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} | & | & | & 0 \\ x_c & y_c & z_c & 0 \\ | & | & | & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} a \\ b \\ c \\ 1 \end{bmatrix} \\ &= \begin{bmatrix} | & | & | & x_{O_c} \\ x_c & y_c & z_c & y_{O_c} \\ | & | & | & z_{O_c} \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} a \\ b \\ c \\ 1 \end{bmatrix} \\ &= \begin{bmatrix} | & | & | & | \\ x_c & y_c & z_c & O_c \\ | & | & | & | \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} a \\ b \\ c \\ 1 \end{bmatrix} \end{align*}
所以上式就是坐标空间的变换矩阵了,总结一下就是:子坐标空间 C 变换到父坐标空间 P 的变换矩阵 M_{c→p},只需要将三个坐标轴分别填入前三列,原点的位置填入最后一列即可。
有了正向变换,逆变换就只需要求出对应的逆矩阵即可。需要注意的是,三个坐标轴不一定是单位向量,如果存在缩放的话,这三个方向向量很可能就不是单位向量。
(二)模型空间变换
1、模型空间
模型空间(model space)也被成为对象空间(object space)或局部空间(local space)。它一般指3D模型的局部坐标系,每个模型都有自己独立的坐标空间,模型空间的主要意义是方便我们建模,模型的顶点等数据都是基于模型空间表达的。
Unity 中模型移动或旋转时,模型空间坐标系也会随着变换, 因为此时模型坐标空间是世界坐标空间的子空间。Unity 使用的是左手坐标系,因此模型空间的 x、y、z 轴对应的是模型的右、上、前三个方向。
2、世界空间
世界空间(world space)是一个特殊的坐标系,它建立了我们所关心的最大的空间,这里的最大指的是我们所关心的最外层的坐标空间。Unity 中,世界空间用的是左手坐标系,且 x、y、z 轴固定不变。
3、模型空间变换
顶点变换第一步,就是将顶点坐标从模型空间变换到世界空间中,这个变换通常叫模型空间变换。
如何进行模型空间变换呢?我们先举个例子,假设该机器人模型的手掌红点位置处于模型坐标空间下的 (0.63, 0.84, -0.04) 点,如果模型位于世界坐标原点,当前的红点位置相对世界空间也会是 (0.63, 0.84, -0.04)。

但是当模型进行缩放、旋转、平移时,该点相对世界空间就会变化。比如我们将模型进行 2 倍缩放,又进行 (0, 45, 0) 的旋转,然后再进行 (5, 0, 5) 的平移,那么红点的相对世界空间坐标是多少呢?这个答案便是模型空间变换的结果。这里有两种方式可以求,下面就来讲。

(1)复合运算
根据我们之前学习的复合运算相关的知识,进行复合运算时,一定遵守先缩放、后旋转、再平移的规则。那么求得的变换矩阵就是:
\begin{align*}
M_{model} &=
\begin{bmatrix}
1 & 0 & 0 & 5 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 5 \\
0 & 0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
\cos45^\circ & 0 & \sin45^\circ & 0 \\
0 & 1 & 0 & 0 \\
-\sin45^\circ & 0 & \cos45^\circ & 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}
1.414 & 0 & 1.414 & 5 \\
0 & 2 & 0 & 0 \\
-1.414 & 0 & 1.414 & 5 \\
0 & 0 & 0 & 1
\end{bmatrix}
\end{align*}
之后运用这个变换矩阵即可求得结果。为什么这样可以呢,因为一开始模型坐标空间和世界坐标空间重合,模型发生缩放、旋转、平移变换时,模型空间下的点和向量也应该发生相同的变换。
注:当存在多层父子关系时,可以一层层往上计算,或者直接使用 Unity 中 Transform 的 position、rotation、lossyScale 变量进行计算,因为这些变量可以直接获取在世界空间的位置。
(2)坐标空间变换
我们可以使用上节的坐标空间变换的公式来计算,需要注意的是如果存在缩放,只需要用 x、y、z 轴向的单位向量 × 对应轴的缩放因子即可。
我们可以使用这几个属性获取到对应轴和原点位置:
print(this.transform.right); // (0.71, 0.00, -0.71)
print(this.transform.up); // (0.00, 1.00, 0.00)
print(this.transform.forward); // (0.71, 0.00, 0.71)
print(this.transform.position); // (5.00, 0.00, 5.00)
之后即可获取到对应的变换矩阵:
\begin{align*}
M_{model} =
\begin{bmatrix}
0.71 \times 2 & 0 & 0.71 \times 2 & 5 \\
0 & 1 \times 2 & 0 & 0 \\
-0.71 \times 2 & 0 & 0.71 \times 2 & 5 \\
0 & 0 & 0 & 1
\end{bmatrix} =
\begin{bmatrix}
1.414 & 0 & 1.414 & 5 \\
0 & 2 & 0 & 0 \\
-1.414 & 0 & 1.414 & 5 \\
0 & 0 & 0 & 1
\end{bmatrix}
\end{align*}
(三)观察空间变换
1、观察空间
观察空间(view space)也被称为摄像机空间(camera space),观察空间可以认为是一个特殊的模型空间,这里的模型指的是场景中的摄像机,摄像机可以认为是一个非常特殊的模型,虽然通常来说摄像机本身不可见,它可以决定我们在屏幕上看到的内容,因此摄像机的模型空间值得单独提出来讨论,并将它称为观察空间。 观察空间的主要意义是摄像机决定了渲染的视角和视野。
2、观察空间注意事项
在模型空间中,我们讲过模型空间的 x、y、z 轴对应的是模型的右、上、前三个方向。因为 Unity 的模型空间遵循左手坐标系原则。但是在Unity的观察空间中,观察空间是遵循右手坐标系原则的,因此它的坐标轴方向有所不同。即观察空间中的 x、y、z 轴的正方向分别对应摄像机的右、上、后方。
主要原因就是在计算机图形学的 OpenGL 中,处理场景中物体的渲染和投影通常使用右手坐标系,为了符合传统,方便之后的数据处理,Unity 中的观察空间也遵循右手坐标系。
3、观察空间变换
顶点变换第二步,就是将顶点坐标从世界空间变换到观察空间中,这个变换通常叫观察空间变换。
如何进行观察空间变换呢?还是上节的例子,我们通过模型变换的变换矩阵,计算出该顶点在世界空间下的坐标为 (5.834, 1.68, 4.052),接下来就是将坐标从世界空间转换到观察空间。

这里的思路也很简单,其实就是和上节的变换反过来,上节我们是求出模型空间到世界空间的矩阵 M_{c→p},即子空间到父空间的变换矩阵;而这里我们就是求出父空间到子空间的变换矩阵 M_{p→c},也就是求出 M_{c→p} 的逆矩阵,这里也是有两种思路。然后摄像机的 Transform 如图:

(1)复合运算
我们想象平移整个观察空间,让摄像机原点位于世界坐标原点,也就是和世界坐标重合。这样复合运算需要反过来,先反向平移、后反向旋转、再反向缩放。(当然这里没缩放)
\begin{align*}
M_{view} &=
\begin{bmatrix}
\cos(-10°) & 0 & \sin(-10°) & 0 \\
0 & 1 & 0 & 0 \\
-\sin(-10°) & 0 & \cos(-10°) & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & -1 \\
0 & 0 & 1 & 2.76 \\
0 & 0 & 0 & 1
\end{bmatrix} \\ &=
\begin{bmatrix}
0.98 & 0 & -0.17 & -0.46 \\
0 & 1 & 0 & -1 \\
0.17 & 0 & 0.98 & 2.7 \\
0 & 0 & 0 & 1
\end{bmatrix}
\end{align*}
但是还没完,我们知道观察空间是右手坐标系,因此需要对 z 分量进行取反操作,才能得到最终的变换矩阵:
\begin{align*}
M_{view} &=
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & -1 & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
0.98 & 0 & -0.17 & -0.46 \\
0 & 1 & 0 & -1 \\
0.17 & 0 & 0.98 & 2.7 \\
0 & 0 & 0 & 1
\end{bmatrix} \\ &=
\begin{bmatrix}
0.98 & 0 & -0.17 & -0.46 \\
0 & 1 & 0 & -1 \\
-0.17 & 0 & -0.98 & -2.7 \\
0 & 0 & 0 & 1
\end{bmatrix}
\end{align*}
(2)坐标空间变换
我们可以使用坐标空间变换的公式来计算,先求出从观察空间到世界空间的变换矩阵(子→父),然后求其逆矩阵得到。先获取到对应轴和原点位置:
print(this.transform.right); // (0.98, 0.00, -0.17)
print(this.transform.up); // (0.00, 1.00, 0.00)
print(-this.transform.forward); // (-0.17, 0.00, -0.98),注意这里是取反哦
print(this.transform.position); // (0.00, 1.00, -2.76)
然后获取到对应的变换矩阵:
M_{c→p} = \begin{bmatrix}
0.98 & 0 & -0.17 & 0 \\
0 & 1 & 0 & 1 \\
-0.17 & 0 & -0.98 & -2.76 \\
0 & 0 & 0 & 1
\end{bmatrix}
再求其逆矩阵得到结果:
\begin{align*}
M_{view} &=
\begin{bmatrix}
0.98 & 0 & -0.17 & 0 \\
0 & 1 & 0 & 1 \\
-0.17 & 0 & -0.98 & -2.76 \\
0 & 0 & 0 & 1
\end{bmatrix}^{-1} =
\begin{bmatrix}
0.99 & 0 & -0.1718 & -0.474 \\
0 & 1 & 0 & -1 \\
-0.1718 & 0 & -0.99 & -2.733 \\
0 & 0 & 0 & 1
\end{bmatrix}
\end{align*}
这里和上面那种方式计算有些误差,这是正常的,因为计算过程不同和四舍五入等原因会造成精度误差。
(四)裁剪空间变换——基本概念
1、视锥体
摄像机的视锥体在三维空间中表示摄像机可见区域的虚拟体积,它类似一个六面体的形状,根据摄像机的属性和投影方式而定。视锥体定义了摄像机在场景中能够看到的物体区域,超出这个区域的物体将在渲染时被裁减掉,从而提高渲染性能。视锥体的 6 块裁剪平面中,有两块裁剪平面比较特殊,分别是近剪裁平面和远剪裁平面,它们决定了摄像机可以看到的深度范围。
- 透视投影中,视锥体类似一个金字塔形状,远裁剪面比近裁剪面大,所以产生透视效果。
- 正交投影中,视锥体类似于长方体的形状,远近裁剪平面大小一致,不会产生透视效果。

之前提到,我们希望根据视锥体围成的区域对顶点等数据进行裁剪,只保留视锥体范围内的坐标。 但是,如果直接使用视锥体定义的空间来进行裁剪,不同的视锥体就需要不同的处理过程,对于透视投影的视锥体来说,判断顶点是否在范围内相对较麻烦。因此我们希望用更通用便捷的方式来进行裁剪工作,就需要将观察空间中的数据转换到齐次裁剪空间中。
2、齐次裁剪空间
顶点变换第三步,就是将顶点坐标从观察空间变换到齐次裁剪空间中,这个变换通常叫裁剪空间变换。
裁剪空间也被称为齐次裁剪空间,这是一个非常特殊的坐标空间,它是一个三维空间,在计算机图形学中用于在图形渲染过程中进行裁剪和投影的。它的坐标范围为 (-1, -1, -1) 到 (1, 1, 1),超出这个范围的坐标在渲染时会被裁减掉,只会保留范围内的坐标。
之后我们会学习视锥体的透视投影和正交投影对应的裁剪矩阵(投影矩阵),通过裁剪矩阵我们就可以将观察空间中的数据转换到齐次裁剪空间中了。

注:严格意义上来说、这里所谓的将坐标 x,y,z 范围映射到 [-1,1] 之间的这个空间,应当称作 NDC 空间,也就是归一化的设备坐标空间。NDC 空间和齐次裁剪空间并不是一个空间,NDC 空间是由裁剪空间归一化范围得到的。这个在总结时会提到。
3、摄像机视锥体的投影方式
- 透视投影:视锥体内顶点和原点连接,在近裁剪面的交点为投影点。
- 正交投影:视锥体内顶点向近裁剪面做左右裁剪面平行线,在近裁剪面交点为投影点。

4、相似三角形的重要性质
这里只需要掌握相似三角形的两个比较重要的性质即可,后面会用上的:
- 相似三角形的对应角相等:∠A = ∠A’,∠B = ∠B’,∠C = ∠C’。
- 相似三角形的对应边成比例:a/a’= b/b’= c/c’= k,这里 k 称为相似比。
(五)裁剪空间变换——正交投影变换
1、正交投影的重要参数
- Projection:参数为 Orthographic 时,为正交摄像机。
- Size:视锥体竖直方向上高度的一半。
- Clipping Planes:远近裁剪平面。远裁剪面这里叫 Far ,近裁剪面叫 Near。
- Aspect:可通过
camera.aspect属性获取宽高比,即宽/高的值。

通过以上信息,我们就可以获取到正交投影的长宽高的属性了:
- 高 = 2 * Size
- 宽 = Aspect * 高 = 2 * Aspect * Size
- 长 = Far - Near
2、正交投影变换矩阵
该变换的目标是将观察空间中摄像机的正交投影转换到齐次坐标裁剪空间,即求出正交投影的裁剪矩阵。求出该矩阵分两步:
- 将视锥体中心位移到观察空间原点中心。
- 将长方体视锥体的 xyz 坐标范围映射到 [-1,1] 长宽高为2的正方体中。

(1)将视锥体中心位移到观察空间原点中心

这步我们要求的就是平移矩阵,这里很容易得知视锥体中心点坐标的 z 轴为:\frac{(-Near) + (-Far)}{2},因此只需要将视锥体往 z 轴正方向移动 -\frac{(-Near) + (-Far)}{2} 即可,因此最终的平移矩阵为:
T =
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & \dfrac{Far+Near}{2} \\[4pt]
0 & 0 & 0 & 1
\end{bmatrix}
(2)将长方体视锥体的 xyz 坐标范围映射到 [-1,1] 长宽高为2的正方体中

这步我们要求的就是缩放矩阵,所以需要求出 xyz 三个轴对应的一个缩放比例,将各分量映射到 [-1, 1] 的范围内。这里我们可以通过视锥体和最终的正方体的线性关系求出,由于只需要知道缩放比例,因此只需要关心斜率 k 即可,下面是三个轴对应的比例关系,需要注意的是z轴最后需要取反,转成左手坐标系。
如果看不惯下面这种方式,可以用 目标长度 / 当前长度 计算比例,比如计算x轴时,目标长度是 2,当前长度为2 * Aspect * Size,相除即可出结果。

这样我们就可以得到最终的缩放矩阵为:
S =
\begin{bmatrix}
\dfrac{1}{Aspect \cdot Size} & 0 & 0 & 0 \\[4pt]
0 & \dfrac{1}{Size} & 0 & 0 \\[4pt]
0 & 0 & -\dfrac{2}{Far - Near} & 0 \\[4pt]
0 & 0 & 0 & 1
\end{bmatrix}
(3)求出最终的投影变换矩阵
我们前两步已经求出了平移和缩放矩阵,接下来相乘即可获得最终的正交投影的裁剪矩阵。
M_{ortho} =
S \cdot T =
\begin{bmatrix}
\dfrac{1}{Aspect \cdot Size} & 0 & 0 & 0 \\
0 & \dfrac{1}{Size} & 0 & 0 \\
0 & 0 & -\dfrac{2}{Far-Near} & -\dfrac{Far+Near}{Far-Near} \\
0 & 0 & 0 & 1
\end{bmatrix}
(六)裁剪空间变换——透视投影变换
1、透视投影的重要参数
- Projection:参数为 Perspective 时,为透视摄像机。
- FOV(Field of View):决定视锥开口角度。
- Clipping Planes:远近裁剪平面。远裁剪面这里叫 Far ,近裁剪面叫 Near。
- Aspect:可通过
camera.aspect属性获取宽高比,即宽/高的值。

通过以上信息,我们就可以获取到透视投影的宽高的属性了:
- 近裁剪面高 = 2 * Near * \tan(\frac{FOV}{2})
- 远裁剪面高 = 2 * Far * \tan(\frac{FOV}{2})
- 近裁剪面宽 = Aspect * 近裁剪面高 = 2 * Aspect * Near * \tan(\frac{FOV}{2})
- 远裁剪面宽 = Aspect * 远裁剪面高 = 2 * Aspect * Far * \tan(\frac{FOV}{2})
2、透视投影变换矩阵
该变换的目标是将观察空间中摄像机的透视投影转换到齐次坐标裁剪空间,即求出透视投影的裁剪矩阵。求出该矩阵分三步,只比正交投影矩阵前面多了一步,后面两步都是一样的:
- 将透视视锥体变成一个长方体,之后即可将该长方体进行正交投影变换的操作。
- 将视锥体中心位移到观察空间原点中心。
- 将长方体视锥体的 xyz 坐标范围映射到 [-1,1] 长宽高为2的正方体中。

(1)将透视视锥体变成一个长方体
这一步就是将透视投影变成平行投影,也就是求出“挤压”的变换矩阵 M_{persp\rightarrow ortho}。上面那个图的这一步有点错误,正确的变换后的图如下,变换后的长方体满足如下特性:
- 近裁剪面上的所有点保持不变,远平面缩小到和近平面一样大,即 x 和 y 值进行一定程度的缩放,使之变成和正交投影一样的长方体。
- 远裁剪面的 z 值不变,远裁剪面的中心点位置不变。
注:除了近裁剪面和远裁剪面的 z 值不变外,中间位置的 z 值是会变化的。至于为什么,我在games101的笔记中透视投影那里有说明我的看法。

根据上面的特征,我们可以确定的是 xy 的一个缩放因子,而 z 值目前不能确认,暂时设为 unknown,因此变换以及变换后的结果应该是这样的:
M_{persp\rightarrow ortho}
\begin{bmatrix}
x \\
y \\
z \\
1
\end{bmatrix} =
\begin{bmatrix}
x\frac{-Near}{z} \\
y\frac{-Near}{z} \\
unknown \\
1
\end{bmatrix}

而这样的话矩阵肯定会和 z 相关,我们希望得到的是一个通用的矩阵,因此根据齐次坐标的一个性质,我们将齐次坐标全部乘以 -z,三维空间中仍然表示该点,同时可以消去 z,于是最终结果变成了:
\begin{bmatrix}
x\cdot Near \\
y\cdot Near \\
unknown \\
-z
\end{bmatrix}
这样我们立马就可以确定这个矩阵的一些值了:
M_{persp\rightarrow ortho} =
\begin{bmatrix}
Near & 0 & 0 & 0 \\
0 & Near & 0 & 0 \\
? & ? & ? & ? \\
0 & 0 & -1 & 0
\end{bmatrix}
接下来的目标就是求第三行的四个未知数,首先前两个数一定是0,因为 z 和 xy 无关,而后两个就是我们要求的未知数,设置为 a 和 b 吧。
M_{persp\rightarrow ortho} =
\begin{bmatrix}
Near & 0 & 0 & 0 \\
0 & Near & 0 & 0 \\
0 & 0 & a & b \\
0 & 0 & -1 & 0
\end{bmatrix}
怎么解 a 和 b 呢?这就需要利用前面提到的一些性质了:
- 近裁剪面的所有点保持不变:
M_{persp\rightarrow ortho} \begin{bmatrix} x \\ y \\ -Near \\ 1 \end{bmatrix} = \begin{bmatrix} x \\ y \\ -Near \\ 1 \end{bmatrix} \overset{\times Near}{==} \begin{bmatrix} x\cdot Near \\ y\cdot Near \\ -Near^2 \\ Near \end{bmatrix}
那么变换矩阵第三行就有这么一个等式:
-aNear + b = -Near²
- 远裁剪面的中心点保持不变:
M_{persp\rightarrow ortho} \begin{bmatrix} 0 \\ 0 \\ -Far \\ 1 \end{bmatrix} = \begin{bmatrix} 0 \\ 0 \\ -Far \\ 1 \end{bmatrix} \overset{\times Far}{==} \begin{bmatrix} 0 \\ 0 \\ -Far^2 \\ Far \end{bmatrix}
那么同样可以得到这样一个等式:
-aFar + b = -Far²
两个等式联立即可解得:
a = Far + Near,\quad b = Far\cdot Near
所以得到的变换矩阵为:
M_{persp\rightarrow ortho}=
\begin{bmatrix}
Near & 0 & 0 & 0 \\
0 & Near & 0 & 0 \\
0 & 0 & Far+Near & Far\cdot Near \\
0 & 0 & -1 & 0
\end{bmatrix}
(2)将视锥体中心位移到观察空间原点中心
这个没啥好说的,直接可以算出来,平移矩阵为:
T=
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & \dfrac{Far+Near}{2} \\
0 & 0 & 0 & 1
\end{bmatrix}
(3)将长方体视锥体的 xyz 坐标范围映射到 [-1,1] 长宽高为2的正方体中
和正交投影一样,我们同样可以求出三个方向的一个缩放比例,宽高用近裁剪面的就行,所以最终的缩放矩阵就是:
S =
\begin{bmatrix}
\dfrac{1}{Aspect \cdot Near \cdot \tan\left(\frac{FOV}{2}\right)} & 0 & 0 & 0 \\
0 & \dfrac{1}{Near \cdot \tan\left(\frac{FOV}{2}\right)} & 0 & 0 \\
0 & 0 & -\dfrac{2}{Far-Near} & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
(4)求出最终的投影变换矩阵
我们前三步已经求出了挤压、平移和缩放矩阵,接下来相乘即可获得最终的透视投影的裁剪矩阵。
M_{persp}=S \cdot T \cdot M_{persp\rightarrow ortho}=
\begin{bmatrix}
\dfrac{1}{Aspect \cdot \tan\left(\frac{FOV}{2}\right)} & 0 & 0 & 0 \\
0 & \dfrac{1}{\tan\left(\frac{FOV}{2}\right)} & 0 & 0 \\
0 & 0 & -\dfrac{Far+Near}{Far-Near} & -\dfrac{2\cdot Far\cdot Near}{Far-Near} \\
0 & 0 & -1 & 0
\end{bmatrix}
(七)裁剪空间变换——总结
1、意义
我们已经知道齐次裁剪空间是通过将摄像机的视锥体投影到一个规范化的立方体而来的,这个立方体就是齐次裁剪空间。如果直接使用视锥体定义的空间来进行裁剪,那不同的视锥体就需要不同的处理过程,比较麻烦,因此裁剪变换的意义就是可以让我们可以更通用、便捷的来进行裁剪工作。
2、如何确定顶点被裁剪
我们学习了正交投影和透视投影的变换矩阵,这样就可以决定一个顶点是否被裁剪。还是以之前的顶点为例:
- 模型空间下的顶点坐标为 (0.63, 0.84, -0.04)。
- 经过模型空间变换,该顶点的世界空间下的坐标为 (5.834, 1.68, 4.052)。
- 经过观察空间变换,该顶点的观察空间下的坐标为 (4.562, 0.679, -7.722, 1)。
- 经过裁剪空间变换,该顶点在裁剪空间下的坐标就决定了是否被裁剪。
我们分正交和透视两种情况讨论。
(1)正交投影
经过正交投影变换,该顶点坐标为:
M_{ortho}v =
\begin{bmatrix}
0.513225 \\
0.1358 \\
0.492284 \\
1
\end{bmatrix}
我们只需判断点是否在立方体内即可:
-1 ≤ x ≤ 1 \\
-1 ≤ y ≤ 1 \\
-1 ≤ z ≤ 1
(2)透视投影
经过正透视投影变换,该顶点坐标为:
M_{persp}v =
\begin{bmatrix}
4.446 \\
1.176 \\
7.2142 \\
7.722
\end{bmatrix}
由于 w 不为 0 和 1,因此真正的坐标应该都除以 w,判断点是否在立方体内也可以这么判断(不除以 w):
-w ≤ x ≤ w \\
-w ≤ y ≤ w \\
-w ≤ z ≤ w
3、NDC 空间
在之前讲裁剪空间时,为了使知识点更容易理解,省略了一个 NDC 空间的概念,因此现在进行补充。
NDC 空间(Normalized Device Coordinates)叫做归一化设备坐标空间,它就是我们一直提到的 xyz 坐标范围在 [-1, 1] 之间的,长宽高为 2 的正方体。

它和齐次裁剪空间的关系也不复杂,在裁剪空间中:
- 正交投影摄像机在 NDC 空间下的坐标就是齐次裁剪空间下的坐标,因为此时坐标范围就是 [-1, 1] 之间的。
- 透视投影摄像机在 NDC 空间下的坐标就是齐次裁剪空间下的坐标除以坐标的 w 分量。而这个除以 w 分量的步骤也叫做齐次除法,或者叫透视除法。
所以说 NDC 空间和齐次裁剪空间是两个概念,当然这个很好理解,经过齐次除法之后,透视投影和正交投影的视锥体才都能变换到同一个立方体内。
(八)屏幕空间变换
1、屏幕空间
屏幕空间(Screen Space)是计算机图形学中的概念,指的是渲染结果在屏幕上显示的二维空间。屏幕空间中对应的位置的信息是真正的像素位置,而不是虚拟的三维坐标,有了相对屏幕空间的坐标位置,才能准确的控制屏幕上像素点的显示效果。
2、屏幕空间的注意事项
在Unity中,屏幕空间左下角为像素坐标原点 (0,0),屏幕空间右上角为像素坐标最大点 (分辨率宽,分辨率高)。
3、屏幕空间变换
顶点变换第四步,就是将顶点坐标从裁剪空间变换到屏幕空间中,这个变换通常叫屏幕空间变换。
这步是四个空间变换里最简单的一个,主要是将三维坐标中的 x,y 分量映射到屏幕上,而z分量一般会被用于深度缓冲、深度测试等。

(1)透视除法
由于正交投影后 w 分量为1,而透视投影后的 w 分量则不一定为1,因此我们需要先进行透视除法进行归一化。
假设裁剪空间中的点为(X_{clip}, Y_{clip}, Z_{clip}, W_{clip})。进行透视除法后的值应该为:
(\frac{X_{clip}}{W_{clip}}, \frac{Y_{clip}}{W_{clip}}, \frac{Z_{clip}}{W_{clip}}, 1)
这样就能确保 xyz 都是属于 [-1, 1] 之间的了。
(2)映射到屏幕位置
将 xy 坐标映射到屏幕中很简单,无非就是进行一个缩放和平移。设屏幕宽高分别为 width 和 height,这里可以直接算出 xy 的两个变换公式:
\begin{aligned}
X_{screen} &=
\frac{Width}{2} * X_{齐} + \frac{Width}{2} \\ &=
\frac{Width * X_{clip}}{2 * W_{clip}} + \frac{Width}{2}
\end{aligned}
\begin{aligned} Y_{screen} &= \frac{Height}{2} * Y_{\text{齐}} + \frac{Height}{2} \\ &= \frac{Height * Y_{clip}}{2 * W_{clip}} + \frac{Height}{2} \end{aligned}
对裁剪空间中的坐标进行公式计算就可以获取屏幕上对应点了。
4、关于其他两个分量
屏幕变换只用到了 xy,那么 z 和 w 怎么处理呢?这就涉及之后的内容了:
- Z:\frac{Z_{clip}}{W_{clip}} 会被存入深度缓冲中(不同硬件商存储方式可能不同),之后用于深度测试等计算。
- W:W_{clip} 也会被保留下来,之后进行一些计算才有用,比如透视校正差值。
六、阶段性总结
1、关于 Shader 开发
我们学习 Shader 开发主要是针对渲染管线中的两个小阶段:
- 几何阶段——顶点着色器:最基本的任务就是把顶点坐标从模型空间转换到裁剪空间中。
- 光栅化阶段——片元着色器:可以得到片元在屏幕空间的像素位置。
通过在这两个小阶段中对渲染管线的数据进行自定义处理来决定最终的渲染效果。
2、关于本章节
开篇时提到过,学习Shader开发,主要学习的是数学相关知识、语法相关知识、着色器开发相关知识等,目前我们已经完成了数学相关知识的学习,可以通过三角函数、向量、线性代数等知识对顶点、向量、矩阵进行变换。
我们在这一部分主要学习了如何将模型的顶点数据变换到屏幕坐标中。变换主要有四个步骤:模型空间→世界空间→观察空间→裁剪空间→屏幕空间。
在 Shader 开发中会频繁使用到数学相关的知识点,虽然空间变换有相应的API,但是内部的实现原理还是需要了解的。
注意
- 在这几个空间中,除了观察空间是基于右手坐标系的,其他都是左手坐标系。
- 这里只展示了最重要的坐标空间和变换,还有一些空间或变换也会用到,比如切线空间和法线映射,后面用到了会讲。
