Rendering to Cubemap for Environment Mapping

Started by
7 comments, last by Vincent_M 12 years, 7 months ago
I've got my 6 cameras at the position of the object I'd like to environment map, but now I'm not sure how to render to a texture for a cubemap using FBOs. I've got rendering to a GL_TEXTURE_2D target working fine, but if I bind my the current frame buffer that's rendering to a Gl_TEXTURE_CUBE_MAP target, how do I set which cubemap target to my rendering camera?
For example, if I have a camera pointing along the positive Z axis, I want to render to the cubemap's GL_TEXTURE_CUBE_MAP_POSITIVE_Z sub-texture each frame. How would I do that?
Advertisement

For example, if I have a camera pointing along the positive Z axis, I want to render to the cubemap's GL_TEXTURE_CUBE_MAP_POSITIVE_Z sub-texture each frame. How would I do that?

Simply by using GL_TEXTURE_CUBE_MAP_POSITIVE_Z as the texture target to a glFramebufferTexture2D call, eg:

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, texture_id, 0);


Alternatively, if you want to render the cube in a single pass, you can attach the entire cubemap using

glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture_id, 0);

and select the target face in the geometry shader.
That last method you mentioned was what I was doing. Can you elaborate on how I'd select the target face in the shader? I'm guessing it's by using a cube map sampler since I'm passing a cube map through.
Just so that we understand each other: you want to render to a cubemap, right ? I got a bit confused when you started talking about texture samplers.

The first code snippet I posted uses a six pass technique. You render your scene six times, once for each of your 6 cameras. glFramebufferTexture2D is called to attach a specific cubemap side as the target for each of the rendering passes. A cubemap side attached this way will behave like a normal 2D texture target. You gradually build up your cubemap by cycling through all the GL_TEXTURE_CUBE_MAP_* constants as targets.

The second method is a single pass approach. You bind the entire cubemap as a target, and only perform a single render pass. This method requires a geometry shader that will duplicate each primitive six times, projecting it onto all six cubemap faces simultaneously (through your six view-projection matrices). The geometry shader directs each duplicated primitive to the appropriate cube target face using the gl_Layer variable.
[color="#1C2837"]Just so that we understand each other: you want to render to a cubemap, right ? I got a bit confused when you started talking about texture samplers.[/quote]

My wording wasn't that clear, but you are correct. There are two reasons I'm rendering to a cubemap: to make a cube depth texture for shadow maps, and an environment map for reflective surfaces and water.




So, after I setup my FBO, I'd do something like this?

// add the color attachments to the cubemap
for(int i=0;i<6;i++)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+i, GL_TEXTURE_CUBE_MAP_POSITIVE_Z+i, textureHandle, 0);



Now for rendering to the cubemap in my Scene management class:


// only render to a texture
void Scene::RenderEnvironmentMap()
{
// cancel early if the cubemap render target is invalid
if(!cubeTarget) return;

// set the cubemap render target's FBO
int oldFBO = GetBoundFBO();
SetBoundFBO(cubeTarget->GetFBO());

// render the scene to each face of the cubemap
for(int i=0;i<6;i++)
{
// set the current camera from the managed cube camera pointers
SetCurrentCamera(cubeCamera);
glBindRenderbuffer(GL_TEXTURE_CUBE_MAP_POSITIVE_Z+i, cubeTarget->GetFaceTextureHandle(i));
RenderObjects(); // render the scene
}

// set the FBO back to the original one
SetBoundFBO(oldFBO);
}
You are mixing up different concepts here (cube map faces, MRTs, render buffers, etc).

It looks like you are indeed rendering the scene 6 times. That would be the first approach I outlined earlier. For this to work, you will need to bind the cube sides one after the other, while rendering the matching camera view:
[source]

// Bind the FBO
glBindFramebuffer(GL_FRAMEBUFFER, fboHandle);

// Attach a depth renderbuffer to the FBO
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbufferHandle);

// Render the six views
for( int i = 0; i < 6; ++i ) {

// Bind the i'th face of the cube texture as target to color attachment 0
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, cubeTextureHandle, 0);

// All following render calls will now end up on the i'th side of the cubemap !

// Setup camera and render scene for the i'th view into the previously bound cubemap face
SetCurrentCamera(cubeCamera);
RenderObjects();

}

// Unbind FBO
glBindFramebuffer(GL_FRAMEBUFFER, 0);

// Cubemap at cubeTextureHandle is now complete.
[/source]

This assumes that your are only interested in a color cubemap texture and it uses a shared depth buffer for the rendering. If you want a depth cubemap generated in the same 6 passes along with the color cubemap, then drop the render buffer and add a second glFramebufferTexture2D call to the loop, attaching the i'th depth cube texture face to GL_DEPTH_ATTACHMENT. Something like:

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, depthCubeTextureHandle, 0);
That looks like what I'm looking for!

Now, not only does this work for setting up the cubemap, but could I use this to re-render the scene each frame?

If I wanted to update the cubemap, could I call this on render frames:

glBindFramebuffer(GL_FRAMEBUFFER, fboHandle);
for(int i=0;i<6;i++)
{
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, cubeTextureHandle, 0);
SetCurrentCamera(cubeCamera);
RenderObjects();
}


Does the call to glFramebufferTexture2D() work, or does that attempt to add another GL_COLOR_ATTACHMENT0 to the framebuffer?
It works. No, it doesn't add other attachements. If you want to add others, you use GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, etc. whatever the max is for your GPU.
Sig: http://glhlib.sourceforge.net
an open source GLU replacement library. Much more modern than GLU.
float matrix[16], inverse_matrix[16];
glhLoadIdentityf2(matrix);
glhTranslatef2(matrix, 0.0, 0.0, 5.0);
glhRotateAboutXf2(matrix, angleInRadians);
glhScalef2(matrix, 1.0, 1.0, -1.0);
glhQuickInvertMatrixf2(matrix, inverse_matrix);
glUniformMatrix4fv(uniformLocation1, 1, FALSE, matrix);
glUniformMatrix4fv(uniformLocation2, 1, FALSE, inverse_matrix);
When rendering to my cube map texture, I'm getting a black texture.

Here's how I create my cube map and FBO:

void EnvironmentMap::CreateFBO()
{
// check if the dimensions are greater than supported
int size;
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &size);
if(width > size || height > size)
return;
DestroyFBO(); // release everything

// allocate the new buffers
int oldFBO = GetCurrentFBO();
glGenTextures(1, &handle);
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);

// setup the texture
SetBoundTexture(GL_TEXTURE_CUBE_MAP, handle);
SetFilter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
SetFilter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
SetWrapMode(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
SetWrapMode(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

// allocate the texture
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

// set the texture rendering targets
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X, handle, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, handle, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, handle, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, handle, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, handle, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, handle, 0);
SetBoundTexture(GL_TEXTURE_CUBE_MAP, -1);

// add a depth attachment
if(useDepth)
{
glGenRenderbuffers(1, &depthRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);
}

// check framebuffer status
unsigned int status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
switch(status)
{
case GL_FRAMEBUFFER_COMPLETE: printf("GL_FRAMEBUFFER_COMPLETE\n"); break;
case 0x8CDB: printf("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER\n"); break;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: printf("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT\n"); break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: printf("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT\n"); break;
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: printf("GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS\n"); break;
case GL_FRAMEBUFFER_UNSUPPORTED: printf("GL_FRAMEBUFFER_UNSUPPORTED\n"); break;
default: printf("Unknown issue (%X).\n",status); break;
}

// set clear the FBO to white
ColorRGBA oldColor = GetClearColor();
SetClearColor(ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// restore the previous FBO
SetBoundFBO(oldFBO); // set the old FBO
SetClearColor(oldColor); // set the old clear color
}



Here's how I render my scene to a texture each frame:

// render the environment map
EnvironmentMap *envMap = sceneOBJModel->GetMesh(0)->GetMaterial()->GetEnvironmentMap();
SetViewport(ViewportData(0, 0, 128,128));
SetBoundFBO(envMap->GetFBO());
for(int i=0;i<6;i++)
{
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, envMap->GetHandle(), 0);
if(cubeCameras) scene->SetCurrentCamera(cubeCameras);
if(scene) scene->Render();
}


The environment map was also allocated to the dimensions of <128, 128> per face.

This topic is closed to new replies.

Advertisement