一 视图/相机 变换 View/Camera transformation
什么是视图变换
在现实生活中是如何拍照的?
- 找一个地方, 将拍摄的内容(人或物)安排位置 -> 模型变换(model transformation)
- 找一个角度, 将相机按照角度摆好 -> 视图变换 (view transformation)
- 拍照 -> 投影变换 (projection transformation)
如何执行视图变换
定义相机
- 定义位置 e
- 看的方向 g
- 向上方向 t
观察点
为了能够观察物体的运动, 做以下约定:
- 相机永远放在原点 (0,0,0)
- 相机永远看向 -Z 轴方向
- 相机向上方向为 Y 轴方向
将相机从任意一点移动到原点:
-
将 e 移动到原点
1, 0, 0, -xe
1, 1, 0, -ye
1, 0, 1, -ze
1, 0, 0, 1
-
旋转 g 到 -Z 方向
-
旋转 t 到 Y 方向
-
(g × t)的结果向量 旋转到 X 方向
对于2-4点不容易求旋转矩阵
由于 X轴, Y轴, Z轴的坐标简单, 那么可以尝试反过来, 从结果变换回原始:
X(1,0,0) -> (g × t)
Y(0,1,0) -> t
Z(0,0,1) -> -g
那么先求旋转矩阵的逆:
R_-1 = [x_(g×t), x_t, x_-g, 0]
[y_(g×t), y_t, y_-g, 0]
[z_(g×t), z_t, z_-g, 0]
[ 0, 0, 0, 1]
根据旋转矩阵的性质: 旋转矩阵的逆 = 旋转矩阵的转置
转置得到旋转矩阵:
R = [x_(g×t), y_(g×t), z_(g×t), 0]
[ x_t, y_t, z_t, 0]
[ x_-g, y_-g, z_-g, 0]
[ 0, 0, 0, 1]
二 投影变换 Projection transformation
两种投影: 正交投影, 透视投影
正交投影并不会给人们带来"近大远小"的感觉, 但是透视投影会有这种现象.
1. 正交投影 Orthographic projection
假设摄像机距离物体无限远, 那么近处的平面与远处的平面会逐渐趋近于完全一样的大小.
简单的理解
- 把相机移动到原点
- 相机看向 -Z 方向
- 相机向上的方向为 Y
- 把 Z 坐标丢掉, 即可得平面矩形
- 将结果矩形平移缩放到 [-1, 1]^2 平面上, 方便后续的计算
问题: 如果把 Z 丢掉, 如何区分物体的前后关系呢?
定义
-
定义空间中的一个立方体
包括x轴上的左右 [l, r], y轴上的下上 [b, t], z轴上的远近 [f, n]
-
将立方体的中心移动到原点, 将 x,y,z 个方向分别拉伸到 [-1, 1]^3
也就是将立方体映射到一个正则(规范, 标准)立方体: canonical cube [-1, 1]^3
矩阵表示
-
平移到原点
立方体的中心: (r+l)/2, (t+b)/2, (n+f)/2
1, 0, 0, -(r+l)/2
0, 1, 0, -(t+b)/2
0, 0, 1, -(n+f)/2
0, 0, 0, 1
-
缩放到 [-1, 1]^3
立方体原本覆盖的范围: r-l, t-b, n-f (因为摄像机面向的是-Z, 所以 近>远)
缩放后的范围: 1-(-1) = 2
那么应该缩放的因子: 2 / (r-l), 2 / (t-b), 2 / (n-f)
2/(r-l), 0, 0, 0
0, 2/(t-b), 0, 0
0, 0, 2/(n-f), 0
0, 0, 0, 1
-
先平移, 后缩放
M_ortho =
2/(r-l), 0, 0, 0\] \[1, 0, 0, -(r+l)/2
0, 2/(t-b), 0, 0\] \[0, 1, 0, -(t+b)/2
0, 0, 2/(n-f), 0\] \[0, 0, 1, -(n+f)/2
0, 0, 0, 1\] \[0, 0, 0, 1
2. 透视投影 Perspective projection
投影原理
把摄像机放在某一个位置上, 将摄像机当做一个点, 从摄像机出发把空间连成一个四棱锥, 其中从某一个深度到另一个深度之间的这块区域称作 "视椎体" Frustum, 把这块区域中的所有物体都显示出来, 显示到近处的平面上.
在远平面上的4个点, 将这些点在同一平面上向内挤压, 挤压到与近平面相同的高度上, 那么从近平面到远平面整体就会形成一个长方体, 有了长方体, 那么再做一次正交投影就可以得到最终的平面.
也就是把透视投影拆分为两部分操作:
1. 挤压长方体 (从透视到正交)
2. 正交投影
性质:
1. 在挤压的过程中, 近平面上的所有点n永远不会变
2. 远平面上的点f由于只是在平面上向内挤压, 所以z值不会变
3. 远平面上的中心点, 在挤压之后没有任何变化
矩阵表示, 推理
-
挤压, 从投影到正交
可以先解决高度y, 也就是远平面上任意一点的 y 如何被挤压到 y'
假设远平面上的点为P(x, y, z), 近平面上对应的点为M(x', y', z')
那么点P在挤压后的高度y应该与点M的高度y'相同
远平面的点P距离摄像机的距离为z, 近平面的点M距离摄像机的距离为n
在相似三角形中, y' 与 y 的关系是什么呢?
底边上 n 与 z 的比值就等于高度 y' 与 y 的比值, 那么:
n / z = y' / y
=>
y' = (n / z) * y = ny / z
同理, 求 x':
x' = (n / z) * x = nx / z
-
改写为矩阵
x\] =\> \[ nx/z
y\] \[ ny/z
z\] \[ ?
1\] \[ 1
由于在齐次坐标中, 点的各个坐标乘以同一个值依旧是同一个点, 那么同时乘以z得到:
[ nx/z ] × z = [ nx ]
[ ny/z ] [ ny ]
[ ? ] [ ? ]
[ 1 ] [ z ]
-
根据向量反推变换矩阵
目前已经知道向量的大部分的值,
那么根据矩阵乘法规则来反推左边需要相乘的那个变换矩阵.
x\] = \[?, ?, ?, ?\] \[nx
y\] \[?, ?, ?, ?\] \[ny
z\] \[?, ?, ?, ?\] \[ ?
1\] \[?, ?, ?, ?\] \[ z
=>
计算 nx
x*? + y*? + z*? + ? = nx
xn + y0 + z*0 + 0 = nx
=>
得到第一行
x\] = \[n, 0, 0, 0\] \[nx
y\] \[?, ?, ?, ?\] \[ny
z\] \[?, ?, ?, ?\] \[ ?
1\] \[?, ?, ?, ?\] \[ z
=>
同理得到其余3行
x\] = \[n, 0, 0, 0\] \[nx
y\] \[0, n, 0, 0\] \[ny
z\] \[?, ?, ?, ?\] \[ ?
1\] \[0, 0, 1, 0\] \[ z
那么接下来的任务就是求出第三行未知的值
同理, 我们先求右侧结果中的第三个未知值, 再反推左侧的矩阵.
根据投影原理中的性质:
-
在近平面上, 任意一点始终不变.
近平面上点的z值是n
x\] =\> \[x
y\] \[y
z\] \[n
1\] \[1
=>
变换后点不变, 也就是映射回自己
x
y
n
1
=>
在齐次坐标中, 点的坐标乘以一个值还是原来的点
x\] \* n = \[ nx
y\] \[ ny
n\] \[ n\^2
1\] \[ n
=>
回过头来再看我们最终需要求的变换矩阵中的第三行的未知值
矩阵中未知的第三行乘以向量的结果是 n^2
?, ?, ?, ?\] \[x\] = n\^2 \[y
n
1
现在拿到结果了, 那么反推左侧矩阵中第三行的未知值
由于结果中的第三个值是 n^2, 根据矩阵乘法规则, 肯定与 x, y 的计算无关
所以左侧矩阵第三行的前两个值一定是 0
0, 0, ?, ?\] \[x\] = n\^2 \[y
n
1
=>
构建一个方程, 设第一个未知值为A, 第二个未知值为B
0, 0, A, B\] \[x\] = n\^2 \[y
n
1
=>
0x + 0y + A*n + B = n^2
=>
方程1
A*n + B = n^2
-
在远平面上, 中心点在变换后依旧不变
远平面上点的z值是f, 它正对着摄像机, 所以 x, y 都是 0
x\] =\> \[0
y\] \[0
z\] \[f
1\] \[1
=>
变换后点不变, 也就是映射回自己
0
0
f
1
=>
在齐次坐标中, 点的坐标乘以一个值还是原来的点
0\] \* f = \[ 0
0\] \[ 0
f\] \[ f\^2
1\] \[ f
=>
与近平面点同理
构建一个方程, 设第一个未知值为A, 第二个未知值为B
0, 0, A, B\] \[0\] = f\^2 \[0
f
1
=>
00 + 00 + A*f + B = f^2
=>
方程2
A*f + B = f^2
-
解方程
An + B = n^2
Af + B = f^2=>
A = n + f
B = -nf
那么到此为止, 所求透视矩阵中的所有未知值就都计算出来了.
[n, 0, 0, 0]
[0, n, 0, 0]
[?, ?, ?, ?]
[0, 0, 1, 0]
=>
M_persp->ortho =
[n, 0, 0, 0]
[0, n, 0, 0]
[0, 0, n+f, -nf]
[0, 0, 1, 0]
- 求最终的投影矩阵
最终矩阵用挤压矩形的矩阵再乘以正交矩阵即可.
自右向左应用:
M_persp = M_ortho M_persp->ortho
=
[2/(r-l), 0, 0, 0] [1, 0, 0, -(r+l)/2] [n, 0, 0, 0]
[ 0, 2/(t-b), 0, 0] [0, 1, 0, -(t+b)/2] [0, n, 0, 0]
[ 0, 0, 2/(n-f), 0] [0, 0, 1, -(n+f)/2] [0, 0, n+f, -nf]
[ 0, 0, 0, 1] [0, 0, 0, 1] [0, 0, 1, 0]