《Fundamentals of Computer Graphics》第五版 第八章 观测

228 阅读3分钟

从 3D 世界到 2D 视图的映射称为观测变换(viewing transformation)。它在物体序渲染中扮演着重要角色,用于计算场景中每个物体在图像空间中的位置,与射线追踪刚好相反。

通常,将 3D 世界中的点投影到 2D 图像只适用于渲染线框(wireframe),即,只渲染物体的边。本章只考虑由 3D 线段构成的线框模型,实心物体渲染由后续章节讨论。

wireframe.png

观测变换

观测变换把世界空间中的点映射到图像的像素上。这一过程涉及到许多方面:相机位置和取向(orientation)、投影类型、视场角(field of view)、图像分辨率等。大多数图形系统会把这一过程分解为下面三个变换:

  • 相机变换(camera transformation, 也称 eye transformation),将世界空间(world space)变换到相机空间(camera space, 也称为 eye space),它把相机以合适的取向摆放到原点。这一变换仅依赖于相机的位置和姿态。
  • 投影变换(projection transformation),将相机空间变换到规范可视体(canonical view volume),对相机空间中的点进行投影,使得所有可见点归一化到 [1,1][-1, 1] 区间上。这一变换仅依赖于投影类型。

    规范可视体也称为裁剪空间(clip space),其中的坐标也称为归一化设备坐标(normalized device coordinate)。

    有些 API 使用术语 “viewing transformation” 来表示相机变换,具体情况需具体分析。

  • 视口变换(viewport transformation, 也称 windowing transformation),将规范可视体变换到屏幕空间(screen space),把单位图像映射到像素坐标下想要的位置。这一变换仅依赖于输出图像的位置和尺寸。

    屏幕空间中的坐标也称为像素坐标(pixel coordinate)。

viewing-transformation.png

视口变换

规范可视体(canonical view volume)就是一个立方体 [1,1]3[-1,1]^{3}。在规范可视体中,相机朝着 z-z 方向,视口变换(viewport transformation)将 x=1x=-1x=+1x=+1y=1y=-1y=+1y=+1 分别映射到屏幕左边界、右边界、下边界、上边界,也就是将 [1,1]2[-1,1]^{2} 映射到 [0.5,nx0.5]×[0.5,ny0.5][-0.5,n_{x}-0.5]\times[-0.5,n_{y}-0.5],其中,图像分辨率为 nx×nyn_{x}\times n_{y}

CVV.png

zz 整合进来可得视口矩阵(viewport matrix):

Mvp=[nx200nx120ny20ny1200100001]\mathbf{M}_{\text{vp}} = \begin{bmatrix} \frac{n_{x}}{2} & 0 & 0 & \frac{n_{x} - 1}{2} \\ 0 & \frac{n_{y}}{2} & 0 & \frac{n_{y} - 1}{2} \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

它将点的坐标从规范可视体映射到屏幕空间:

[xscreenyscreenzcanonical1]=Mvp[xcanonicalycanonicalzcanonical1]\begin{bmatrix} x_{\text{screen}} \\ y_{\text{screen}} \\ z_{\text{canonical}} \\ 1 \end{bmatrix} = \mathbf{M}_{\text{vp}} \begin{bmatrix} x_{\text{canonical}} \\ y_{\text{canonical}} \\ z_{\text{canonical}} \\ 1 \end{bmatrix}

zz 值可用来确定前后关系。

正交投影变换

对于正交投影,相机空间中的可视区域可推广为 [l,r]×[b,t]×[f,n][l,r]\times[b,t]\times[f,n],这也称为正交可视体(orthographic view volume),它由 6 个平面围成:左平面(left plane)、右平面(right plane)、下平面(bottom plane)、上平面(top plane)、远平面(far plane)、近平面(near plane)。

orthographicViewVolume.png

正交投影变换(orthographic projection transformation)可以将正交可视体变换为规范可视体:

Morth=[2rl00r+lrl02tb0t+btb002nfn+fnf0001]\mathbf{M}_{\text{orth}} = \begin{bmatrix} \frac{2}{r - l} & 0 & 0 & -\frac{r + l}{r - l} \\ 0 & \frac{2}{t - b} & 0 & -\frac{t + b}{t - b} \\ 0 & 0 & \frac{2}{n - f} & -\frac{n + f}{n - f} \\ 0 & 0 & 0 & 1 \end{bmatrix}

相机变换

下面这些信息可以确定观测者(viewer)的位置和姿态:

  • 眼位(eye position)e\vec{e}
  • 视向(gaze direction)g\vec{g}
  • 向上向量(view-up vector)t\vec{t}

其中,t\vec{t} 是相机镜头垂直平分面上的任意向量。根据这些信息可以建立一个坐标系,原点为 e\vec{e},基向量为 u\vec{u}v\vec{v}w\vec{w}

viewer.png

相机变换(camera transformation)可以将规范坐标变换为相机坐标:

Mcam=[uxyzvxyzwxyzexyz0001]1=[xuyuzu0xvyvzv0xwywzw00001][100xe010ye001ze0001]\begin{aligned} \mathbf{M}_{\text{cam}} &= \begin{bmatrix} \vec{u}_{xyz} & \vec{v}_{xyz} & \vec{w}_{xyz} & \vec{e}_{xyz} \\ 0 & 0 & 0 & 1 \end{bmatrix}^{-1} \\ &= \begin{bmatrix} 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 \end{bmatrix} \begin{bmatrix} 1 & 0 & 0 & -x_{e} \\ 0 & 1 & 0 & -y_{e} \\ 0 & 0 & 1 & -z_{e} \\ 0 & 0 & 0 & 1 \end{bmatrix} \end{aligned}

将三个变换结合起来,可以给出绘制 3D 线段的伪代码:

construct Mvpconstruct Morthconstruct McamM=MvpMorthMcamfor each line segment (ai,bi) dop=Maiq=Mbidrawline(xp,yp,xq,yq)\begin{aligned} &\text{construct}\ \mathbf{M}_{\text{vp}} \\ &\text{construct}\ \mathbf{M}_{\text{orth}} \\ &\text{construct}\ \mathbf{M}_{\text{cam}} \\ &\mathbf{M} = \mathbf{M}_{\text{vp}}\mathbf{M}_{\text{orth}}\mathbf{M}_{\text{cam}} \\ &\text{\textbf{for} each line segment}\ (\vec{a}_{i}, \vec{b}_{i})\ \textbf{do} \\ &\quad\begin{aligned} &\vec{p} = \mathbf{M}\vec{a}_{i} \\ &\vec{q} = \mathbf{M}\vec{b}_{i} \\ &\text{drawline}(x_{p}, y_{p}, x_{q}, y_{q}) \end{aligned} \end{aligned}

射影变换

结合下图可知,透视投影满足:

ys=dzyy_{s} = \frac{d}{z}y

其中,dd 是相机焦点到投影屏的距离。

perspective.png

虽然分式无法通过仿射变换来表示,但如果将齐次坐标的最后一个分量扩展为任意实数,并约定齐次坐标 [x,y,z,w]T[x,y,z,w]^{T} 表示点 (x,y,z)=(x/w,y/w,z/w)(x',y',z')=(x/w,y/w,z/w),则齐次坐标的可逆线性变换:

[x~y~z~w~]=[a1b1c1d1a2b2c2d2a3b3c3d3efgh][xyzw]\begin{bmatrix} \tilde{x} \\ \tilde{y} \\ \tilde{z} \\ \tilde{w} \end{bmatrix} = \begin{bmatrix} 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 \end{bmatrix} \begin{bmatrix} x \\ y \\ z \\ w \end{bmatrix}

对应非齐次坐标的分式线性变换(fractional linear transformation):

(x~,y~,z~)=(x~/w~,y~/w~,z~/w~)(\tilde{x}', \tilde{y}', \tilde{z}') = (\tilde{x}/\tilde{w}, \tilde{y}/\tilde{w}, \tilde{z}/\tilde{w})

x~\tilde{x}' 为例:

x~=a1x+b1y+c1z+d1ex+fy+gz+h\tilde{x}' = \frac{a_{1}x' + b_{1}y' + c_{1}z' + d_{1}}{ex' + fy' + gz' + h}

上式也称为关于 xx'yy'zz'线性有理函数(linear rational function)。

齐次坐标的可逆线性变换也称为射影变换(projective transformation,又名 homography)。实际上,上面对齐次坐标的约定正是射影几何中齐次坐标的原始定义。

射影空间可以通过高一个维度的线性空间来定义。使用非零数乘对线性空间中的向量建立等价关系:

xαx, α0\vec{x}\sim\alpha\vec{x},\quad\forall\ \alpha\neq 0

其中,x=[x1,x2,,xn]T\vec{x}=[x_{1},x_{2},\cdots,x_{n}]^{T} 称为齐次坐标,或齐次射影坐标。非零向量的等价类就是射影空间中的点,而非齐次坐标 (x1/xn,x2/xn,,xn1/xn)(x_{1}/x_{n},x_{2}/x_{n},\cdots,x_{n-1}/x_{n}) 可看作 xn0x_{n}\neq 0 的等价类的一种表示。利用线性空间上的可逆线性变换可以定义射影空间上的变换:

A^x=defAx\mathbf{\hat{A}}\overline{\vec{x}}\xlongequal{def}\overline{\mathbf{A}\vec{x}}

其中,A\mathbf{A} 是线性空间中的可逆线性变换,A^\mathbf{\hat{A}} 是相应的射影变换,x\overline{\vec{x}} 是向量 x\vec{x} 所属的等价类。根据线性代数的基础知识容易知道,上式确实给出了射影空间上的可逆变换,称为射影变换(projective transformation);而且 A^=λA^\mathbf{\hat{A}}=\mathbf{\widehat{\lambda A}},反之亦然,即同一个射影变换的齐次坐标表示之间最多差一个非零数乘。

据前文所述,射影变换可以表示非齐次坐标的分式线性变换,在一定条件下,这又可以退化为仿射变换。实际上,仿射变换就是一种特殊的射影变换,它将无穷远点/有限远点仍映射为无穷远点/有限远点,而透视投影之所以可以用射影变换来表示,是因为中心投影本身就是射影映射(projective mapping)。

射影变换可以构成一个群:

  1. A^B^=AB^\mathbf{\hat{A}}\mathbf{\hat{B}} = \mathbf{\widehat{AB}}
  2. (A^B^)C^=A^(B^C^)(\mathbf{\hat{A}}\mathbf{\hat{B}})\mathbf{\hat{C}} = \mathbf{\hat{A}}(\mathbf{\hat{B}}\mathbf{\hat{C}})
  3. I^A^=A^I^=A^\mathbf{\hat{I}}\mathbf{\hat{A}} = \mathbf{\hat{A}}\mathbf{\hat{I}} = \mathbf{\hat{A}}
  4. A^1=A1^\mathbf{\hat{A}}^{-1} = \mathbf{\widehat{A^{-1}}}

齐次坐标可以连续做多次射影变换或逆变换,而且可以随时通过齐次化(homogenize),即除以最后一个分量(如果非零),来获取当前等价类的非齐次坐标表示。

homogeneous-affine-coordinate.png

透视投影

透视投影仍采用近平面和远平面来限制可视范围。近平面作为投影平面(projection plane),像面距(image plane distance)为 n-n。中心投影变换可以用透视矩阵(perspective matrix)来实现:

P=[n0000n0000n+ffn0010]\mathbf{P} = \begin{bmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n + f & -fn \\ 0 & 0 & 1 & 0 \end{bmatrix}

透视矩阵有很多种选择,它们都会非线性地扭曲 zz 值。

该矩阵对齐次坐标的作用如下:

P[xyz1][nxznyzn+ffnz1]\mathbf{P} \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} \sim \begin{bmatrix} \frac{nx}{z} \\ \frac{ny}{z} \\ n + f - \frac{fn}{z} \\ 1 \end{bmatrix}

由上式可知,矩阵 P\mathbf{P} 的选择保持了远平面上点的 zz 值、近平面上的点,以及可视区域中 zz 值的相对顺序。

perspective-matrix.png

由于数乘不改变射影变换,因此 P\mathbf{P} 的逆矩阵可以取为:

P1=[f0000f00000fn001n+f]\mathbf{P}^{-1} = \begin{bmatrix} f & 0 & 0 & 0 \\ 0 & f & 0 & 0 \\ 0 & 0 & 0 & fn \\ 0 & 0 & -1 & n + f \end{bmatrix}

透视矩阵 P\mathbf{P} 只是将透视可视体(perspective view volume,也称为 frustum,即视锥体)映射为正交可视体。而透视投影矩阵(perspective projection matrix)将透视可视体变为规范可视体:

Mper=MorthP=[2nrl0l+rlr002ntbb+tbt000n+fnf2fnfn0010]\mathbf{M}_{\text{per}} = \mathbf{M}_{\text{orth}}\mathbf{P} = \begin{bmatrix} \frac{2n}{r - l} & 0 & \frac{l + r}{l - r} & 0 \\ 0 & \frac{2n}{t - b} & \frac{b + t}{b - t} & 0 \\ 0 & 0 & \frac{n + f}{n - f} & \frac{2fn}{f - n} \\ 0 & 0 & 1 & 0 \end{bmatrix}

不同的图形学 API 采用的规范略有不同,比如 OpenGL 中 nnffzcanonicalz_{\text{canonical}} 与这里的正负号刚好相反,有些图形学 API 将规范可视体定为 [0,1]3[0,1]^{3},这些都会略微改变投影矩阵。

透视投影变换将线段映射为线段,进而将三角形映射为三角形、将平面映射为平面。实际上,任何矩阵 M\mathbf{M} 都会把线段 q+t(Qq)\vec{q}+t(\vec{Q}-\vec{q}) 变换为 Mq+t(MQMq)=r+t(Rr)\mathbf{M}\vec{q}+t(\mathbf{M}\vec{Q}-\mathbf{M}\vec{q})=\vec{r}+t(\vec{R}-\vec{r}),齐次化后:

q\vec{q}Q\vec{Q} 是齐次化后的齐次坐标,表示变换前线段的起点和终点。

r+t(Rr)wr+t(wRwr)=rwr+f(t)(RwRrwr)其中,f(t)=wRtwr+t(wRwr)\frac{\vec{r} + t(\vec{R} - \vec{r})}{w_{r} + t(w_{R} - w_{r})} = \frac{\vec{r}}{w_{r}} + f(t)\left(\frac{\vec{R}}{w_{R}} - \frac{\vec{r}}{w_{r}}\right) \\ \text{其中,}f(t) = \frac{w_{R}t}{w_{r} + t(w_{R} - w_{r})}

而透视投影矩阵 Mper\mathbf{M}_{\text{per}} 可以保证 f(t)f(t)[0,1][0,1] 上单调增。

结合相机变换和视口变换可得透视观测变换:

M=MvpMorthPMcam\mathbf{M} = \mathbf{M}_{\text{vp}}\mathbf{M}_{\text{orth}}\mathbf{P}\mathbf{M}_{\text{cam}}

综上,绘制 3D 线段的伪代码如下:

compute Mvpcompute Mpercompute McamM=MvpMperMcamfor each line segment (ai,bi) dop=Maiq=Mbidrawline(xp/wp,yp/wp,xq/wq,yq/wq)\begin{aligned} &\text{compute}\ \mathbf{M}_{\text{vp}} \\ &\text{compute}\ \mathbf{M}_{\text{per}} \\ &\text{compute}\ \mathbf{M}_{\text{cam}} \\ &\mathbf{M} = \mathbf{M}_{\text{vp}}\mathbf{M}_{\text{per}}\mathbf{M}_{\text{cam}} \\ &\text{\textbf{for} each line segment}\ (\vec{a}_{i}, \vec{b}_{i})\ \textbf{do} \\ &\quad\begin{aligned} &\vec{p} = \mathbf{M}\vec{a}_{i} \\ &\vec{q} = \mathbf{M}\vec{b}_{i} \\ &\text{drawline}(x_{p}/w_{p}, y_{p}/w_{p}, x_{q}/w_{q}, y_{q}/w_{q}) \end{aligned} \end{aligned}

视场角

有时为简单起见,进一步约定:

l=rb=tnxny=rt\begin{aligned} &l = -r \\ &b = -t \\ &\frac{n_{x}}{n_{y}} = \frac{r}{t} \end{aligned}

即,观测窗口中心在 zz 轴上,而且图像没有变形。如果 nxn_{x}nyn_{y} 已经确定,则仅剩一个自由度,一般用视场角(field-of-view,也称为视场)θ\theta 来描述:

tanθ2=tn\tan\frac{\theta}{2} = \frac{t}{|n|}

这里的视场角也称为垂直视场角(vertical field-of-view)。

field-of-view.png