-1

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.

Smoove
  • 163
  • 9
  • Using a multisample texture/renderbuffer as the FBO attachment, and then copying it to the default framebuffer with `glBlitFramebuffer()`, sounds reasonable. To find out why that didn't work for you, we would have to see the code you tried. – Reto Koradi Oct 01 '16 at 02:28
  • Thank you Reto, I will add the code i used for glBlitFramebuffer soon. In the meantime you might have pointed out a misunderstanding of mine: I have been trying to use my non-multisampled texture and 'Blit' into a multi-sample one - then display my fullscreen quad textured with the latter: I never actually did Blit directly onto the default frame buffer, with a source frame buffer that was multi-sampled. – Smoove Oct 01 '16 at 10:07
  • @RetoKoradi Done - I hope this helps. Also, just to be clear: am I right in saying that the use of sampler2DMS and texelFetch in the shader is a different approach entirely i.e. it would be another way than glBlitFramebuffer to solve my problem? – Smoove Oct 01 '16 at 10:53
  • That'll work, but it's generally pretty slow. Not that a framebuffer blit is fast by any means, it's just slower than fetching n-many samples explicitly in a shader and then averaging them to do the resolve yourself. For fbo->fbo copies with identical image storage it's usually faster to use shaders and stretch a textured quad than blitting, but this isn't one of those situations ;) – Andon M. Coleman Oct 03 '16 at 02:00
  • @AndonM.Coleman I see, thank you for your answer :) Going back to my code above, any idea why I'm not seeing any result? Continuing to read on the subject, I found out that for the multi-sampled fbo, I would actually not need to use a multi-sampled texture, but could use a M.S. render buffer instead (because I'm not using the M.S. texture directly). Unfortunately even that is not working... :( I'll update my post with the M.S. render buffer code as well. – Smoove Oct 03 '16 at 18:39

2 Answers2

0

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 ...
Zos42
  • 1
  • 1
0

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:

no framebuffers and no post-processing

before anti-aliasing

aliasing on longer distances

anti-aliasing

anti-aliasing on longer distances

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.

shybovycha
  • 11,556
  • 6
  • 52
  • 82