在三维渲染的过程中,锯齿总是让人讨厌的东西。抗锯齿的一种采用方式是多重采样,本文主要小记一下FBO与多重采样的关系。——ZwqXin.com

首先,关于FBO(Frame Buffer Object),想必都已经十分熟悉了。可以参考本博客(zwqxin.com)之前的几篇文章:
[学一学,FBO
[OpenGL怎样近似进行同时到FBO和屏幕的渲染]
[联结FBO与Texture Array]

另外,本人以前也写了些关于全屏幕反锯齿(FSAA)的文章:
[全屏反锯齿 - 多重采样Ⅰ
[全屏反锯齿 - 多重采样Ⅱ]

以上关于通过多重采样进行屏幕抗锯齿的方法,顾名思义,是针对“屏幕”的。在[OpenGL怎样近似进行同时到FBO和屏幕的渲染]中也说过,屏幕上的内容作为OpenGL的“main
frame buffer”的输出,与FBO是没太大关系的。通过渲染窗口的二重创建而获得支持多重采样的像素格式并应用到渲染中,这种方法对于[渲染到FBO的渲染目标内的内容]是没太大作用的,所以需要通过别的方式,专门地来对FBO内的渲染内容进行反锯齿处理。

本文来源于 ZwqXin (http://www.zwqxin.com/),
转载请注明
      原文地址:http://www.zwqxin.com/archives/opengl/multisample-fbo-antialiasing.html

什么时候需要这样做呢?FBO是离屏渲染手段,而抗锯齿的目的是为了让最终在"屏幕"上显示的内容(或者线条)显得平滑一些,所以只有在FBO里的内容关系到渲染的最终结果时,才有必要,这是大前提;其次,FBO的特点是它设立的“画布”可以无视渲染窗口的大小(也就是说,它不受那些所谓pixel
owner
test之类的限制),而你如果只是把一幅巨大“画布”的内容“粘贴”到渲染窗口可显示范围内一个小矩形之类的,就没必要去专门抗锯齿了,因为这种纹理的过采样(over-sampling)本身就有弱化锯齿的功效,只要这种功效接近或超过专门去做多重采样的功效,就不要专门进行抗锯齿手段,这姑且算第二个前提吧;还有很多其他情况,不一一赘述,但是最后很重要的一点,就是多重采样抗锯齿同时也是一种枪杀FPS的手法,你必然也要“老生常谈”般地考虑performence
vs quality的问题了。

考虑一种最直接,最迫切需要应用反锯齿的场合吧:场景后处理。很多图形学算法都是针对后处理这个过程的(甚至由此衍生出deffered

shading这种渲染模式),例如辉光啦模糊啦HDR啦,这涉及到把要渲染的内容先渲染到一个后台缓冲区(对于OpenGL来说就是FBO维护的那些存储区域了),再将该缓冲区反馈到屏幕(利用屏幕矩形的纹理贴图),执行后处理(shader啦)——把FBO内的内容反馈回来之后就有锯齿啦!(就算按[全屏反锯齿
- 多重采样Ⅱ
]那样开启了FSAA也无效)——我们就在渲染到FBO那步进行针对FBO的抗锯齿吧!

1. MSAA-FBO的传图(Blit)方式

回顾一下FBO的内容([学一学,FBO]),
关联的FBO的通常是一张或多张的纹理,还有就是render-buffer。如果能够在渲染到纹理的过程中执行多重采样就好啦——可惜,如果你的显卡不支持texture_multisample(这个是OpenGL3.2引入的),那么这个是做不到的。这时候就只能依赖那些render-buffer了。这不坑爹嘛,render-buffer不能当作纹理用,难道还要用glReadPixels把里面的内容读出来再Copy给纹理,再拿去使用么?——呵呵,你猜对了......大致上。

不过,过程可以稍微不那么复杂一点。利用在[OpenGL怎样近似进行同时到FBO和屏幕的渲染]中提及的“传图”方法(glBlitFrameBuffer)即可。关键是你需要对需要多重采样抗锯齿的内容,建立两个FBO:其中一个就是一般的attach了纹理的FBO,一切跟往常一样;另一个就特别点:我们向这个FBO上attach一个对应的color-render-buffer,并采用如下的形式:

    glGenRenderbuffers(1, &renderTarget.nHandle);

  1. glBindFramebuffer(GL_FRAMEBUFFER, m_nHandle);
  2. glBindRenderbuffer(GL_RENDERBUFFER, renderTarget.nHandle);
  3. if(0 == m_nMultiSample)
  4. {
  5. glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, m_nWidth, m_nHeight);
  6. }
  7. else
  8. {
  9. glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_nMultiSample, GL_RGBA8, m_nWidth, m_nHeight);
  10. }
  11. glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderTarget.nHandle);
  12. glBindRenderbuffer(GL_RENDERBUFFER, NULL);
  13. glBindFramebuffer(GL_FRAMEBUFFER, NULL);

其中,m_nMultiSample就是我们要进行多重采样的采样参数(1x,2x,4x,8x,16x等,对应m_nMultiSample=1或m_nMultiSample=2....m_nMultiSample=16等),我们在使用多重采样(MSAA)的时候,给这个颜色渲染缓冲对象分配存储区域,使用glRenderbufferStorageMultisample这个API,代替以往的glRenderbufferStorage。(因为并不是针对屏幕,所以不能称为FSAA,姑且称之MSAA-FBO【multisampled
Frame Buffer
Object】。)还有一点要注意的是,如果要把一个FBO作为MSAA-FBO,则所有关联(attach)到它身上的render-target都必须使用glRenderbufferStorageMultisample定义缓存大小(否则FBO的glCheckFramebufferStatus会返回GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE错误)。一般来说,进行离屏的三维渲染还需要添加一个depth-renderbuffer,否则深度信息会....你知道的。

在渲染场景的时候,渲染到上面这个MSAA-FBO上(提醒一下,渲染到FBO的时候别忘了那些必须要做的事情啊[包括glDrawBuffer/glDrawBuffers...]),再通过blit的方式把color-renderbuffer的内容传送给那个以纹理为attahment的常规FBO(m_SrcScreenFBO)上:

    glBindFramebuffer(GL_READ_FRAMEBUFFER, m_MultiSampleScreenFBO.GetFBOHandle());

  1. glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_SrcScreenFBO.GetFBOHandle());
  2. glBlitFramebuffer(0, 0, m_nRenderWidth, m_nRenderHeight, 0, 0, m_nRenderWidth, m_nRenderHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
  3. glBindFramebuffer(GL_READ_FRAMEBUFFER, NULL);
  4. glBindFramebuffer(GL_DRAW_FRAMEBUFFER, NULL);

因为两个FBO相同大小(定义的时候最好以相同尺寸定义“画布”),所以glBlitFrameBuffer的末参数GL_NEARST即可。现在FBO(m_SrcScreenFBO)就包含了一个经过多重采样后的颜色信息——在该FBO中的纹理里。

这个blit过程,相当于把多重采样像素格式(multisample)的像素缓冲区映射到单一采样像素(singlesample)的像素缓冲区。FBO的反锯齿就是通关过这种方式运作起来的。

另外,进行这样的操作时,你不必太担心MSAA-FBO的color-renderbuffer和常规FBO的texture2D之间像素格式问题。我的意思是说,前者的像素格式是GL_RGBA8,后者的像素格式是GL_RGBA32F,也全然没问题的。glBlitFrameBuffer能够进行数据格式的转换。但是如果是整型数据格式(又可分为有符号/无符号,即类似GL_RGBA8I或GL_RGBA8UI的)就不能转为其他数据格式的像素格式了。

---------------------------我是分割线的说-----------------------------------

2. MultiSample-Texture的纹素抓取(texel-fetch)

上面提及的另一种方法,就是直接使用支持多重采样的渲染纹理(rendable-texture),作为FBO的Attachment。那么,这是不是就是上一方法的简化版呢?我们只使用一个FBO,给这个FBO关联一张纹理,利用FBO把场景渲染进这个纹理,再使用这个纹理(譬如作为全屏矩形的贴图)。——呵呵,你又猜对了......大致上。

关键是怎么使用这种MSAA-TEXTURE的技术。它与以往我们使用纹理的方式是8同的。MultiSample-Texture是这样一种Texture:没有mipmap

level没没有wrap方式有滤波方式(filter,或者说GL_NEAREST吧,反正都是不用设的)——这一点跟那些render-buffer是一样的(甚至它目前也只能像render-buffer一样只供给FBO“使用”),不同的是它作为“纹理”的特性——可进行采样(sample)。不要作什么奇怪的遐想哦,既然人家都是GL3.2的东西了,当然只能通过Shader进行采样了!是的,必须得通过shader,而且还是别样的采样模式。

对于FBO来说,也许无论texture还是renderbuffer也都是“那么一回事”吧。所以这个初始化跟方法1还是很类似的:

    glGenTextures(1, &nTexHandle);

  1. glBindFramebuffer(GL_FRAMEBUFFER, m_nHandle);
  2. glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, nTexHandle);
  3. glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_nMultiSample, GL_RGBA8, m_nWidth, m_nHeight, GL_TRUE);
  4. glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, nTexHandle, 0);
  5. glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, NULL);
  6. glBindFramebuffer(GL_FRAMEBUFFER, NULL);

这个glTexImage2DMultisample函数也跟glRenderbufferStorageMultisample函数差不多样子(最后一个参数“boolean

FixedSampleLocations”真心不明白,难道日后纹素的sample的位置有什么方法可以改变?)。注意的是对于这种纹理,任何时候你都不要误用回GL_TEXTURE_2D喇,是GL_TEXTURE_2D_MULTISAMPLE喇。注意一个FBO里面所有attachment的sample数(m_nMultiSample)一致这个条件还是有的哦。(还有个3D版本那是给三维纹理或者二维纹理数组[学一学,
Texture Array纹理数组
]用的呃...)

好了,渲染到这个FBO,然后这张MSAA-Texture怎么用呢?看这段fragment shader代码参考一下:

    #version 130

  1. #extension GL_EXT_gpu_shader4 : enable
  2. #extension GL_ARB_texture_multisample : enable
  3. uniform sampler2DMS   basetex;
  4. uniform int nMultiSample;
  5. varying vec2 varying_texcoord;
  6. out vec4 FragDataScene;
  7. void main(void)
  8. {
  9. ivec2 texSize = textureSize(basetex);
  10. vec4 fTexCol = vec4(0.0);
  11. if(0 == nMultiSample)
  12. {
  13. FragDataScene = texelFetch(basetex, ivec2(varying_texcoord * texSize), 0);
  14. }
  15. else
  16. {
  17. for(int i = 0; i < nMultiSample; ++i)
  18. {
  19. fTexCol += texelFetch(basetex, ivec2(varying_texcoord * texSize), i);
  20. }
  21. FragDataScene = fTexCol / nMultiSample;
  22. }
  23. }

大概意思意思一下就好。sampler要用sampler2DMS这个(什么sampler2DMSArray啊的自己类推吧),采样函数要用:

vec4 texelFetch(gsampler2DMS  sampler, ivec2  P, int  sample);

其实就是抓取纹理某个位置的值了。第二个参数是位置参数,其实也就是纹理坐标乘以纹理的尺寸大小(textureSize)了。重要的是第三个参数——抓取该纹理对应第n个sample的值。如果多重采样参数是16x,那么这个int值应该是[0~15]——把多重采样的各个sample的值叠加平均一下,嘛,这样算比较粗糙了(汗~)。

相对于第一种方式的两个FBO间的映射,这种方式是手动用shader来采样和计算,效果差不多的,也没设定复杂场景针对效率仔细对比,我只能说各有特点吧。至于哪种好,哪种效率高,哪种空间优,哪种方便哪种有型哪种变态,就见仁见智了哈。

下面是使用MSAA-FBO前后的效果(都是通过屏幕矩形的纹理贴图):

好久没动笔了,多少有点言不达意了呵呵。感谢看官支持。

本文来源于 ZwqXin (http://www.zwqxin.com/),
转载请注明
      原文地址:http://www.zwqxin.com/archives/opengl/multisample-fbo-antialiasing.html

多重采样(MultiSample)下的FBO反锯齿 【转】的更多相关文章

  1. Windows下,通过程序设置全屏抗锯齿(多重采样)的方法

    这里说的全屏抗锯齿,不是基于着色器的FXAA之类的方式,而是兼容性更好的,基于固定管线的多重采样方式. 先来说一下开发环境,我用的是VC2013+GLEW1.11. 要通过程序设置多重采样,首先需要进 ...

  2. Unity3D学习(七):Unity多重采样抗锯齿设置无效的解决办法

    前言 学习Shader的过程中发现模型锯齿严重,于是去Edit--Project Settings--Quality选项下将反锯齿设置为了8X Multi Sampling.结果没有任何改变,如图: ...

  3. OpenGL ES3使用MSAA(多重采样抗锯齿)的方法

    昨晚花费了我2个多小时的时间终于把OpenGL ES3.0中的MSAA给搞定了.在OpenGL ES2.0中,Khronos官方没有引入标准的MSAA全屏抗锯齿的方法,而Apple则采用了自己的GL_ ...

  4. [译]Vulkan教程(33)多重采样

    [译]Vulkan教程(33)多重采样 Multisampling 多重采样 Introduction 入门 Our program can now load multiple levels of d ...

  5. osg如何设置抗锯齿(反走样,反锯齿)

    首先抗锯齿是什么? 举个最简单的例子 你用windows画图软件画一根直线(准确说这个叫做线段),当水平或者垂直的时候,如下图,这是绝对完美的 但是当线段出现倾斜时,就无法做到完美了此时就会出现锯齿 ...

  6. opengl多重采样

    效果图如下,两幅图效果是一样的,只是换了个背景.两幅图均是左侧使用了多重采样,右侧的没有使用多重采样.

  7. select下拉菜单反显不可改动,且submit能够提交数据

    首先通过后台funcA()将下拉菜单反显不可改动的数据response到disable.jsp页面,disable.jsp: <script> var data1=${result.obj ...

  8. apktool 在mac下的使用 -反编译安卓apk文件

    1.下载apktool 点击这里下载 ,里面有两个文件,一个是.jar,一个是自己写的脚本.sh 注:最新的apktool.jar 文件可以点击这里下载 .sh脚本是自写脚本可不用更新最新,下载的ja ...

  9. 转: MyEclipse 10.0,9.0,8.0 下添加jadClipse反编译插件

    MyEclipse 10.0,9.0,8.0 下添加jadClipse反编译插件 (2012-11-19 15:36:35) 转载▼ 标签: myeclipse jad 反编译 插件 it 分类: M ...

随机推荐

  1. return 与 exit() 的区别

    return是一个关键字,返回函数值:exit()是一个函数: return是语言级的:exit()是操作系统提供的函数: return表示函数退出:exit()表示进程退出: 非主函数中调用retu ...

  2. python学习笔记 IO 文件读写

    读写文件是最常见的IO操作.python内置了读写文件的函数. 读写文件前,我们先必须了解一下,在磁盘上读写文件的功能都是由操作系统完成的,现代操作系统不允许普通的程序直接对磁盘进行操作,所以, 读写 ...

  3. 关于hrtimer_forward小段代码的分析【转】

    转自:http://blog.csdn.net/wowuyinglingluan/article/details/45720151 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?) ...

  4. git 提示 Please move or remove them before you can merge 解决办法

    解决Git冲突造成的Please move or remove them before you can merge git clean -d -fx其中x -----删除忽略文件已经对git来说不识别 ...

  5. 【反演复习计划】【bzoj2154】Crash的数字表格

    膜拜cdc……他的推导详细到我这种蒟蒻都能看得懂! 膜拜的传送门 所以我附一下代码就好了. #include<bits/stdc++.h> #define N 10000005 #defi ...

  6. JSP(1) - JSP简介、原理、语法 - 小易Java笔记

    1.JSP简介 (1)JSP的全称是Java Server Pages(运行在服务器端的页面),实际就是Servlet(学习JSP的关键就是时刻联想到Servlet) (2)JSP.Servlet各自 ...

  7. CSS3制作旋转的小风车

    制作旋转小风车 一 我先搭建一个大盒子400x400px大盒子里面嵌套四个小盒子200x200px,放在一起肯定是四个排在一行,我想要的效果是上下各两个, css样式 *{ margin:0; pad ...

  8. 【SQL】约束与触发器2

    3.修改约束 3.1给约束命名 按如下格式命名: name ) CONSTRAINT NameIsKey PRIMARY KEY gender ) CONSTRAINT NoAndro CHECK ( ...

  9. 用LoopBack搭建RESTful 风格的API

    1.安装node.NPM 2.安装strongloop npm install -g --unsafe-perm install strongloop 3.创建工作目录并且配置loopback应用 m ...

  10. summernote文本编辑内容在前端的显示

    1.summernote文本的编辑与文件的上传 在上一篇文章中,我们写了summernote文本编辑器的使用还有图片文件的上传,http://www.cnblogs.com/jingmin/p/659 ...