0%

Viewing

Viewing

The transformations is project 3D points in the scene (world space) to 2D points in the image (image space)

Rasterization(光栅化) 本质上是从 物体 $\rightarrow$ 经过矩阵变换 $\rightarrow$ 砸到屏幕(眼睛)上

三视图
左图:正交投影(Orthographic Projection)中图:透视投影(Perspective Projection) 右图:移除了隐藏线(Hidden Lines)的 透视投影

The Viewport Transformation (视口变换)

视图变换(The viewing transformation)的任务是将以规范坐标系(canonical coordinate system)中的 (x, y, z) 坐标表示的 3D 位置,映射为以像素为单位表示的图像中的坐标。

最好的处理方法是将其分解为几个更简单的变换的乘积。大多数图形系统通过使用以下三个变换序列来实现这一点:

  • 相机变换(camera transformation)或 眼变换(eye transformation): 这是一个刚体变换(rigid body transformation),负责将相机放置在原点,并调整到一个方便的朝向。它仅取决于相机的位置和朝向,即相机的 “位姿”(pose)。
  • 投影变换(projection transformation): 它将点从相机空间进行投影,使得所有可见点在 $x$ 和 $y$ 方向上都落在 $-1$ 到 $1$ 的范围内。它仅取决于所需的投影类型。
  • 视口变换(viewport transformation)或 窗口变换(windowing transformation): 它将这个(范围在 -1 到 1 的)单位图像矩形,映射为像素坐标系中所需的矩形。它仅取决于输出图像的大小和位置。

为了便于描述这个过程的各个阶段,我们给这些作为变换输入和输出的坐标系起了名字:
object Transformations to screen
将物体从其原始坐标(变换)到屏幕空间的一系列坐标空间和变换过程。

  • 相机变换(The camera transformation) 将点从规范坐标(或世界空间)转换到相机坐标,或者说将它们放置在相机空间中。
  • 投影变换(The projection transformation) 将点从相机空间移动到规范视体积(canonical view volume) 中。
  • 视口变换(The viewport transformation) 将规范视体积映射到屏幕空间(screen space)。

World Space 与 canonical view volume 是两个不同的空间
World Space (起点) $\xrightarrow{\text{相机变换}}$ Camera Space $\xrightarrow{\text{投影变换}}$ Canonical View Volume (中转站) $\xrightarrow{\text{视口变换}}$ Screen Space

The Viewport Transformation

规范视体积” (canonical view volume) 中,并且我们希望用一个看向 $-z$ 方向的正交相机来观察它,规范视体积是一个包含所有笛卡尔坐标在 $-1$ 和 $+1$ 之间的 3D 点的立方体——即 $(x, y, z) \in [-1, 1]^3$
coordinator cube 典型视图体积是一个边长为二、以原点为中心的立方体。

我们将 $x = -1$ 投影到屏幕左侧,$x = +1$ 投影到屏幕右侧,$y = -1$ 投影到屏幕底部,以及 $y = +1$ 投影到屏幕顶部。

每个像素“拥有”一个以整数坐标为中心的单位正方形;图像边界比像素中心多出半个单位的延伸量(overshoot);并且最小的像素中心坐标是 $(0, 0)$。
[注:这意味着第0个像素的中心是0,但它的左边缘是 -0.5,右边缘是 0.5]

如果我们要绘制到一个有 $n_x \times n_y$ 个像素的图像(或屏幕上的窗口)中,我们需要将正方形 $[-1, 1]^2$ 映射到矩形 $[-0.5, n_x - 0.5] \times [-0.5, n_y - 0.5]$。

由于视口变换将一个轴对齐矩形映射到另一个轴对齐矩形,它是公式 (6.6) 给出的窗口变换的一种情况:

注意,这个矩阵(指上一张图的 3x3 矩阵)忽略了规范视体积中点的 $z$ 坐标,因为一个点沿着投影方向的距离(即它有多远),并不会影响该点在图像(平面)上的投影位置。之前,我们(给矩阵)添加了一行和一列,以便在不改变 $z$ 坐标的情况下将其保留下来。最终我们将需要这些 $z$ 值,因为它们可以用来使较近的表面遮挡较远的表面

正交投影变换 (The Orthographic Projection Transformation)

我们通常想要渲染的几何体,是位于“规范视体积”以外的某个空间区域内的。我们泛化(推广)视图的第一步是:保持观察方向和朝向固定不变(依然是沿着 $-z$ 轴看,$+y$ 轴朝上),但允许观察任意大小的矩形区域。与其替换掉(刚才讲的)视口矩阵,我们不如通过在它的右侧乘以另一个矩阵来扩充它。在这些约束下,视体积是一个“轴对齐的盒子”(axis-aligned box)。我们将命名其各个面的坐标,使得视体积表示为 $[l, r] \times [b, t] \times [f, n]$,如图所示orthography view volume
我们把这个盒子称为“正交视体积”,并将其边界平面定义如下:

1
2
3
4
5
6
x =l ≡left plane,
x =r≡rightplane,
y =b≡bottomplane,
y =t≡topplane,
z =n≡nearplane,
z =f ≡farplane.

假设观察者是沿着 负 $z$ 轴观看的,且其头部指向 $y$ 方向。这意味着 $n > f$,这可能有些反直觉。但如果你假设整个正交视体积的 $z$ 值都是负数,那么只有当 $n > f$ 时,$z = n$ 的“近”平面才会离观察者更近;$f$ 是一个比 $n$ 更小的数,也就是说,它是一个绝对值比 $n$ 更大的负数。(eg:-10>-100)个概念展示在图
从正交视体积到规范视体积的变换是另一种窗口变换,所以我们可以直接把正交视体积和规范视体积的边界代入方程 (6.7),从而得到这个变换的矩阵:

为了在正交视体积中绘制 3D 线段,我们将它们投影到屏幕的 $x$ 和 $y$ 坐标中,并忽略 $z$ 坐标。我们通过组合方程 (7.2) 和 (7.3) 来实现这一点。请注意,在程序中,我们将这些矩阵相乘形成一个(复合)矩阵,然后按如下方式对点进行变换操作:

(变换后的)$z$ 坐标现在将位于 $[-1, 1]$ 范围内。
因此,绘制许多具有端点 $\mathbf{a}_i$ 和 $\mathbf{b}_i$ 的 3D 线段的代码变得既简单又高效:

1
2
3
4
5
6
7
construct M_vp                              // 构建视口变换矩阵
construct M_orth // 构建正交投影矩阵
M = M_vp * M_orth // 关键步骤:矩阵预合并!
for each line segment (a_i, b_i) do // 遍历每一条线
p = M * a_i // 用合并后的矩阵变换点 a
q = M * b_i // 用合并后的矩阵变换点 b
drawline(x_p, y_p, x_q, y_q) // 画线(只用 x, y 坐标)

相机变换 (The Camera Transformation)

我们希望能能够在 3D 空间中改变视点,并朝向任意方向观察。关于如何指定观察者的位置和朝向,存在许多不同的惯例。我们将采用如下定义

  • 眼位置(the eye position) $\mathbf{e}$,
  • 视线方向(the gaze direction) $\mathbf{g}$,
  • 视图向上向量(the view-up vector) $\mathbf{t}$。
    眼位置是眼睛进行“观察”所在的点。如果将图形学看作摄影过程,它就是镜头的中心。视线方向是沿着观察者观看方向的任意向量。视图向上向量是位于将观察者头部平分为左右两半的那个平面内的任意向量,并且对于一个站在地面上的人来说,该向量指向“天空”。这些向量为我们提供了足够的信息,可以建立一个以 $\mathbf{e}$ 为原点、以及包含 $\mathbf{uvw}$ 基底的坐标系,使用如下结构公式:

如果我们需要变换的所有点,都已经是以 $\mathbf{e}$ 为原点,且以基向量 $\mathbf{u}, \mathbf{v}, \mathbf{w}$ 为基底的坐标形式存储的,那么我们的工作就完成了。但是如图所示,模型的坐标是根据规范(或世界)原点 $\mathbf{o}$ 以及 $x, y, z$ 轴来存储的。
ordinary coordinate 为了任意查看,我们需要将要存储的点转换到“合适”的坐标系统。在这种情况下,它的原点为 e,并且坐标偏移以 uvw 表示。

为了使用我们已经开发好的(投影和视口变换)机制,我们只需要将希望绘制的线段端点的坐标,从 $xyz$ 坐标转换为 $uvw$ 坐标。执行此变换的矩阵是相机坐标系的“规范-基底”矩阵(即世界坐标到相机坐标的变换矩阵):

或者,我们可以将这同一个变换理解为:首先将 $\mathbf{e}$ 移动到原点,然后将 $\mathbf{u}, \mathbf{v}, \mathbf{w}$ (旋转)对齐到 $\mathbf{x}, \mathbf{y}, \mathbf{z}$。为了使我们之前那个仅适用于(沿)$z$ 轴观察的算法,能够适用于具有任意位置和朝向的相机,我们只需要将这个相机变换添加到视口变换和投影变换的乘积中,这样它就能在投影之前将输入的点从世界坐标转换为相机坐标:

1
2
3
4
5
6
7
8
construct M_vp                                          // 构建视口矩阵
construct M_orth // 构建正交投影矩阵
construct M_cam // 构建相机变换矩阵
M = M_vp M_orth M_cam // 将它们乘在一起(注意顺序:先乘相机,再投影,最后视口)
for each line segment (a_i, b_i) do // 遍历每条线段
p = M a_i // 变换点 a
q = M b_i // 变换点 b
drawline(x_p, y_p, x_q, y_q) // 画线

再一次地,一旦矩阵基础设施就位,几乎不需要编写什么代码。