---
title: "矩阵与坐标转换"
author: "Perrin Yong"
author_profile: https://www.pystone.net/profile/
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/matrix-coordinate-transform/
type: note
content_role: unspecified
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/05-游戏图形与运行时/渲染基础/矩阵与坐标转换.md"
content_hash: c8d2cefbb46e84877b3919fba2176c2caf9603dc01c0226dfc51b235555a88e9
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
# 矩阵与坐标转换

> 创建时间：2021/3/25 16:44

  * 矩阵与坐标转换
    * 基本坐标变换
      * 线性变换（linear transform）
      * 仿射变换（affine transform）
        * 平移
        * 缩放
        * 旋转
        * 复合
    * 坐标空间变换
      * 坐标空间变换基础
      * 渲染管线中的坐标空间
        * 模型空间
        * 世界空间(world space)
        * 观察空间(view space) space)
        * 裁剪空间（clip space）
        * NDC空间
        * 屏幕空间（screen space）与视口空间（viewport space）
      * 投影矩阵变换
        * 正交投影（orthographic projection）
          * 投影矩阵
          * 矩阵的作用结果
          * 矩阵的推导
        * 透视投影（perspective projection）
          * 投影矩阵
          * 矩阵的作用结果
          * 如何理解
          * 矩阵的推导
      * 法线变换
    * Ref

对任意坐标向量x ，矩阵变换的组合M1M22M3…Mn 作用于x存在两种解释：

  * 坐标系不变，位置变换

  * 位置不变，坐标系变换

## 基本坐标变换

> 变换（transform）：把一些数据，如点、向量甚至颜色等，通过某种方式进行转换的过程。

### 线性变换（linear transform）

  * 可以保留矢量加和标量乘的变换

![Alt text](/media/80a9bb9ef648602545c6.png)

  * 保持网格平行且等距分布的变换

> 3x3矩阵可以表示所有的对三维矢量的线性变换

  1. 缩放 - scale

  2. 错切 - shear

  3. 镜像 - mirroring / reflection

  4. 正交投影 - orthographic projection

### 仿射变换（affine transform）

> **平移变换** \- 不是线性变换，不满足标量乘法与矢量加法

**仿射变换** \- 合并线性变换和平移变换的变换类型
可以用4x4的矩阵表示。

需要把矢量扩展到四维空间下 - **齐次坐标空间（homogeneous coordinate space）**

> 转齐次坐标：
>  点 - 把w分量设置为1
>  向量 - 把w分量设置为0

仿射变换矩阵:
\- 表示旋转和缩放
\- 表示平移

#### 平移

平移点

![Alt text](/media/4e21f6b372999c13d0b9.png)

平移向量

![Alt text](/media/81e28b56406ed2340819.png)

逆矩阵

![Alt text](/media/2aa6db7e0262b17e0fb3.png)

#### 缩放

![Alt text](/media/d7ea8183ea423cc8108d.png)

![Alt text](/media/a045c885b3d43245e22e.png)

统一缩放（uniform scale） -
不会改变角度和比例信息

非统一缩放（nonuniform scale） - 拉伸或挤压，改变模型相关角度和比例，影响到法线

逆矩阵

![Alt text](/media/3622030b43afd4f4ea03.png)

#### 旋转

绕x

![Alt text](/media/e3a3441fe185f4ac8340.png)

绕y

![Alt text](/media/7906f31518251721546a.png)

绕z

![Alt text](/media/f6883ad853b39388a00a.png)

旋转顺序：
当给出
这样的旋转角度时

  * Unity旋转顺序为 - 绕固定坐标系，z->x->y

  * 绕坐标系E下的z轴旋转θz, 在坐标系E下绕z轴旋转θz后的新坐标系E’下的y轴旋转θy，在坐标系E’下绕y轴旋转θy后的新坐标系E”下的x轴旋转θx，即在旋转时，把 坐标系一起转动。

以上两种方式旋转结果相同。

![Alt text](/media/c5e134ddceb9f444c073.png)

#### 复合

**矩阵乘法不满足交换律，变换结果依赖变换顺序**
一般情况：缩放->旋转->平移

## 坐标空间变换

### 坐标空间变换基础

定义一个坐标空间，必须指名其原点位置和3个坐标轴的方向。数值上相对于另一个坐标空间。
每个坐标空间都是另一个坐标空间的子空间，每个空间都有一个父坐标空间。对坐标空间的变换实质上就是在父空间和子空间之间对点和矢量进行变换。

![Alt text](/media/752f297e011678c691a9.png)

已知点在子坐标空间c中点
，求A在父空间p中的坐标：
c的3个坐标轴在p空间中的坐标表示为
![Alt text](/media/2530c3fa30901ffb19de.png)

![Alt text](/media/39d4981640a0b56af8c7.png)

![Alt text](/media/6f734f0b1acce4324bcf.png)

平移变换对矢量没有影响，因此在Shader中可以通过变换矩阵的前3行和前3列来对法线方向和光照方向等进行空间变换。

如果矩阵是正交矩阵，则求逆操作就是求转置操作：

![Alt text](/media/04a2dafd7106a0036edc.png)

如果知道子坐标空间的x、y、z轴在父空间下的坐标表示（必须是正交的单位矢量），就可以把他们依次放在矩阵的每一行，就可以得到从父坐标空间到子空间的变换矩阵了。

> 思考：这种求法是已知坐标系c的三个轴在坐标系p下的表示才能求得c到p的变换矩阵，如果不知道这个表示关系如何求变换矩阵呢？
>  事实上，这个命题是不存在的。坐标系本身就是相对定义的，多个坐标系之间本身就是存在父子关系的。先相对某种尺度定义一个坐标系，在有一个坐标系的基础上，才能定义另外一个坐标系，而两个坐标系定义出来后，他们的坐标轴的相对关系就是已知的。没有这种已知的坐标轴的相对关系，就无法定义坐标系。只是有可能两个坐标系之间的关系隔了好几个坐标系，那需要做好几个坐标转换，才能从一个坐标系转换到另外一个坐标系。

### 渲染管线中的坐标空间

![Alt text](/media/79ff3ee882075a5b7eb4.png)

#### 模型空间

  * 模型空间（model space） | 对象空间（object space） | 局部空间（local space）
自然方向：前(forward), 后(back), 左(left), 右(right), 上(up), 下(down)
Unity模型空间使用左手坐标系：

![Alt text](/media/f414addd8a32788bcd99.png)

#### 世界空间(world space)

我们所关心的最外层的空间
Unity中，Transform.Position指的是相对于这个transform的父节点(parent)的模型坐标空间中原点的位置。如果一个Transform没有父节点，这个位置就是世界坐标系中的位置。

**模型变换（Model transform）：模型空间- >世界空间**

#### 观察空间(view space) space)

观察空间(view space) ，摄像机空间(camera space)

右手坐标系，+z轴指向摄像机后方，符合OpenGL传统。
观察变换（view transform）：世界空间-> 观察空间

#### 裁剪空间（clip space）

齐次裁剪空间（clip space）

#### NDC空间

**归一化的设备坐标（Normalized Device Coordinates, NDC）** ：用齐次坐标的w分量去除x、y、z分量，得到。

齐次除法（homogeneous division） | 透视除法（perspective division）

经过透视投影变换后的裁剪空间，经过齐次除法后会变换到一个立方体内。

> OpenGL传统（Unity），z分量的范围是[-1, 1]。DirectX中，z的范围是[0, 1]。

#### 屏幕空间（screen space）与视口空间（viewport space）

**屏幕坐标：** Unity中，屏幕左下角像素坐标是(0, 0)，右上角像素坐标是(pixelWidth, pixelHeight)。
z分量用于深度缓冲。
Unity中，裁剪空间->屏幕空间的过程是由底层帮我们完成的。

**视口空间（viewport space）坐标：** 将屏幕空间坐标归一化，屏幕坐标除以分辨率，得到视口空间坐标。屏幕左下角是(0, 0)，右上角是(1, 1)。

### 投影矩阵变换

**裁剪矩阵（clip matrix） | 投影矩阵（projection matrix）** 由视锥体（view frustum）决定

投影矩阵的目的：

  1. 为投影做准备。经过投影矩阵变换后，顶点的w分量将会具有特殊的意义。

投影矩阵并不进行投影。真正地投影过程会在 **屏幕映射** 时发生，通过 **齐次除法（homogeneous division）** 得到二维坐标。

  2. 对x、y、z分量进行缩放。

#### 正交投影（orthographic projection）

保留了物体的距离和角度，用于2D游戏或渲染小地图等其他HUD元素

> HUD（Head-Up Display）来源于航空，在飞行器中，HUD 指投射到挡风玻璃上的读数，让飞行员可以不用低头就可以看到这些信息。
>
![Alt text](/media/c45aa95a8c7469b510ad.png)

##### 投影矩阵

![Alt text](/media/79a08f605aa9a5333378.png)

##### 矩阵的作用结果

视口空间下，将w的值设置为1。
作用前：
z值：近平面为 -Near，远平面为 -Far。

![Alt text](/media/ae5c4de968c07582f2ba.png)

z值：近平面为 -1，远平面为 1，靠近近平面的某个位置为0。
w值：仍然恒为1 - 归一化不进行缩放。

##### 矩阵的推导

#### 透视投影（perspective projection）

![Alt text](/media/02fb78208a1c1e01ed34.png)

##### 投影矩阵

![Alt text](/media/1b30b2588a1d9be0fa3f.png)

本质是对x、y和z分量进行了不同程度的缩放，z分量还做了一个平移。

##### 矩阵的作用结果

视口空间下，将w的值设置为1。
作用前：
z值：近平面为 -Near，远平面为 -Far。

![Alt text](/media/d9734318d0256d065a66.png)

z值：近平面为 -Near，远平面为 Far，靠近近平面的某个位置为0。
w值：近平面为 Near，远平面为 Far - 归一化后，越远的位置缩小的越多。

![Alt text](/media/fd7533bafa977c8624cc.png)

该矩阵会视锥体变换为中心在相机空间原点、边长为2的正方体。
w分量为z的绝对值，表示到相机的距离，表示缩放值。
转换为三维空间坐标(x/w, y/w, z/w)。
顶点在视锥体内，要求
即，若一个顶点在视锥体内，必须满足
##### 如何理解

本质是：求一个非线性变换，使得变换后的视锥体内点的坐标满足一定的条件。

如何理解四维坐标，如何理解w分量？
本质问题是，将一个frustum(截头锥体)压缩成一个正方体，求frustum中的点的坐标到正方体空间中点的坐标的一个映射关系。并用矩阵、线性代数的语言描述这一变换，使得所有的点的变换可以用同一个矩阵乘法进行描述，从而可以让计算机对所有顶点批量处理这一变换。

关键点：这个变换不是线性变换，无法用三维矩阵乘法描述。这个变换对所有点不是统一的，还与点到摄像机的距离有关系，不能对所有的点进行统一的变换，意味着对每个点增加一个属性，来描述点到摄像机的距离属性，从而使得这个变换与距离有关，这个属性就是点的w分量。

引入w分量之后，点的坐标扩充到四维，变换矩阵是4x4的矩阵。使用四维矩阵进行投影变换本质上是使用四维空间下的线性变换描述三维空间下的非线性变换（将frustum“捏”成正方体的变换）。
引入w分量后，实际上将一个点映射成了无数个点。我们定义了三维空间中点(x, y, z)对应的齐次坐标为(wx, wy, wz, w),(w!=0)。

之前“捏”frustum的操作，用三维空间线性变换是无法描述的，用3x3矩阵乘法进行变换是无法达到要求的。我们发现，当扩充一个维度之后，看待这个问题，之前的一些”无理要求”是可以满足的。

按照把frustum“捏”成正方体的条件，建立方程，在齐次空间下求这个映射函数，求出了满足要求的变换矩阵。变换之后，点的坐标是下图右边这个样子：

![Alt text](/media/e65a5e181a6c80128583.png)

再映射到三维空间的点就是这个样子：

![Alt text](/media/eaab5c735209bd920ffc.png)

这样一来，我们对所有的点都可以进行这个变换过程:

  1. 映射到齐次坐标空间（增加一个w分量，变为四维坐标）

  2. 按照我们对视锥体进行形变的要求，求得4x4变换矩阵。

  3. 齐次空间下做统一的缩放、平移变换，这个变换过程中，点将到摄像机的距离体现在w分量上面。

  4. 变换后，再将点映射到三维空间，发现原视锥体经变换后成功映射为正方体，其他所有点也经过了相同的映射，结果是合理的。

##### 矩阵的推导

### 法线变换

  * 法线（normal）| 法矢量（normal vector）

  * 切线（tangent）|切矢量（tangent vector） - 通常与纹理空间对齐，与法线方向垂直

原坐标空间下：
得到
* 变换只包括旋转变换 => 变换矩阵是正交矩阵 =>
=> 直接用顶点变换矩阵来变换法线

  * 包含旋转和统一缩放 =>
* 包含非统一缩放 => 必须求解逆矩阵

## Ref

《Unity Shader入门精要》 - 冯乐乐
