GAMES101 系列总结(二):光栅化
GAMES101 系列总结(一):线性代数与模型变换中我们讲了如何通过MVP矩阵将模型上的点坐标变为的一个立方体之中的坐标,这篇文章我们继续介绍,如何将这个立方体中的点绘制到屏幕上。
光栅化主要分为三个部分,首先是将所有的点拆分为一个个的三角形,这个过程叫做Triangles,在这个过程中,可能出现某些三角形覆盖的位置没法用像素来表示而导致的锯齿,所以我们要做抗锯齿,这个过程叫做Antialiasin,还有就是我们从三维映射到二维的过程中,如何进行深度测试,即如何使用Z-Buffer。
Triangles
屏幕是什么
首先我们要明白,屏幕是由什么组成的。这个问题的答案是,屏幕由一个个像素组成,其实我们平时说的屏幕分辨率,如1920*1080,2K,4K指的就是这个屏幕一共有多少个像素点,每个像素点我们可以先简单理解为一个个可以发出不同颜色的小灯。
当然,像素点越多,我们能够模拟的和还原的效果越细腻,像素点多到一定程度,即我们肉眼完全无法识别出有像素点的时候,就是我们常说的视网膜屏了。
我们假设一个块屏幕由以下几个像素点组成;
- 我们可以用坐标(x,y)来表示像素点,并且x和y都是整数
- 像素的坐标从(0,0)到(width - 1,height - 1)
- 像素(x,y)的中心在(x + 0.5, y + 0.5)
- 屏幕的覆盖范围是(0,0)到(width,height)
我们经过之前的MVP矩阵,其实已经把整个视锥体调整到了一个的立方体之中,现在我们要做的事,将这个立方体中的点的坐标调整到的屏幕之中
具体分为两个步骤:
- 暂时忽略z坐标(这里说暂时忽略的意思是,z坐标在这里用不到,但是后面的深度测试还要用到),那么点的坐标就从变成了
- 使用一个矩阵将转换到
三角面
我们刚才讲了如何将点的坐标变为屏幕上的像素坐标,但是一个个孤立的点并不能表示一个物体,我们需要这些点连接起来形成的面来表示物体。
那么我们应该将这些点连接成几边形呢?
答案是三角形?
那么为什么采用三角形呢?原因有如下几点:
- 三角形是最基础的多边形,任意的多边形都可以拆分为几个三角形组成
- 独特的属性
- 保证是平面的。比如四边形,四个点可能不在同一个平面上
- 内部定义明确。比如四边形,有凸多边形和凹多边形,后者比较难判断点是否在四边形内部
- 定义良好的插值方法,顶点在三角形上(重心插值)
三角面转换为屏幕像素点
现在,我们已经将模型上的点转化到了二维的屏幕空间到了,并且变成了一个个的三角面片,现在的问题是,如何确定这些面片覆盖了那些像素点,或者说,如何用这个三角面的颜色来确定屏幕像素的颜色,即如下图所示:
采样
第一种方法就是简单的进行采样,对屏幕上的每个像素点进行一次遍历,对于每个像素点的颜色,我们定义一个函数来通过计算得到,如下:
1 | for (int x = 0; x < xmax; ++x) |
那么这个f又如何定义呢?
一种方式是,判断一下这个像素的中心是否在三角形内部,如果在,则将三角形面上盖点的颜色赋值给它,如下图所示:
用公式表达就是:
1 | for (int x = 0; x < xmax; ++x) |
那么如何判断一个点是否在三角形内部呢?可以看上一篇博客,讲过这个问提,通过向量的叉乘
那么,这个方法是不是这样就完成了呢?还没有,还有一种情况我们没有考虑,即,如果像素的中心点不在三角形内部也不在三角形外部怎么办,也就是正好在边上,甚至同时在两个三角形的边上怎么办,如下图所示:
解决方法比较简单,可以随便选一个就好
包围盒
除此之外,还有一个问题,我们是否要对屏幕上的每个像素点都执行一遍这个inside函数?
答案是不用的,我们可以先计算出三角形的包围盒,然后遍历包围盒之内的像素点就好,包围盒之外的像素点绝对不会被这个三角形影响到。
采样结果
经过上面的过程,一个三角形在屏幕上的像素点就会被表示为:
可以看出来,如果分辨率不够就会出现明显的锯齿,如下,
采样会出现什么问题
Jaggies(锯齿)
锯齿的效果就是上一张图展示的样子
那么我们有什么方式去作抗锯齿呢?
Blurring (Pre-Filtering) Before
这种方式的核心思想,出现锯齿的原因是所有的像素点都是要不纯粹的红色,要不纯粹的白色
我们可以通过提前的进行一定的模糊来避免这种非红即白的情况,如下图所示:
超采样
所谓超采样的意思是,之前我们是一个像素点的值对应一个坐标去三角形上去做判断,现在我们一个像素点我们拆分成多个采样点去三角形上去采样
基本步骤为:
- 选择一个N*N的矩阵去对每个像素点进行采样
- 对每个采样点的进行采样,然后取平均值作为该像素点的最终值
摩尔纹
占个位置
Wagon wheel effect – sampling in time
占个位置
z-bufferings
其实这个问题就是,我们虽然刚才光栅化的时候我们虽然暂时忽略了z坐标,但是我们是3D的,在同一个像素点上可能有多个三角形覆盖,这个时候我们应该怎么做呢?
一种方式是我们遍历每个三角形,看看他的z坐标大小,如果遇到了一个距离摄像机更近的三角形,我们就用它的颜色值替代当前像素的颜色。
但是有个问题是,我们只有每个点的坐标,如何判断一个三角形的z坐标,其次是,如果遇到三个三角形互相盖在上面的情况,怎么处理,如下图:
解决方案就是,我们还是遍历每个三角形,不过深度值由每个像素自己记录,我们记录的是像素点当前的颜色对应在三角形上某一点的深度信息,而不是三角形的深度信息
1 | for (each triangle T) |