Viewing Transformation
视图变换包含三部分 —— MVP(Model-View-Projection),模型、视图和投影。
模型变换就是前两节阐述的直接总用于空间中模型的变换,但如果想看到图像,我们还需要定义一个摄像机,这一部分也包含变换,被称为视图变换,我们下面就来叙述它。
View/Camera
定义一个摄像机
- Position e ⃗ \vec{e} e
- Look-at/gaze direction g ^ \hat{g} g^
- Up direction t ^ \hat{t} t^
前两个参数将摄像机固定下来,第三个参数给了摄像机在将来进行变换的依据。
有了这三个参数,摄像机就像一个普通物体模型那样,也可以被变换,这三个参数将用来描述摄像机的位置信息,并用于对其进行各种变换。
视图变换矩阵
不管摄像机位于空间中哪一点,最终都将被变换到原点,并旋转到一个约定俗成的方向,就叫它"标准位置"。同时,也要以同样的方式变换其他物体,以保持变换的同步。
这个标准位置定义如下:
- 线性空间与世界坐标系重合
- 位于原点
- 定义一个新基,将世界坐标变换到这个基的坐标下
- Up direction t ^ \hat{t} t^ 是新基的 y 轴正方向
- Lock-at direction g ^ \hat{g} g^ 是新基的 z 轴负方向
- 此时新基的 x 轴方向就是 g ^ × t ^ \hat{g} \times \hat{t} g^×t^ 的方向
根据上述定义,此时我们要做的就是平移变换和 基变换:
平移变换将摄像机与世界坐标系放在一个线性空间下,
基变换则是将世界坐标变换到摄像机坐标,原先的世界坐标系是第一组基,通过过渡矩阵变换到由 g ^ t ^ \hat{g}\ \hat{t} g^ t^ 所确定的另一组基 g ^ × t ^ , t ^ , − g ^ \hat{g} \times \hat{t},\ \hat{t},\ -\hat{g} g^×t^, t^, −g^ 上。
此后,其他物体的变换均要先进行平移变换,然后根据这个过渡矩阵进行坐标变换,从世界坐标变换到视图坐标。
形象地讲,就像下面这样:
变换分两步,第一步是对 e ⃗ \vec{e} e 的变换,即将摄像机平移到原点,第二步是根据上面三个参数将摄像机旋转到标准位置。
那么这个变换矩阵的形式就是 M v = R v T v M_{v} = R_v T_v Mv=RvTv.
其中平移到原点的平移矩阵为:
T
v
=
[
1
0
0
−
x
0
1
0
−
y
0
0
1
−
z
0
0
0
1
]
T_v = \begin{bmatrix} 1 & 0 & 0 & -x \\ 0 & 1 & 0 & -y \\ 0 & 0 & 1 & -z \\ 0 & 0 & 0 & 1 \end{bmatrix}
Tv=⎣⎢⎢⎡100001000010−x−y−z1⎦⎥⎥⎤
旋转矩阵是将位于世界坐标系的摄像机坐标旋转到 x y z 轴上。
其逆过程是比价好写的,也就是对应地将 x y z 轴上的单位向量变换到
g
^
×
t
^
,
t
^
,
−
g
^
\hat{g} \times \hat{t},\ \hat{t},\ -\hat{g}
g^×t^, t^, −g^ 方向:
(
1
,
0
,
0
)
→
g
^
×
t
^
(
0
,
1
,
0
)
→
t
^
(
0
,
0
,
1
)
→
g
^
\begin{array}{lcc} (1,\ 0,\ 0) & \to & \hat{g} \times \hat{t} \\ (0,\ 1,\ 0) & \to & \hat{t} \\ (0,\ 0,\ 1) & \to & \hat{g} \end{array}
(1, 0, 0)(0, 1, 0)(0, 0, 1)→→→g^×t^t^g^
R v − 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 ] \begin{array}{llc} R_v^{-1} & = & \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} \end{array} Rv−1=⎣⎢⎢⎡xg^×t^yg^×t^zg^×t^0xtytzt0x−gy−gz−g00001⎦⎥⎥⎤
所以:
R
v
=
(
R
v
−
1
)
−
1
=
(
R
v
−
1
)
T
=
[
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
]
\begin{array}{llc} R_v & = & (R_v^{-1})^{-1} \\ \\ & = & (R_v^{-1})^{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} \end{array}
Rv===(Rv−1)−1(Rv−1)T⎣⎢⎢⎡xg^×t^xtx−g0yg^×t^yty−g0zg^×t^ztz−g00001⎦⎥⎥⎤
Projection
这是 MVP 的最后一部分,投影变换。
正交投影(Orthographic projection)
透视投影(Perspective projection)
正交投影
在摄像机坐标系中选取一个立方体范围,设在 x, y, z 方向上的范围分别是
[
l
,
r
]
;
[
b
,
t
]
;
[
f
,
n
]
[l,\ r];\ [b,\ t];\ [f,\ n]
[l, r]; [b, t]; [f, n],然后平移并缩放至
[
−
1
,
1
]
3
[-1,\ 1]^3
[−1, 1]3。
M
o
r
t
h
o
=
[
2
/
(
r
−
l
)
0
0
0
0
2
/
(
t
−
b
)
0
0
0
0
2
/
(
n
−
f
)
0
0
0
0
1
]
[
1
0
0
−
(
r
+
l
)
/
2
0
1
0
−
(
t
+
b
)
/
2
0
0
1
−
(
n
+
f
)
/
2
0
0
0
1
]
M_{ortho} = \begin{bmatrix} 2/(r - l) & 0 & 0 & 0 \\ 0 & 2/(t - b) & 0 & 0 \\ 0 & 0 & 2/(n - f) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 & 0 & -(r + l)/2 \\ 0 & 1 & 0 & -(t + b)/2 \\ 0 & 0 & 1 & -(n + f)/2 \\ 0 & 0 & 0 & 1 \end{bmatrix}
Mortho=⎣⎢⎢⎡2/(r−l)00002/(t−b)00002/(n−f)00001⎦⎥⎥⎤⎣⎢⎢⎡100001000010−(r+l)/2−(t+b)/2−(n+f)/21⎦⎥⎥⎤
之所以 f < n (far < near),即在 z 轴上远坐标小于近坐标,是因为我们的 Look-at 方向是顺着 z 轴负方向,所以越远的点值越小。
这样选取 Look-at 的方向是为了配合 model,因为 model 是以所有轴正方向作为正向的,以 -z 为 Look-at 方向可以模拟人类正常的观察方向,如果反向,则会关于原点对称。
透视投影
我们现在希望得到一个变换矩阵 M p → o M_{p \to o} Mp→o,他可以将透视投影转换成正交投影。
如下图,这个矩阵可以将所有平面压缩到与近平面大小相同,然后就可以通过简单地进行一个正交投影来实现透视投影。
下面我们来推导这个矩阵,
他被这样子应用于所有平面上的所有点:
[
x
′
y
′
z
′
ω
]
=
M
p
→
o
⋅
[
x
y
z
1
]
\begin{bmatrix} x^{'} \\ y^{'} \\ z^{'} \\ \omega \end{bmatrix} = M_{p \to o} \cdot \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix}
⎣⎢⎢⎡x′y′z′ω⎦⎥⎥⎤=Mp→o⋅⎣⎢⎢⎡xyz1⎦⎥⎥⎤
我们规定以齐次坐标来表示变换后的点,这些点的每个维度都同时乘 z。
所以齐次坐标的形式就是:
[
x
′
y
′
z
′
ω
]
=
[
z
⋅
?
z
⋅
?
z
⋅
?
z
]
\begin{bmatrix} x^{'} \\ y^{'} \\ z^{'} \\ \omega \end{bmatrix} = \begin{bmatrix} \textcolor{orange}{z}\cdot ? \\ \textcolor{orange}{z}\cdot ? \\ \textcolor{orange}{z}\cdot ? \\ \textcolor{orange}{z} \end{bmatrix}
⎣⎢⎢⎡x′y′z′ω⎦⎥⎥⎤=⎣⎢⎢⎡z⋅?z⋅?z⋅?z⎦⎥⎥⎤
现在来探究这个齐次坐标应该是什么:
我们侧过来观察透视的几何关系,在相似三角形可以得到下面的比例关系:
n / z = x ′ / x n/z = x^{'}/x n/z=x′/x
n / z = y ′ / y n/z = y^{'}/y n/z=y′/y
所以:
$$
\begin{array}{lc}
\begin{bmatrix}
x^{’} \
y^{’} \
z^{’} \
\omega
\end{bmatrix}
M_{p \to o} \cdot
\begin{bmatrix}
x \
y \
z \
1
\end{bmatrix}
& = & \begin{bmatrix}
\textcolor{orange}{z}\cdot \textcolor{#0aa}{n(x/z)} \
\textcolor{orange}{z}\cdot \textcolor{#0aa}{n(y/z)} \
? \
\textcolor{orange}{z}
\end{bmatrix} \ \
& = & \begin{bmatrix}
\textcolor{#0aa}{nx} \
\textcolor{#0aa}{ny} \
? \
\textcolor{orange}{z}
\end{bmatrix}
\end{array}
从
而
得
到
:
从而得到:
从而得到:
M_{p \to o} = \begin{bmatrix}
\textcolor{orange}{n} & 0 & 0 & 0 \
0 & \textcolor{orange}{n} & 0 & 0 \
? & ? & ? & ? \
0 & 0 & \textcolor{orange}{1} & 0
\end{bmatrix}
$$
为了得到在 z 方向上的变换规则,我们取两个特殊点:
- 任意位于 near 平面的点 [ x n , y n , n , 1 ] T [x_n,\ y_n,\ n,\ 1]^T [xn, yn, n, 1]T
- 任意位于 far 平面的点 [ x f , y f , f , 1 ] T [x_f,\ y_f,\ f,\ 1]^T [xf, yf, f, 1]T
这两个特殊点在变换前后的 z 值不会改变。
他们变换后的坐标分别为:
- near [ n x n , n y n , n 2 , n ] T [nx_n,\ ny_n,\ n^2,\ n]^T [nxn, nyn, n2, n]T
- far [ n x f , n y f , f 2 , f ] T [nx_f,\ ny_f,\ f^2,\ f]^T [nxf, nyf, f2, f]T
所以有:
{
a
n
+
b
=
n
2
a
f
+
b
=
f
2
=
>
{
a
=
n
+
f
b
=
−
n
f
\begin{cases} an + b = n^2 \\ af + b = f^2 \end{cases} => \begin{cases} a = n + f \\ b = -nf \end{cases}
{an+b=n2af+b=f2=>{a=n+fb=−nf
故得到最终的用于压缩平面的矩阵:
M
p
→
o
=
[
n
0
0
0
0
n
0
0
0
0
n
+
f
−
n
f
0
0
1
0
]
\Large M_{p \to o} = \begin{bmatrix} \textcolor{orange}{n} & 0 & 0 & 0 \\ 0 & \textcolor{orange}{n} & 0 & 0 \\ 0 & 0 & \textcolor{orange}{n + f} & \textcolor{orange}{-nf} \\ 0 & 0 & \textcolor{orange}{1} & 0 \end{bmatrix}
Mp→o=⎣⎢⎢⎢⎢⎢⎡n0000n0000n+f100−nf0⎦⎥⎥⎥⎥⎥⎤
关于 z 的变换
在透视投影中,我们通过 M p → o M_{p \to o} Mp→o 变换远平面上的点,从而得到了压缩后的齐次坐标 [ z ⋅ x ′ , z ⋅ y ′ , z ⋅ z ′ , z ] T [z\cdot x^{'},\ z\cdot y^{'},\ z\cdot z^{'},\ z]^T [z⋅x′, z⋅y′, z⋅z′, z]T。
对应的非齐次坐标为
[
x
′
,
y
′
,
z
′
,
1
]
T
[x^{'},\ y^{'},\ z^{'},\ 1]^T
[x′, y′, z′, 1]T,其中
x
′
y
′
z
′
x^{'}\ y^{'}\ z^{'}
x′ y′ z′ 分别是关于 x y z 的函数:
{
x
′
(
x
)
=
x
⋅
n
/
z
y
′
(
y
)
=
y
⋅
n
/
z
z
′
(
z
)
=
(
n
+
f
)
z
−
n
f
z
\large \begin{cases} x^{'}(x) = x\cdot n/z \\\\ y^{'}(y) = y\cdot n/z \\\\ z^{'}(z) = \frac{(n + f)z - nf}{z} \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎧x′(x)=x⋅n/zy′(y)=y⋅n/zz′(z)=z(n+f)z−nf
x’, y’ 都是关于 x, y 的线性函数,而 z 的变换却不太寻常,它是一个反比例函数,我们可以通过画图来直观感受一下:
其中取远平面 f = -100,取近平面 n = -10,图像均在 x = y 函数的下侧,故所有被变换的 z 值均靠近远平面,且越接近近平面,靠近远平面的幅度越大(斜率大)。
定义立方体范围
这是最后一步,我们在 Projection 这一节的阐述均基于一个抽象的 立方体范围,这个立方体范围来自一个 Frustum,这个 Frustum 实际上是摄像机视锥的一部分,我们把它通过一个压缩矩阵进行压缩,最后进行了正交投影,完成了透视投影。
现在我们要进入这个抽象,并解释如何通过具体的参数获得这个抽象。
很简单,我们现在需要定义摄像机的视野范围:
-
摄像机的垂直视野角度 field-of-view Y (fovY)
-
摄像机的视野宽高比 radio = w / h
-
摄像机的近平面、远平面距离 n f
这里选择 y 方向上的视野角度,根据根据宽高比,x 方向上的视野角度会被唯一确定。
我们来看一个例子:
这个例子通过传入上述定义中的三种参数,最后返回一个透视投影变换矩阵,来自作业一的 main.cpp
。
Eigen::Matrix4f get_projection_matrix(float eye_fov,
float aspect_ratio,
float zNear, float zFar) {
// Students will implement this function
Eigen::Matrix4f projection = Eigen::Matrix4f::Identity(),
orthoProjMat, persp2ortho;
// [1, -1]^3 的所有参数
float l, r, b, t, f = -zFar, n = -zNear;
b = -tan(eye_fov * MY_PI / 180) * -n / 2;
t = -b;
l = -(t - b) * aspect_ratio / 2;
r = -l;
// 正交投影变换矩阵
orthoProjMat =
(Eigen::Matrix4f() <<
2 / (r - l), 0, 0, 0,
0, 2 / (t - b), 0, 0,
0, 0, 2 / (n - f), 0,
0, 0, 0, 1
).finished()
*
(Eigen::Matrix4f() <<
1, 0, 0, -(r + l) / 2,
0, 1, 0, -(t + b) / 2,
0, 0, 1, -(n + f) / 2,
0, 0, 0, 1
).finished()
;
// 平面压缩变换矩阵
persp2ortho <<
n, 0, 0, 0,
0, n, 0, 0,
0, 0, n + f, -n * f,
0, 0, 1, 0
;
// 先压缩变换后正交投影
projection = orthoProjMat * persp2ortho;
return projection;
}
其中这段代码演示了如何通过上述三个用来定义摄像机视野范围的参数来获得这个 Frustum 的所有参数:
// [1, -1]^3 的所有参数
float l, r, b, t, f = -zFar, n = -zNear;
b = -tan(eye_fov * MY_PI / 180) * -n / 2;
t = -b;
l = -(t - b) * aspect_ratio / 2;
r = -l;