In order to optain the information on the CPU, you indeed have to render on textures using a Frame Buffer Object (FBO). You can attach several Texture Images to a single FBO, by using several GL_COLOR_ATTACHMENTi
, and draw in all at once in your fragment shader. This is called Multiple Render Target (MRT). A typical use seems to be deferred shading, according to datenwolf and Wikipedia (I never did that personally, but I did MRT, for GPU picking).
Vertex shader:
First, you need to transfer the information from the vertex shader to the fragment shader.
#version 330
uniform mat4 pvmMatrix;
in vec3 position;
in vec3 normal;
out vec3 fragPosition;
out vec3 fragNormal;
void main()
{
fragPosition = position;
fragNormal = normal;
gl_Position = pvmMatrix * vec4(position, 1.0);
}
Fragment shader
Then, you can render this information in the fragment shader by specifying different output.
#version 330
in vec3 fragPosition;
in vec3 fragNormal;
layout(location=0)out vec3 mrtPosition;
layout(location=1)out vec3 mrtNormal;
void main()
{
mrtPosition = fragPosition;
mrtNormal = fragNormal;
}
layout(location=0)
specify that the render target will be GL_COLOR_ATTACHMENT0
(location=1
is for GL_COLOR_ATTACHMENT1
, and so on). The name you give to the variable doesn't matter.
Creating the FBO
In your C++ code, you have to setup a FBO with the multiple render targets. For brevity, I will not give what is common to any FBO, use the first link for this. What is important here is:
Generate several textures:
// The texture for the position
GLuint texPosition;
glGenTextures(1, &texPosition);
glBindTexture(GL_TEXTURE_2D, texPosition);
// [...] do the glTexParameterf(...) that suits your needs
glTexImage2D(GL_TEXTURE_2D, 0,
GL_RGB32F, // this should match your fragment shader output.
// I used vec3, hence a 3-componont 32bits float
// You can use something else for different information
viewportWidth, viewportHeight, 0,
GL_RGB, GL_FLOAT, // Again, this should match
0);
// the texture for the normal
GLuint texNormal;
glGenTextures(1, &texNormal);
// [...] same as above
Note how you can used different datatype for the informationyou want to store on your texture. Here, I used vec3
in the fragment shader, hence I have to use GL_RGB32F
for the texture creation. Check here for an exhaustive list of types you can use (For instance, I use unsigned int to perform GPU picking).
Attach these textures to different color attachments of your FBO:
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, texPosition, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1,
GL_TEXTURE_2D, texNormal, 0);
Of course, you may want to add a depth attachment, and all other "classical" things you would do with a FBO.
Drawing
Next step, you have to draw your scene using the FBO.
// set rendering destination to FBO
glBindFramebuffer(GL_FRAMEBUFFER, myFBO);
// Set which GL_COLOR_ATTACHMENTi are the targets of the fragment shader
GLenum buffers_to_render[] = {GL_COLOR_ATTACHMENT0,GL_COLOR_ATTACHMENT1};
glDrawBuffers(2,buffers_to_render); // `2` because there are two buffers
// Draw your scene
drawScene();
At this point, all your information you are looking for is stored in the appropriate format (three-component 32bits floats, no need to convert to byte values as in your question) in the form of textures, on the GPU.
Retrieving the data on CPU
Finally, you can get back the data in texture from the GPU to the CPU by using the glRead...
methods.
float * positions = new float[3*width*height];
float * normals = new float[3*width*height];
glBindFramebuffer(GL_FRAMEBUFFER, 0); // unbind the FBO for writing (and reading)
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_); // bind the FBO for reading
glReadBuffer(GL_COLOR_ATTACHMENT0); // set which attachment to read from
glReadPixels(0, 0, width, height, // read the pixels
GL_RGB, GL_FLOAT, // use the right format here also
positions);
glReadBuffer(GL_COLOR_ATTACHMENT1); // repeat for the normals
glReadPixels(0, 0, width, height,
GL_RGB, GL_FLOAT,
normals);
And you should be good to go :-)