本文共 9603 字,大约阅读时间需要 32 分钟。
图形绘制的时候总能看见那些不美观的锯齿。尤其是边缘锯齿给用户带来的突兀感,给了计算机图形学几乎一个属于“领域”的研究——反锯齿。视觉上的美观优化,以及改进后的运行效率,两手都要硬。——ZwqXin.com
本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
原文地址:http://www.zwqxin.com/archives/opengl/antialiasing-multi-sample-1.html锯齿是由于屏幕分辨率低所造成的赝项,想必大家也知道这指什么。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那章的代码段为例(ZwqXin也弄过[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来实现一下。
请留意下篇文章:全屏反锯齿 - 多重采样Ⅱ
实现无关shader编写的全屏反走样的扩展,全名叫WGL_ARB_multisample。关键字1,ARB,说明它真是扩展(别打~);关键字2,WGL,说明它并非一般的扩展。锯齿,或者,早已把此扩展忘记了吧。本为下篇,有意者可先看上篇,请点:[全屏反锯齿 - 多重采样Ⅰ] 。——ZwqXin.com
本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
原文地址:http://www.zwqxin.com/archives/opengl/antialiasing-multi-sample-2.html如果你的程序INCLUDE目录里面只有glew.h或者glext.h,你是用不起这个扩展的。它属于ARB委员会的闲置物,因此才会被安排在wglew.h或者wglext.h里的吧。是不是呢?请好心人路过告知。总之,要使用这个扩展,两道。1.程序包含wglew.h,并拥有glew32.lb库文件(与glew.h不同,wglew.h不必要置于gl.h之前,本扩展也没看到需要Initglew()之类的初始化函数);2.程序包含wglext.h......恩,貌似只需要wglext.h就可。
另外,当时发现本扩展存在EXT版本,惊喜之余专门用了一下,发现是无法用的。用opengl extension viewer看了看,本显卡支持的EXT扩展里没有这个的EXT版本,而且貌似其他显卡也没有。只有用WGL_ARB_multisample了。(ARB与EXT是哪门子区别?可求助GOOGLE大神,通俗点说是一个不能直接用,一个可以。见下所述。)
参考NEHE #46,弄了一个类。最初是想封装得好点的,但后来发现这难度大,因为使用该扩展的位置是程序初始化之前——屏幕像素设置,DC,RC设置的地方。好吧,还是先说这个类:
- class MultiSample
- {
- public:
- MultiSample();
- ~MultiSample();
- bool InitMultiSample(HWND hwnd);
- bool ExtentionSurpported(){ return multisampleSupportted;}
- int GetMultiSampleFormat(){ return MultiSampleFormat;}
- void SetMultiSample(UINT sample){Multisample = sample; }
- UINT GetSupportedFormatNum(){ return numofFormats;}
- int GetSupportedFormats(UINT i){ return i < numofFormats ? pixelformat[i] : 0;}
- private:
- bool WGLisExtensionSupported(const char *extension);
- UINT Multisample;
- bool multisampleSupportted;
- int MultiSampleFormat;
- UINT numofFormats;
- int pixelformat[20];
- };
最重要的函数是InitMultiSample(),它完成了“全屏反走样必要的设置”,更准确地说,它给下面列出的私有成员变量赋值了。这些变量中最重要的——也是本扩展所需要的东西和最后给出的东西:输入Multisample,输出MultiSampleFormat。前者就是我们要告诉扩展的采样参数,譬如4代表4X,8代表8X等等(详见[全屏反锯齿 - 多重采样Ⅰ] );后者就是:像素格式。
恩,没错,最后要的就是像素格式。就是我们在初始化OPENGL窗口设置时用来设置渲染窗口像素的模式的那样东西。(详细见[自剖一下自己用的NEHE OpenGL框架(下篇)] 。)作用的话,接下来再探讨,先来看看这个UINT“像素格式”是怎么得到的:
- bool MultiSample::InitMultiSample(HWND hwnd)
- {
- multisampleSupportted = WGLisExtensionSupported("WGL_ARB_multisample");
- if(!multisampleSupportted)return false;
- PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
- HDC hdc = ::GetDC(hwnd);
- float fAttributes[] = {0,0};
- int iAtributes[] =
- {
- WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
- WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
- WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
- WGL_COLOR_BITS_ARB, 24,
- WGL_ALPHA_BITS_ARB, 8,
- WGL_DEPTH_BITS_ARB, 16,
- WGL_STENCIL_BITS_ARB, 0,
- WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
- WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
- WGL_SAMPLES_ARB, Multisample,
- 0,0
- };
- if(!wglChoosePixelFormatARB(hdc, iAtributes, fAttributes, 20 , pixelformat, &numofFormats))
- {
- multisampleSupportted = false;
- return false;
- }
- if(numofFormats <= 0)return false;
- MultiSampleFormat = pixelformat[0];
- return true;
- }
本函数接受一个表征窗口的句柄,并接下来从该句柄得出绘图环境DC。这是本扩展第2个需要知道的东西:产生新像素格式必然要先了解绘图环境。multisampleSupportted返回你的显卡是否支持本WGL扩展,WGLisExtensionSupported是直接从NEHE46那里拿来的。然后我们来获取本ARB扩展功能函数:wglChoosePixelFormatARB的“实体”,获取后我们就能用了。用的方法无非是输入-输出。我们输入绘图环境DC,还有一个iAtributes数组指针,一个fAttributes数组指针。fAttributes只是单纯的0数组,我不知道其他值会有什么效果,只知道这里只需要float类型的0数组就够了,或者NULL,我尝试过,也可以的。至于iAtributes,可是戏玉!
它很明显在设置类似PIXELFORMATDESCRIPTOR(像素描述器,见[自剖一下自己用的NEHE OpenGL框架(下篇))的东西,告诉wglChoosePixelFormatARB函数,我们所需要的像素格式的模样,譬如彩色通道位数,是否使用双缓冲等等(注意数组内是:“属性,值”的格式),然后由它去设置新的像素格式。值得注意的是WGL_SAMPLE_BUFFERS_ARB和WGL_SAMPLES_ARB。根据opengl官方给出的的本扩展的文档,前者表明新增一个缓冲(缓存),叫“多重采样缓冲”(如何?),该缓冲启用下,传统5大缓冲中的3个将会失效,只有颜色缓冲和辅助缓冲能继续。是不是因为多重采样缓冲其实包含了这些了呢?文档没兴致看仔细,算了。启用该缓冲,并把WGL_SAMPLES_ARB设置为我们需要的采样参数后,就能用它来描述像素格式了。
像素格式,其实表面上看就是一个简单的UINT,真正的含义只有SetPixelFormat函数才知道。wglChoosePixelFormatARB接收输入后,输出满足要求的像素格式(用UINT表示),我把这些满足的像素格式扔进成员数组pixelformat里了,并规定最多接收20个,numofFormats是实际满足的个数。我做过试验的,其他像素属性按上,1X的不算,2X下共有16种像素格式可以满足(UINT为20,24,28~64);3X~4X,共有12种满足;5X~8X,8种;9X~16X,4种;16X以上,0种。由此一来说明本显卡确实最多支持16X,二来印证上篇所言,只有2的N次方的采样参数有用(中间那些都被默认向大者靠了)。
函数最后让输出的像素格式取第一个有用的(最准确满足的)像素格式。
看MainFrame里的像素格式设置部分(OnCreateClient函数),在两个地方作了代码添加:
- BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
- {
- //.........常规的像素格式描述器设置
- //.........
- if(!SecondWindowCreate)
- {
- if ( !( PixelFormat = ChoosePixelFormat ( m_hDC, &pfd ) ) ) {
- KillGLWindow ();
- MessageBox ( "Can't Find A Suitable PixelFormat.", "ERROR", MB_OK | MB_ICONEXCLAMATION );
- return FALSE;
- }
- }
- else
- PixelFormat = MultiSampleFormat;
- if ( !SetPixelFormat ( m_hDC, PixelFormat, &pfd ) ){
- KillGLWindow ();
- MessageBox ( "Can't Set The PixelFormat.", "ERROR", MB_OK | MB_ICONEXCLAMATION );
- return FALSE;
- }
- //.........DC,RC关联设置
- //..........
- if(SecondWindowCreate)
- {
- if (MessageBox("渲染一堆三角形(YES) 渲染几个一般几何模型(NO)",
- "RederTest Selection",MB_YESNO|MB_ICONQUESTION)==IDYES)
- RenderOb=FALSE;
- else
- RenderOb = TRUE;
- }
- else
- {
- MutiSampleSelectDlg MSdLG;
- if(MSdLG.DoModal() == IDOK)
- {
- MultiSample = MSdLG.GetMultiSample();
- Multisam.SetMultiSample(MultiSample);
- Multisam.InitMultiSample(m_hWnd);
- PixelFormat = Multisam.GetMultiSampleFormat();
- return bRet;
- }
- }
- //.....
- //....初始化....
- }
SecondWindowCreate是CMAINFRAME的成员函数,顾名思义,就是表明OnCreateClient函数要执行两次。SecondWindowCreate为FALSE的时候,按照原设置来设置像素格式。然后关联好RC后,调用InitMultiSample,接收当前opengl渲染窗口(原窗口)句柄和从某对话框(我弄的)得来的采样参数,得出的像素格式存入CMAINFRAME另一成员函数PixelFormat里。不必初始化直接返回。
注意,返回后,我会保存生成的PixelFormat,然后把这个刚刚建立的窗口(原窗口)——连显示都不让它显示,直接让它夭折。重新建一个CMAINFRAME对象,生成新窗口,让SecondWindowCreate为TRUE,并把刚才的PixelFormat交给它。好了,第二次来到OnCreateClient,直接用支持多重采样的PixelFormat去SetPixelFormat(),一路下去。
这是因为只有原来的RC建立好后,wglChoosePixelFormatARB才能知道当前有哪些像素格式适合吧。是不方便但也没办法。对了,最后再看一下夭折-再生的处理在我的NEHE框架里怎么改吧,是在APP类的InitInstance函数里(不改动前的,可见[自剖一下自己用的NEHE OpenGL框架(中篇)] ):
- BOOL CAntialiasing_MultiSampleApp::InitInstance()
- {
- // Standard initialization
- // Change the registry key under which our settings are stored.
- // TODO: You should modify this string to be something appropriate
- // such as the name of your company or organization.
- SetRegistryKey(_T("Local AppWizard-Generated Applications"));
- // To create the main window, this code creates a new frame window
- // object and then sets it as the application's main window object.
- m_pMainWnd = NULL;
- CMainFrame* pFrame = new CMainFrame;
- pFrame->CreateWindowSecondTime(false);
- if (!pFrame->Create(NULL,"MFC OpenGL"))
- return FALSE;
- int MultiSampleFormat = pFrame->GetMultiSampleFormat();
- pFrame->DestroyWindow();
- //delete pFrame;
- pFrame = new CMainFrame;
- pFrame->CreateWindowSecondTime(true);
- pFrame->SetMultiSampleFormat(MultiSampleFormat);
- if (!pFrame->Create(NULL,"MFC OpenGL"))
- return FALSE;
- m_pMainWnd = pFrame;
- pFrame->ShowWindow(m_nCmdShow);
- pFrame->UpdateWindow();
- return TRUE;
- }
在下对MFC不熟,中间那里为什么无法DELETE掉呢?好了,看结果吧:
2009.5.2-5.3
转载地址:https://blog.csdn.net/weixin_33800724/article/details/112092221 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!