关于支持多重采样的FBO和Texture
闲话不唠,简单粗暴版。
简单提一句MSAA(Multisample Anti Aliasing),依本人愚见,MSAA就是光栅化阶段对一个像素内部进行多次采样(采4次就是4X,采8次就是8X多重采样),然后根据按照一定规则(默认是取平均值)将同一个像素内的多个采样点融合成一个的过程(resolve 过程)。
先讲简单的版本,从OpenGL的使用角度上讲,多重采样可以分为两种,context-based 和 Fbo-based。
Context-based Multisample需要显式(明确地,explicitly)创建一个支持多重采样的上下文(OpenGL Render Context)。使用时只需要调用glEnable(GL_MULTISAMPLE),那么接下来要渲染的物体都会进行多重采样。轻松加愉快! 前提是你使用GLUT、GLFW等库,可以很简单地通过调用一个函数来创建支持多重采样渲染环境的窗口。但是!当你像我一样,要基于win32 或者 mfc构建OpenGL 应用程序时,这个过程就相对复杂一点。此过程在之前的随笔中有讲到,要先创建窗口、建立临时context、初始化glew库,获得相关函数指针、销毁原窗口,创建新context等等操作,在此按下不表。
Fbo-based Multisample不需要显式地去创建一个支持多重采样的上下文。采用这种方法给了我们更多的灵活性。Fbo-based Multisample也可以分为小两种。第一种,渲染到RenderBuffer,然后调用glBlitFramebuffer到default framebuffer。采用第一种方法,多重采样的resolve过程由glBlitFramebuffer完成了,所以我们不用担心这个环节。第二种:渲染到Texture,然后在fragment shader中手动地对 sample 进行 resolve 操作。默认的resolve是取平均值的,现在我们可以在fragment shader中获取各个sample的值,要怎么resolve,决定权在你,这就是所谓的灵活性。
萝莉啰嗦,全程代码版
第一种!基于OpenGL Context的多重采样。实在没啥好讲的。重点完全在于如何建立支持多重采样的Context。好麻烦的,但是我在之前的随笔中有讲到,以Nehe的代码为例(我还是把Nehe的原版代码也贴上吧,免得以后找不着)。注意,在创建支持多重采样的渲染环境时,有一段代码是OpenGL环境设置的,如下:
1 int iAttributes[] =
2 {
3 WGL_DRAW_TO_WINDOW_ARB,GL_TRUE,
4 WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
5 WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
6 WGL_COLOR_BITS_ARB,24,
7 WGL_ALPHA_BITS_ARB,8,
8 WGL_DEPTH_BITS_ARB,24,
9 WGL_STENCIL_BITS_ARB,0,
10 WGL_DOUBLE_BUFFER_ARB,GL_TRUE,
11 WGL_SAMPLE_BUFFERS_ARB,GL_TRUE, //!要将多重采样使用的buffers设置为真。
12 WGL_SAMPLES_ARB,4, //!在此设置采样点的数量。
13 0,0
14 };
15
16 //First We Check To See If we can get a pixel format for 4 Samples
17 valid = wglChoosePixelFormatARB(hDc,iAttributes,fAttributes,1,&pixelFormat,&numFormats);
注意设置采样缓冲区为真,设置采样点数量就ok了,别的没啥好说的。渲染的时候调用glEnable(GL_MULTISAMPLE)就好了。
第二种!这个值得说道下!
先说下FBO渲染到RenderBuffer的多重采样方式吧。 Talk is cheap, show me the code!
1 glGenRenderbuffers(1,&m_multiSampleColor);
2 glBindRenderbuffer(GL_RENDERBUFFER,m_multiSampleColor);
3 glRenderbufferStorageMultisample(GL_RENDERBUFFER,4,GL_RGBA8,m_width,m_height); //与普通Renderbuffer不同的是,在分配空间时,我们调用glRenderBufferStorageMultisample(),第二个参数是采样数。
4
5 glGenFramebuffers(1,&m_multiSampleFBO);
6 glBindFramebuffer(GL_FRAMEBUFFER,m_multiSampleFBO);
7
8 glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_RENDERBUFFER,m_multiSampleColor);
9
10
11 //glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D_MULTISAMPLE,m_texID,0);
12
13 glGenRenderbuffers(1,&m_multiSampleDepth);
14 glBindRenderbuffer(GL_RENDERBUFFER,m_multiSampleDepth);
15 glRenderbufferStorageMultisample(GL_RENDERBUFFER,4,GL_DEPTH_COMPONENT,m_width,m_height); //用作Depth Test的RenderBuffer也一样,要调用glRenderBufferStorageMultisample()
16
17
18 glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_RENDERBUFFER,m_multiSampleDepth);
19
20 GLenum drawBufs[] = {GL_COLOR_ATTACHMENT0};
21 glDrawBuffers(1, drawBufs);
22
23 GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER);
24 if( result == GL_FRAMEBUFFER_COMPLETE) {
25 printf("Framebuffer complete!\n");
26 } else {
27 printf("Framebuffer incomplete!\n");
28 }
注意上面的代码红色标注的部分,与普通Renderbuffer不同的是,用于多重采样渲染的Renderbuffer需要调用glRenderbufferStorageMultisample来为Renderbuffer分配空间,第二个参数为采样点个数。
以上是Framebuffer的初始化部分。以下是渲染的部分:
1 glBindFramebuffer(GL_FRAMEBUFFER,m_multiSampleFBO); //绑定你之前申请的FBO 2 3 // Shader Programs,Setup Matrices, Light, Materials, And Render Whatever You Want To Render Here! //放开了画吧,骚年!
4 5 glBindFramebuffer(GL_READ_FRAMEBUFFER,m_multiSampleFBO); //设定之前的FBO为将要读取的Framebuffer 6 glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0); //设定屏幕(default framebuffer)为将要写入的Framebuffer 7 glBlitFramebuffer(0,0,m_width,m_height,0,0,m_width,m_height,GL_COLOR_BUFFER_BIT,GL_NEAREST); //传送FBO的ColorBuffer到default framebuffer的ColorBuffer去,期间进行了Multisample的Resolve操作。 8 glBlitFramebuffer(0,0,m_width,m_height,0,0,m_width,m_height,GL_DEPTH_BUFFER_BIT,GL_NEAREST); //传送FBO的DepthBuffer到default framebuffer的DepthBuffer去,期间进行了Multisample的Resolve操作。 9 10 glBindFramebuffer(GL_READ_FRAMEBUFFER,0); 11 glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0);
值得注意的是,在绑定了之前申请的FBO,渲染所有需要渲染的物体到其绑定的RenderBuffer之后,需要进行从FBO到default framebuffer的Blit操作,此期间Multisample的Resolve操作由OpenGL为你悄无声息地完成了。
下面来说道下,FBO渲染到Texture的多重采样方式!
首先先来看看FBO初始化部分的代码:
1 GLint iUnits; 2 glGetIntegerv(GL_MAX_TEXTURE_UNITS,&iUnits); 3 printf("Max Texutre Units Supported is %d.h\n",iUnits); 4 5 glActiveTexture(GL_TEXTURE0); 6 glGenTextures(1,&m_texID); 7 glBindTexture(GL_TEXTURE_2D_MULTISAMPLE,m_texID); //注意与普通Texture的不同,这里绑定的Target是 GL_TEXTURE_2D_MULTISAMPLE,意图已经很明显了吧! 8 9 glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE,4,GL_RGBA8,m_width,m_height,GL_TRUE); //分配空间的函数也是调用的Multisample版本,Target依然是GL_TEXTURE_2D_MULTISAMPLE 10 11 glGenRenderbuffers(1,&m_multiSampleDepth); 12 glBindRenderbuffer(GL_RENDERBUFFER,m_multiSampleDepth); 13 glRenderbufferStorageMultisample(GL_RENDERBUFFER,4,GL_DEPTH_COMPONENT,m_width,m_height); //我们依然需要支持多重采样的Depth Buffer,不然Framebuffer完整性检查会通不过。 14 15 glGenFramebuffers(1,&m_multiSampleFBO); 16 glBindFramebuffer(GL_FRAMEBUFFER,m_multiSampleFBO); 17 glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D_MULTISAMPLE,m_texID,0); //注意Target依然是GL_TEXTURE_2D_MULTISAMPLE 18 glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_RENDERBUFFER,m_multiSampleDepth); 19 20 GLenum drawBufs[] = {GL_COLOR_ATTACHMENT0}; 21 glDrawBuffers(1, drawBufs); 22 23 GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); 24 if( result == GL_FRAMEBUFFER_COMPLETE) { 25 printf("Framebuffer complete!\n"); 26 } else { 27 printf("Framebuffer incomplete!\n"); 28 }
在上述代码的注释中已经写得很清楚,我们需要支持Multisample的Texture,相关的绑定Target以及内存空间分配函数都有各自的Multisample版本。
接下来看看渲染部分的代码:(只挑重要的部分)
//Pass 1: glBindFramebuffer(GL_FRAMEBUFFER,m_multiSampleFBO); //Select Shader Programs, Setup Matrices, Lights, Materials. //Render Objects //Pass 2: glBindFramebuffer(GL_FRAMEBUFFER,0); //Select Shader Programs, Setup Shader Uniforms. //Render A Full Screen Quad!
上面的代码貌似啥内容都没有哈,其实简单的说,分为2个render pass。 第一个Pass,将物体渲染到多重采样过的纹理中。第二个pass,将纹理resolve到defaul Framebuffer,即屏幕上。
其实在关键在于第二个pass中的resolve过程。贴上代码。这是pass 2的Fragment shader。
1 #version 400 2 3 in vec2 Coord; 4 5 layout (location = 0) out vec4 FragColor; 6 7 uniform sampler2DMS baseTex; //注意,此处不再是普通的sampler2D,而是sampler2DMS,专职疑难多重采样问题。 8 9 uniform int nMultiSample; //采样个数,此处传入为4 10 11 12 13 void main(void) 14 { 15 ivec2 texSize = textureSize(baseTex); 16 17 vec4 fTexCol = vec4(0.0); 18 19 if( 0 == nMultiSample ) 20 { 21 FragColor = texelFetch(baseTex,ivec2(Coord * texSize),0); 22 } 23 else 24 { 25 for( int i = 0 ; i < nMultiSample ; i++ ) 26 { 27 fTexCol += texelFetch(baseTex, ivec2(Coord * texSize), i); //获取每个采样点的颜色,然后求平均。如果想试其他的resolve方法,It's up to you! 28 } 29 FragColor = fTexCol / nMultiSample; 30 } 31 }
在Fragment shader中,通过一个sampler2DMS 获取纹理中的4个采样点,通过texelFetch内置函数获取采样点颜色,求平局值,作为这个像素的最终值!
终于写完了!呼~