Perspective Projection

透视投影的第一步是将透视投影转换成正交投影,我们要保证近和远两个平面都是不变的,近、远两个平面都会被变成和近平面一样大。还记得在正交投影里,我们如何定义三维空间的长方体,分别是用x轴的覆盖定义它的左和右,用y轴的覆盖定义它的上和下,用z轴的覆盖表示远和近。在透视投影和正交投影中远和近的near和far是一样的,我们认为其已知。

在做透视投影的时候,我们要把这个frustum变成正交投影的cube。该如何定义这个frustum或者说视锥呢?可见下图:

视锥

我们可以从摄像机出发看向某个区域,如果我们看的就是这个进的平面,那么我们可以给这个平面定义一个宽度和高度。因此我们定义宽高比(aspect ratio)为宽度除以高度,定义视角(field-of-view(fovY))为可以看到的角度的范围(垂直可视角度),这个角度一般用来定义可以看到的视界。通过这两个定义我们也可以推出水平的可视角度。

我们也可以把这个概念迁移到空间中的定义,如下所示:

概念迁移

从侧面看透视投影的三角形,我们获得了上述定义的两个量的计算。

Canonical Cube to Screen

对于图形学来说,我们抽象地认为一个屏幕是一个二维数组,数组中的每一个元素是一个像素。这个二维数组的大小也叫做分辨率,而屏幕是一个典型的光栅成像设备,光栅化就是指把东西画在屏幕上。像素(pixel)来源于picture element的缩写,在这门课里,我们把它认为成一个一个的小方块,并且在一个方块内的颜色不会有变化。

我们可以定义屏幕空间在一个坐标系中,从屏幕的左下角为原点出发。像素的坐标我们用整数来描述,形如(x, y)(下标从0开始)。坐标范围从(0, 0)到(width - 1, height - 1)。虽然我们用整数坐标来表述像素坐标,但是像素的中心是在(x + 0.5, y + 0.5)。

接下来要做的事情就是将图像映射到屏幕空间,即[-1,1]3到屏幕上,所以这里暂时先不管z,只看xy。只要将xy从[-1,1]2拉伸到[0, width]×[0, height]。这个变换的矩阵如下:

拉伸的矩阵

左上角的3×3矩阵就是拉伸的部分,因为原来的中心在(0, 0)而现在的中心在(width/2, height/2),所以要进行平移。这个变换就叫做视口变换。

解决了变换的问题之后,要解决的是如何光栅化,也就是将三角形画到像素中。

Television-Raster Display CRT(Cathode Ray Tube)

早期的显示器成像原理为阴极射线管,将电子加速并经过磁场的偏转打在屏幕上,由于这个过程足够快,所以可以在屏幕上看到完整的画面,而不是一个一个电子的光点。

阴极射线管

通过一种扫描的方式,控制每一个电子打在什么位置,比如说可以在屏幕上从左往右,从上往下的画线。当画的足够密集时,基本上就可以覆盖整个屏幕了。为了让他画的快一点,可以隔一行画一条线,比如一开始画奇数行,到下一张图的时候只画偶数行,这个技术就叫做隔行扫描技术。这样,每张图的工作量都减少了一半,并且人眼很难感知察觉到这个中间缺少的部分,相当于每一次看到的都是完整的画面。但是对于高速运动的画面会造成严重的撕裂。

Frame Buffer:Memory for a Raster Display

现在拿到一个显示器,它如何知道要显示什么呢?其实,显示器是通过映射内存中的一块区域到屏幕上来生成图像。通过映射不同的区域,生成不同的图像。并且在这个过程中,这个内存通常来自于显存。

Flat Panel Displays

现在的主要显示设备是平板显示设备,主要就是LCD(Liquid Crystal Display)。

液晶显示器利用液晶的能力来控制一个像素显示什么。液晶通过不同的排布来影响光的极化也就是光的偏振方向。

Rasterization

我们可以用三角形表现三维空间中的物体,也可以用三角形来描绘二维空间。三角形的表现能力是很强的,它是最基础的多边形,并且他一定是一个平面的,三角形的内外定义十分清晰,在三角形内部如何插值是简单的。

所以我们光栅化时,用三角形来作为描述图像的载体。这样,我们在光栅化时,只要考虑像素和三角形的位置关系。

Sampling

一个简单的方法来做光栅化就是采样。这里的采样是指可以利用像素的中心对屏幕空间进行一个采样。

采样函数

比如这个采样的函数,给定任意一个三角形t,一个像素中心的坐标(x, y),当像素的中心在三角形内时,结果为1,否则为0。

for (int x = 0; x < xmax; ++x) 
	for (int y = 0; y < ymax; ++y) 
		image[x][y] = inside(tri, x + 0.5, y + 0.5);

我们如何进行这个采样,可以用上述代码完成。而inside函数内部如何判断像素中心点是否在三角形内就可以用之前学到的知识来解决。即通过将三角形的每一个点和被判断的点相连得到的向量叉乘按照某个方向旋转得到的三角形三条边的向量,如果这三次叉乘的结果中z得到相同的符号,则在三角形内,否则在三角形外(若在三角形边上,自己判断是在内部还是外部)。

但是在考虑三角形的光栅化时,要对所有的像素进行采样是不合理的。因为一个三角形只会覆盖一片区域,所以我们只要考虑这个区域附近的像素即可,所以我们可以用一个包围盒来表示上述的“区域”。

包围盒

即一个长方形围住的区域,盒子的大小来自于三角形三个顶点得到的x和y的最小最大值。如果像素本身不在这个区域内,就不需要对它进行判断。

甚至我们可以对于三角形覆盖的区域的每一行都只找最左和最右,这样一个像素都不会多考虑。