Multiple FrameBuffers and MRT

In my last article I wrote about the Render To Texture (RTT) technique used in many games for advanced rendering. Now we can talk about Multiple FrameBuffers Objects and MRT (Multiple Render Targets) to create even more complex scenes using RTT.

Portal 2

Portal 2


Multiple FrameBuffers Objects

Let’s start with an example where we have a complex scene to render. Imagine that you have a game with portals and each portal renders a different scene; there is some movement in those scenes, like robots or birds (dynamic movement) and you have to choose in which portal you should jump into, based on how dangerous is that world. Also the size of these portals are different. Now, you can render those portal scenes into different textures or renderbuffers and composite them into the final scene where we finally render everything. We can achieve this by using multiple framebuffers (FBOs). I created a simple example (without portals of course) where I have 2 framebuffers. To create them I used the same class that we generate a FBO  from my last article . Also for rendering we need 3 shaders: one (or more) to render the scene, one to render the depth and one to render with blur:

  • In the first pass we render the scene into first FBO and we get the color texture and the depth texture.
  • In the second pass we will use the second FBO and render the depth buffer texture from the first pass with the second shader.
  • Finally we take the depth texture from second pass and we apply some blur to it in pass 3.

It’s not the best example in the world but it should give you a good idea on how to use more framebuffers. Also this example could be done by using the same shader for depth and blur (pass 2 and pass3) and just send a control variable as an uniform to decide what you want. For both textures we have the  same  width and height. Tips: Don’t render depth texture last, because in pass 2 we render a quad in NDC and you end up with a black image (0.0 is close and 1.0 is far).

Multiple framebuffers

Multiple framebuffers

multiple FBO

Multiple framebuffers

//drawing loop
 fbo.bind();//PASS1 draw scene
	//Draw rim lighting scene

fbo2.bind();//PASS 2 -> draw depth; rtt_pass2_shader is a render to texture shader

	glActiveTexture(GL_TEXTURE0 + 1);
	glBindTexture(GL_TEXTURE_2D, fbo.getColorTexture());//texture from fbo
	glUniform1i(glGetUniformLocation(rtt_pass2_shader, "texture_color"), 1);
	glActiveTexture(GL_TEXTURE0 + 2);
	glBindTexture(GL_TEXTURE_2D, fbo.getDepthTexture());//texture from fbo
	glUniform1i(glGetUniformLocation(rtt_pass2_shader, "texture_depth"), 2);
	glBindVertexArray(rtt_vao);//same VAO, for different scene should be another
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

{//PASS 3 -> draw blur

	glActiveTexture(GL_TEXTURE0 + 1);
	glBindTexture(GL_TEXTURE_2D, fbo2.getColorTexture());//textures from fbo2
	glUniform1i(glGetUniformLocation(rtt_program_shader, "textura_color"), 1);
	glActiveTexture(GL_TEXTURE0 + 2);
	glBindTexture(GL_TEXTURE_2D, fbo2.getDepthTexture());
	glUniform1i(glGetUniformLocation(rtt_program_shader, "textura_depth"), 2);
	glUniform1i(glGetUniformLocation(rtt_program_shader, "screen_width"), screen_width);
	glUniform1i(glGetUniformLocation(rtt_program_shader, "screen_height"), screen_height);
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);


Multiple Render Targets

One super important feature of a framebuffer is the ability to render to multiple buffers simultaneously which gives us a lot of power. With this power you can render your scene in a bunch of textures with different storage formats. You can use all these textures in another pass to create multiple post-processing effects or deferred lighting. Also you can create multi-pass rendering with just a single framebuffer by drawing scenes in different textures or renderbuffers using OpenGL’s function glDrawBuffers to tell in which attachment it should draw.

Now let’s see how MRT actually works. To access multiple buffers we first have to get the output from our main scene fragment shader. Maybe we want to see how world position looks like and we want to render it in a texture. To do this we output world position to a new layout location.

Multiple Render Target

Multiple Render Target

As you can see from the picture above, we also need to change the GenerateFBO method ( from Part I). What we have to do is to create another texture to store the world positions in order to render it in Pass 2

//generate color texture, format RGB32F 
glGenTextures(1, &texture_position);
glBindTexture(GL_TEXTURE_2D, texture_position);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, width, height, 0, GL_RGB, GL_FLOAT, 0);

//attach texture
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, texture_position, 0);

//add attachements
glDrawBuffers(drawbuffer.size(), &drawbuffer[0]);

Finally, we also have to modify our Render To Texture fragment shader adding just the position texture sampler 2D as an uniform.

 vec3 world_position(){
      return texture(texture_position, texcoord).xyz;
World Position

World Position

Multiple Render Target

Multiple Render Target

In conclusion, use MRT when you need to get more buffers from your scene and use multiple FBOs when you have a bunch of additional scenes (eg. mirrors or portals) that are rendered into main scene. It’s usually a good practice to keep your code clean in this complex scenario and use one FBO for one additional scene. In this way you can create and use different textures sizes and formats.

My code is structured from one of my projects and it should be used only for learning purpose.

Source code:

render_To_texture_MRT (Visual Studio 2013)

render_To_texture_multipleFBO ( Visual Studio 2013)

For further reading I recommend OpenGL SuperBible 4th and 5th editions

Tagged under:
blog comments powered by Disqus