赞
踩
观测主要解决的问题是如何把物体的三维“模型”变成我们在屏幕所看到的二维“图片”,我们在计算机看到实体模型可以分成这样几步:
下面这个照相的类比非常地生动形象。
一般来说,我们规定相机沿着 − z -\mathbf{z} −z方向,在观测过程中为了简化会使用canonical view volume(CCV):它是一个正方体, x x x, y y y, z z z坐标都位于 − 1 -1 −1到 1 1 1之间,也即 ( x , y , z ) ∈ [ − 1 , 1 ] 3 (x,y,z)\in[-1,1]^3 (x,y,z)∈[−1,1]3,我们将 x = − 1 x=-1 x=−1投影到电脑屏幕的左侧,将 x = + 1 x=+1 x=+1投影到屏幕的右侧,将 y = − 1 y=-1 y=−1投影到屏幕的底部,将 y = + 1 y=+1 y=+1投影到屏幕的顶部。
如果我们定义屏幕每个像素的长和宽为1,最小的像素中心坐标是 ( 0 , 0 ) (0,0) (0,0),则图像的中心到其边界为 0.5 0.5 0.5,如果屏幕上的像素总长度为 n x n_x nx,总宽度为 n y n_y ny,那么我们可以将canonical view volume(CCV)的 x o y \mathbf{xoy} xoy平面的方形 [ − 1 , 1 ] 2 [-1,1]^2 [−1,1]2映射为长方形 [ − 0.5 , n x − 0.5 ] × [ − 0.5 , n y − 0.5 ] [-0.5,n_x-0.5]\times[-0.5,n_y-0.5] [−0.5,nx−0.5]×[−0.5,ny−0.5]。
注意我们现在假设所有的线都在CCV正方体里,后面这个假设将在讲裁剪的时候放松这个条件。
对于视口变换,我们需要想把CCV正方体的 x o y \mathbf{xoy} xoy平面进行放缩然后将原点平移到屏幕的左下角(CCV的原点在 x o y \mathbf{xoy} xoy正方形的正中心),其可以写作一个二维的变换【这里相当于计算上面的线性映射: − 1 → − 0.5 , 1 → n x − 0.5 ( n y − 0.5 ) -1\rightarrow -0.5,1\rightarrow n_x-0.5(n_y-0.5) −1→−0.5,1→nx−0.5(ny−0.5)】:
[
x
screen
y
screen
1
]
=
[
n
x
2
0
n
x
−
1
2
0
n
y
2
n
y
−
1
2
0
0
1
]
[
x
canonical
y
canonical
1
]
这里忽略了 z \mathbf{z} z轴的坐标,因为投影最终和 z z z坐标无关,这里我们可以扩充矩阵(尽管在这里没有用):
M
v
p
=
[
n
x
2
0
0
n
x
−
1
2
0
n
y
2
0
n
y
−
1
2
0
0
1
0
0
0
0
1
]
\mathbf{M}_{\mathrm{vp}}=
我们通常想把在渲染某个空间区域的几何元素而不是CCV,我们要调整我们的坐标轴的方向来实现正射变换,让坐标轴的 − z -\mathbf{z} −z轴对着物体,让 y \mathbf{y} y轴朝上, x \mathbf{x} x轴按照右手定则定义。我们看到的view volume是一个 [ l , r ] × [ b , t ] × [ f , n ] [l,r]\times[b,t]\times[f,n] [l,r]×[b,t]×[f,n]的box。
关于 l , r , b , y , f , n l,r,b,y,f,n l,r,b,y,f,n的物理含义可以看下面的表格:
{% tabs active:1 align:center %}
plane | meaning | plane | meaning |
---|---|---|---|
x = l x=l x=l | left plane | x = r x=r x=r | right plane |
y = b y=b y=b | bottom plane | y = t y=t y=t | top plane |
z = n z=n z=n | near plane | z = f z=f z=f | far plane |
{% endtabs %}
我们同样可以写出变换矩阵把这个box映射为CCV(参考原书公式的6.7,英文原版132页),变换的好处是简化数字在 − 1 -1 −1到 1 1 1之间,方便后续计算:
M
orth
=
[
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
]
=
[
2
r
−
l
0
0
−
r
+
l
r
−
l
0
2
t
−
b
0
−
t
+
b
t
−
b
0
0
2
n
−
f
−
n
+
f
n
−
f
0
0
0
1
]
现在我们可以转换视角中任意看到的点 ( x , y , z ) (x,y,z) (x,y,z)在像素上看到的位置 ( x p i x e l , y p i x e l , z c a n o n i c a l ) (x_{pixel},y_{pixel},z_{canonical}) (xpixel,ypixel,zcanonical)
[
x
pixel
y
pixel
z
canonical
1
]
=
(
M
v
p
M
orth
)
[
x
y
z
1
]
\left[
CCV坐标变换至屏幕坐标直线算法流程:
z \mathbf{z} z坐标的范围是 [ − 1 , 1 ] [-1,1] [−1,1],现在我们还没有用到,这将在z-buffer算法时很有用。
当我们需要改变3D视角和观测的方向时,我们需要重新定义观测者的位置和方向(改变相机的放置位置)。可以定义相机坐标系,我们期望的相机的朝向可以由两个向量 g \mathbf{g} g和向量 t \mathbf{t} t来定义,以及一个点 e \mathbf{e} e来表示。
于是我们可以根据上面所说的向量和电定义我们的相机坐标系 u v w \mathbf{uvw} uvw(世界坐标系是 x y z \mathbf{xyz} xyz),其中坐标系的原点就是 e \mathbf{e} e, v \mathbf{v} v轴和 t \mathbf{t} t矢量方向相同, w \mathbf{w} w轴和 − g -\mathbf{g} −g矢量方向相同, u \mathbf{u} u轴根据右手定则确定。
w
=
−
g
∥
g
∥
u
=
t
×
w
∥
t
×
w
∥
v
=
w
×
u
接下来我们会把世界坐标系的点坐标转换到相机坐标系中。我们可以把变换矩阵分解为两步,先平移再旋转。
由于将坐标系 u v w \mathbf{uvw} uvw转换为坐标系 x y z \mathbf{xyz} xyz的变换矩阵可以看做是
M
c
a
m
−
1
=
[
u
v
w
e
0
0
0
1
]
\mathbf{M}_{\mathrm{cam}}^{-1}=\left[
相机坐标系是要将坐标系 x y z \mathbf{xyz} xyz转换到坐标系 u v w \mathbf{uvw} uvw,于是这等价于对矩阵求一个逆。
M
c
a
m
=
[
u
v
w
e
0
0
0
1
]
−
1
=
[
x
u
y
u
z
u
0
x
v
y
v
z
v
0
x
w
y
w
z
w
0
0
0
0
1
]
[
1
0
0
−
x
e
0
1
0
−
y
e
0
0
1
−
z
e
0
0
0
1
]
\mathbf{M}_{\mathrm{cam}}=\left[
世界坐标正式投影变换至屏幕坐标画直线算法流程:
投影变换符合我们的视觉直观,主要体现在有近大远小的特性,平行线相交于一点。在实际的计算机图形中用得更多。
如上图所示,我们从视点 e \mathbf{e} e沿着 g \mathbf{g} g方向看,看到的实际的点的高度为 y y y,反映在观察平面上高度为 y s y_s ys,观察平面距离视点为 d d d,实际的点距离视点为 z z z,根据简单的相似三角形的关系,我们有(这里我们认为 z z z是距离,为正数,而不是坐标意义下的负数):
y s = d z y y_{s}=\frac{d}{z} y ys=zdy
虎书里还提到了线性有理变换,这里把变换矩阵简单写一下(感兴趣可以直接看虎书)
[
x
~
y
~
z
~
w
~
]
=
[
a
1
b
1
c
1
d
1
a
2
b
2
c
2
d
2
a
3
b
3
c
3
d
3
e
f
g
h
]
[
x
y
z
1
]
\left[
( x ′ , y ′ , z ′ ) = ( x ~ / w ~ , y ~ / w ~ , z ~ / w ~ ) \left(x^{\prime}, y^{\prime}, z^{\prime}\right)=(\tilde{x} / \tilde{w}, \tilde{y} / \tilde{w}, \tilde{z} / \tilde{w}) (x′,y′,z′)=(x~/w~,y~/w~,z~/w~)
上面的第二个公式刚好使用了计算机图形学笔记1-Transformation Matrix-变换*中注意的第二点:齐次坐标的等价性。使用上述的变换可以进行下面的操作:
前面我们已经讲了二维的情况。但是我们实际要处理的是三维的情况。经过投影变换就好像把一个棱台给变成了一个轴平行的box。这将方便我们使用正交投影变换矩阵变成CCV。
这里我们就搬出之前在相机变换所建立的坐标系,我们依然使用 z = n z=n z=n近端平面和 z = f z=f z=f远端平面,并使用 z = n z=n z=n近端平面作为观察平面。需要注意的是,上面的 n n n和 z z z在坐标系定义下都是小于 0 0 0的。对于投影变换后的 x s x_s xs和 y s y_s ys,类似前面二维情况:
y s = n z y x s = n z x y_{s}=\frac{n}{z} y\quad x_{s}=\frac{n}{z} x ys=znyxs=znx
我们可以整理成矩阵的形式:
(
x
y
z
1
)
⇒
(
n
x
/
z
n
y
/
z
u
n
k
n
o
w
n
1
)
≜
(
n
x
n
y
u
n
k
n
o
w
n
z
)
于是我们所要求的投影变换矩阵满足:
P
(
x
y
z
1
)
=
(
n
x
n
y
u
n
k
n
o
w
n
z
)
⇒
P
=
(
n
0
0
0
0
n
0
0
?
?
?
?
0
0
1
0
)
\mathbf{P}
那么 z s z_{s} zs是多少呢?对投影变换我们注意到:
近端平面 z = n z=n z=n的点映射以后点不发生变化。
远端平面 z = f z=f z=f的点映射以后z坐标不变。
根据第(1)点:近端平面 z = n z=n z=n的点映射以后点不发生变化。我们有:
P
(
x
y
n
1
)
=
(
x
y
n
1
)
≜
(
n
x
n
y
n
2
1
)
\mathbf{P}
所以
P
\mathbf{P}
P的第三行形式为
(
0
0
A
B
)
(
0
0
A
B
)
(
x
y
n
1
)
=
n
2
⇒
A
n
+
B
=
n
2
根据第(2)点:远端平面 z = f z=f z=f的点映射以后z坐标不变。我们有:
(
0
0
f
1
)
⇒
(
0
0
f
1
)
≜
(
0
0
f
2
f
)
⇒
A
f
+
B
=
f
2
\left(
于是我们有:
{
A
n
+
B
=
n
2
A
f
+
B
=
f
2
⇒
{
A
=
n
+
f
B
=
−
n
f
\left\{
这样我们就确定了投影变换的变换矩阵:
P
=
[
n
0
0
0
0
n
0
0
0
0
n
+
f
−
f
n
0
0
1
0
]
\mathbf{P}=
经过投影变换最终我们看到坐标发生了如下的变化:
P
[
x
y
z
1
]
=
[
n
x
n
y
(
n
+
f
)
z
−
f
n
z
]
≜
[
n
x
z
n
y
z
n
+
f
−
f
n
z
1
]
\mathbf{P}\left[
有时候我们想把屏幕坐标变换回世界坐标,这时候我们就需要用到 P \mathbf{P} P的逆矩阵:
P
−
1
=
[
1
n
0
0
0
0
1
n
0
0
0
0
0
1
0
0
−
1
f
n
n
+
f
f
n
]
≜
[
f
0
0
0
0
f
0
0
0
0
0
f
n
0
0
−
1
n
+
f
]
\mathbf{P}^{-1}=\left[
完整的透视变换包括了正视变换和投影变换的组合,先处理近大远小的投影变换为box,然后把box变回一个CCV。
M
p
e
r
=
M
o
r
t
h
P
=
[
2
n
r
−
l
0
l
+
r
l
−
r
0
0
2
n
t
−
b
b
+
t
b
−
t
0
0
0
f
+
n
n
−
f
2
f
n
f
−
n
0
0
1
0
]
\mathbf{M}_{\mathrm{per}}=\mathbf{M}_{\mathrm{orth}} \mathbf{P}=\left[
在OpenGL中,这一矩阵的定义可能不一样:
M
OpenGL
=
[
2
∣
n
∣
r
−
l
0
r
+
l
r
−
l
0
0
2
∣
n
∣
t
−
b
t
+
b
t
−
b
0
0
0
∣
n
∣
+
∣
f
∣
∣
n
∣
−
∣
f
∣
2
∣
f
∣
∣
n
∣
∣
n
∣
−
∣
f
∣
0
0
−
1
0
]
\mathbf{M}_{\text {OpenGL }}=\left[
这里我们使用了 M o r t h \mathbf{M}_{\mathrm{orth}} Morth,随之而来的问题是: M o r t h \mathbf{M}_{\mathrm{orth}} Morth中的 l , r , t , b l,r,t,b l,r,t,b这些值怎么定义呢,它们定义了我们的窗口看到的物体,由于近端平面 z = n z=n z=n的 x x x和 y y y不变,我们这里选择了近端平面 z = n z=n z=n来定义 l , r , t , b l,r,t,b l,r,t,b。
我们最后来看一下从相机坐标经过透视变换到屏幕坐标的算法流程:
我们通过 ( l , r , t , b ) (l,r,t,b) (l,r,t,b)和 n n n定义我们的窗口,我们可以进一步简化,如果我们屏幕的中心是原点,那么有:
l
=
−
r
,
b
=
−
t
.
另外我们可以添加每个像素是正方形的约束,使得图形没有形状的畸变,我们使用的 r r r和 t t t一定要和水平像素数 n x n_x nx和竖直像素数 n y n_y ny成比例:
n x n y = r t \frac{n_{x}}{n_{y}}=\frac{r}{t} nynx=tr
当 n x n_x nx和 n y n_y ny被确定以后,只剩下一个自由度,我们经常使用 θ \theta θ作为视场,下图为垂直视场,它满足关系:
tan θ 2 = t ∣ n ∣ \tan\frac{\theta}{2}=\frac{t}{|n|} tan2θ=∣n∣t
通过确定 n n n和 θ \theta θ,我们就可以计算 t t t来得到更一般的观测系统。
参考资料
- GAMES101-现代计算机图形学入门-闫令琪
- Shirley & Marschner, Fundamentals of Computer Graphics. forth edition, 139-156
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。