Monday, June 6, 2011

Multitexture FBO in OS X


It's been a while since I put my code in here, thanks to the internet bandwidth around here *sigh*. So here we are, geeking around with graphic source code, and it got nothing to do with Jake Gyllenhaal! *dry joke*. I'm using the oldest Macbook Pro: Core Duo @ 2.0GHz (yup, the 32 bit one), 2 GB Memory, ATI X1600, running OS X 10.6.


Now before you go through the FBO settings, first check if your computer capable of doing so. I use the code from FBOBunnies example program, that can be downloaded from Apple's site.



// checking the FBO support

int supported = 1;
sscanf((char *)glGetString(GL_VERSION), "%f", &glCoreVersion);
printf("%s %s\n", (char *)glGetString(GL_RENDERER), (char*)glGetString(GL_VERSION));

printf("----------------------------------\n");
int j = sizeof(extension)/sizeof(glExtension);
for (int i = 0; i < j; i++) {
extension[i].supported = glutExtensionSupported(extension[i].name) |
(extension[i].promoted && (glCoreVersion >= extension[i].promoted));
printf("%-32s %d\n", extension[i].name, extension[i].supported);
supported &= extension[i].supported;
}
printf("----------------------------------\n");
if (!supported) {
printf("Required functionality not available on this renderer.\n");
// A robust app could fall back to other methods here, like glCopyTexImage.
// This is just a demo, so quit.
exit(0);
}
// end of checking FBO support


If your machine is compatible, it should print '1' at the end of the line. Now here is the result of my image:

The top left is still empty (its an on progress project ;) ), top right is diffuse-albedo with shadows (shadow mapping), bottom left is depth values, and bottom right is normal. A better .obj is on the way :) I render this with single FBO with 3 texture: diffuse, depth, and normal. Both diffuse and normal is saved as a normal texture with RGBA value, and the depth texture is a 32 bit renderbuffer treated as a texture.


So here is how I did the FBO settings. First I created four GLuints as buffer id, and generate the ID using this syntax:


glGenFramebuffersEXT(1, &fb1);
glGenRenderbuffersEXT(1, &rb1);
glGenTextures(1, &tx1);
glGenTextures(1, &tx2);


Then I set the properties for the two texture as usual:



// first texture for the diffuse albedo, type 8 bit RGBA
glBindTexture(GL_TEXTURE_2D, tx1);
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);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
// second texture for the normal-rgba, type 8 bit RGB
glBindTexture(GL_TEXTURE_2D, tx2);
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);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);

and the renderbuffer-later-texture like this:



// render buffer, kept as texture to keep the depth information,
// later attached as texture
glGenTextures(1, &rb1);
glBindTexture(GL_TEXTURE_2D, rb1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, rb1, 0);

Now we have to attach those 'textures' to the FBO, notice that the first texture attached as GL_COLOR_ATTACHMENT0_EXT and the second one is GL_COLOR_ATTACHMENT1_EXT. This is important since FBO need to differ the first and the second texture attached to it.


glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb1);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D,tx1,0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT1_EXT,
GL_TEXTURE_2D,tx2,0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_DEPTH_ATTACHMENT_EXT,
GL_TEXTURE_2D,rb1,0);
status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
cout << "Error in setting up FBO" << endl;

I'll fill the first texture with diffuse and the second one with normal. Since it contains different RGB value, we have to render it the scene twice: the first for diffuse and depth, and the second for normal. Before we render the texture we have to bind the FBO and define which texture to render.


// draw real scene inside fbo
glEnable(GL_DEPTH_TEST);
glDisable(GL_TEXTURE_2D);
glClearColor(0.2, 0.2, 0.2, 1.0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb1);
// first we draw the diffuse albedo (tx1) and depth (rb1)
glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
drawTriangles();
glPopMatrix();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tx1);
glGenerateMipmapEXT(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, rb1);
glGenerateMipmapEXT(GL_TEXTURE_2D);

To render the second texture (normal), we have to override the rendering pipeline using shaders. Here are the vertex and the fragment shader used to render the normal texture:


//vert
varying vec3 normal;
void main()
{
normal = normalize(gl_NormalMatrix * gl_Normal);;
gl_Position = ftransform();
}

//frag
varying vec3 normal;
void main() {
gl_FragColor = vec4(normal.x, normal.y, normal.z, 1.0);
}

By using those shader, we render the scene once more, but define that this scene is rendered into the second texture:

// then we draw the normal-rgb
glDrawBuffer(GL_COLOR_ATTACHMENT1_EXT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0.9, 0.9, 0.9, 1.0);
glPushMatrix();
glUseProgram(shaderId[1]);
drawTriangles();
glUseProgram(0);
glPopMatrix();
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, tx2);
glGenerateMipmapEXT(GL_TEXTURE_2D);

Now we are ready to render the scene. Since we have textures, we need to generate a polygon to be attached with those textures. We use a simple polygon from -1 to 1 (top bottom left right) and modify the glLookAt so we can properly view the whole polygon. Since we use shaders, we need to define the value of each texture ID, in this case is 1, 2, and 3 for diffuse, depth, and normal respectively.


// draw the scene on a polygon
glPushMatrix();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
gluLookAt(0.0, 0.0, 2.4,
0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f);
glUseProgram(shaderId[0]);
glUniform1i(glGetUniformLocation(shaderId[0], "texture0"), 1);
glBegin(GL_QUADS);
{
glTexCoord2f(0, 0); glVertex3f(-1.0, -1.0, 0.0);
glTexCoord2f(1.0, 0); glVertex3f(0.0, -1.0, 0.0);
glTexCoord2f(1.0, 1.0); glVertex3f(0.0, 0.0, 0.0);
glTexCoord2f(0, 1.0); glVertex3f(-1.0, 0.0, 0.0);
}
glEnd();
glUseProgram(shaderId[2]);
glUniform1i(glGetUniformLocation(shaderId[2], "texture0"), 0);
glBegin(GL_QUADS);
{
glTexCoord2f(1.0, 1.0); glVertex3f(1.0, 1.0, 0.0);
glTexCoord2f(0.0, 1.0); glVertex3f(0.0, 1.0, 0.0);
glTexCoord2f(0.0, 0.0); glVertex3f(0.0, 0.0, 0.0);
glTexCoord2f(1.0, 0.0); glVertex3f(1.0, 0.0, 0.0);
}
glEnd();
glUniform1i(glGetUniformLocation(shaderId[2], "texture0"), 2);
glBegin(GL_QUADS);
{
glTexCoord2f(1.0, 0.0); glVertex3f(1.0, -1.0, 0.0);
glTexCoord2f(1.0, 1.0); glVertex3f(1.0, 0.0, 0.0);
glTexCoord2f(0.0, 1.0); glVertex3f(0.0, 0.0, 0.0);
glTexCoord2f(0.0, 0.0); glVertex3f(0.0, -1.0, 0.0);
}
glEnd();

And on the last part of the rendering, set the glUseProgram() to 0 and flush it!

So there you go. Multitexture FBO. Here is the whole code at the main class:

http://pastie.org/private/1hsoqzcrb3keg5imgs4jg

If you need the whole project, feel free to contact me!

1 comment:

Prometheus said...

Amazing job! Thanks a lot for this incredibly useful tutorial! I've struggled for weeks to find a good and simple tutorial to start with multipass-rendering using FBO and then I've found it! Thanks a lot!