[Computer Graphics]3 光栅化与抗锯齿

在进入具体的直线光栅化以及三角形光栅化算法之前,我们首先需要知道光栅化是一个什么样的过程。简单来说光栅化的目的就是将想要展现的物体给真正现实到屏幕上的过程,因为我们的物体其实都是一个个顶点数据来表示的,如何表这些蕴含几何信息的数据转化为屏幕上的像素就是光栅化所考虑的东西。比如说一条直线,究竟该用哪些像素点去逼近它,一个三角形,又用哪些像素集合表示它,这都是光栅化的过程。本节主要讨论介绍两个直线光栅化和一个三角形光栅化算法。

1 屏幕像素的表示

在这里插入图片描述

屏幕中的每一个像素点我们都用整数坐标进行表示,最大最小值与分辨率相对应,考虑到每个像素都有一定的面积,我们定义(x+0.5,y+0.5)为该(x,y)像素的中心,如图中黑圈所示。

2 直线光栅化算法

2.1 DDA数值微分算法

DDA算法是一个非常简单直观的算法。
首先当任何一条直线知道任意两点时都可以用 y = k x + b y = kx + b y=kx+b来表示,其中k代表斜率,如果 ∣ k ∣ < 1 |k| <1 k<1,那么它的主要行进方向就是x轴,即x轴的变化要比y轴快,相反如果如果 ∣ k ∣ > 1 |k| >1 k>1,那么它的主要行进方向就是y轴,即y轴的变化要比x轴快。如下图所示:
在这里插入图片描述
我们分别就图上两种情况进行考虑(假设起点与终点给定(确定了直线方程),就像图中一样)

1 当 ∣ k ∣ < 1 |k| <1 k<1时,从起点开始画起每次x = x+1, y = y+k, 并将y四舍五入,得到新的x,y就是像素点应该画的地方
2 当 ∣ k ∣ > 1 |k| >1 k>1时,从起点开始画起每次y = y+1, x = x+1/k, 并将x四舍五入,得到新的x,y就是像素点应该画的地方

图中的两种情况的光栅化结果也已给出供参考。

2.2 中点Bresenham算法

我们首先规定想要光栅化的线段的起点 P 0 ( x 0 , y 0 ) P_0(x_0,y_0) P0(x0,y0)与终点 P 1 ( x 1 , y 1 ) P_1(x_1,y_1) P1(x1,y1),则该直线方程可以用y = kx + b的形式来表示,定义 f ( x , y ) = y − k x − b f(x,y)=y−kx−b f(x,y)=ykxb

中点Bresenham算法的思想其实也比较简单,我们在这里只给出 0 < k < 1 0<k<1 0<k<1的情况,其它情况可以类推,除却起点与终点,我们每次的画点只会考虑右边或者右上的点两种情况(由斜率所决定的),因此我们只需要在这二者之间做出选择。那么该依据什么进行判断呢,给出如下两种情况,第一:
在这里插入图片描述
我们已经成功画出了前三个蓝色方格之后,所要考虑的便是第三个蓝色方格右边或者右上的橙色方格,此时我们取这两个橙色方格的中点,如图中圆圈符号所对应的那个点,倘若这个点在直线方程的下面,那么很明显我们应该选择右上的方格。

第二种情况:
在这里插入图片描述
此时中点位于直线方程的上方,此时选择右边的橙色方格。

至此,如何判断两种方格选择的条件已很明显,就是确定中点与直线的位置关系,这里就可以使用到一开始定义的 f ( x , y ) = y − k x − b f(x,y)=y−kx−b f(x,y)=ykxb的方程了。

显然,当f(x+1,y+0.5) > 0的时候中点在直线上方,当f(x+1,y+0.5) < 0的时候中点在直线下方 (其中x+1,y+0.5是为了表示两个橙色方格的中点,此时x,y为前一个确定的像素坐标)

伪代码如下:
在这里插入图片描述
其中的,some condition也就很明显的是
在这里插入图片描述
目前为止,算法整体便已完成,但有一个问题是,我们每次都要进行一次F(x,y)的计算,倘若直线方程比较复杂,这是很消耗资源的(因为在底层可能是几百万次的重复调用)。因此一种改进方法便是利用增量算法。不难具体算出f(x,y)方程具体如下:
在这里插入图片描述
观察可以得出
在这里插入图片描述
那么算法只需要算出第一次的 f(x+1,y+0.5), 之后的每次只需根据上述式子进行相应增量计算即可,如下:
在这里插入图片描述
如果还有不解,不妨具体的取两个点推一边算法便可加深理解。
当然中点bresenham算法其实还在这之上进一步做了一步优化,因为第一个d其中存在浮点数0.5,所以将有关d的等式两边都乘以2,消除了该0.5的浮点,增加了计算效率。

3 三角形光栅化算法

有读者可能会疑问,为什么不讲四边形五边形的光栅化算法,偏偏要谈三角形呢,因为三角形是最基本的多边形,大部分的模型都是用一个个三角形面表示,且任意的其它多边形其实都可以转化成多个三角形的形式,因此三角形的光栅化可以说是图形学中最基础的部分了。那么该怎么去做呢?
在这里插入图片描述
其实有一个非常直观的做法,我们对屏幕中的每一个像素进行采样,如果这个像素点在三角形之中那么这个像素点就应该被采用!对,这其实就是该算法的做法。那么该如何去判断一个点在不在三角形内部呢,那么其中一种办法就是利用叉乘的性质了
在这里插入图片描述
如图所示,我们事先知道想要光栅化的三角形的三个顶点P0,P1,P2,以及检测点Q。
只要分别计算 P 0 P 1 × P 0 Q P_0P_1\times P_0Q P0P1×P0Q P 1 P 2 × P 1 Q P_1P_2\times P_1Q P1P2×P1Q P 2 P 0 × P 2 Q P_2P_0\times P_2Q P2P0×P2Q,如果三者同号则代表点P在三条线段的同一边,那么必然处于三角形内部,如果不同号则代表该点一定在三角形外部!

因此自然的,只需要遍历每一个点就可以得出三角形的光栅化结果了!当然我们还可以进一步的进行优化,因为显然并没有必要去测试屏幕中的每一个点,一个三角形面可能只占屏幕很小的部分,可以利用一个bouding box 包围住想要测试的三角形,只对该bounding box内的点进行采样测试,如下图:
在这里插入图片描述
这样就可以得到很大的效率提升了!

4 锯齿 (走样,Aliasing)

在这里插入图片描述
利用上一节的三角形光栅化算法之后,我们可以把该三角形表示成一个如下图所示的像素点集合
在这里插入图片描述
对!发现问题了没有?
三角形变的“歪歪扭扭”的,哪能说它是一个标准的三角形呢。这种问题本质是因为我们在采样的时候的频率过低无法跟上图像的频率,导致最后结果的失真,当然这是从信号处理的角度去看这个问题,在这里不会做过多的展开。
从简单的角度去解释这种问题出现的原因就是,我们用有限离散的像素点去逼近连续的三角形,那么自然会出现这种锯齿走样的现象,因为这种近似是不准确的。接下来会介绍两种解决走样的方法,具体来说第二种可以当成第一种的改良

4.1 超采样反走样(Super Sampling AA)

SSAA的想法其实是非常直观的,如果有限离散像素点逼近结果不好,那么我们用更多的采样点去逼近不就会得到更好的结果了吗?所以根据这个思想我们可以把原来的每个像素点进行细分,比如下例中,我们讲每个像素点细分成了4个采样点:
在这里插入图片描述在这里插入图片描述
我们根据每个采样点来进行shading(该概念还未提及,可以理解为计算每个像素点的颜色的过程,当然这里是一个纯红色的三角形,如果该点在三角形内,它的颜色值可以直接得到为(1,0,0)),这样得到了每个采样点的颜色之后,我们讲每个像素点内部所细分的采样点的颜色值全部加起来再求均值,作为该像素点的抗走样之后的颜色值!结果如下:
在这里插入图片描述
当然读者可能还是觉得,这还不是有锯齿,咋就抗锯齿了呢?

仔细观察可以发现因为将4个采样点的颜色求均值的之后,靠近三角形边缘的像素点有的变淡了,从宏观角度来看的话,这个锯齿就会变得不那么明显了。我们可以看看这样一个具体例子。
在这里插入图片描述
怎么样,效果还是相当明显吧!

(tips:SSAA并不局限于分成4个,也可以分更多的,可以自己决定,如果喜欢玩游戏的读者一定知道游戏里面其实就有一个抗锯齿的选项,其中的 × 2 , × 3 , × 4 \times2,\times3,\times4×2,×3,×4,分别代表的就是4个,9个,16个采样点,显然采样点越多抗锯齿效果越好,但计算负担也会随之增加)。

4.2 多采样反走样(Multi-Sampling AA)

MSAA其实是对SSAA的一个改进,显然SSAA的计算量是非常大的,每个像素点分成4个采样点,我们就要进行4次的shading来计算颜色,额外多了4倍的计算量,如何降低它呢?

MSAA的做法也很容易理解,我们依然同样会分采样点,但是只会去计算究竟有几个采样点会被三角形cover,计算颜色的时候只会利用像素中心坐标计算一次颜色(即所有的信息都会被插值到像素中心然后取计算颜色),如下图:
在这里插入图片描述
只有两个采样点被我们的三角形cover了,将该像素中心计算出来的颜色值乘以50%即可,这样大大减少了计算量,并且得到反走样效果也是很不错的。

4.3 Z-Buffer算法

解决了走样问题之后,还有一个仍需解决的问题,我们如何判断物体先后关系?更具体的说每个像素点所对应的可能不止一个三角形面上的点,我们该选择哪个三角形面上的点来显示呢?答案显然易见,离摄像头最近的像素点显示。这里便要利用到我们之前做 m o d e l − v i e w − p r o j e c t i o n model-view-projection modelviewprojection变换之后所得到的深度值z了,这里定义z越大离摄像机越远!

以下我们介绍Z-Buffer算法,主要有2步。

  1. Z-Buffer算法需要为每个像素点维持一个深度数组记为zbuffer,其每个位置初始值置为无穷大(即离摄像机无穷远)。
  2. 随后我们遍历每个三角形面上的每一个像素点[x,y],如果该像素点的深度值z,小于zbuffer[x,y]中的值,则更新zbuffer[x,y]值为该点深度值z,并同时更新该像素点[x,y]的颜色为该三角形面上的该点的颜色。

没错,根据上述两个步骤,我们就已经能够成功得到正确遮挡顺序的结果了,伪代码如下:
在这里插入图片描述
一个计算实例如下:
在这里插入图片描述
如果一下子不能反应过来,建议利用该实例推一边应该就能明白了。

好了,整套笔记到目前位置,我们已经解决大部分光栅化的问题了,接下来就是如何真正的计算每个三角形面上点的颜色了(shading)!该部分内容会在之后的笔记中给出。

以下是我的一些个人思考记录,可略过。

tips:这里的MSAA说的有些简单,实际操作的时候,同样会维护所有子采样点的color buffer和depth buffer,每次对pixel中心计算shading的时候,会根据depth值(因为可能不止一个三角形拥有这个像素)和三角形覆盖关系判断是否把颜色值写入。
如下面这个例子(灵魂画手见谅)
在这里插入图片描述
红色和蓝色三角形同时覆盖了黑色的像素中心,那么在第一次渲染红色三角形的时候会将颜色写入1,3号采样点,同时更新depth buffer,但此时2,4号采样点的depth buffer依然是无穷(初始值),所以在渲染到蓝色三角形的时候会将蓝色写入2,4号并更新depth buffer。如果还有更多的三角形覆盖黑色像素中心的话,会重复上述这个过程,即根据是否覆盖以及深度值判断是否写入颜色值。最后将这4个子采样点颜色平均即可。

个人感觉这种MSAA的效果是没有SSAA好的,因为MSAA相当于默认了采样点的颜色与像素中心差别不大,导致了误差的存在,特别是在纹理映射的时候,如果纹理过大,MSAA可能完全没啥效果,因为一个屏幕空间像素覆盖了多个纹理空间像素,屏幕空间像素内微小的采样点距离差距都会导致在纹理空间中完全不一样的颜色信息,如果还是只做一次屏幕空间像素中心的shading的话,误差就会非常大。

5 转载补充 走样的原理

转载补充:
关于走样的原因,老师补充了信号处理中的混频现象作为说明
个人觉得非常贴切,能够直击灵魂,充分体现出图形学是一门交叉学科,所以根据回忆进行一定的补充
在这里插入图片描述
我们知道,当我们使用低频去采样一个高频函数时,往往会得到一个错误的结果,如图所示最后一个函数,我们几乎把它等同于第一个函数了。
走样aliasing就是这样一个过程:同样的频率去采样两个不同函数,得到完全相同的结果。

图像本质也是包含多种频率的信号。对图像进行滤波(即去掉特定频率的信号),可以得到下图所示的结果。
在这里插入图片描述
即变化剧烈的边缘是高频信号,它往往指图像中的一些轮廓、细节,比如衣服的褶皱、一个很小的痘印等等。低频则是整个画面的整体,它占据了整个信号的大部分信号。
在这里插入图片描述
我们对图像做卷积,本质上就是在提取特定频域下的信息(还可以参考canny边缘检测算法)。卷积核越大,保留的高频信息越少,低频信息越多,对应到频域图上,高频区域的亮度就降低了。

在信号处理中,除了显而易见的升高采样频率,通常会选择滤掉高频信息后再采样来解决走样(混频)问题。
在这里插入图片描述
回到我们的图像走样话题,我们也可以选择滤掉图像的高频信息后再进行光栅化,即:
在这里插入图片描述

注意:先采样后滤波是无法解决走样问题的,回到信号处理本质很容易理解,你先采样就已经发生混频了,你再滤波,其实没有准确滤掉混频的那部分

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值