閑話不嘮,簡單粗暴版。
簡單提一句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內置函數獲取采樣點顏色,求平局值,作為這個像素的最終值!
終於寫完了!呼~