文章目录
1 反走样 Antialiasing
1.1 采样的理论
采样不止可以发生在不同的位置,还可以发生在不同的时间。
我们把一系列的图按照一定的时间放出来,就可以形成一系列的动画。这个动画就可以称为在时间中进行的采样。因为我们其实没有见过连续意义上的动画,我们看到的是所谓很连贯的视频。视频是由一帧一帧的图来组成的,一秒内依次放出 24 帧图像,让大家视觉上会认为是个连续的过程。但是其实它在每个时间点上都是离散的一幅幅图。
Sampling Artifacts:Errors / Mistakes / Inaccuracies in Computer Graphics.
采样会产生不同的问题:
- 锯齿现象
- 摩尔纹 (Moire Patterns) 平时拿手机去拍屏幕,也会看到一些扭曲的纹路。
- 车轮错觉 (Wagon Wheel illusion) 车轮在高速运动的时候,人们会看到车轮在倒着转的错觉(解释)。因为人眼在时间中的采样跟不上轮子运动的速度,就会出现这样的现象。
产生以上问题的本质在于:信号变化的速度太快了,但是采样的速度太慢了,跟不上信号变化的速度。
反走样的理念:采样之前做个模糊操作(滤波)
采样前做个模糊操作,采样的呢就是个模糊的三角形,该什么颜色对应像素就是什么颜色。解决效果还不错。
但是为什么呢?
① 为什么采样的速度跟不上信号变化的速度就会产生走样现象?
② 为什么需要在采样之前做模糊操作?先采样后做模糊操作出来的效果就不行呢?
接下来就需要用到频域方面的知识了。
频域
傅里叶变换
傅里叶级数展开:任何一个周期函数,都可以把它写成一系列正弦和余弦函数的线性组合,以及一个常数项。(下图中的f(x)式子)
傅里叶级数展开可以描述很多不同的正弦余弦项的和。通过傅里叶级数展开我们可以知道:任何不同的函数都可以分解成不同的频率。(通过下图的右边 4 幅图就可以看出:加入的函数频率越来越高,最终的近似效果也越来越好。也意味着这个最终的图像也可以分解为这么不同的频率。)
我们给定一个函数,可以让它经过相当相当复杂的操作,变成另外一个函数。并且可以通过逆变换把它变回原来的函数。这就是傅里叶变换和傅里叶的逆变换。
傅里叶变换就是把函数给变成不同的频率的段,并且我们把这些不同频率的段给显示出来。通过傅里叶变换,我们就可以用来分析函数有着什么样的频率。
下图的五个函数有着不同的频率,从低到高,从上到下。我们用完全相同的采样方法对这些函数进行一次采样。
**得出的最终结论是:**更高频率的函数需要更密的采样。
通过频率来分析走样是怎么回事儿
用同样的采样方法采样两种频率完全不同的函数,得出的结果却是完全一致,无法区分它们,这样的现象就叫做“走样”。
滤波 = 去除某些频率内容 = 卷积(=平均)
傅里叶变换可以把函数从时域变到频域,如下图所示。
卷积:在周围的区域做一个平均。
时域上,如果想对两个信号进行卷积。其实对应到两个信号各自的频域上,是两个信号的频域的乘积。所以卷积操作和乘积操作是挺接近的。
**卷积定理:**函数的卷积的傅里叶变换是函数傅里叶变换的乘积。卷积定理揭示了时间域与频率域的对应关系。
- 时域卷积定理:时域内的卷积对于频域内的乘积
- 两信号在时域的卷积积分 = 在频域中该两信号的傅里叶变换的乘积
- 频域卷积定理:频域内的卷积对应时域内的乘积
- 两信号在时域的乘积 = 这两个信号傅里叶变换的卷积除以 2π
通过卷积定理,我们就知道如何做一个卷积:
- 可以直接拿到一幅图,直接用卷积的滤波器去做一个卷积操作。
- 也可以把这张图
- 先做一个傅里叶变换,变到频域上;
- 后把卷积的滤波器也变到频域上;
- 两者相乘,得到频域的结果;
- 然后再将结果傅里叶逆变换为时域上。
采样 = 重复频率内容
假如说我要采样第一个函数a,那就是需要把这个函数变成一系列离散的点,只留下在某些位置上函数的值。这就好像在这个函数上乘以另外一个函数,比如第二个函数c,它只在某些地方上有值,其他地方值为 0。最终相乘的结果就是第三个函数e。
如何做采样呢?
给一个原始的信号,去乘上第二个这样的函数,就可以得到第三个函数,就是采样的结果。
这是在时域上,如果在频域上呢?已知时域上的乘积对应的就是频域上的卷积。
那么就是b卷积d 得到的结果为 f。
从f 图可以总结出:采样是什么?采样就是在重复原始信号的频谱。
我们就可以理解了“为什么会产生走样现象”这个问题:
因为采样不同的间隔会引起频谱以另外一个不同的间隔移动。走样在频域的角度上来说,就是频谱在搬移的情况下发生了混叠。如下图所示:
1.2 实际的图形学中如何做反走样
方法一:增加采样率
这是一个终极解决方法。但是代价昂贵且可能需要非常高分辨率。
方法二:反走样
先模糊,后采样。也就是先把原始的高频信号拿掉,后采样。原理如下:
模糊操作
那我们如何进行滤波操作?也就是如何进行模糊处理呢?
先模糊操作,也就是卷积操作,求个平均。
我们期望做到的:通过计算平均像素值进行抗锯齿。在栅格化一个三角形时,函数f(x,y) = inside(triangle,x,y) 的像素区域内的平均值等于该三角形覆盖的像素的面积。
MSAA
用更多的采样点来反走样。MSAA是上述期望做法的一种近似操作。毕竟算出三角形覆盖每一个像素的面积大小是需要很大计算量的。
MSAA 的思想:认为一个像素内被划分为好多小的像素。假设被划分为 4 x 4 = 16个小像素。每个小的像素假设有个中心,然后判断这些点是否在三角形内,将最终得到的结果平均起来。这个平均后的结果就可被看做三角形对这个像素点覆盖区域的近似。
MSAA 做的是模糊操作。它绝不是靠提升频率分辨率来增加效果,只是靠增加采样点来更好地近似三角形的覆盖率而已。
MSAA 为增加效果付出的代价?
增加了计算量。一个像素被划分为 4 x 4,那么就是多了 16 倍的计算量。
为了解决这个弊端,后续人们会用更加有效的图案来分布采样点,而且这些采样点有的还会被邻近的复用。
采样操作
采样就很简单,此时一整个像素格子就是一个颜色,直接采样在模糊操作完之后的像素点中心取这个颜色就好。
小结:
1、很重要的另外两种抗锯齿方法
- FXAA 快速近似抗锯齿。和增加样本数没有任何关系,它是一个图像的后期处理。原理:通过图像匹配的方式找到有锯齿的地方,然后把这些有锯齿的边界给替换成平滑的边界。
- TAA 简单高效,与时间相关。原理是复用上一帧的信息。
2、超分辨率/超采样
- 从低分辨率到高分辨率。把小图变成大图,又不想看到锯齿现象。
- 基本上还是“样本不足”的问题
- DLSS(深度学习超级采样)
2 可见性与遮挡
2.1 画家算法
原理
先对远处的物体进行光栅化,后逐步对近处的物体进行光删化操作。(画近处的物体时会覆盖掉之前画的远处的一些物体)这样就可以正确地处理遮挡的问题了。
涉及到一个问题
这里涉及到一个问题:当某些物体在深度上存在互相遮挡的情况,此时没有办法定义顺序关系,这样就无法使用画家算法,因为不知道应该按照什么样的顺序画,或者不管先画什么都是错的。如下图所示:
2.2 问题解决方法:深度缓存/缓冲 z-buffer
思想:在生成最终图像的同时,也会生成一个“只存像素所看到的几何物体最浅的深度信息”的图像。然后利用深度缓存来维护遮挡信息。
-
每一个像素内存储最浅深度值,即 z 值。
-
需要额外的深度值缓冲区
- 帧缓冲区(frame buffer)存储颜色值
- 深度缓冲区(z缓冲区)存储深度
之前的讲述是摄像机永远看向z轴的负方向,因此所有的 z 都是负的。以为这 z 值越大距离反而越近,z 值越小,离摄像机的距离越远。
为了简化计算,将之前看到的 z 换一个概念。***我们现在认为,摄像机到物体之间的距离,也就是深度信息,这个值永远都是正的。越小的值表示越近,值越大表示距离越远。***
深度缓存算法思想:
- 一开始将所有像素记录的深度缓存距离都初始化为无限远,即初始化为无限大的值。
- 在光栅化的过程中,找到任意一个三角形所覆盖的任意一个像素。
- 如果新的三角形要画在这个像素内,对于这个像素来说,如果新的 z 值比之前所存储的深度信息还要小,那么就将像素点的颜色值和深度信息值都进行更新。否则,什么都不做。
注意这里并未进行排序,只是在不断求最小值而已。复杂度为O(n)。
该算法有一个很重要的特点:和绘制顺序是没有关系的,只要正确求得最小的深度信息,最后绘制出来的图像就是对的。(前提条件,不出现在同一个像素点上出现相同的深度信息的情况。)
总结:
这是一个非常非常重要的算法,几乎广泛应用在所有的硬件中。每一个像素维护一个深度测试,就可以得到正确的遮挡算法。
之前有提到过 MSAA 的方法,对于这里面的采样点进行深度缓冲的话。意味着也不一定是对每一个像素进行 z-buffer, 也有可能是对采样点~
z-buffer 一定处理不了透明物体的深度,这个需要特殊处理。
绘制透明物体
缓冲区其实就是一张和最终输出图像分辨率一样大的图,每个像素存储了不同的数据。
1)深度缓冲区 z-buffer:每个像素记录了离相机最近的片元的深度。
2)颜色缓冲区 Frame buffer:也叫帧缓冲区,每个像素记录了当前该位置对应的颜色。
1、场景中存在半透明物体的正确渲染
由于通过透明物体是可以看到被其遮挡的物体的,因此对于深度缓存中的深度值就不能直接替换更新,深度值应该是离相机最近的不透明物对应的片元的深度值,否则就会出现透明物体挡住不透明物体的渲染错误。
因此在渲染半透明物体时,需要关闭深度写入,但要保留深度测试。(深度测试的目的在于判断:是否因为遮挡而舍弃当前片元,如果该透明片元在不透明物体后,它被遮挡了,深度值比较大,当然应该被丢弃。当它在不透明物体前,它就不会被丢弃,但是又不会将当前的深度值写入,还是会以不透明物体的深度信息为准。)
同时对于颜色也不可以和以前一样替换更新,而是要做透明度混合,即用帧缓存中已有的颜色和透明片元的透明色混合出一个新的颜色。
场景总存在透明物体时的渲染步骤:
-
先渲染不透明物体,后渲染透明物体
-
对透明物体进行排序,然后从远到进的顺序依次渲染。(因为只有这样才能正确地叠上,并混合出正确的颜色)
-
并开启它们的深度测试,但关闭深度写入。
但此时还存在两个问题:
1)仍然无法解决透明物体相互交叠的问题。
2)透明物体内部如果自身互相交叠也会存在问题。无法判断片元的先后顺序。
2、半透明物体间相互交叠的问题
3、半透明物体的自身交叠问题及双面渲染
关闭背面剔除,将双面渲染的工作分成两个pass,渲染两次:
- 第一个pass只渲染背面
- 第二个pass只渲染正面
保证背面总是在正面被渲染之前渲染,从而可以保证正确的深度渲染关系