html 判断是否全屏_全屏反锯齿 - 多重采样
发布日期:2021-06-24 16:21:19 浏览次数:2 分类:技术文章

本文共 9603 字,大约阅读时间需要 32 分钟。

ad263d969dd7a9016817a96feddf3de5.png

图形绘制的时候总能看见那些不美观的锯齿。尤其是边缘锯齿给用户带来的突兀感,给了计算机图形学几乎一个属于“领域”的研究——反锯齿。视觉上的美观优化,以及改进后的运行效率,两手都要硬。——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阴影贴图技术之探Ⅲ] ):

  1. //Listing 13.7. Fragment shader for generating shadows, using four dithered samples
  2. uniform sampler2DShadow ShadowMap;
  3. uniform float Epsilon;
  4. uniform bool SelfShadowed;
  5. uniform float SelfShadowedVal;
  6. uniform float NonSelfShadowedVal;
  7. varying vec3 ShadowCoord;
  8. float Illumination;
  9. float lookup(float x, float y)
  10. {
  11. float depth = shadow2D(ShadowMap,
  12. ShadowCoord + vec2(x, y) * Epsilon).x;
  13. return depth != 1.0 ? Illumination : 1.0;
  14. }
  15. void main()
  16. {
  17. // lighten up the self-shadows
  18. Illumination = SelfShadowed ? SelfShadowedVal : NonSelfShadowedVal;
  19. // use modulo to vary the sample pattern
  20. vec2 o = mod(floor(gl_FragCoord.xy), 2.0);
  21. float sum = 0.0;
  22. sum += lookup(vec2(-1.5, 1.5) + o);
  23. sum += lookup(vec2( 0.5, 1.5) + o);
  24. sum += lookup(vec2(-1.5, -0.5) + o);
  25. sum += lookup(vec2( 0.5, -0.5) + o);
  26. gl_FragColor = vec4(sum * 0.25 * gl_Color.rgb, gl_Color.a);
  27. }

这里你可以认为是为了消缓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 引擎技术的简介。
  • 如果您不能肯定如何配置平滑处理,请使用“应用程序控制的”选项。您的显示将根据应用程序的规定自行调整。

有粗略的设置方法(质量-效率权衡):

bf167aa97c9757c1e5f05abdc9e751fd.png

也有可能做个精确一点的设置:

52bfbfe5fdc63e36ff2e3e1c6da5f608.png

的确是可以设置的。那么,既然显卡提供了这样的功能,那么有对应的集成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设置的地方。好吧,还是先说这个类:

  1. class MultiSample
  2. {
  3. public:
  4. MultiSample();
  5. ~MultiSample();
  6. bool InitMultiSample(HWND hwnd);
  7. bool ExtentionSurpported(){ return multisampleSupportted;}
  8. int GetMultiSampleFormat(){ return MultiSampleFormat;}
  9. void SetMultiSample(UINT sample){Multisample = sample; }
  10. UINT GetSupportedFormatNum(){ return numofFormats;}
  11. int GetSupportedFormats(UINT i){ return i < numofFormats ? pixelformat[i] : 0;}
  12. private:
  13. bool WGLisExtensionSupported(const char *extension);
  14. UINT Multisample;
  15. bool multisampleSupportted;
  16. int MultiSampleFormat;
  17. UINT numofFormats;
  18. int pixelformat[20];
  19. };

最重要的函数是InitMultiSample(),它完成了“全屏反走样必要的设置”,更准确地说,它给下面列出的私有成员变量赋值了。这些变量中最重要的——也是本扩展所需要的东西和最后给出的东西:输入Multisample,输出MultiSampleFormat。前者就是我们要告诉扩展的采样参数,譬如4代表4X,8代表8X等等(详见[全屏反锯齿 - 多重采样Ⅰ] );后者就是:像素格式。

恩,没错,最后要的就是像素格式。就是我们在初始化OPENGL窗口设置时用来设置渲染窗口像素的模式的那样东西。(详细见[自剖一下自己用的NEHE OpenGL框架(下篇)] 。)作用的话,接下来再探讨,先来看看这个UINT“像素格式”是怎么得到的:

  1. bool MultiSample::InitMultiSample(HWND hwnd)
  2. {
  3. multisampleSupportted = WGLisExtensionSupported("WGL_ARB_multisample");
  4. if(!multisampleSupportted)return false;
  5. PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
  6. HDC hdc = ::GetDC(hwnd);
  7. float fAttributes[] = {0,0};
  8. int iAtributes[] =
  9. {
  10. WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
  11. WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
  12. WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
  13. WGL_COLOR_BITS_ARB, 24,
  14. WGL_ALPHA_BITS_ARB, 8,
  15. WGL_DEPTH_BITS_ARB, 16,
  16. WGL_STENCIL_BITS_ARB, 0,
  17. WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
  18. WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
  19. WGL_SAMPLES_ARB, Multisample,
  20. 0,0
  21. };
  22. if(!wglChoosePixelFormatARB(hdc, iAtributes, fAttributes, 20 , pixelformat, &numofFormats))
  23. {
  24. multisampleSupportted = false;
  25. return false;
  26. }
  27. if(numofFormats <= 0)return false;
  28. MultiSampleFormat = pixelformat[0];
  29. return true;
  30. }

本函数接受一个表征窗口的句柄,并接下来从该句柄得出绘图环境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函数),在两个地方作了代码添加:

  1. BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
  2. {
  3. //.........常规的像素格式描述器设置
  4. //.........
  5. if(!SecondWindowCreate)
  6. {
  7. if ( !( PixelFormat = ChoosePixelFormat ( m_hDC, &pfd ) ) ) {
  8. KillGLWindow ();
  9. MessageBox ( "Can't Find A Suitable PixelFormat.", "ERROR", MB_OK | MB_ICONEXCLAMATION );
  10. return FALSE;
  11. }
  12. }
  13. else
  14. PixelFormat = MultiSampleFormat;
  15. if ( !SetPixelFormat ( m_hDC, PixelFormat, &pfd ) ){
  16. KillGLWindow ();
  17. MessageBox ( "Can't Set The PixelFormat.", "ERROR", MB_OK | MB_ICONEXCLAMATION );
  18. return FALSE;
  19. }
  20. //.........DC,RC关联设置
  21. //..........
  22. if(SecondWindowCreate)
  23. {
  24. if (MessageBox("渲染一堆三角形(YES) 渲染几个一般几何模型(NO)",
  25. "RederTest Selection",MB_YESNO|MB_ICONQUESTION)==IDYES)
  26. RenderOb=FALSE;
  27. else
  28. RenderOb = TRUE;
  29. }
  30. else
  31. {
  32. MutiSampleSelectDlg MSdLG;
  33. if(MSdLG.DoModal() == IDOK)
  34. {
  35. MultiSample = MSdLG.GetMultiSample();
  36. Multisam.SetMultiSample(MultiSample);
  37. Multisam.InitMultiSample(m_hWnd);
  38. PixelFormat = Multisam.GetMultiSampleFormat();
  39. return bRet;
  40. }
  41. }
  42. //.....
  43. //....初始化....
  44. }

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框架(中篇)] ):

  1. BOOL CAntialiasing_MultiSampleApp::InitInstance()
  2. {
  3. // Standard initialization
  4. // Change the registry key under which our settings are stored.
  5. // TODO: You should modify this string to be something appropriate
  6. // such as the name of your company or organization.
  7. SetRegistryKey(_T("Local AppWizard-Generated Applications"));
  8. // To create the main window, this code creates a new frame window
  9. // object and then sets it as the application's main window object.
  10. m_pMainWnd = NULL;
  11. CMainFrame* pFrame = new CMainFrame;
  12. pFrame->CreateWindowSecondTime(false);
  13. if (!pFrame->Create(NULL,"MFC OpenGL"))
  14. return FALSE;
  15. int MultiSampleFormat = pFrame->GetMultiSampleFormat();
  16. pFrame->DestroyWindow();
  17. //delete pFrame;
  18. pFrame = new CMainFrame;
  19. pFrame->CreateWindowSecondTime(true);
  20. pFrame->SetMultiSampleFormat(MultiSampleFormat);
  21. if (!pFrame->Create(NULL,"MFC OpenGL"))
  22. return FALSE;
  23. m_pMainWnd = pFrame;
  24. pFrame->ShowWindow(m_nCmdShow);
  25. pFrame->UpdateWindow();
  26. return TRUE;
  27. }

在下对MFC不熟,中间那里为什么无法DELETE掉呢?好了,看结果吧:

25537f4b8dfb255173c7968782195d9b.png

(场景1是用了NEHE #46里的旋转矩形场景。设置为1X时,可见基本跟不设置差不多的,边缘锯齿严重)

2952bbfd59d4bc7dbfb09314fac3bdc5.png

(设置为4X时,边缘锯齿还是明显的,但稍微不那么严重了吧)

ee3e7499a19177c7902b1a6bd94fd23b.png

(设置为16X时,边缘锯齿效应基本上消除了哦)

aa5da66ceee71229455497b069492530.png

(16X时,再来一张,很好哦。效率呢?这么多次采样平滑,肯定会下降的,但因为场景简单+偶家显卡比较好,所以FPS没有下降)

e4c7cb9cabd0b4d3b185f6b4a17680cf.png

(自家的场景2,模型边缘,在1X下的严重锯齿)

f211d59219e4febdf476bdb75e3604b9.png

(自家的场景2,模型边缘,在16X下的锯齿现象消解)

本日志demo放出:AntialiasingMultiSampleDemobYzwqxin.rar
DEMO使用了扩展,需要下载glew库到指定目录,下载见此:OpenGL常用的库

2009.5.2-5.3

转载地址:https://blog.csdn.net/weixin_33800724/article/details/112092221 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:e、g、f盘可以给c盘扩容吗_关于给C盘扩容绝对简易可行的方法
下一篇:button点击后变色_HTML | 两只“变色龙”带你玩转色彩变幻

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月20日 06时55分32秒