Here is a short tutorial I wrote to help others that could be having the same problem. I would like to thank everyone in the forum that helped me to track this particular problem.
How to get multiple outputs from a shader
This tutorial teaches you how to get multiple data out from a single fragment shader using renderBuffers and a custom FBO. Take into account that this might not be the best way to solve your particular problem, there are other ways of getting the data:
- Use multiple shaders each with a single output.
- Use the default FBO and the GL_AUXi buffers to get the data.
- Use textures attached to the FBO to get the data.
PART0 - glGetError is your friend!
A lot of OpenGL functions can silently throw errors at runtime and you will never know except when you notice that something is not being drawn. I recommend at least putting a call to glGetError in the begging of your render loop in order to get errors from the previous frame.
Since more than one error might have occured you need to call the function on a loop:
GLenum error;
while( (error=glGetError())!= GL_NO_ERROR )
LOG_ERROR(error);
Check the documentation of the function for more information.
PART1 - The default FBO
Each framebuffer is a collection of images. Each image is attached to an attachement point on the FBO.
The default FBO is the one created togheter with your openGL context. It is used to represent the following images:
1 - The pixels currently on the screen (Attached to GL_FRONT)
2 - The pixels on the back buffer that will be shown when you swap the buffers (Attached to GL_BACK)
3 - A bunch of images related to stereographic rendering (GL_FRONT_LEFT, GL_FRONT_RIGHT, GL_BACK_LEFT, GL_BACK_RIGHT)
4 - Auxiliary buffers (GL_AUXi …)
All those images are created when you create the openGL context and are automatically resized to the current screen widhtxheight.
PART2 - Differences between the default FBO and a custom one
A custom FBO is created by the glGenFramebuffers and destroyed by the glDeleteFramebuffers in the same way as other OpenGL objects.
GLuint myFBO;
glGenFramebuffers(1,&myFBO); //Create the FBO
//Code using myFBO goes here
glDeleteFramebuffers(1,myFBO); //Delete the FBO
All the attachement points on a custom FBO have names that are different than the default FBO:
1 - Color attachment points (GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 …)
Differently than the default FBO, custom FBO’s do not have any of its images created. Those images could be created by a variaty of methods. For this tutorial we will use RenderBufferObjects.
Another important difference is that custom FBO’s are only memory regions and, as so, are not show on the screen. To show it on the screen you need to do one of the following:
1 - Render onde of the FBO attached images to a quad or other geometry (only possible if you attached a texture) and display the quad using the default FBO.
2 - Blit the content of the image to the default FBO using glBlitFramebuffer
Since the custom FBO’s are not managed they do not have their sizes updated with the screen so, if you need to have your FBO matching the screen resolution you need to do this manually by recreating the attached buffers, reseting their storage and finally rebinding them to the FBO.
PART3 - The Render Buffer Object
The render buffer object represent a region on the memory of the video card where you can render pixels. In order to create a render buffer you need to do:
GLuint myRBO;
glGenRenderbuffers(1,&myRBO);
//Use the render buffer
glDeleteRenderbuffers(1,myRBO);
The generated render buffers will not have any memory allocated to them. In order to get memory for them you need to set the storage for the RBO with a call like:
glBindRenderbuffer(GL_RENDERBUFFER,myRBO);
glRenderbufferStorage(GL_RENDERBUFFER,internalformat,width,height);
glBindRenderbuffer(GL_RENDERBUFFER,0);
The first argument should always be GL_RENDERBUFFER. Internalformat is the internal image format used to store the data. As an example you could use GL_RGBA32F (32 bits floating point format) or GL_RGBA32UI (32 bits unsigned int format). Look at the wiki for the image formats for more information. Width and Height are the size of the render buffer.
Now you need to attach the render buffer to the FBO.
//First we bind the FBO
glBindFramebuffer(GL_FRAMEBUFFER,myFBO);
//Now we bind the render buffer to COLOR_ATTACHMENT0.
//You can repeat this to bind other buffers to other color attachments points you need.
glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_RENDERBUFFER,myRBO);
glBindFramebuffer(GL_FRAMEBUFFER,0);
It is not recomended to reset the buffer storage more than once for each buffer. It is better to delete the buffer, create a new one and reattach it to the FBOs.
PART4 - The shader
To use multiple outputs on your shader you only need to declare them :
/**
Sample Frag shader
This frag shader receives a color value from the vertex/geom shader and generates two outputs per
fragment:
colorOutput
- The same color passed by the vtx shader.
invertedRGOutput
- The color passed by the vtx shader with r and g components swapped.
**/
smooth in vec4 color;
out vec4 colorOutput;
out vec4 invertedRGOutput;
void main() {
colorOutput = color;
invertedRGOutput = vec4(color.g,color.r,color.b,color.a);
}
It is important to note that the OpenGL driver is free to allocate the location of colorOutput and invertedRGOutput as it see fit. I recomend to use the “layout” keyword to bind them to especific locations. If you do not want to do it you could:
1 - Use glBindFragDataLocation before linking the program to force a binding.
2 - Use glGetFragDataLocation to figure out where the driver linked your output. (not recommended)
[code:using the layout keyword]
layout(location = 0) out vec4 colorOutput;
layout(location = 1) out vec4 invertedRGOutput;
PART5 - Binding shader outputs to the FBO's attachment points
After reading this part you will understand why I do not recommend using glGetFragDataLocation.
To bind the shader output to the FBO you will need a set of calls:
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,myFBO); //Binds your FBO for drawing
/**
Shader location 0 will be rendered to GL_COLOR_ATTACHMENT0.
Shader location 1 will be rendered to GL_COLOR_ATTACHMENT1.
**/
GLenum buffers_to_render[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}
glDrawBuffers(2,buffers_to_render);
// bind the program and draw here
// it is safe to unbind the program here too (at least for the FBOs
).
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0); //Returns the default FBO to the DRAW location
The glBindFramebuffer sets myFBO as the target of every draw operation you want to do. The glDrawBuffers is the call that links the shader locations to the FBO's attachment points. Each position on the "buffers_to_render" vector correspond to a location in the shader. So by calling glDrawBuffers you are saying "shader_location[i] binds to buffers_to_render[i]". So, if your driver decides that a good location for your output is 50 you need to create a 50 elements buffers. If you do not want one of the outputs of the shader simply pass GL_NONE to glDrawBuffers.
/**
Shader location 1 will be rendered to GL_COLOR_ATTACHMENT0. Shader location 0 will be discarted.
**/
GLenum buffers_to_render[] = {GL_NONE, GL_COLOR_ATTACHMENT0}
glDrawBuffers(2,buffers_to_render);
PART6 - Blitting the FBO to the screen / Acessing the data
==========================================================
Ok, so you rendered everything to the memory. Now you need to display it somehow or use your data.
To blit the data to the output you need to set the default FBO as the DRAW_FRAMEBUFFER and your FBO as the READ_FRAMEBUFFER:
glBindFramebuffer(GL_READ_FRAMEBUFFER,myFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0);
//Set what color attachment we are reading
glReadBuffer(GL_COLOR_ATTACHMENT0);
//Set to what attachment we will write
glDrawBuffer(GL_BACK);
//This function does the magic of copying your FBO to the default one.
//You could use it to copy only a region of the FBO to another region of the screen
//The parameter GL_LINEAR is the function to use in case the FBOs have different sizes.
glBlitFramebuffer(0, 0, myFBOwidth, myFBOheight,
0, 0, ScreenWidth, ScreenHeight,
GL_COLOR_BUFFER_BIT, GL_LINEAR);
//Resets the Draw Buffer to the default one
glDrawBuffer(GL_BACK);
//Unbinds our framebuffer
glBindFramebuffer(GL_READ_FRAMEBUFFER,0);
To access the data without displaying it on the screen you could use glReadPixels:
glBindFramebuffer(GL_READ_FRAMEBUFFER,myFBO); //binds the fbo
glReadBuffer(GL_COLOR_ATTACHMENT0);
glReadPixels(x,y,1,1,GL_RGBA,GL_UNSIGNED_BYTE,&data); //Call for float data (only use if the render buffer attached to 0 is float)
glReadPixels(x,y,1,1,GL_RGBA_INTEGER,GL_UNSIGNED_INT,&udata); //Call for int data (only use if the render buffer attached to 0 in an integer)
glBindFramebuffer(GL_READ_FRAMEBUFFER,0); //unbinds the fbo
PART7 - A note about image formats
==================================
If you want to use float data types on your shader, your renderbuffers and pixel reading operation need also to operate with float datatypes. The same goes for integer ones. ( I am not sure if this an OpenGL requirement but I can garantee that if you do not do this it will normaly result in something that is not what you want).