Framebuffer & Postprocessing
OpenGL provides a special buffer where all the draw calls will end up writing to. Every time We draw something, it gets written on a buffer on the GPU that will be used as the new frame when we swap buffers. This buffer is called the framebuffer. As a developer we don’t have too much control over that buffer.
But OpenGL also provides a way to create our own framebuffer. This allows us to control the post-processing of a scene after all the draw calls have been made. We can add cool effects to the entire framebuffer.
This is how it would work. First we need to setup the framebuffer:
// Create Framebuffer VAO & set attributes.
glGenVertexArrays(1, &Renderer.FrameBufferVAO);
glGenBuffers(1, &Renderer.FrameBufferVBO);
glBindVertexArray(Renderer.FrameBufferVAO);
glBindBuffer(GL_ARRAY_BUFFER, Renderer.FrameBufferVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(ScreenQuadVertices),
&ScreenQuadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
(void *)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
(void *)(2 * sizeof(float)));
// Framebuffer Configuration
glGenFramebuffers(1, &Renderer.FrameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, Renderer.FrameBuffer);
// Create a color attachment texture
glGenTextures(1, &Renderer.TextureColorBuffer);
glBindTexture(GL_TEXTURE_2D, Renderer.TextureColorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, ScreenWidth, ScreenHeight, 0, GL_RGB,
GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
Renderer.TextureColorBuffer, 0);
// Create a Renderbuffer object for depth and stencil attachment
glGenRenderbuffers(1, &Renderer.RBO);
glBindRenderbuffer(GL_RENDERBUFFER, Renderer.RBO);
// Use a single Renderbuffer object for both a depth AND stencil buffer.
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, ScreenWidth,
ScreenHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, Renderer.RBO);
// Check that the framebuffer is complete
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!"
<< std::endl;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
This is quite some set up. But now every thing should be ready for using the framebuffer and apply some nice effects.
// 1st. Pass: Draw the scene to the framebuffer
// bind to framebuffer and draw scene as we normally would to color
// texture
glBindFramebuffer(GL_FRAMEBUFFER, Renderer.FrameBuffer);
glViewport(0, 0, Context.FramebufferWidth, Context.FramebufferHeight);
DrawEntities();
// 2nd. Pass: Draw whatever is in the Framebuffer to the screen quad
// now bind back to default framebuffer and draw a quad plane with the
// attached framebuffer color texture
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, Context.FramebufferWidth, Context.FramebufferHeight);
// disable depth test so screen-space quad
// isn't discarded due to depth test.
glDisable(GL_DEPTH_TEST);
// clear all relevant buffers
// set clear color to white (not really necessary actually,
// since we won't be able to see behind the quad anyways)
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(Renderer.ScreenShaderProgram.ID);
glBindVertexArray(Renderer.FrameBufferVAO);
// use the color attachment texture as
// the texture of the quad plane
glBindTexture(GL_TEXTURE_2D, Renderer.TextureColorBuffer);
glDrawArrays(GL_TRIANGLES, 0, 6);
First we need to bind the custom framebuffer and draw the entire scene. This is normally called the first pass.
For the second pass, we bind the default buffer and use the screen shader program to draw the texture color buffer that should have all the scene drawn. With the screen shader we can then apply some cool effects to the entire texture.
For example, this is how we would make the scene look like it is in Grayscale:
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D u_screen_texture;
void main()
{
vec4 color = texture(u_screen_texture, TexCoords);
// Grayscale
color = texture(u_screen_texture, TexCoords);
float average = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
color = vec4(average, average, average, 1.0);
FragColor = vec4(color.rgb, 1.0);
}