图形绘制的时候总能看见那些不美观的锯齿。尤其是边缘锯齿给用户带来的突兀感,给了计算机图形学几乎一个属于“领域”的研究——反锯齿。视觉上的美观优化,以及改进后的运行效率,两手都要硬。
锯齿是由于屏幕分辨率低所造成的赝项,想必大家也知道这指什么。CSDN里一位高人仁兄问道,在显卡设置里面有所谓的“反锯齿”选项,可以设置覆盖采样的参数,那么在OpenGL里是否也有相应的API能够做相应的设置呢?我刚开始是认为所谓显卡里的抗锯齿是shader里对屏幕做全屏PCF(百分比渐进过滤),而OPENGL的API里面也没见过有什么可以接收类似覆盖采样这样的参数的,顶多是glHint那类函数可以通过混合等缓解点线的锯齿现象罢了。可接下来有人回帖说了NEHE46课,我马上“觉醒”,对喔,NEHE里[搜集的优良OpenGL教程] 确实有课叫什么“全屏反锯齿”的,马上在电脑里找出来看——还真的有呢!
所谓采样参数,也可以说是“平滑处理级别”,一般就有1X,2X,4X,8X,16X这几个值。在[图像处理里的空间域滤波] 一文中,谈到了图像的平滑处理(模糊),其本质就是通过把一个像素作邻域处理(领域加合平均)得出新像素值而已,领域大小被称为模板参数,一般是个奇数,这样才能把待处理像素包围在中间。而这里的采样参数,乍一看就是2的N次方这样的数,但窃认为其实都是那么回事,只不过更适合于描述而已。
通常认为在实时图形学中,这种像素级别的操作应该交给强大的Shder。在shader里被称为dithered samples的,就是PCF系数。因为它的可编程性能,对显卡的要求就更可具弹性了,实际上任何X都可以,不限制于2的N次方。
为了说得清晰点,以橙书里关于对Shadow map做PCF那章的代码段为例([Shadow Map阴影贴图技术之探Ⅲ] ):
- //Listing 13.7. Fragment shader for generating shadows, using four dithered samples
- uniform sampler2DShadow ShadowMap;
- uniform float Epsilon;
- uniform bool SelfShadowed;
- uniform float SelfShadowedVal;
- uniform float NonSelfShadowedVal;
- varying vec3 ShadowCoord;
- float Illumination;
- float lookup(float x, float y)
- {
- float depth = shadow2D(ShadowMap,
- ShadowCoord vec2(x, y) * Epsilon).x;
- return depth != 1.0 ? Illumination : 1.0;
- }
- void main()
- {
- // lighten up the self-shadows
- Illumination = SelfShadowed ? SelfShadowedVal : NonSelfShadowedVal;
- // use modulo to vary the sample pattern
- vec2 o = mod(floor(gl_FragCoord.xy), 2.0);
- float sum = 0.0;
- sum = lookup(vec2(-1.5, 1.5) o);
- sum = lookup(vec2( 0.5, 1.5) o);
- sum = lookup(vec2(-1.5, -0.5) o);
- sum = lookup(vec2( 0.5, -0.5) o);
- gl_FragColor = vec4(sum * 0.25 * gl_Color.rgb, gl_Color.a);
- }
这里你可以认为是为了消缓shadow map形成的锯齿而做的“全屏反锯齿”,或者说“全屏模糊化”“全屏平滑化”(针对阴影帖图覆盖之场景)。参数是4X。看最下面几行,它做了一些检索纹理(阴影图)的动作。sum是个颜色计数器,它把当前处理像素所在纹素的周边四个纹素的值相加,并在输出时除以4以归一化回[0,1]区间——这是典型的模板滤波操作。当然还有一点小动作(取非整值并根据纹理索引的奇偶再偏移0个或1个纹素),不多提,但总体思路就是这样——这就是PCF的雏形。一张纹理的颜色过渡只有在边缘处巨变,而人眼对边缘的部分总是比较敏感,边缘处产生的模糊正能消除这种突兀感。
好吧,如果这算4X,那么5X,6X如此下去都可以。但是4X和5X你能想到两者有啥区别么?无非后者多搅和了一个纹素而已,如果这个被搅和的纹素不用中心纹素,你还得决定是在哪个方向多加一个呢!6X就更不用说了.......所以说,下一个“级别”应该是关于中心纹素得包围圈向外延伸一个纹素,每方向上加多一个,也就是8X了.......如此类推,你能发现比较匀称和谐的“采样参数”都是2的N次方。
另一方面,总不能无限地32X,64X下去吧,要知道,每多一个“级别”,对每个像素就得做多一倍的加法,运算效率下来了,这还不算,更主要的是,整张纹理都被错位叠加N次了,纹理被覆物最后真被模糊得血肉模糊,不成人形(喂,本来就不成嘛)了——因为平滑模糊,就相当于去处细节!
概念讲到这里,回到开头那里。显卡里的PCF?我看了看自己的显卡的控制面板(NV9),里面解释到:
平滑处理可设置为有利于提高系统性能或改进图象质量。
- 如果要显示三维动画效果和强调场景的流畅变换,最好使用性能设置。
- 如果要以显示非常精细和逼真的三维物体为主要目的时,最好使用质量设置。
- 在“管理 3D 设置”页面上(高级视图),您可以设置具体的平滑处理级别。
值越高,对应的平滑处理的级别就越高。例如,16x 的质量要高于 2x。
在 GeForce 8 系列 GPU 中,NVIDIA 推出了一种新形式的平滑处理,称为“覆盖采样”,该功能对 8x、16x 和 16xQ 平滑处理设置产生影响。如需更多信息,请参照 NVIDIA.com 网站上关于 Lumenex 引擎技术的简介。 - 如果您不能肯定如何配置平滑处理,请使用“应用程序控制的”选项。您的显示将根据应用程序的规定自行调整。
有粗略的设置方法(质量-效率权衡):
也有可能做个精确一点的设置:
的确是可以设置的。那么,既然显卡提供了这样的功能,那么有对应的集成API也很正常吧。但是,看完NEHE46课后,我知道OPenGL里能实现这种设置的方法是使用一个叫"multi sample"的扩展。而且也不能一步到位,需要设置不少东西。我不能肯定它与PCF有没有什么特别的联系,于是我想自己按照NEHE 46来实现一下。