喳谍 发表于 2025-6-24 19:52:48

《Fundamentals Of Computer Graphics》第八章 视图 总结

开篇

  上一章主要讲了使用变换矩阵和改变坐标系统,还有一个次重要的一点就是使用矩阵在物体的三维位置和物体在二维视图的位置之间进行变换。其中三维到二维的映射就叫做视图变换(Viewing Transformation),这种映射在物体顺序渲染中很重要,因为这种渲染方式需要我们快速地为场景中的每个物体找到它在图像空间的位置。第四章讲的光线追踪,覆盖了透视视图和正交视图,以及如何为给定的视图生成光线,这章讲的就是这个过程的反向过程,如何使用矩阵变换来表达任意平行或透视视图。
  要注意的是把点从世界映射到图像上只能很好地进行线框(Wireframe)渲染,也就是只能看到被绘制的物体的边缘部分,如下图所示。而且更近的物体可能不会遮蔽更远的物体,就像光线追踪器需要为每个视线找到最近的交点一样,一个能显示物体的实体表面的基于物体顺序的渲染器需要在影响相同像素着色的所有表面中挑选出最近的表面。在这章,我们假设模型只由三维线段组成,后面的章节将会讨论渲染实体表面所需要的机制。

视图变换(Viewing Transformation)

  视图变换的作用就是把规范坐标系统的\((x,y,z)\)三维位置映射到以像素为单位的图像中,不过这稍微有点复杂,因为依赖于摄像头的位置和朝向还有投影类型、视场、图像的分辨率。和其它的很多复杂变换一样,我们最好把视图变换拆解为几个更简单的变换,许多图形系统通过一序列的三个变换来达到视图变换。

[*]一个摄像头变换或者眼变换:它是一个刚体变换,通过把一个有着便捷朝向的摄像头放到原点来做到,因此只取决于位置、朝向或者摄像头的姿态。
[*]一个投影变换:它会变换在摄像头空间中的点并且让可见的点都落在\(x,y \in [-1,1]\)的归一化坐标空间内,它只取决于投影的类型。
[*]一个视口变换(Viewport Transformation)或窗口变换(Windowing Transformation):它把单位的图像矩形映射到期望的像素坐标矩形内,它只取决于输出图像的大小和位置。
  为了更好的描述如下图所示的过程的几个阶段,我们给予一些坐标系统名字。

摄像头变换把规范坐标系内的坐标转换到摄像头空间(Camera Space)中,投影变换把摄像头空间中可见的点变换到规范视图体内,视口变换在最后把规范视图体映射到屏幕空间(Screen Space)。这几个单独的变换都很简单,下面先从正交投影开始,后续会覆盖支持透视投影所需要的改变。
视口变换(Viewport Transformation)

  我们假设被看到的物体最后都在规范视图体(Canonical View Volume)内,而且期望有个正交摄像头朝着\(+z\)方向观察。规范视图体是个立方体,它包含着笛卡尔坐标在\([-1,1]\)之内的所有三维点,也就是\((x,y,z) \in [-1,1]^3\)如下图所示。我们把\(x=-1\)投影到屏幕左边,把\(x=1\)投影到屏幕右边,把\(y=-1\)投影到屏幕底边,把\(y=1\)投影到屏幕顶边。如果屏幕左下角的像素中心坐标为\((0,0)\)且像素中心间距一单位,假设一个有着水平\(n_x\)像素和竖直\(n_y\)像素的屏幕,这个时候我们得把正方形\([-1,1]^2\)映射到矩形\([-0.5,n_x-0.5] \times [-0.5,n_y-0.5]\)。

  出于初学的目的,我们先假设所有被绘制的线段在被变换后都完全处于规范视图体内部。不过,当我们之后讨论裁剪(Clipping)的时候会取消这个限制。因为视口变换只是映射矩形,因此视口变换可以如下所示

\[\begin{bmatrix} x_{screen} \\ y_{screen} \\ 1 \end{bmatrix} = \begin{bmatrix} \frac{n_x}{2} & 0 & \frac{n_x-1}{2} \\ 0 & \frac{n_y}{2} & \frac{n_y-1}{2} \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x_{canonical} \\ y_{canonical} \\ 1 \end{bmatrix}\]
要注意的是式子中的矩阵忽视了\(z\)坐标,因为点沿着投影方向的距离和它所影响的像素位置无关。在正式称之为视口矩阵(Viewport Matrix)前,我们增加一行和一列来支持\(z\)坐标,

\
当前这个章节不需要这些,不过当我们想让更近的表面遮蔽更远的表面时会用到\(z\)值。
正交投影变换(The Orthographic Projection Transformation)

  当然了,被看到物体一般都不会直接处于规范视图体内,我们需要把位于摄像头空间的视图体内的物体变换到规范视图体中。我们首先确定摄像头空间中相机的观察方向为\(-z\)方向并且以\(+y\)为向上方向,对于正交投影来说的摄像头空间内的视图体为\( \times \times \),我们称这个视图体为正交视图体(Orthographic View Volume),它的包围平面为:

\[\begin{align*}x&=l\equiv左平面\\x&=r\equiv右平面\\y&=b\equiv底平面\\y&=t\equiv顶平面\\z&=n\equiv近平面\\z&=f\equiv远平面\end{align*}\]

由于我们确定了观察方向为\(-z\)方向并且以\(+y\)为向上方向,这会导致\(n>f\),可能看起来怪怪的。不过考虑到整个正交视图体都有负\(z\)值,\(z=n\)的近平面有着更大的\(z\)值因此离观察者最近,下图是一个正交视图体的直观展示。

  从正交视图体到规范视图体的变换其实是另外一种窗口变换,因此正交投影变换矩阵为

\[\mathbf{M}_\mathrm{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}{f-n} & -\frac{f+n}{f-n} \\ 0 & 0 & 0 & 1 \end{bmatrix}\]
摄像头变换(The Camera Transformation)

  我们通常可能想在世界空间中的任意位置摆放有着任意朝向的摄像头,通过摄像头变换我们就能把位于世界空间中的物体变换到摄像头空间中,为此我们需要

[*]\(\mathbf{e}\):眼睛位置
[*]\(\mathbf{g}\):视线方向
[*]\(\mathbf{t}\):向上向量

要注意的是这里的向上向量\(\mathbf{t}\)只是一个辅助向量,它会把观察者的头分成两半,而且对于站在地面上的人来说会指向天空。有了这些信息,我们就能构建一个坐标系统,以\(\mathbf{e}\)为原点,以\(\mathbf{uvw}\)为基,如上图所示。利用第二章提到的知识,我们可以这样得到基向量

\[\begin{align*}\mathbf{w} &= - \frac{\mathbf{g}}{||\mathbf{g}||} \\\mathbf{u} &= \frac{\mathbf{t} \times \mathbf{w}}{||\mathbf{t} \times \mathbf{w}||} \\\mathbf{v} &= \mathbf{w} \times \mathbf{u}\end{align*}\]
得到标准正交基后,接下来我们利用上一章提到的坐标系统之间转换的知识,把规范坐标系内的物体变换到摄像头空间中,变换矩阵为

\[\mathbf{M}_\mathrm{cam}=\begin{bmatrix} \mathbf{u} & \mathbf{v} & \mathbf{w} & \mathbf{e} \\ 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}\]
使用这个矩阵变换后,摄像头会位于摄像头空间中的原点,并且以\(+y\)为向上方向,向\(-z\)方向观察,这就对应了上一部分开头对摄像头做的假设。这里你可能会问为什么会朝着\(-z\)观察,这是因为\(\mathbf{w} = - \mathbf{g}/||\mathbf{g}||\),所以观察方向\(\vec{g}\)在摄像头空间中为\(-z\)。综上用于把规范坐标系内的物体投影到屏幕空间的矩阵\(\mathbf{M}\)为

\[\mathbf{M}=\mathbf{M}_\mathrm{vp}\mathbf{M}_\mathrm{orth}\mathbf{M}_\mathrm{cam}\]
透视投影变换

  透视投影变换和正交投影变换一样,都是要把摄像头空间内的视图体变换到规范视图体,不过透视投影变换的视图体和正交投影变换的六面体不一样,下图是这两者的区别

我们称透视投影变换的视图体为视锥台(View Frustum),它是一个视锥体被近平面截去后所获得的一个棱台,因此接下来要做的就是把视锥台映射映射到规范视图体\([-1,1]^3\)。假设近平面的左边和右边分别有\(x=l\)和\(x=r\),近平面的底边和顶边分别有\(y=b\)和\(y=t\)。现有视锥台内一点\(\mathbf{p}(x_p,y_p,z_p)\),我们先以\(y_p\)的变换为例。向\(+x\)方向观察,我们可以得到这样一张图

图中绿色的是一条边缘视线,这根边缘视线很特殊,有两个关键的洞悉。我们可以使用\(z=z_p\)平面截取视锥台从而获得一个矩形截面,令截面的高为\(\mathrm{height}\)。我们还可以还可以获得\(z=z_p\)平面与这根边缘视线的交点\(\mathbf{q}\),\(\mathrm{height}\)与\(y_q\)的关系为\(\mathrm{height}/2=y_q\)。除此之外,另外一个易得的点就是这根线上的点进行投影变换后的\(y\)坐标应该都等于1。
  这个时候就发现我们可以进行一次简单的线性变换,把\(\mathbf{p}\)的\(y\)坐标映射到\([-1,1]\)区间内,而这个变换和\(z=z_p\)平面截取视锥台所获得的矩形截面的高有关,具体可以用下面这个公式描述

\
而矩形截面的高\(\mathrm{height}\)可以用相似三角形得到

\[\begin{align*}\frac{n}{\frac{t-b}{2}}&=\frac{z_p}{\frac{\mathrm{height}}{2}} \\\mathrm{height} &= \frac{z_p(t-b)}{n}\end{align*}\]
因此\(y_p\)的变换公式为

\
以此为基础的\(x_p\)的变换公式为

\
到这里就完成了\(x_p\)和\(y_p\)的变换,用上述两个公式我们就能为摄像头空间中的点找到它能影响的像素。这个时候你可能会想\(z_p\)的变换也和上述公式类似,是利用近平面和远平面进行线性变换的,而且公式如下

\
而实际情况却不是这样的,通过翻阅用于不同图形API的数学库,你就会发现这些数学库用到的透视投影矩阵都会非线性地扭曲\(z\)坐标。GLM中的右手透视投影perspectiveRH_NO用到的变换如下

\
当\(z=-|n|\)时,\(z^\prime=-1\)。当\(z=-|f|\),\(z^\prime=1\)。而DirectXMath中的右手透视投影XMMatrixPerspectiveFovRH用到的变换如下

\
当\(z=-|n|\)时,\(z^\prime=0\)。当\(z=-|f|\)时,\(z^\prime=1\)。这里要说明一下,因为Direct3D用到的规范视图体的\(z\in \),所以配套的DirectXMath数学库使用的是这种变换。这里先不去深究非线性扭曲的原因,对于我们的情况来说可以直接使用GLM中的perspectiveRH_NO函数所用到的变换。因此\(x_p\)、\(y_p\)、\(z_p\)的变换分别为

\[\begin{align*}x^{\prime}_p&=\frac{2n}{r-l} \frac{x_p}{z_p} \\y^{\prime}_p&=\frac{2n}{t-b} \frac{y_p}{z_p} \\z^{\prime}_p&=\frac{|f|+|n|}{|f|-|n|}+\frac{2|f||n|}{|f|-|n|} \frac{1}{z_p} = (\frac{(|f|+|n|)z_p}{|f|-|n|}+\frac{2|f||n|}{|f|-|n|}) \frac{1}{z_p}\end{align*}\]
通过观察,发现这三个分量的变换都得除以\(z_p\),但是我们的\(4\times4\)矩阵不能直接完成这件事。于是这个时候齐次坐标就登场了,我们先赋予\(\mathbf{p}\)齐次坐标到\(\mathbf{p}(x_p,y_p,z_p,1)\)。对于以上三个分量的变换,我们先不考虑\(1/z_p\)对变换的影响,即为每个分量的变换计算除了\(1/z_p\)之外的部分,此外我们还让变换后的齐次坐标\(w^{new}_p\)等于\(z_p\),这样就把\(\mathbf{p}(x_p,y_p,z_p,1)\)变换到了4D空间内,这个变换如下所示

\[ \mathbf{p}^{new} =\begin{bmatrix} \frac{2n}{r-l} & 0 & 0 & 0 \\ 0 & \frac{2n}{t-b} & 0 & 0 \\ 0 & 0 & \frac{|f|+|n|}{|f|-|n|} & \frac{2|f||n|}{|f|-|n|} \\ 0 & 0 & 1 & 0 \end{bmatrix} \begin{bmatrix} x_p \\ y_p \\ z_p \\ 1 \end{bmatrix}\]
我们称等式右侧的矩阵为透视投影变换矩阵,变换后\(\mathbf{p}^{new}\)的四个分量分别为

\[\begin{align*}x^{new}_p&=\frac{2n}{r-l} x_p \\y^{new}_p&=\frac{2n}{t-b} y_p \\z^{new}_p&=\frac{(|f|+|n|)z_p}{|f|-|n|}+\frac{2|f||n|}{|f|-|n|} \\w^{new}_p&=z_p\end{align*}\]
最后我们进行一次透视除法也就是齐次化(Homogenize),从而得到\(\mathbf{p}^{\prime}=\mathbf{p}^{new}/w^{new}_p\)。这种先变换到4D空间再进行透视除法也是其它数学库所用到的透视矩阵和一些图形管线会实现的事。
透视变换的一些属性(Some Properties of the Perspective Transform)

  透视变换的一个重要的属性就是直线被变换后依旧是直线,平面被变换后依旧是平面。下面我们证明这一点,现有摄像头空间内的两点\(\mathbf{q}\)、\(\mathbf{Q}\),我们用参数\(t\in\)描述线段\(\mathbf{qQ}\)

\[\mathbf{q}+t(\mathbf{Q}-\mathbf{q})\]
令这两个点在4D空间内的坐标分别为\(\mathbf{r}\)和\(\mathbf{R}\),使用透视矩阵\(\mathbf{M}\)变换可得

\[\mathbf{Mq}+t(\mathbf{MQ}-\mathbf{Mq})\equiv\mathbf{r}+t(\mathbf{R}-\mathbf{r})\]
齐次化后的三维线段为

\[\frac{\mathbf{r}+t(\mathbf{R}-\mathbf{r})}{w_r+t(w_R-w_r)}\]
经过暴力运算后可重写成

\[\frac{\mathbf{r}}{w_r}+f(t)(\frac{\mathbf{R}}{w_R}-\frac{\mathbf{r}}{w_r})\]
而\(f(t)\)为

\
视场(Field-of-View)

  视场即FOV(Field-of-View),它是透视投影变换部分提到的边缘视线与观察方向\(\vec{g}\)之间的夹角的两倍,我们令它为\(\theta\)。之前的部分提到的透视变换矩阵为

\[\begin{bmatrix} \frac{2n}{r-l} & 0 & 0 & 0 \\ 0 & \frac{2n}{t-b} & 0 & 0 \\ 0 & 0 & \frac{|f|+|n|}{|f|-|n|} & \frac{2|f||n|}{|f|-|n|} \\ 0 & 0 & 1 & 0 \end{bmatrix}\]
\(y\)的缩放因子为

\[\frac{2n}{t-b}\]

由上图易得\(\theta\)和它的关系为

\[\tan\frac{\theta}{2}=\frac{t-b}{2|n|}\]
因此我们可以通过视场\(\theta\)、近平面和远平面的\(z\)值的绝对值\(|n|\)和\(|f|\)、输出图像的宽高比\(\mathrm{AspectRatio}\)得到透视变换矩阵。下面分别列举GLM和DirectXMath用到的右手透视变换列矩阵\(\mathbf{M}\),以列矩阵形式有

\[\mathbf{M}_{\mathrm{GLM}} = \begin{bmatrix} \frac{1}{\tan(\theta/2)\mathrm{AspectRatio}} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan(\theta/2)} & 0 & 0 \\ 0 & 0 & -\frac{|f|+|n|}{|f|-|n|} & -\frac{2|n||f|}{|f|-|n|} \\ 0 & 0 & -1 & 0 \end{bmatrix}\]

\[\mathbf{M}_{\mathrm{DXMATH}} = \begin{bmatrix} \frac{1}{\tan(\theta/2)\mathrm{AspectRatio}} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan(\theta/2)} & 0 & 0 \\ 0 & 0 & \frac{|f|}{|n|-|f|} & \frac{|n||f|}{|n|-|f|} \\ 0 & 0 & -1 & 0 \end{bmatrix}\]
视图变换中的逻辑

  这本书有很多地方我觉得都写得不错的,不过这一章视图真的非常非常重要,但是书中只讲了“要这么做”,在刚开始写图形程序的时候,由于还是一知半解的状态,因此犯了很多和它相关的错误。因此只知道“要这么做”是不够的!还应该查阅相关资料并且理解“为什么要这么做”,这个部分就来解释下背后的一些逻辑。
  视口变换还好,没有什么太要关注的地方。对于摄像头变换来说你可能会想,为什么摄像头在摄像头空间中会向\(z\)轴看以及\(\mathbf{uvw}\)中的\(\mathbf{w}\)基向量为什么和观察方向\(\vec{g}\)相关联。这正是因为我们的规范视图体内的\(z\)坐标是指代远近的,所以必须这么做。
  此外你还可能想为什么摄像头变换要使用\(\mathbf{u}\)、\(\mathbf{v}\)、\(\mathbf{w}\)这三个基向量,除了这三个基向量能建立一个右手系外,我们其实还能基于\(\mathbf{u}\)、\(\mathbf{v}\)、\(\mathbf{w}\)使用别的右手系来进行摄像头变换,例如以\(\mathbf{-u}\)、\(\mathbf{v}\)、\(\mathbf{-w}\)这三个基向量建立右手系。不过使用这三个基向量有个缺点,稍微观察一下就会发现这么做的话,所有可见的物体在摄像头空间中都会有\(+z\)值,而且在左手边的物体在摄像头空间内都有\(+x\)值,如果直接进行透视除法会发现理论上在左手边的物体会出现在图像的右边!因此我们还得修改透视投影变换矩阵来修正使用\(\mathbf{-u}\)、\(\mathbf{v}\)、\(\mathbf{-w}\)这三个基向量的摄像头变换矩阵带来的错误。别的右手系就不赘述了,这个时候就发现\(\mathbf{u}\)、\(\mathbf{v}\)、\(\mathbf{w}\)实际上是最便捷的选择,使用这三个基向量只需除以\(-z\)就能完成透视除法而且不会产生别的错误,就如同之前提到的矩阵所示

\[\mathbf{M}_{\mathrm{GLM}} = \begin{bmatrix} \frac{1}{\tan(\theta/2)\mathrm{AspectRatio}} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan(\theta/2)} & 0 & 0 \\ 0 & 0 & -\frac{|f|+|n|}{|f|-|n|} & -\frac{2|n||f|}{|f|-|n|} \\ 0 & 0 & -1 & 0 \end{bmatrix}\]
  摄像头变换要理解的差不多就这些,对于透视投影变换来说要注意的就是\(z\)坐标的非线性变换,这实际上是为了更好地分配精度,来让近处的深度精度更高,从而让人眼不易观察出近处的瑕疵。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 《Fundamentals Of Computer Graphics》第八章 视图 总结