一、光栅化(Rasterization)
1.1、概念
光栅raster这个词就是德语中屏幕的意思,光栅化的意思就是将图像绘制在屏幕上进行显示。
1.2、三角形光栅化过程简述
首先,为什么要以三角形的光栅化为例呢,因为三角形是最基本的多边形,大部分的模型都是用一个个三角形表示,任意的其它多边形其实都可以转化成多个三角形的形式,因此以三角形的光栅化为例
如下图所示,给定了三个点,如何将得到的三角形绘制在右边的屏幕上?
首先需要进行采样,采样的意思就是对给定的函数进行离散化,比如上图中,要实现一个判断屏幕上的像素是否在三角形内的函数,然后将三角形顶点构成的最大长方形区域内所有点作为函数的输入,判断这些点是否在三角形内,如果在三角形内,就将屏幕上的像素点进行点亮。这个过程就是采样。其他采样的例子包括对视频进行时间上的采样,这样就可以得到视频中的某几帧画面
对函数进行采样之后,就知道了哪些点在三角形内,然后将对应的像素进行点亮就得到了要绘制的三角形
1.3、如何确定一个点是否在三角形内部
这里参考学习|判断一个点是否在三角形内 - 知乎 (zhihu.com)的同侧叉积法
首先,如何判断某两个点在某条直线的同一侧?
根据向量的叉乘以及右手定则,AB*AM (*表示叉乘)的方向为向外垂直屏幕,AB*AN也是向外垂直屏幕,但AB^AO的方向是向内垂直屏幕,因此MN在直线AB的同侧,MO在直线AB的两侧。
实际计算时,只需要考虑叉积的数值正负,向量的叉积的符号判别可以直接将向量写成行列式,然后求解行列式的正负得到向量的方向
假设各点坐标为A(0,0), B(4,0), M(1,2), N(3,4), O(3,-4), 则:
AB*AM = (4,0)*(1,2) = 4*2 - 0*1 = 8
AB*AN = (4,0)*(3,4) = 4*4 – 0*3 = 16
AB*AO = (4,0)*(3,-4) = 4*-4 – 0*3 = –16
因为数值的正负表示叉乘结果向量的方向。即,如果叉积AB*AM 和 AB*AN的结果同号,那么M,N两点就在直线的同侧,否则不在同一侧。
以上的问题解决了,就很容易的用来判断某个点是否在三角形内,如果P在三角形ABC内部,则满足以下三个条件:P,A在BC的同侧、P,B在AC的同侧、PC在AB的同侧。某一个不满足则表示P不在三角形内部。
1.4、显示
但是得到的三角形边缘变得不规则,产生了锯齿,和期望得到的三角形不一致,所以这就需要进行抗锯齿处理
二、抗锯齿
2.1、锯齿产生的原因
锯齿产生的原因就是因为信号的变化频率高,而相应的采样频率低。就三角形边缘不规则的情况来说,因为三角形的边上是无限多个点,而用有限个方块去逼近无限多个点的三角形的边,所以当然会产生不规则的锯齿,硬件的解决办法一是可以加大屏幕的分辨率,使得像素变小,从而可以得到更多个有限的方块去逼近三角形。而软件的方法就是加入抗锯齿算法。无论是硬件方法还是软件方法,都不能完全解决锯齿问题,只能缓解锯齿问题,直到人眼察觉不出来
2.2、软件上抗锯齿的方法及原理
1、SSAA(super sampling antialisa,超采样抗锯齿算法)
SSAA的思想就是将一个像素分解成2*2,3*3,4*4......,然后判断分解后像素中的4个或者9个或者16个像素点是否在三角形内,如果在像素内,就将点绘制成红色,然后将这4个或者9个或者16个像素点的像素值求平均值,最后作为整个像素点的像素值
以2*2为例,比如采样结果是这样的
然后将三角形周围的每个像素点一分为4,就会变成下面这样的
之后,分别计算每个大像素中四个小像素的像素平均值作为整个大像素的填充,最终,整体变成下面这样的
到此,SSAA算法结束
如前面所说,使用SSAA过后,锯齿依然会存在,只不过SSAA通过超采样的方式把原本是纯白色的像素绘制成了介于白色和红色之间的像素,从而使得图形边缘锯齿看起来不那么突兀,从而达到了抗锯齿的作用
效果对比如下
右边是使用了SSAA的结果,可以看到,锯齿不那么突兀了
2、MSAA(multi-sampling antialisa,多采样抗锯齿算法)
SSAA每次计算时,都要计算每个子采样像素的像素值,如果采样频率越高,则计算量越大,对此,MSAA出现了
MSAA的思想就是如果有子采样像素在三角形内,那么,就认为像素点需要着色,采样后,看有多少个子采样像素点在三角形内,然后计算在三角形内的子采样像素占所有采样点的比重,最后把整体的像素值(比如红色)乘以该比重,就得到了最终绘制的像素值。
比如下面这个像素被采样了四次
使用MSAA并不会计算四次子像素值,而是将红色的像素值*0.5,最终得到一个浅红色作为该整个像素的像素值,节约计算量,加快算法速度
光栅化并缓解了锯齿问题后,就可以将一系列的问题画到屏幕上,但是物体和物体之间的深度信息或者前后位置关系该如何表达?表达物体深度的方式有两种,一种是画家算法,另一种是z-buffer算法
三、画家算法
画家算法就是首先绘制距离较远的场景,然后用绘制距离较近的场景覆盖较远的部分。
画家算法首先将场景中的多边形根据深度进行排序,然后按照深度从大到小顺序进行描绘。
比如下面的场景,深度顺序背景、山、草地、数目,所以先绘制背景,再绘制山,然后绘制草地,最后绘制数目
画家算法通常会将不可见的部分覆盖,可以解决可见性问题。
但是在某些情况下,画家算法也会失效,比如下面这幅图,pqr三个图形彼此互相都有遮挡,无法对三个物体的深度进行排序。
此时,无法通过画家算法来显示物体之间的深度关系,因此,深度缓冲算法出现了
四、深度缓冲算法(Z-buffer)
因为在透视投影钟,可以得到每个像素的深度信息z,
深度缓冲的算法过程如下:
1.首先分配一个数组buffer,数组的大小为像素的个数,数据中的每个数据都表示深度,初始深度值为无穷大
2. 随后遍历每个三角形上的每个像素点[x,y],如果该像素点的深度值z
伪代码如下
示例
如上图,首先把屏幕上所有的像素点的深度都设置为无穷大,然后先绘制一个所有深度都为5、颜色为红色的三角形,因为5比无穷大小,所以,把三角形中所有的像素点都绘制到屏幕上,颜色为红色,深度更新为5,之后,又有一个紫色的三角形。其深度值各不相同,以深度为8的顶点为例,8>5,所以这点像素会被深度为5的像素遮挡,不做任何处理。再以深度为3的顶点为例,因为3>5,所以更新这点的像素颜色为紫色,深度为3。其余像素同理
z-buffer算法的好处就是不区分三角形绘制的先后顺序,只需要找到每个像素上深度值的最小的像素和颜色即可。
深度图的样子
深度图分为两种,一种是近处物体被描画为深色,而远处物体被描画为浅色,这是因为在透视投影时,将相机的朝向定为了Z轴正方向,那么在Z轴方向,离远点越远,像素值就越大。
而另一种如下图所示,在透视投影时,将相机的朝向定为了Z轴负方向,那么在Z轴方向,离远点越远,Z值越小,像素值就越小,所以越远深度图的颜色就越黑
参考:
GAMES101-现代计算机图形学入门-闫令琪_哔哩哔哩_bilibili
欢迎大家评论交流,作者水平有限,如有错误,欢迎指出