I have created a classic 2D a texture, and rendered into some values, in my case depth. In the c++ code I use the glGenerateMipmap() function to auto generate mipmaps. In fragment shader I can access the texture, like a normal sampler2D uniform, and I would like to write into the given mipmap level, specifically I want to calculate values on r+1 level, according to values on level r, and write in it. Is there some useful tool to achieve that? Using GLSL 4.30
For example:
// That's how I get the value, I need some kind of method to set
float current_value = textureLod(texture, tex_coords, MIP_LVL).y;
Yes, use the function glFramebufferTexture2D and on the last parameter specify the mipmap level.
After you attached everything you need, render the image you want to be at the specific mipmap.
Note: Don't forget to change to glViewport so it will render to the appropriate mipmap size!
Here is a simple example:
unsigned int Texture = 0;
int MipmapLevel = 0;
glGenFramebuffers(1, &FBO);
glGenRenderbuffers(1, &RBO);
glGenTextures(1, &Texture);
glBindTexture(GL_TEXTURE_2D, Texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, TextureWidth, TextureHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
// glTexParameteri here
// Use shader and setup uniforms
glViewport(0, 0, TextureWidth, TextureHeight);
glBindFramebuffer(GL_FRAMEBUFFER, FBO);
glBindRenderbuffer(GL_RENDERBUFFER, RBO);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, TextureWidth, TextureHeight);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Texture, MipmapLevel); // Here, change the last parameter to the mipmap level you want to write to
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Render to texture
// Restore all states (glViewport, Framebuffer, etc..)
EDIT:
If you want to modify a generated mipmap (generated with glGenerateMipmap or externally), you can use the same method, use glFramebufferTexture2D and choose the mipmap you want to modify.
After you have done all the setup stuff (like the above sample), you can either completely render a new mipmap, or you can send the original texture, and modify it in the fragment shader.
I have a 3D graphics application that is exhibiting bad texturing behavior (specifically: a specific texture is showing up as black when it shouldn't be). I have isolated the texture data in the following call:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, fmt->gl_type, data)
I've inspected all of the values in the call and have verified they aren't NULL. Is there a way to use all of this data to save to the (Linux) filesystem a bitmap/png/some viewable format so that I can inspect the texture to verify it isn't black/some sort of garbage? It case it matters I'm using OpenGL ES 2.0 (GLES2).
If you want to read the pixels from a texture image in OpenGL ES, then you have to attach the texture to a framebuffer and read the color plane from the framebuffer by glReadPixels
GLuint textureObj = ...; // the texture object - glGenTextures
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureObj, 0);
int data_size = mWidth * mHeight * 4;
GLubyte* pixels = new GLubyte[mWidth * mHeight * 4];
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
All the used functions in this code snippet are supported by OpenGL ES 2.0.
Note, in desktop OpenGL there is glGetTexImage, which can be use read pixel data from a texture. This function doesn't exist in OpenGL ES.
To write an image to a file (in c++), I recommend to use a library like STB library, which can be found at GitHub - nothings/stb.
To use the STB library library it is sufficient to include the header files (It is not necessary to link anything):
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
Use stbi_write_bmp to write a BMP file:
stbi_write_bmp( "myfile.bmp", width, height, 4, pixels );
Note, it is also possible to write other file formats by stbi_write_png, stbi_write_tga or stbi_write_jpg.
Implementing some effect, I end up with 1 frame buffer associated to 1 texture, which holds my final scene. This texture is then applied on a fullscreen quad.
The result is what I expect as far as the effect goes, but I noticed that edges on the scene thus rendered, weren't smooth - presumably, because multi-sampling did not apply during render-to-framebuffer passes, as it does when I render directly to the screen buffer.
So my question is
How can I apply/use multi-sampling on this final texture, so that its content shows smooth edges?
EDIT: I have removed the original version of my code here, which was using
a classic FrameBuffer + Texture not multi-sampled. Below is the lastest,
following suggestions in the comments.
For now also, I'll focusing on getting the glBlitFramebuffer approach to work!
So my code now goes like so:
// Unlike before, finalTexture is multi-sampled, thus created like this:
glGenFramebuffers(1, &finalFrame);
glGenTextures(1, &finalTexture);
glBindFramebuffer(GL_FRAMEBUFFER, finalFrame);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, finalTexture);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA, w, h, GL_TRUE);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D_MULTISAMPLE,
finalTexture,
0);
// Alternative using a render buffer instead of a texture.
//glGenRenderbuffers(1, &finalColor);
//glBindRenderbuffer(GL_RENDERBUFFER, finalColor);
//glRenderbufferStorageMultisample(GL_RENDERBUFFER, 8, GL_RGBA, w, h);
//glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, finalColor);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Then I introduced a new frame buffer to resolve the multi-sampling:
// This one's not multi-sampled.
glGenFramebuffers(1, &resolveFrame);
glGenTextures(1, &resolveTexture);
glBindFramebuffer(GL_FRAMEBUFFER, resolveFrame);
glBindTexture(GL_TEXTURE_2D, resolveTexture);
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
resolveTexture,
0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Now a lot of code to produce a glowing effect, things like:
// 1. Generate 1 frame buffer with 2 color attachments (textures) - no multisampling
// 2. Render the 3D scene to it:
// - texture 0 receives the entire scene
// - texture 1 receives glowing objects only
// 3. Generate 2 frame buffers with 1 color attachment (texture) each - no multisampling
// - we can call them Texture 2 and texture 3
// 4. Ping-pong Render a fullscreen textured quad on them
// - On the first iteration we use texture 1
// - Then On each following iteration we use one another's texture (3,2,3...)
// - Each time we apply a gaussian blur
// 5. Finally sum texture 0 and texture 3 (holding the last blur result)
// - For this we create a multi-sampled frame buffer:
// - Created as per code here above: finalFrame & **finalTexture**
// - To produce the sum, we draw a full screen texured quad with 2 sampler2D:
// - The fragment shader then computes texture0+texture3 on each pixel
// - finalTexture now holds the scene as I expect it to be
// Then I resolve the multi-sampled texture into a normal one:
glBindFramebuffer(GL_READ_FRAMEBUFFER, finalFrame);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFrame);
glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// And the last stage: render onto the screen:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, resolveTexture);
drawFullScreenQuad( ... );
The resulting output is correct, meaning that I can see the scene with the desired glowing effect... But no apparent multi-sampling! :(
Note: I am starting to wonder, if I am using multi-sampling at the right stage - I will be experimenting on this - but any chance I should use it when rendering the initial 3D scene for the first time, on the initial FBOs? (the ones I refer to in the comments and I didn't want to post here to avoid confusion :s)
I added more detailed comments on what's going on before this last stage with final & resolve frame buffers.
You have: "step 5. Finally sum texture 0 and texture 3 (holding the last blur result) - For this we create a multi-sampled frame buffer". But this way multisampling will only apply to fullscreen quad.
"if I am using multi-sampling at the right stage" so the answer to your question is no, you need to use multisampling on another stage when you render a scene.
I have very similar setup with framebuffers (that one which is used to render the scene is multisampled) two output textures (for color info and for highlights which will later be blurred to achieve glow) and ping-pong framebuffers. I also use glBlitFramebuffer solution (also I use 2 blit calls for each color attachment, each one will go in own texture), have not found any way of making it render directly into framebuffer with attached texture.
If you want some code, this is solution that worked for me (it is in C# though):
// ----------------------------
// Initialization
int BlitFrameBufferHandle = GL.GenFramebuffer();
GL.BindFramebuffer(FramebufferTarget.Framebuffer, BlitFrameBufferHandle);
// need to setup this for 2 color attachments:
GL.DrawBuffers(2, new [] {DrawBuffersEnum.ColorAttachment0, DrawBuffersEnum.ColorAttachment1});
// create texture 0
int ColorTextureHandle0 = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, ColorTextureHandle0);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear); // can use nearest for min and mag filter also
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
// for HRD use PixelInternalFormat.Rgba16f and PixelType.Float. Otherwise PixelInternalFormat.Rgba8 and PixelType.UnsignedByte
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba16f, Width, Height, 0, PixelFormat.Rgba, PixelType.Float, IntPtr.Zero);
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, ColorTextureHandle0, 0);
// create texture 1
int ColorTextureHandle1 = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, ColorTextureHandle1);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba16f, Width, Height, 0, PixelFormat.Rgba, PixelType.Float, IntPtr.Zero);
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment1, TextureTarget.Texture2D, ColorTextureHandle1, 0);
// check FBO error
var error = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
if (error != FramebufferErrorCode.FramebufferComplete) {
throw new Exception($"OpenGL error: Framwbuffer status {error.ToString()}");
}
int FrameBufferHandle = GL.GenFramebuffer();
GL.BindFramebuffer(FramebufferTarget.Framebuffer, FrameBufferHandle);
// need to setup this for 2 color attachments:
GL.DrawBuffers(2, new [] {DrawBuffersEnum.ColorAttachment0, DrawBuffersEnum.ColorAttachment1});
// render buffer 0
int RenderBufferHandle0 = GL.GenRenderbuffer();
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, RenderBufferHandle0);
GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, 8, RenderbufferStorage.Rgba16f, Width, Height);
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, RenderbufferTarget.Renderbuffer, RenderBufferHandle0);
// render buffer 1
int RenderBufferHandle1 = GL.GenRenderbuffer();
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, RenderBufferHandle1);
GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, 8, RenderbufferStorage.Rgba16f, Width, Height);
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment1, RenderbufferTarget.Renderbuffer, RenderBufferHandle1);
// depth render buffer
int DepthBufferHandle = GL.GenRenderbuffer();
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, DepthBufferHandle);
GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, 8, RenderbufferStorage.DepthComponent24, Width, Height);
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment, RenderbufferTarget.Renderbuffer, DepthBufferHandle);
// check FBO error
var error = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
if (error != FramebufferErrorCode.FramebufferComplete) {
throw new Exception($"OpenGL error: Framwbuffer status {error.ToString()}");
}
// unbind FBO
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
// ----------------------------
// Later for each frame
GL.BindFramebuffer(FramebufferTarget.Framebuffer, FrameBufferHandle);
// render scene ...
// blit data from FrameBufferHandle to BlitFrameBufferHandle
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, FrameBufferHandle);
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, BlitFrameBufferHandle);
// blit color attachment0
GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
GL.BlitFramebuffer(
0, 0, Width, Height,
0, 0, Width, Height,
ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest
);
// blit color attachment1
GL.ReadBuffer(ReadBufferMode.ColorAttachment1);
GL.DrawBuffer(DrawBufferMode.ColorAttachment1);
GL.BlitFramebuffer(
0, 0, Width, Height,
0, 0, Width, Height,
ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest
);
// after that use textures ColorTextureHandle0 and ColorTextureHandle1 to render post effects using ping-pong framebuffers ...
Just implemented a bloom effect myself, faced the same aliased edges on the resulting image and faced the exactly same issues. Hence sharing my experience here.
Aliasing happens when you render the lines with OpenGL - e.g. edges of a triangle or a polygon, since OpenGL draws "diagonal" (or simply put non-straight) lines on the screen using quite simple (yet fast) algorithms.
That being said, if you want to anti-alias something - that would be a 3D shape, not a texture - it is just a plain image after all.
Off-topic: in order to fix aliasing on an image you would apply the similar technique, but you would need to figure out where the "edges" are on the image and then follow the same algorithm per "edge" pixel. "Edge" (in quotes) since they are just ordinary pixels from the image perspective and being an edge is just extra context we humans attach to those pixels.
With that out of our way, the thing with two image attachments is actually a nice optimization - you do not need to render your entire scene twice to different framebuffers. But you will pay the price of copying the data from each multi-sampled framebuffer attachment to a separate non-multisampled texture for post-processing.
A bit off-topic: performance-wise, I think this is exactly the same (or within a very small threshold) - rendering an entire scene twice, to two separate framebuffers with two separate multi-sampled attachments (as inputs for the post-processing) and then copying each of them separately to two separate non-multisampled textures.
So the last step before you can apply your (any) post-processing to the multi-sampled scene is to convert each multi-sampled render result to non-multisampled texture - so that your shaders work with plain sampler2D.
It would be something similar to this:
glBindFramebuffer(GL_READ_FRAMEBUFFER, bloomFBOWith2MultisampledAttachments);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, temporaryFBOWith1NonMultisampledAttachment);
// THIS IS IMPORTANT
glReadBuffer(GL_COLOR_ATTACHMENT0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glBlitFramebuffer(0, 0, windowWidth, windowHeight, 0, 0, windowWidth, windowHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
// bloomFBOWith2MultisampledAttachments is still bound
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, blurFramebuffer1);
// THIS IS IMPORTANT
glReadBuffer(GL_COLOR_ATTACHMENT1);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glBlitFramebuffer(0, 0, windowWidth, windowHeight, 0, 0, windowWidth, windowHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
Given you are rendering your scene to two attachments in one framebuffer, you will then need to copy from each of those multi-sampled attachments to non-multi-sampled textures and use them for additive rendering and blurring, correspondingly.
If you don't mind messy code and the use of globjects for OpenGL APIs abstraction, here's my entire bloom solution with anti-aliasing.
And few screenshots:
The first screenshot does not use a framebuffer to render to, so the lines are really smooth.
The second screenshot is the first implementation of a bloom effect (available as a separate CMake project).
Aliasing is more visible on longer distances, so the third screenshots shows a bit more of a scene - the edges look really stairs-like.
The last two screenshots show the bloom effect with anti-aliasing applied.
Note how lantern only has somewhat low-resolution texture, hence aliased lines, whilst the paper has its edges smoothed out by anti-aliasing.
I'm looking how to convert a GL_RGBA framebuffer texture to GL_COMPRESSED_RGBA texture, preferably on the GPU. Framebuffers apparently canĀ“t have the GL_COMPRESSED_RGBA internal format, thus I need a way to convert.
See this document that describes OpenGL Texture Compression. The sequence of steps is like (this is hacky - Buffer objects for the textures throughout would improve things somewhat)
GLUint mytex, myrbo, myfbo;
glGenTextures(1, &mytex);
glBindTexture(GL_TEXTURE_2D, mytex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA, width, height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, 0 );
glGenRenderbuffers(1, &myrbo);
glBindRenderbuffer(GL_RENDERBUFFER, myrbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, width, height)
glGenFramebuffers(1, &myfbo);
glBindFramebuffer(GL_FRAMEBUFFER, myfbo);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, myrbo);
// If you need a Z Buffer:
// create a 2nd renderbuffer for the framebuffer GL_DEPTH_ATTACHMENT
// render (i.e. create the data for the texture)
// Now get the data out of the framebuffer by requesting a compressed read
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA,
0, 0, width, height, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glDeleteRenderbuffers(1, &myrbo);
glDeleteFramebuffers(1, &myfbo);
// Validate it's compressed / read back compressed data
GLInt format = 0, compressed_size = 0;
glGetTexLevelParameteri(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format);
glGetTexLevelParameteri(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE,
char *data = malloc(compressed_size);
glGetCompressedTexImage(GL_TEXTURE_2D, 0, data);
glBindTexture(GL_TEXTURE_2D, 0);
glDeleteTexture(1, &mytex);
// data now contains the compressed thing
If you'd use a PBO object for the texture, you'd be able to get away without the malloc().
If you would like to perform the compression on the GPU without transfer to the CPU - here's two samples you might be able to repurpose for OpenGL (they're DX based)
GPU accelerated texture compression
GPU accelerated texture compression 2
Hope this helps!
I have an FBO object with a color and depth attachment which I render to and then read from using glReadPixels() and I'm trying to add to it multisampling support.
Instead of glRenderbufferStorage() I'm calling glRenderbufferStorageMultisampleEXT() for both the color attachment and the depth attachment. The frame buffer object seem to have been created successfully and is reported as complete.
After rendering I'm trying to read from it with glReadPixels(). When the number of samples is 0 i.e. multisampling disables it works perfectly and I get the image I want. when I set the number of samples to something else, say 4, the frame buffer is still constructed OK but glReadPixels() fails with an INVALID_OPERATION
Anyone have an idea what could be wrong here?
EDIT: The code of glReadPixels:
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, ptr);
where ptr points to an array of width*height uints.
I don't think you can read from a multisampled FBO with glReadPixels(). You need to blit from the multisampled FBO to a normal FBO, bind the normal FBO, and then read the pixels from the normal FBO.
Something like this:
// Bind the multisampled FBO for reading
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, my_multisample_fbo);
// Bind the normal FBO for drawing
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, my_fbo);
// Blit the multisampled FBO to the normal FBO
glBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
//Bind the normal FBO for reading
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, my_fbo);
// Read the pixels!
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
You can't read the multisample buffer directly with glReadPixels since it would raise an GL_INVALID_OPERATION error. You need to blit to another surface so that the GPU can do a downsample. You could blit to the backbuffer, but there is the problem of the "pixel owner ship test". It is best to make another FBO. Let's assume you made another FBO and now you want blit. This requires GL_EXT_framebuffer_blit. Typically, when your driver supports GL_EXT_framebuffer_multisample, it also supports GL_EXT_framebuffer_blit, for example the nVidia Geforce 8 series.
//Bind the MS FBO
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, multisample_fboID);
//Bind the standard FBO
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, fboID);
//Let's say I want to copy the entire surface
//Let's say I only want to copy the color buffer only
//Let's say I don't need the GPU to do filtering since both surfaces have the same dimension
glBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
//--------------------
//Bind the standard FBO for reading
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboID);
glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
Source: GL EXT framebuffer multisample