FBO中多重采样抗锯齿(MSAA:MultiSampling Anti-Aliasing)

举报
ShaderJoy 发表于 2021/12/30 00:32:58 2021/12/30
【摘要】 原文 今天在写这样一个程序,就是导入一个OBJ模型然后显示出来的时候,遇到了一个问题。我在程序中开启了多重采样,在屏幕上显示出来的效果确实有抗锯齿。但是当我用FBO离屏渲染,然后保存为BMP图像的时候,发现保存出来的BMP图像并没有抗锯齿效果。 问题产生原因及解决方案: 在默认帧缓冲中启用多重采样并不会导致FBO里也...

原文

今天在写这样一个程序,就是导入一个OBJ模型然后显示出来的时候,遇到了一个问题。我在程序中开启了多重采样,在屏幕上显示出来的效果确实有抗锯齿。但是当我用FBO离屏渲染,然后保存为BMP图像的时候,发现保存出来的BMP图像并没有抗锯齿效果。

问题产生原因及解决方案:

在默认帧缓冲中启用多重采样并不会导致FBO里也会启用多重采样。因此要在FBO里达到多重采样的效果,必须创建适用多重采样的FBO,而不是普通的FBO。

关于FBO介绍及使用可参考:FBO

下面分别介绍多重采样抗锯齿以及怎样在FBO中使用这一技术。


一、多重采样抗锯齿技术(multisampling anti-aliasing)

1、抗锯齿技术种类分类

全屏反锯齿


超级采样抗锯齿




多重采样抗锯齿


覆盖采样抗锯齿

可编程过滤抗锯齿


2、多重采样抗锯齿技术详解

首先看下面这幅图,左右对比了采样多重采样抗锯齿技术和不采用的效果对比:

图一 采样多重采样抗锯齿技术前后对比

可以看出,左图边缘有很明显的锯齿状。导致这一现象的原因是:每个像素的绘制是由它是否完全位于多边形内部所决定的。如果它在多边形内部,则渲染它;否则不渲染。很显然,这是不准确的。一些像素恰好位于边缘上面。如果我们依据一个像素它位于多边形内部的区域的大小来决定它的渲染,那么获得的效果要好很多。最终像素的颜色就是多边形颜色和其外部的颜色的混合。你也许会认为这样会导致性能上的消耗,事实却是如此。但是我们可以针对每个像素使用多个采样来近似估计最终结果。

MSAA技术包括针对每个像素实施多个采样,然后对这些采样的结果进行混合来决定这个像素的最终值。采样点位于像素内部的不同位置。很显然,大多数的采样点会位于多边形内部,但是对于那些位于多边形边缘的像素,一些采样点会位于多边形外部。

如果对每个像素进行4次采样,那么光栅化的频率将是不进行多重采样的4倍。对于每个像素,片断着色器执行一次,输出结果由4个采样点中位于多边形内部的数量决定。


3、在OpenGL中实现MSAA技术

在OpenGL中实施这一技术非常简单,不需要过多的操作。它是通过使用额外的缓冲区来存储子像素样本来实现的。然后这些样本被合成以生成片断的最终颜色。

下面以在Qt中为例来进行说明,它和使用GLUT等API很相似。

(1)在创建OpenGL窗口的时候,需要选择支持MSAA的OpenGL上下文:


  
  1. QGLFormat format;
  2. format.setVersion(4,0);
  3. format.setProfile(QGLFormat::CoreProfile);
  4. format.setSampleBuffers(true);
  5. format.setSamples(4);
  6. QGLWidget *glView = new QGLWidget(format);


EGL的配置方法


(2)判断多重采样缓冲区是否存在,以及对每个像素使用几个采样:


  
  1. GLint bufs, samples;
  2. glGetIntegerv(GL_SAMPLE_BUFFERS, &bufs);
  3. glGetIntegerv(GL_SAMPLES, &samples);
  4. printf("MSAA: buffers = %d samples = %d\n", bufs, samples);


(3)启用MSAA:

glEnable(GL_MULTISAMPLE);
 


(4)如果要禁用MSAA,使用以下代码:

glDisable(GL_MULTISAMPLE);
 



如果使用GLUT,则使用以下代码:


  
  1. //申请一个采用了双重缓存,包含颜色,深度的帧缓存和多重采样。
  2. glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULTISAMPLE);
  3. glEnable(GL_MULTISAMPLE);//开启多重缓存
  4. glDisable(GL_MULTISAMPLE);//关闭多重缓存




二、在FBO中使用多重采样抗锯齿技术

首先我们需要创建1个适用于多重采样的FBO:


  
  1. //创建FBO:multisampling
  2. glGenFramebuffers(1,&m_frameBufferMS);
  3. glBindFramebuffer(GL_FRAMEBUFFER,m_frameBufferMS);
  4. glGenRenderbuffers(1,&m_renderBufferColorMS);
  5. glBindRenderbuffer(GL_RENDERBUFFER,m_renderBufferColorMS);
  6. glRenderbufferStorageMultisample(GL_RENDERBUFFER,4,
  7. GL_RGB,m_subImageWidth,m_subImageHeight);
  8. glBindRenderbuffer(GL_RENDERBUFFER,0);
  9. glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,
  10. GL_RENDERBUFFER,m_renderBufferColorMS);
  11. glGenRenderbuffers(1,&m_renderBufferDepthMS);
  12. glBindRenderbuffer(GL_RENDERBUFFER,m_renderBufferDepthMS);
  13. glRenderbufferStorageMultisample(GL_RENDERBUFFER,4,
  14. GL_DEPTH_COMPONENT24,m_subImageWidth,m_subImageHeight);
  15. glBindRenderbuffer(GL_RENDERBUFFER,0);
  16. glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,
  17. GL_RENDERBUFFER,m_renderBufferDepthMS);
  18. glBindFramebuffer(GL_FRAMEBUFFER,0);




记住需要在应用程序后面进行清除操作:


  
  1. glDeleteRenderbuffers(1,&m_renderBufferColorMS);
  2. glDeleteRenderbuffers(1,&m_renderBufferDepthMS);
  3. glDeleteFramebuffers(1,&m_frameBufferMS);



然后在绘制物体的时候进行绑定:


  
  1. glBindFramebuffer(GL_FRAMEBUFFER,m_frameBufferMS);
  2. GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
  3. if (status != GL_FRAMEBUFFER_COMPLETE)
  4. {
  5. cout << "The frame buffer status is not complete!" << endl;
  6. return;
  7. }
  8. drawing();//draw something
  9. glBindFramebuffer(GL_FRAMEBUFFER,0);



接下来我要用函数glReadPixels读取数据,然后保存为BMP图像。在这里我使用了PBO(像素缓冲区对象)。在这里需要注意的是:不能用glReadPixels直接读取多重采样缓冲区里面的数据,否则会出现GL_INVALID_OPERATION错误。那么应该怎么做呢?

一个常用的方法是创建另外一个FBO。它是一个普通的FBO,用于进行传图操作(Blit).

创建用于Blit的普通FBO:


  
  1. //创建普通FBO
  2. glGenFramebuffers(1,&m_frameBuffer);
  3. glBindFramebuffer(GL_FRAMEBUFFER,m_frameBuffer);
  4. glGenRenderbuffers(1,&m_renderBufferColor);
  5. glBindRenderbuffer(GL_RENDERBUFFER,m_renderBufferColor);
  6. glRenderbufferStorage(GL_RENDERBUFFER,GL_RGB,
  7. m_subImageWidth,m_subImageHeight);
  8. glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,
  9. GL_RENDERBUFFER,m_renderBufferColor);
  10. glGenRenderbuffers(1,&m_renderBufferDepth);
  11. glBindRenderbuffer(GL_RENDERBUFFER,m_renderBufferDepth);
  12. glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH_COMPONENT,
  13. m_subImageWidth,m_subImageHeight);
  14. glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,
  15. GL_RENDERBUFFER,m_renderBufferDepth);
  16. glBindFramebuffer(GL_FRAMEBUFFER,0); //解除绑定



记住也要进行清除操作:


  
  1. glDeleteRenderbuffers(1,&m_renderBufferColor);
  2. glDeleteRenderbuffers(1,&m_renderBufferDepth);
  3. glDeleteFramebuffers(1,&m_frameBuffer);



然后绑定两个FBO,一个用于读,一个进行写入,进行blit操作:


  
  1. glBindFramebuffer(GL_FRAMEBUFFER,0);
  2. glBindFramebuffer(GL_READ_FRAMEBUFFER,m_frameBufferMS);
  3. status = glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
  4. if (status != GL_FRAMEBUFFER_COMPLETE)
  5. {
  6. cout << "The frame buffer status is not complete!" << endl;
  7. return;
  8. }
  9. glBindFramebuffer(GL_DRAW_FRAMEBUFFER,m_frameBuffer);
  10. status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
  11. if (status != GL_FRAMEBUFFER_COMPLETE)
  12. {
  13. cout << "The frame buffer status is not complete!" << endl;
  14. return;
  15. }
  16. glBlitFramebuffer(0,0,m_subImageWidth,m_subImageHeight,
  17. 0,0,m_subImageWidth,m_subImageHeight,
  18. GL_COLOR_BUFFER_BIT,GL_LINEAR);
  19. glBindFramebuffer(GL_READ_FRAMEBUFFER,0);
  20. glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0);



之后,绑定普通的FBO,读取像素:


  
  1. glBindBuffer(GL_PIXEL_PACK_BUFFER,m_subImageBuffer);
  2. glPixelStorei(GL_PACK_ALIGNMENT,1);
  3. glBindFramebuffer(GL_FRAMEBUFFER,m_frameBuffer);
  4. glReadPixels(0,0,m_subImageWidth,m_subImageHeight,
  5. GL_BGR,GL_UNSIGNED_BYTE,bufferOffset(0));



这样所有的操作就完成了。

参考资料:

StackOverflow问题1

stackoverflow问题2

stackoverflow问题3

stacioverflow问题4

GL_framebuffer_multisample详解


延伸阅读:

多重采样(MultiSample)下的FBO反锯齿

OpenGL 这两种Multisample方式(RenderBuffer/Texture)有什么区别和好处?

关于支持多重采样的FBO和Texture

iOS上FBO的MSAA源码


文章来源: panda1234lee.blog.csdn.net,作者:panda1234lee,版权归原作者所有,如需转载,请联系作者。

原文链接:panda1234lee.blog.csdn.net/article/details/52348967

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。