计算机图形学笔记(二)光栅化
本篇博文为闫令琪大佬的Games101 P5、P6课程的笔记,同时补充了一些其他内容。
文章目录
1. 什么是光栅化
参考百科中的描述:光栅化就是把顶点数据转换为片元的过程,片元中的每一个元素对应于帧缓冲区中的一个像素。虎书中对光栅化的描述:光栅化就是找到图像中被几何图元占据的所有像素的过程。
2. 屏幕空间与屏幕映射
要将图元绘制到屏幕上,首先定义屏幕空间。屏幕空间有多种定义方式,这里参考Game101中的定义:
如下图所示
1. 对于分辨率为width x height的屏幕,由width x height个1x1的像素填充,屏幕空间范围是(0,0)到(width,height)
2. 像素索引位置(x,y)是整型,索引范围从(0,0)到(width-1,height-1)。
3. 将像素中心作为像素的坐标位置,即像素的坐标为(x+0.5,y+0.5),如图中蓝色像素坐标为(2.5,1.5)。
定义好屏幕空间后,便可以进行屏幕空间映射(也叫视口变换),这个过程是将NDC空间的x、y屏幕拉伸到屏幕分辨率的过程(可以参考上一篇笔记—渲染管线概述),即将[-1,1]变换到[0,width]、[0,height],而对于z分量这里暂时不考虑(实际上z分量被用作深度缓冲)。
上述的变换矩阵如下(实际上是一个缩放到屏幕分辨率,然后将原点平移到屏幕中心的过程):
3. 三角形光栅化
在游戏引擎中,模型网格通常使用三角面,即三维模型由一个个三角面片组成,几乎所有的商用图形加速硬件都是为三角形光栅化而设计的,引擎中使用三角面的优势如下:
1. 三角形是最基本的多边形,其他多边形都可以划分成多个三角形,三角形无法再继续拆分。三角形避免了凸多边形、凹多边形的特殊处理。
2. 三角形构成的面必然是一个平面。
3. 平面内任意一点容易判定是在三角形的内外(可以通过叉乘、判断重心坐标进行判断)。
4. 三角形容易实现插值(通过重心坐标系)。
3.1 如何将三角形转换为屏幕像素。
若要光栅化一个三角形,一种简单的方式是对屏幕像素进行采样,逐个判断像素坐标(像素中心点位置)是否在三角形内,从而决定哪些像素被三角形所覆盖。找出三角形所覆盖的所有像素后,就能在屏幕中大致绘制出三角形的样子(三角形的边缘会出现锯齿,需要特殊处理)。
对于一个像素点,判断其是否在三角形内的方法:
1. 叉乘方法
如下图所示,三角形的三个顶点分别是
P
0
P_0
P0,
P
1
P_1
P1
,
P
2
,P_2
,P2,可以通过逐个叉乘的方式判断点Q是否在三角形内。依次计算叉乘
P
0
P
1
⃗
\vec {P_0P_1}
P0P1×
P
0
Q
⃗
\vec {P_0Q}
P0Q、
P
1
P
2
⃗
\vec {P_1P_2}
P1P2×
P
1
Q
⃗
\vec {P_1Q}
P1Q、
P
2
P
0
⃗
\vec {P_2P_0}
P2P0×
P
2
Q
⃗
\vec {P_2Q}
P2Q,若得到的三个向量同号则说明Q点在三角形内,否则则在三角形外。
2. 重心坐标系
计算Q点在该三角形重心坐标系坐标(α,β,γ),若重心坐标的三个分量均大于零则表明Q点在三角形内。
对于点在三角形边上的情况,可以采取特殊的处理方式,或者不做处理。
在OpenGL中,若点在三角形的上边或者左边,则认为是在三角形内。
3.2 性能优化
对于某个三角形,若将所有的像素点参与其在三角形内外的判断,会产生很多不必要的性能开销,一种简单的优化方式是采用AABB包围盒,首先计算三角形在x、y方向的包围盒,从而确定像素遍历的范围,然后再判断像素点是否在三角形内。
还可以通过增量横扫的方式,如下图,这种方式对不同三角形类型需要不同的处理方式,另外需要进行边界计算,因此,引入了额外的计算,具体实现方式可以参考三角形光栅化。
4. 采样、走样(锯齿)、反走样(抗锯齿)
在屏幕空间,通过判断像素点是否在三角形内,实现三角形光栅化,由于每个像素都会被填充为均匀的颜色,因此受限于屏幕分辨率大小,在三角形边缘会产生锯齿,如下图。
4.1 采样
采样是指:对一个函数,用一组离散值作为输入得到函数的一组离散值作为输出,将函数离散化的过程。
在图形学中会涉及到很多不同的采样,如对时间的采样,对平面的采样,对方向的采样以及对体积的采样等。
在利用采样来光栅化的过程中,使用像素中心对屏幕空间进行采样,更具体来说,利用采样,判断一个像素中心是否在三角形内,以此可得出一个三角形覆盖了哪些像素,判断一个点是否在三角形内
在空间位置的采样
在拍照时,光学信息到达感光元件所在平面,被离散成一个个像素,是在不同空间位置的采样。将下图放大后,可以看到栅格状的像素排布。
在时间上的采样
视频动画是在不同时间点上的采样,每一个时间点(每一帧)均是一副图像。
4.2 走样
本质上来讲,走样是因为对函数(信号)的采样频率不足,或者说信号的变化速率远大于采样的频率。如下图所示,某个周期函数经过傅里叶级数展开后,可以被分解为下列不同频率函数的线性组合,对于不同频率的周期函数,使用某个固定的频率分别进行采样,对于频率较低的函数,该固定频率下所采集的离散样本能够近似的反应出原函数的信息,而随着频率的增大,对该频率采样效果会越来越失真(信号的频率发生混叠,参考香农定理)。
在计算机渲染中,由于绘制的图形在数学上是连续的,而渲染的像素点是离散的,从而导致在光栅化的三角形遍历阶段,将图形打散为像素时,受限于屏幕分辨率的限制(采样频率不足体现在这里,后面会解释),会不可避免的产生锯齿(Jaggies)即为走样,这种现象不可避免,只能减轻。主要通过硬件(像素点数量加倍)和软件(各种算法,如超级采样算法等)来减轻走样。
下面列举了两种走样现象,分别是锯齿和摩尔纹。
4.3 反走样
这里先放结论:在采样前先进行模糊处理(平滑处理),能够有效的减轻走样问题,下图是直接采样和经过模糊后采样的效果对比,可以看出,经过模糊操作后,明显降低了边缘的锯齿感。
有了上面的结论后,从频域方面解释反走样的原理
如下图所示,首先对一张图像进行傅里叶变换,得右图中的结果,即为该图像对应的频谱图。右图的图片中心代表低频区域,以这个点向外扩散,越向外频率越高。在不同频率的位置上有多少信息用亮度来代替,该频谱图表示该图像的信息集中在低频部分(实际上,对图片进行傅里叶变换基本都会得到类似的频谱图,即大多数图像信息都集中在低频部分)。仔细观察还可以发现,右图中的中心还出现了水平、竖直的两条亮线,这是由于在对信号进行分析时,通常认为是周期重复的信号,对于图片来说,就认为其在水平、竖直方向重复相接,而图片的左右、上下边界相差较大,也就在相接处产生了高频信号。
对图像进行傅里叶变换的意义:
从纯粹的数学意义上看,傅里叶变换是将一个图像函数转换为一系列周期函数来处理的;
从物理效果看,傅里叶变换是将图像从空间域转换到频率域,其逆变换是将图像从频率域转换到空间域。即傅里叶变换的物理意义是将图像的灰度分布函数变换为图像的频率分布函数,傅里叶逆变换是将图像的频率分布函数变换为灰度分布函数。
实际上对图像进行二维傅里叶变换得到频谱图就是图像梯度的分布图,傅里叶频谱图上看到的明暗不一的亮点,就是图像上某一点与邻域点差异的强弱,即梯度的大小,即该点的频率大小。如果频谱图中暗的点数更多,则实际图像是比较柔和的;反之,如果频谱图中亮的点数多,则实际图像是比较尖锐的,边界分明且边界两边像素差异较大。
进一步的,若对图像进行高通滤波(左图)和低通滤波(右图),可以得到如下两种结果。对比两幅图像,发现经过高通滤波处理的图像只保留了图像的边界;而进行低通滤波的图像去除了图像的边界。(实际上低通滤波就是上述内容提到的“模糊的操作”,后面会进行解释)
滤波= 卷积(=平均)
我们说滤波其实就是剔除特定频率的信号,在图像处理中,滤波可以被近似的认为是卷积(详细的滤波与卷积的定义可以参考图像处理中滤波与卷积的区别这篇博文)。
并且,卷积具有如定理:
卷积定理指出,函数卷积的傅立叶变换是函数傅立叶变换的乘积。具体分为时域卷积定理和频域卷积定理,时域卷积定理即时域内的卷积对应频域内的乘积;频域卷积定理即频域内的卷积对应时域内的乘积,两者具有对偶关系。
参考上图,可以帮助我们进一步理解卷积定义。对一个图像进行卷积可以到右边模糊的图像,依照卷积定理,可以将原图像和卷积核(用于卷积的滤波器) 分别进行傅里叶变换便得到两者的频谱图,然后将两者在频域上的结果相乘,就得到了右边模糊图像的频谱图,将其进行逆傅里叶变换后就得到了相同的模糊图像。观察相乘后的频谱图发现,将原图像的频谱图与卷积核的频谱相乘,实际上就是对原图像进行了低通滤波的操作。
采样=重复频域上的内容(前面都是铺垫,重点来了!)
上图是使用冲激串函数c采样连续函数a的过程(这被称为冲击串采样),e是采样结果;b、d、f分别是a、c、e的频谱(冲击串函数经过傅里叶变换得到的结果还是冲击串)。卷积定理指出时域上的乘积对应频域上的卷积,从图中我们可以看出:在时域上进行采样,反应在频域上就是将a的频谱不断重复。在采样的这个过程中,如果采样的频率不够(采样点间的距离过大),就会导致在这个“重复”的过程中频谱间的距离变小,进而导致复制的频谱出现混叠,这就是产生走样的原因。密级采样和稀疏采样的结果如下图。
上文说到,模糊操作其实就是对图像进行低通滤波处理。如下图所示,将图像中的高频信息滤掉后,再以原本的采样频率进行采样,就不会再出现频率混叠,也就达到了反走样的目的。
4.4 常见抗锯齿算法
从4.2内容中,我们知道了在采样前对三角形进行模糊操作能够减轻走样问题,在实际工程应用中,可以使用一个像素大小的卷积核对像素进行卷积,从另一个角度说,这就是根据三角形在当前像素所占比例对像素进行填充,如下图所示。
但是计算出三角形覆盖像素的面积并不容易,一种解决方式是将每个像素继续划分为多个小像素进行计算,这是MSAA抗锯齿的实现方式,主流的抗锯齿算法可以参考这位大佬的文章:
主流抗锯齿方案详解(一)MSAA
主流抗锯齿方案详解(二)TAA
主流抗锯齿方案详解(三)FXAA
主流抗锯齿方案详解(四)SMAA
Reference
- GAMES101-现代计算机图形学入门-闫令琪 p5、p6
- 计算机图形学(六)-光栅化、采样、走样与反走样、滤波与卷积
- 《3D数学基础 图形和游戏开发》
- 《Fundamentals of Computer Graphics-5th》
- 三角形光栅化
- 图形学随笔—采样
- 图形学随笔—走样
- 图像处理之图像傅里叶变换
- 主流抗锯齿方案详解(一)MSAA
- 主流抗锯齿方案详解(二)TAA
- 主流抗锯齿方案详解(三)FXAA
- 主流抗锯齿方案详解(四)SMAA