三维旋转的符号问题

旋转矩阵的符号差异源于坐标系的手系规则和旋转方向定义。

首先是我们最常规的绕着z轴旋转,这是右手系下的标准定义,符合"x轴转向y轴"的正方向。

\[\mathbf{R}_z(\alpha) = \begin{pmatrix}
\cos \alpha & -\sin \alpha & 0 & 0 \\
\sin \alpha & \cos \alpha & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}\]

这个时候,我们通常都把x轴与y轴线性变换之后所形成的角度设为\(\alpha\) ,不同角的设置就会出现不同的符号,我们通常设置角的优先度就是x>y>z,所以就有了下面不同的符号。

  • X轴为基准轴:其旋转矩阵最直观,Y-Z平面完全遵循右手法则
  • Y轴次之:由于Y轴旋转时Z-X平面的定义需保持坐标系一致性,导致项符号反转
  • Z轴最后:作为最常用的旋转轴,其定义需兼容前两者的嵌套结果。
\[\mathbf{R}_x(\alpha) = \begin{pmatrix}
1 & 0 & 0 & 0 \\
0 & \cos \alpha & -\sin \alpha & 0 \\
0 & \sin \alpha & \cos \alpha & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}\]
\[\mathbf{R}_y(\alpha) = \begin{pmatrix}
\cos \alpha & 0 & \sin \alpha & 0 \\
0 & 1 & 0 & 0 \\
-\sin \alpha & 0 & \cos \alpha & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}\]

欧拉角

我们把沿着每个角的旋转角度分开来表示被称之为欧拉角

[!note]

\(\mathbf{R}_{xyz}(\alpha, \beta, \gamma) = \mathbf{R}_x(\alpha) \, \mathbf{R}_y(\beta) \, \mathbf{R}_z(\gamma)\)

这里挖坑:可以去探究下罗德里格斯旋转公式是什么,以及怎么推导的,参考[数学]罗德里格旋转公式(Rodrigues' rotation formula) - 知乎

视图变换

相机放置的标准位置

相机本体放在(0,0,0)上,正方向为-Z方向,正上方向为+Y方向,然后场景移动,而不是相机移动

视图变换

有了这些概念之后我们就应该明白,即便我们要把相机放在世界坐标系的原点上也是世界坐标系移动,使得原点跑到了相机的坐标上,而不是移动相机跑到原点上。那么此刻我们感受到的应该是反客为主,此时相机的坐标就是原点,而世界坐标系才是需要移动的。

既然反客为主,这个时候世界坐标系的原点也不一样了,应该是相机视角下世界坐标系原点的坐标。比如,此刻相机的坐标系为(10,5,3),那么此时原点现对于相机的坐标为 (\(0-10,0-5,0-3\)) ,或者一个物品此时的坐标为(15,7,8),那么这个物品的相对坐标就为:(15-10,7-5,8-3)。

所以想要把世界坐标轴原点移动到相机就得直接减去相对坐标才行。

那么此时的变换矩阵就应该是\(T_{view} = \begin{bmatrix}1 & 0 & 0 & -x_e \\0 & 1 & 0 & -y_e \\0 & 0 & 1 & -z_e \\0 & 0 & 0 & 1\end{bmatrix}\)

当我们有了找个标准的视角之后,如果相机不是标准方向,那么就需要我们把相机的视角旋转到找个标准的位置。我们就要尝试找到找个旋转矩阵。但是这个矩阵肉眼并不好看出来,只能靠其他的办法去求了。

我们虽然不知道怎么直接拿到世界坐标系旋转到详细坐标系的旋转矩阵,但是能知道怎么把相机坐标系的点怎么在世界坐标系来表示:

\[R = \begin{bmatrix} x_{\hat{g}\times\hat{t}} & x_t & x_{-g} & 0 \\ y_{\hat{g}\times\hat{t}} & y_t & y_{-g} & 0 \\ z_{\hat{g}\times\hat{t}} & z_t & z_{-g} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}
\]

那么表示起来就是这样:\(p_{\text{world}} = R \cdot p_{\text{camera}}\) 这里的\(p_{\text{camera}}\) 就是相机坐标的点(基本坐标 x 倍数 = 坐标系下的坐标),那么我们要把世界坐标转换到相机坐标只需要我们用\(R^T R = R R^T = I\) 的性质对其进行转换。

我们把\(p_{\text{world}} = R \cdot p_{\text{camera}}\) 两边同时乘一个\(R^T\) 那么就出现了 \(R^T \cdot p_{\text{world}} = RR^T \cdot p_{\text{camera}}\) 最后结果就是$$R^T \cdot p_{\text{world}} = p_{\text{camera}}$$

所以旋转矩阵就是:

\[R^T=\begin{bmatrix} x_{\hat{g}\times\hat{t}} & y_{\hat{g}\times\hat{t}} & z_{\hat{g}\times\hat{t}} & 0 \\ x_t & y_t & z_t & 0 \\ x_{-g} & y_{-g} & z_{-g} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}
\]

最后再加上平移矩阵,最后的总矩阵就是:

\[\begin{bmatrix} x_{\hat{g}\times\hat{t}} & y_{\hat{g}\times\hat{t}} & z_{\hat{g}\times\hat{t}} & -x_e \\ x_t & y_t & z_t & -y_e \\ x_{-g} & y_{-g} & z_{-g} & -z_e \\ 0 & 0 & 0 & 1 \end{bmatrix}
\]

透视投影与正交投影

正交投影

正交投影应该没什么可说的,很直观,我们这里只看一个东西。

很多时候我们都把一个物体等价缩放到一个2x2的标准正方体里面,如图:

当然整个移动缩放矩阵就很清晰了

透视投影

我们重点还是透视投影。

透视投影的核心概念是远近缩放效应,即物体越远,看起来越小。

透视投影说白了就是个三角形,关系如下:

\[y' = \frac{n}{z} y, \quad x' = \frac{n}{z} x
\]

其中:

  • (x,y,z)是原始三维坐标
  • (x′,y′)是投影后的二维坐标
  • n 是近平面的位置

这个关系说明,投影后的 x′,y′值与z 反比,物体越远 (z 大),投影坐标越小。


这个时候我们希望有一个矩阵在齐次坐标能够表示到把\(\begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix}\) 映射为 \(\begin{pmatrix} \frac{nx}{z} \\ \frac{ny}{z} \\ \text{unknown} \\ 1 \end{pmatrix}\)

让我们一点一点地分析

分析:

为什么z轴的数值是unknown?
  1. 透视投影会改变 zz 的表现方式,在透视投影之后,我们仍然希望能够正确地表示物体的深度 z 。
  2. 这个公式不能简单地设为 \(\frac{n}{z} z\),因为那样会导致 z' 始终是 n,无法正确区分不同深度的物体。我们希望 z' 能保留某种线性变换形式,以便在深度缓冲(Z-buffer)等操作中使用。
变换矩阵的初步构造

为了方便计算我们将整个映射后的矩阵再乘以一个z

\[\begin{pmatrix} nx \\ ny \\ \text{still unknown} \\ z \end{pmatrix}
\]

那么这个变换矩阵就应该是这样:

\[M_{\text{persp}\rightarrow\text{ortho}}\begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} nx \\ ny \\ \text{still unknown} \\ z \end{pmatrix}
\]

所以我们直接看都能看出来,x与y只用乘一个\(n\) ,而最后保证和z的值一样就行,所以就有了初步的结果:

\[M_{\text{persp}\rightarrow\text{ortho}}=\begin{pmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ ? & ? & ? & ? \\ 0 & 0 & 1 & 0 \end{pmatrix}
\]
为什么最后一行为(0,0,1,0)?

我们看到 \(\begin{pmatrix} nx \\ ny \\ \text{still unknown} \\ z \end{pmatrix}\) 的最后一行实际上就是 \(\begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix}\) 的z。

寻找第三行的关系

其中第三个分量 ? 就是新的 z',它必须满足以下要求:

  1. 必须是深度的线性函数,即形如 z′=Az+B
  2. 在近裁剪面 z = n 时,z' 应该保持为 n,以保证近裁剪面不会被误判深度。
  3. 在远裁剪面 z = f 时,z' 应该保持为 f,以保证远裁剪面深度正确。

我们其实早就能发现z的值与xy的值是没有关系的,

所以前面的两个数都会是\((0,0,?,?)\)

我们知道:

[!note] 近平面上的点 (z = n) 不能变动,即:

\[M_{\text{persp}\rightarrow\text{ortho}} \begin{pmatrix} x \\ y \\ n \\ 1 \end{pmatrix} = \begin{pmatrix} x \\ y \\ n^2 \\ 1 \end{pmatrix}
\]

这表明,第三行应该是 \((0 \ 0 \ A \ B)\) 同时满足:\(An + B = n^2\)

[!note] 远平面上的点 (z = f) 不能变动

\[Af + B = f^2
\]

通过解方程:

\[\begin{cases}
An + B = n^2 \\
Af + B = f^2
\end{cases}
\]

得到:

\[\begin{cases}
A = n + f \\
B = -nf
\end{cases}
\]

所以,第三行最终确定为:

\[(0 \ ,0 \ ,(n+f) \ ,-nf)
\]

最终的完整矩阵就为

\[M_{\text{persp}\rightarrow\text{ortho}} = \begin{pmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & -nf \\ 0 & 0 & 1 & 0 \end{pmatrix}
\]

得到了这个投影之后在做一个正交投影就能直接投射到摄像机上

最后的思考题

[!note]

当空间中有一个点,在frustum内,但是不在近平面与远平面上的时候,经过压缩变换之后,这个点在Z轴上的坐标,会变得离近平面更近、还是离远平面更近、还是不变?

这里的意思实际上是想让我们思考:

  • 在视锥体(frustum)内部、但不在近平面或远平面上的一个点,
  • 经过透视投影矩阵的压缩变换后,
  • 该点的 Z 轴坐标是变得更靠近近平面,还是更靠近远平面,还是不变?
解析

这个地方我们的第一反应就应该去求 Z 轴坐标的值,这个地方实际上很好求,因为前面我们已经有了这样一个旋转矩阵了:\(\begin{pmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & -nf \\ 0 & 0 & 1 & 0 \end{pmatrix}\) 。

我们设这个frustum内一点P的齐次坐标为:\(\begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix}\)

变换后,得到新的齐次坐标:

\[M_{\text{persp} \rightarrow \text{ortho}} \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} nx \\ ny \\ (n+f)z - nf \\ z \end{pmatrix}
\]

那么我们的 z' = \(\frac{(n+f)z - nf}{z} = (n+f) - \frac{nf}{z}\) ,其中 n < z < f

这个时候就很简单了,我们只需要知道这个式子的单调性就能知道整个式子随着 z’ 的增大是减小还是增加,如果是前者,那么就是更靠近远平面,反之亦然。也就是说,当 z 越发靠近 n ,变换后的值也会越小,越小,那么离摄像机越近,离摄像机,那可不越靠近n吗

这里值得一提的是,这里n,f,z都是大于0的,因为在摄像机视角中,只能看见正面,后面是看不见的,也不用进行处理,所以这里的n,f,z一定都是大于0的。

这个式子不用多说,想知道单调性直接求个导就出来了:

\[\frac{d}{dz} z' = \frac{d}{dz} \left( (n+f) - \frac{nf}{z} \right) = \frac{nf}{z^2}
\]

无论 z 等于何值,倒数都大于0,整个函数单调递增。

  • 透视投影的本质是“压缩”远处的深度范围。
  • 远离近平面的点 z 变换后会向近平面挤压,因为 透视变换非线性缩小了远处的深度范围。
  • 所有在 frustum 内的点,变换后 z′ 都比原来的 z 更靠近近平面。

问题解决。

计算机图形学——Games101深度解析_第二章的更多相关文章

  1. 《Spring源码深度解析》第二章 容器的基本实现

    入门级别的spring配置文件 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi=&q ...

  2. ArcGIS for Desktop入门教程_第二章_Desktop简介 - ArcGIS知乎-新一代ArcGIS问答社区

    原文:ArcGIS for Desktop入门教程_第二章_Desktop简介 - ArcGIS知乎-新一代ArcGIS问答社区 1 Desktop简介 1.1 ArcGIS for Desktop ...

  3. Java编程思想_笔记_第二章_一切都是对象

    第二章对于知识只是点到,会在以后章节会详细展开. 笔记的侧重会偏向记录自己知识模糊的地方.比如 xxx 很重要很难很实用,但是已经熟练使用就没有记录,而 “使用对象.成员名称来使用成员变量”,较简单而 ...

  4. 深入理解java虚拟机_第二章_读书笔记

    1.本章内容目录: 概述 运行时数据区域 程序计数器 java虚拟机栈 本地方法栈 java堆 方法区 运行时常量池 直接内存 HotSpot虚拟机对象探秘 对象的创建 对象的内存布局 对象的访问定位 ...

  5. 《python语言程序设计》_第二章笔记

    #2.2_编写一个简单的程序 项目1: 设计:radius=20,求面积area? 程序: radius=20 #给变量radius复制area=radius*radius*3.14159 #编写ar ...

  6. 《python语言程序设计》_第二章笔记之2.13_软件开发流程

    #程序1: 设计:由用户键入利率.贷款数以及贷款的年限,系统计算出每月还贷数和总还款数 注意:输入的年利率是带有百分比的数字,例如:4.5%.程序需要将它除以100转换成小数.因为一年有12个月,所以 ...

  7. 【工具解析】瑞士军刀bettercap2.X_解析_第二期_内网钓鱼(嗅探)工具编写

    /文章作者:Kali_MG1937 CNBLOG博客:ALDYS4 QQ:3496925334/ 第一期: https://www.cnblogs.com/aldys4/p/14877783.html ...

  8. iPhone开发基础教程_第二章

    1.各个子文件夹的作用        Classes:                    编写的大多代码都保存在这里,其中包括所有的Objective-C类,可以在Classes文件夹下创建一些子 ...

  9. 算法竞赛入门经典_第二章:循环结构程序设计_上机练习_MyAnswer

    习题2-1 位数 输入一个不超过109的正整数,输出它的位数.例如12735的位数是5.请不要使用任何数学函数,只用四则运算和循环语句实现. #include<stdio.h> int m ...

  10. 《python语言程序设计》_第二章编程题

    2.1 程序: Celsius=eval(input("Enter a degree in Celsius:"))#输入摄氏度的值Celsiusfahrenheit =(9/5)* ...

随机推荐

  1. 洛谷P4390 [BalkanOI2007] Mokia 摩基亚 题解

    题目传送门. 想必 我的另外一篇题解 已经把这道题的思路说的很清楚了,但是那道题是把所有的修改全部告诉你,然后再一个一个问你矩阵和,但是这道题他是修改中夹着询问,但是没有关系,我们照样可做. 考虑将所 ...

  2. 探秘Transformer系列之(9)--- 位置编码分类

    探秘Transformer系列之(9)--- 位置编码分类 目录 探秘Transformer系列之(9)--- 位置编码分类 0x00 概述 0x01 区别 1.1 从直观角度来看 1.2 从模型处理 ...

  3. 【ABAQUS文档笔记】ABAQUS刚体单元和可变形单元的review

    学习笔记,帮助文档学习笔记 目录 A. finite element DOF of Elem Order of Elem Formulation of Elem Integration A.1 con ...

  4. 从RNN、LSTM到NTM、MANN——神经网络的记忆与推理进化

    从RNN.LSTM到NTM.MANN--神经网络的记忆与推理进化 一.前言:为什么要研究记忆? (温馨提示:在阅读本文之前,请确保你已经对深度学习中最基本的概念有一定的了解,例如:激活函数.多层感知机 ...

  5. PostgreSQL configure: error: readline library not found

    前言 安装 PostgreSQL 时报错,以下 configure: error: readline library not found If you have readline already in ...

  6. mysql查询指定表所有的字段信息 columns

    show columns from 表名: desc 表名;(全写:describe 表名) show create table 表名;

  7. 阿里Java开发手册泰山版来袭

    阿里Java开发手册自2016年12月7日发布公开版以来,距今已发布7个版本,被越来越多的公司拿来直接或略微修改后作为公司的Java开发规范手册,嫣然成为行业的标杆. 就在昨天早上8点,阿里Java开 ...

  8. 使用 PHP cURL 实现 HTTP 请求类

    类结构 创建一个 HttpRequest 类,其中包括初始化 cURL 的方法.不同类型的 HTTP 请求方法,以及一些用于处理响应头和解析响应内容的辅助方法. 初始化 cURL 首先,创建一个私有方 ...

  9. CoreOS 手动更新

    以阿里云 ECS 安装的 CoreOS 为例,你家装的 CoreOS 基本也一样啦. 查看和修改更新组 第一个问题:"什么是更新组?",请先看CoreOS 发行版本介绍 # 查看更 ...

  10. D的SDK的设置

    有点烦,被困扰.看大虾的文章一并感谢: 进入D:\Users\Public\Documents\Embarcadero\Studio\22.0\CatalogRepository\AndroidSDK ...