上一节介绍了如下内容:
Viewing (观测) transformation
- View (视图) / Camera transformation
- Projection (投影) transformation
- Orthographic (正交) projection
- 平移到原点
- 缩放成([-1,1]^3)的立方体
- Perspective (透视) projection
- 将frustum挤压成长方体
- 运用正交投影得到([-1,1]^3)的立方体
- Orthographic (正交) projection
具体来说上一节其实就是介绍了如何把3D空间物体变换为一个大小([-1,1]^3)的立方体内,那么下一步所要做的事情(把立方体画在屏幕上,即光栅化)就是这一节所要介绍的。
1. Perspective Projection
下图灰色平面表示近平面,可由四个变量表示((l,r,b,t)),另外假设近平面是对称的,即(l=-r,b=-t)。下面引出如下几个定义:
- Aspect ratio = width / height
- width和height指的是近平面的宽、高
- Aspect ratio即为宽高比,比如手机的 4:3或者 16:9的比例
- Vertical Field of View (fovY): 垂直可视角度。即下图中两条红线之间的角度。
下面给出了垂直可视角度的侧面可视图(只画出了上半部分):
fovY和aspect 与 ((l,r,b,t))的关系如下:
2. Canonical Cube to Screen
MVP表示Model transformation (placing objects),
View transformation (placing camera),Projection transformation的首字母缩写。MVP之后我们需要把Cuboid转化到屏幕上去。
2.1 What is Screen?
Raster在德语中就是screen的意思。
Rasterize表示drawing onto the screen
Screen is an array of pixels, its size is called resolution.
2.2 屏幕空间
屏幕空间定义如下:
- 原点位于左下角,坐标值为(0, 0)
- 每个像素坐标值由(x,y)表示,且x,y均为整数
- 每个像素其实是一个小方框,像素中心点其实是(x+0.5,y+0.5)
- 屏幕覆盖范围是 (0,0) ~ (width, height)
下图中蓝色像素坐标值为(2,1),其中心点坐标为(2.5,1.5)
将cuboid变换到屏幕空间
下面介绍如何将([-1,1]^3)的立方体变换到屏幕空间。
- 第一步是将cuboid拉伸到和屏幕一样的aspect ratio。注意此时我们先不管Z轴方向,只是把XY平面做拉伸,即将([-1,1]^{2}) 转化为 [0, width ]x[0, height]。因为cuboid边长为2, 所以先除以2,再对应乘上width或者height,Z轴不用管,所以参数为1 (见下面的矩阵)。
- 第二步是平移。因为拉伸前后的中心点在屏幕的原点(即屏幕左下角顶点),而我们要想在屏幕上完美展示,应该把中心点移到屏幕的中心点(以上图为例,即为((2.5, 1.5)))。
那么最终的视口变换(viewport transform)矩阵表示如下:
3. Rasterization (光栅化)
光栅化是将向量图形格式表示的图像转换成位图以用于显示器或者打印机输出的过程。
Rasterisation (or rasterization) is the task of taking an image described in a vector graphics format (shapes) and converting it into a raster image (a series of pixels, dots or lines, which, when displayed together, create the image which was represented via shapes).
前面已经介绍了如何将空间中的物体变换成屏幕上的一个多边形,而实际上多边形的表示还可以进一步划分,即用一些基础的多边形来表示复杂的多边形。
下图中的老虎其实是由四边形拼凑而成的
当然三角形其实也是可以作为fundamental shape primitives的。
三角形用的非常广泛的,原因是因为它有如下几个优秀特性:
- 三角形是最基本的多边形
- 独特性质
- Guaranteed to be planar (保证是平面的)
- well-defined interior(判断一个点在不在三角形内非常方便)
- Well-defined method for interpolating values at
vertices over triangle (barycentric interpolation,重心插值)
在经过MVP,以及将cuboid变换到屏幕空间(视口变换) 等一系列,假设我们得到了下图左边所示的三角形,三个顶点坐标已给出。
可以看到目前的顶点坐标还是小数,也就是说我们还需要将左边的三角形转化成像素表示形式。仔细观察左边的三角形你会发现三角形在某些像素格只占了很小的一部分,比如右边的顶点,那么该顶点对应的像素到底是亮还是不亮呢? 下面就主要针对这个问题进行介绍,即判断一个像素点和三角形的位置关系。
3.1 像素点位置关系判断:采样
判断一个像素点和三角形的位置关系的一个比较简单的办法就是采样(sampling)。
下面的代码给出了采样过程的示例,我们定义一个函数f(x),然后遍历每个点,根据函数f(x)计算结果来判断位置关系,即通过采样来对函数进行离散化处理。 采样在图形学里应用的非常广。
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函数如何实现呢?这个其实在之前的笔记里有介绍,我们只需要通过叉乘即可知道点在三角形的内部还是外部。
以下图(左)中的P点为例,我们分别计算
- (vec{CA} imes vec{CP}),计算得到的向量方向朝上;
- (vec{BC} imes vec{CP}),计算得到的向量方向也朝上;
- (vec{AB} imes vec{CP}),计算得到的向量方向也朝下。
三条边叉乘后的得到的向量方向不一致,所以可知(P)点在三角形外(注意(P)选取的是各个像素的中心点)。通过遍历所有像素,即可知道下图(右)中位于三角形的像素有哪一些了。最后得到的像素三角形如下:
有的时候一个点可能是两个三角形的顶点,那应该算那个三角形呢?这个没有硬性要求,我们可以根据自己需要定义。
3.2 加速光栅化
另外对三角形的遍历还有一些可以加速的操作:
-
根据顶点坐标确定bounding box,进而避免遍历一些无必要的像素
-
下图给出了一个更极致的加速方法,遍历起来一个额外像素点都没有。但是这种方法实现起来复杂怡丢丢。