How to read back depth buffer values?

Hello all!

I’m working on a project about Ray Tracing with the help of OpenGL. My current task is to enhance a simple Ray Caster with Shadow Mapping… And there I have a problem: I want to render the scene from the light’s point of view and then read back the Z Buffer to get the light’s depth map…

Here is my code:

// Shadow Mapping structure
typedef struct _ShadowMap {
double MVPMatrix[16]; // light’s model view projection matrix
double modelviewMatrix[16]; // light’s model view matrix
double projectionMatrix[16];// light’s model view projection matrix
float left; // Frustum borders
float right; // Frustum borders
float bottom; // Frustum borders
float top; // Frustum borders
float near; // Frustum borders
float far; // Frustum borders
float *shadowMap; // Shadow Map
int size; // Map size
} ShadowMap;

//------------------------------------------------------------------------------
// Initializes the shadow map structure
//------------------------------------------------------------------------------
ShadowMap *createShadowMap(Shared *data, int size)
{
GLuint shadowMapFBODepth;
GLuint shadowMapFBOColour;
ShadowMap result = (ShadowMap)malloc(sizeof(ShadowMap));

result->size = size;
result->shadowMap = (float*)malloc(size * size * sizeof(float)); 

result->left = -5;
result->right = 5;
result->bottom = -5;
result->top = 5;
result->near = 0;
result->far = 5;

//model view
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
gluLookAt(2*data->lights[0].position[0],
          2*data->lights[0].position[1], 
          2*data->lights[0].position[2],
          0., 0., 0., 
          0., 1., 0.);
          
glGetDoublev(GL_MODELVIEW_MATRIX, result->modelviewMatrix);

glPopMatrix();

//projection
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(result->left, result->right, 
        result->bottom, result->top, 
        result->near, result->far);

glGetDoublev(GL_PROJECTION_MATRIX, result->projectionMatrix);

float scaleBias[16] = {0.5, 0, 0, 0, 
                       0, 0.5, 0, 0, 
                       0, 0, 0.5, 0,
                       0.5, 0.5, 0.5, 1 };

//MVP matrix = S * P * MV
glLoadIdentity();
glLoadMatrixf(scaleBias);
glMultMatrixd(result->projectionMatrix);
glMultMatrixd(result->modelviewMatrix);

glGetDoublev(GL_PROJECTION_MATRIX, result->MVPMatrix);

glPopMatrix();

return result;

}

//------------------------------------------------------------------------------
// Updates the shadow map
//------------------------------------------------------------------------------

void updateShadowMap(Shared *data, ShadowMap *shadowMap)
{
//Look from light
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glLoadMatrixd(shadowMap->modelviewMatrix);

//orthographic projection
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glLoadMatrixd(shadowMap->projectionMatrix);

glViewport(0, 0, shadowMap->size, shadowMap->size);

//render depth values
glClearDepth(1.0f);
glEnable( GL_DEPTH_TEST);
glClear( GL_DEPTH_BUFFER_BIT );

int i;
float *face;

glBegin( GL_TRIANGLES );

for( i=0; i<data->model->numFaces; i++ )
{
    face = data->model->faces[i].a;
    glVertex3f( face[0], face[1], face[2] );
    face = data->model->faces[i].b;
    glVertex3f( face[0], face[1], face[2] );
    face = data->model->faces[i].c;
    glVertex3f( face[0], face[1], face[2] );
}

glEnd();

glReadPixels(0, 0, shadowMap->size, shadowMap->size, GL_DEPTH_COMPONENT, GL_FLOAT, shadowMap->shadowMap);

#if 1 // Set to 1 to output the shadow map as RAW image file
FILE *shadowMapDump;
shadowMapDump = fopen(“shadowMap.raw”, “wb”);
fwrite(shadowMap->shadowMap, sizeof(ubyte), shadowMap->size * shadowMap->size, shadowMapDump);
fclose(shadowMapDump);
#endif

glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();

glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glViewport(0, 0, data->winWidth, data->winHeight);

CheckErrorsGL("updateShadowMap");

}

When running this, I get an OpenGL error: ‘1468 [Error]: OpenGL error ‘invalid operation’ (updateShadowMap)’. Apparently, the readback of the shadow map fails…

Can anyone point me in the right direction how to read back the contents of the depth buffer?

I’ve read back depth in the past for experimentation purposes, generally by using http://oss.sgi.com/projects/ogl-sample/registry/ARB/depth_texture.txt

I would render to the depth buffer, and then copy the results (CopyTexSubImage) to the depth texture. Then I would get the data from the texture (I did this on NVidia GeForce 6600 GT) using GetTexImage. As weird of a case as this is, I couldn’t believe it actually worked!

Out of curiosity, is there some reason you don’t render depth information to a color target and then read back the results of the color target? That’s far less hacky, and I imagine the loss of double speed Z (NV hardware) wouldn’t even be noticable in a case like this (I think it would be trumped by the speed of reading back rendered data from the graphics card to the system memory).

Kevin B

I tried to render into an FBO with a depth texture attached and then getTexImage()the buffer back, but it didn’t work either, I attach this code below…

And for your second suggestion: How could one write depth information into a color target? I assume I have to use a fragment shader (something like gl_FragColor = gl_FragPos.z) or can I achieve it otherways?

Anyway, here is my full code (using FBO’s):

//------------------------------------------------------------------------------
// Shadow Mapping structure
typedef struct _ShadowMap {
double MVPMatrix[16]; // light’s model view projection matrix
double modelviewMatrix[16]; // light’s model view matrix
double projectionMatrix[16];// light’s model view projection matrix
float left; // Frustum borders
float right; // Frustum borders
float bottom; // Frustum borders
float top; // Frustum borders
float near; // Frustum borders
float far; // Frustum borders
float *shadowMap; // Shadow Map
unsigned int texId; // Texture ID
unsigned int FBO; // Shadow map FBO
int size; // Map size
} ShadowMap;

//------------------------------------------------------------------------------
// Initializes the shadow map structure
//------------------------------------------------------------------------------

ShadowMap *createShadowMap(Shared *data, int size)
{
GLuint shadowMapFBODepth;
GLuint shadowMapFBOColour;
ShadowMap result = (ShadowMap)malloc(sizeof(ShadowMap));

result->size = size;
result->shadowMap = (float*)malloc(size * size * sizeof(float)); 

result->left = -5;
result->right = 5;
result->bottom = -5;
result->top = 5;
result->near = 0;
result->far = 5;

// Check FBO's
if (!GLEW_EXT_framebuffer_object) 
{
    GBLog(GB_LOG_ERROR, "FBO's not supported! Stopping..." );
    exit(1);
}

//create the shadow map texture
GBLog(GB_LOG_DEBUG,"Creating the shadowmap FBO");

glGenFramebuffersEXT(1,&result->FBO); 
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, result->FBO);

glGenTextures(1, &result->texId);
glBindTexture(GL_TEXTURE_2D, result->texId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, size, size, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

// initialize a dummy color renderbuffer

#ifdef DO_DUMMY_COLOR_BUFFER
glGenRenderbuffersEXT(1, &shadowMapFBOColour);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, shadowMapFBOColour);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA, size, size);
// attach renderbuffer to framebuffer color buffer
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, shadowMapFBOColour);
#endif

// Depth renderbuffer
glGenRenderbuffersEXT(1, &shadowMapFBODepth);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, shadowMapFBODepth);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT32, size, size);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, shadowMapFBODepth);

glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, result->texId, 0);
                          
glFBOCheckStatus();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
CheckErrorsGL("setupShaders - indexBufferFBO");

//model view
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
gluLookAt(2*data->lights[0].position[0],
          2*data->lights[0].position[1], 
          2*data->lights[0].position[2],
          0., 0., 0., 
          0., 1., 0.);
          
glGetDoublev(GL_MODELVIEW_MATRIX, result->modelviewMatrix);

glPopMatrix();

//projection
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(result->left, result->right, 
        result->bottom, result->top, 
        result->near, result->far);

glGetDoublev(GL_PROJECTION_MATRIX, result->projectionMatrix);

float scaleBias[16] = {0.5, 0, 0, 0, 
                       0, 0.5, 0, 0, 
                       0, 0, 0.5, 0,
                       0.5, 0.5, 0.5, 1 };

//MVP matrix = S * P * MV
glLoadIdentity();
glLoadMatrixf(scaleBias);
glMultMatrixd(result->projectionMatrix);
glMultMatrixd(result->modelviewMatrix);

glGetDoublev(GL_PROJECTION_MATRIX, result->MVPMatrix);

glPopMatrix();

return result;

}

//------------------------------------------------------------------------------
// Updates the shadow map
//------------------------------------------------------------------------------

void updateShadowMap(Shared *data, ShadowMap *shadowMap)
{
//Render to FBO
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,shadowMap->FBO);

//Look from light
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glLoadMatrixd(shadowMap->modelviewMatrix);

//orthographic projection
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glLoadMatrixd(shadowMap->projectionMatrix);

glViewport(0, 0, shadowMap->size, shadowMap->size);

//render depth values
glClearDepth(1.0f);
glEnable( GL_DEPTH_TEST);
glClear( GL_DEPTH_BUFFER_BIT );

//Disable color writes, and use flat shading for speed
glPushAttrib(GL_LIGHTING_BIT);
glShadeModel(GL_FLAT);
glColorMask(0, 0, 0, 0);

int i;
float *face;

glBegin( GL_TRIANGLES );

for( i=0; i<data->model->numFaces; i++ )
{
    face = data->model->faces[i].a;
    glVertex3f( face[0], face[1], face[2] );
    face = data->model->faces[i].b;
    glVertex3f( face[0], face[1], face[2] );
    face = data->model->faces[i].c;
    glVertex3f( face[0], face[1], face[2] );
}

glEnd();
glFlush();

//read shadow map texture

#ifdef DO_SHADOWMAP_GETTEXIMAGE
glBindTexture(GL_TEXTURE_2D, shadowMap->texId);
glGetTexImage(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GL_FLOAT, shadowMap->shadowMap);
#else
glReadPixels(0, 0, shadowMap->size, shadowMap->size, GL_DEPTH_COMPONENT, GL_FLOAT, shadowMap->shadowMap);
#endif

#if 0 // Set to 1 to output the shadow map as RAW image file
FILE *shadowMapDump;
shadowMapDump = fopen(“shadowMap.raw”, “wb”);
fwrite(shadowMap->shadowMap, sizeof(ubyte), shadowMap->size * shadowMap->size, shadowMapDump);
fclose(shadowMapDump);
#endif

//clean up
glPopAttrib();
glColorMask(1, 1, 1, 1);

glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glViewport(0, 0, data->winWidth, data->winHeight);

glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();

//Render to framebuffer again
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

CheckErrorsGL("updateShadowMap");

}

Okay, it seems I’m wrong. I was using glReadPixels with a packed depth-stencil texture on an FBO. I suggest using a depth-stencil render buffer and trying the following:

glReadPixels(0, 0, shadowMap->size, shadowMap->size, GL_DEPTH_STENCIL_EXT, GL_UNSIGNED_INT_24_8_EXT, shadowMap->shadowMap);

You’ll have to convert the depth data to float yourself, but I bet you’ll at least get back valid data.

Basically, the problem you’re running into has to do with the ARB_depth_texture spec. You cannot use glReadPixels with a depth texture. However, http://oss.sgi.com/projects/ogl-sample/registry/EXT/packed_depth_stencil.txt
does support glReadPixels on depth-stencil textures or renderbuffers.

Kevin B

sorry for posting so late, I just wanted to say that the issue has been resolved: The problem was that I had boud a PBO while trying to read back the depth values… When I unbind the PBO, all the above code works flawlessly.

Thanks for anyone’s suggestions anyway!