视图变换包括了我们的模型变换,取景变换,投影变换,这是整个由 3 维转向 2 维的重要一步。我们可以类比我们日常生活中的拍摄的过程,首先我们需要找一个我们需要拍摄的对象,这部分叫做 model transformation (译:模型变换),这里完成的是将需要的模型构建出来,然后我们需要给相机找一个好的位置,好的角度,好的姿态,这部分叫做 view transformation(译:取景变换),这里完成的是取景器的设置工作,我们需要明确取景的位置,角度,姿态。最后我们需要按下我们的快门。这部分叫做 projection transformation(译:投影变换),这里完成将图像进行映射。这个过程简称为 MVP,因为模型变换部分需要建模部分,我们这里不讨论,只讨论图像部分的取景变换和投影变换。
# Basic Definition
View/Camera Transformation 取景变换,这里需要完成对取景点的设置,这是非常重要的一部,取景点的信息直接决定了投影变换的各种信息,也决定了最后成像的效果。决定取景点主要有以下几个信息:
- Position:e.
- Look-at / Gaze direction:g^ .
- Up direction:t^ .
现在我们考虑一个情景,相机和目标能够通过移动特殊的数值使得获取的信息和移动前一样。这就出现了一种麻烦的情况,我们需要进行两次计算,才能获取到一样的信息。所以为了简化相机与目标的相对关系,我们规定了相机的位置,我们称之为 Key observation,即相机的位置是在原地位置,以 -Z 为观察方向,Y 轴为水平偏差标准。这样所有的变换就成了目标的移动。
有了这个规定以后,我们就能够将任意一个取景点进行变换,变到 Key observation,只需要:
- Rotates g^ to -Z
- Rotates t^ to Y
- Rotates (g^×t^) To X
# Math Conversion
在数学上,按照之前在目标转化分解一节中的描述,我们现进行平移变换使得位置到达原点,此时线性变换就能以原点为轴。
Mview=RviewTview
平移变换Tview 的部分
Tview=⎣⎢⎢⎢⎡100001000010−xe−ye−ze1⎦⎥⎥⎥⎤
线性变换 $R_{view} $ 不好写,因为本身从特殊点进行的变换,但是我们可以通过到达目标位置进行逆变换来获取,然后利用其性质,其逆矩阵就是其转置矩阵:
Rview−1=⎣⎢⎢⎢⎡xg^×t^yg^×t^zg^×t^0xtytzt0x−gy−gz−g00001⎦⎥⎥⎥⎤
Rview=⎣⎢⎢⎢⎡xg^×t^xtx−g0yg^×t^yty−g0yg^×t^ztz−g00001⎦⎥⎥⎥⎤
# Basic Definition
投影变换是取景的最后一步,根据不同的场景设定有两种不同的投影类型,分别是:
- Perspective projection:透视投影
- Orthographic projection:正交投影
这两者最大的区别就是是否有近大远小的性质。
# Orthographic Projection
正交投影作为最简单的投影方式,实际上假设取景点在无穷远的地方,这样就能让成像的画面和实际取景的每一个点都构成平行线。也正因取景点在无穷远的地方,使得目标物体间的具体可以忽略不记,这样物体的深度信息就丢失了,也就是 Z 的信息。
对于一个已知的物体,我们需要 6 个信息来进行正交变换,分别是:
同理(目标转化分解),我们先进行平移变换,使各个维度的中点的投影在原点上,然后再进行线性变换:
Mortho=⎣⎢⎢⎢⎡r−l20000t−b20000n−f200001⎦⎥⎥⎥⎤⎣⎢⎢⎢⎡100001000010−2r+l−2t+b−2n+f1⎦⎥⎥⎥⎤
Note: n > f
# Perspective Projection
透视投影是非常常见的投影方式,在 CG,艺术等领域都有广泛的应用,因为能够提供深度的信息,同时也符合人眼的成像习惯,因而能够给人带来真实感。透视投影的实质在特定的取景点下,成像画面会将取景画面进行 “挤压”,每个点到点之间构成的线不再能够平行。
如果我们想要获取一个物体的透视投影,我们可以通过两步来实现:
- 第一步:将物体图像挤压到和成像大小一样
- 第二部:将挤压后的物体图像进行正交投影
遵守几个规定:
- 近平面的点永远不变
- 远平面的点挤压后 Z 不会改变
- 远平面的中心点在挤压中不会改变
有了这些基本的规定后,我们试着来推导一下整个过程。
首先看 “挤压” 的过程,我们以其中一个切面的角度看:
因此我们能够获得:
y′=znyx′=znx
我们就能转换成
Mpresp→ortho⎝⎜⎜⎜⎛xyz1⎠⎟⎟⎟⎞=⎝⎜⎜⎜⎛nxnyunknownz⎠⎟⎟⎟⎞
根据结果我们可以推算出
Mpresp→ortho=⎝⎜⎜⎜⎛n0?00n?000?100?0⎠⎟⎟⎟⎞
接着我们根据规定近平面的点永远不变和远平面的点挤压后 Z 不会改变:
近平面的点永远不变
因此我们能够得出:
(00AB)⎝⎜⎜⎜⎛xyn1⎠⎟⎟⎟⎞=An+B=n2
远平面的点挤压后 Z 不会改变
因此我们能够得出:
(00AB)⎝⎜⎜⎜⎛00f1⎠⎟⎟⎟⎞=Af+B=f2
我们就获得一个方程组:
{An+B=n2Af+B=f2
解的得:
{A=n+fB=−nf
因此 “挤压” 的转化矩阵为:
Mpresp→ortho=⎝⎜⎜⎜⎛n0000n0000n+f100−nf0⎠⎟⎟⎟⎞
# Application
了解了两种核心的投影方式和相应的计算后,我们还需要知道一些常用的数据转化
- fovY :vertical field-of-view 指的是相机的广角角度
- aspect ratio=heightwidth : 显示的长宽比
通过这两个常用的数据转为我们需要的数据(r, l, b, t)
这里我们一般假设: l = -r, b= -t
这里我们可以获得
angle:2fovYtan2fovY=∣n∣taspect=tr
当我们在做透视投影或者正交投影时,都会最后转为规范立方矩阵,然后我们先需要通过 Viewport Transform 矩阵将图像转为显示器尺寸的矩阵:
Mpresp→ortho=⎝⎜⎜⎜⎛2width00002height0000102width2height01⎠⎟⎟⎟⎞
# Case One
问题:求出已知图像绕一个经过原点的任意轴旋转θ 的转化矩阵
分析:之前我们已经分析过绕 X,Y,Z 轴旋转的旋转矩阵了,现在我们需要的是任意过原点的旋转轴。我们可以通过把未知的旋转方式转为已知的进行旋转(Z,X,Y), 然后旋转完,再逆变换回去。
如图所示,假设我们需要绕向量 V 为轴进行旋转θ 角度,那么首先我们可以将向量进行旋转,使得 V 与已知的(X,Y,Z)其中一个重合,我这里演示的是与 Z 轴重合:分为两步法
- 将向量绕 X 轴旋转到 ZOX 平面 获得V_
- 然后将VZOX 绕 Y 轴旋转到和 Z 轴重合
第一步:将向量绕 X 轴旋转到 ZOX 平面 获得V_
绕 X 轴旋转的角度α 等于向量 V 在 ZOY 平面上的投影向量VZOY 与 Z 轴正向的夹角。其实就是VZOY 和 V 组成的面上的所有经过原点的向量要想通过 X 轴旋转到 ZOX 都是转的角度α,由投影可知
VZOY(0,b,c)
则夹角就可以通过简单的三角函数获得:
sinα=b2+c2b, cosα=1−sin2α
获得到角度后,我们就能够列出绕 X 轴旋转的矩阵:
Vx(α)=⎝⎜⎜⎜⎛10000cosαsinα00−sinαcosα00001⎠⎟⎟⎟⎞
第二步:然后将VZOX 绕 Y 轴旋转到和 Z 轴重合
绕 Y 轴旋转的角度β 等于向量VZOX 与 Z 轴正向的夹角,这里我们可以知道VZOX 是由 V 绕 X 轴旋转而来,所以 X 轴的分量保持不变,Y 轴分量为 0,因为模长不变,所以可知
VZOX(a,0,b2+c2)
同理,夹角就可以通过简单的三角函数获得:
sinβ=a2+b2+c2a, cosα=1−sin2α
获得到角度后,我们就能够列出绕 Y 轴旋转的矩阵:
Vy(β)=⎝⎜⎜⎜⎛cosβ0−sinβ00100sinβ0cosβ00001⎠⎟⎟⎟⎞
完成这两步后我们就能对目标进行绕 Z 轴θ 角度的旋转:
Vz(θ)=⎝⎜⎜⎜⎛cosθsinθ00−sinθcosθ0000100001⎠⎟⎟⎟⎞
最后,将物体旋转回之前的位置。具体做法是乘以之前矩阵的逆矩阵。至此,我们得到物体旋转所需要的最终矩阵:
V(θ)=Vx(−α)Vy(−β)Vz(θ)Vy(β)Vx(α)
而我们知道旋转矩阵的逆矩阵等于其转置矩阵,于是我们可以得到:
V(θ)=VxT(α)VyT(β)Vz(θ)Vy(β)Vx(α)
# Case Two
问题:求出已知图像绕任意一个轴旋转θ 的转化矩阵
分析:之前我们已经分析过绕过原点的任意轴旋转的旋转矩阵了,现在我们需要的是绕任意轴旋转。我们可以通过把未知的轴起点平移到原点,这样就能转为过原点的任意轴旋转的问题,然后旋转后,再把轴平移回去即可。
如图所示,假设我们需要绕向量SP为轴进行旋转θ 角度,那么首先我们可以将向量进行平移,使得向量变成以原点为初始点的向量,这样就能用 Case One 的方法解答分:
- 将向量SP平移使得起点在原点
- 解答绕过原点任意轴的问题
第一步:将向量SP平移使得起点在原点
T(x,y,z)=⎝⎜⎜⎜⎛100001000010a1−ab1−bc1−c1⎠⎟⎟⎟⎞
第二步:使用 Case One 的方法
V(θ)=VxT(α)VyT(β)Vz(θ)Vy(β)Vx(α)
最后,将物体平移回之前的位置。具体做法是乘以之前矩阵的反方向。至此,我们得到物体旋转所需要的最终矩阵:
V(θ)=T(−x,−y,−z)VxT(α)VyT(β)Vz(θ)Vy(β)Vx(α)T(x,y,z)