Basic shadow mapping fails on NVIDIA card?
- by James
Recently I switched from an AMD Radeon HD 6870 card to an (MSI) NVIDIA GTX 670 for performance reasons.
I found however that my implementation of shadow mapping in all my applications failed. In a very simple shadow POC project the problem appears to be that the scene being drawn never results in a draw to the depth map and as a result the entire depth map is just infinity, 1.0 (Reading directly from the depth component after draw (glReadPixels) shows every pixel is infinity (1.0), replacing the depth comparison in the shader with a comparison of the depth from the shadow map with 1.0 shadows the entire scene, and writing random values to the depth map and then not calling glClear(GL_DEPTH_BUFFER_BIT) results in a random noisy pattern on the scene elements - from which we can infer that the uploading of the depth texture and comparison within the shader are functioning perfectly.)
Since the problem appears almost certainly to be in the depth render, this is the code for that:
const int s_res = 1024;
GLuint shadowMap_tex;
GLuint shadowMap_prog;
GLint sm_attr_coord3d;
GLint sm_uniform_mvp;
GLuint fbo_handle;
GLuint renderBuffer;
bool isMappingShad = false;
//The scene consists of a plane with box above it
GLfloat scene[] = {
-10.0, 0.0, -10.0, 0.5, 0.0,
10.0, 0.0, -10.0, 1.0, 0.0,
10.0, 0.0, 10.0, 1.0, 0.5,
-10.0, 0.0, -10.0, 0.5, 0.0,
-10.0, 0.0, 10.0, 0.5, 0.5,
10.0, 0.0, 10.0, 1.0, 0.5,
...
};
//Initialize the stuff used by the shadow map generator
int initShadowMap()
{
//Initialize the shadowMap shader program
if (create_program("shadow.v.glsl", "shadow.f.glsl", shadowMap_prog) != 1)
return -1;
const char* attribute_name = "coord3d";
sm_attr_coord3d = glGetAttribLocation(shadowMap_prog, attribute_name);
if (sm_attr_coord3d == -1) {
fprintf(stderr, "Could not bind attribute %s\n", attribute_name);
return 0;
}
const char* uniform_name = "mvp";
sm_uniform_mvp = glGetUniformLocation(shadowMap_prog, uniform_name);
if (sm_uniform_mvp == -1) {
fprintf(stderr, "Could not bind uniform %s\n", uniform_name);
return 0;
}
//Create a framebuffer
glGenFramebuffers(1, &fbo_handle);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_handle);
//Create render buffer
glGenRenderbuffers(1, &renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
//Setup the shadow texture
glGenTextures(1, &shadowMap_tex);
glBindTexture(GL_TEXTURE_2D, shadowMap_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, s_res, s_res, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
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);
return 0;
}
//Delete stuff
void dnitShadowMap()
{
//Delete everything
glDeleteFramebuffers(1, &fbo_handle);
glDeleteRenderbuffers(1, &renderBuffer);
glDeleteTextures(1, &shadowMap_tex);
glDeleteProgram(shadowMap_prog);
}
int loadSMap()
{
//Bind MVP stuff
glm::mat4 view = glm::lookAt(glm::vec3(10.0, 10.0, 5.0),
glm::vec3(0.0, 0.0, 0.0),
glm::vec3(0.0, 1.0, 0.0));
glm::mat4 projection = glm::ortho<float>(-10,10,-8,8,-10,40);
glm::mat4 mvp = projection * view;
glm::mat4 biasMatrix(
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0
);
glm::mat4 lsMVP = biasMatrix * mvp;
//Upload light source matrix to the main shader programs
glUniformMatrix4fv(uniform_ls_mvp, 1, GL_FALSE, glm::value_ptr(lsMVP));
glUseProgram(shadowMap_prog);
glUniformMatrix4fv(sm_uniform_mvp, 1, GL_FALSE, glm::value_ptr(mvp));
//Draw to the framebuffer (with depth buffer only draw)
glBindFramebuffer(GL_FRAMEBUFFER, fbo_handle);
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
glBindTexture(GL_TEXTURE_2D, shadowMap_tex);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowMap_tex, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (GL_FRAMEBUFFER_COMPLETE != result) {
printf("ERROR: Framebuffer is not complete.\n");
return -1;
}
//Draw shadow scene
printf("Creating shadow buffers..\n");
int ticks = SDL_GetTicks();
glClear(GL_DEPTH_BUFFER_BIT); //Wipe the depth buffer
glViewport(0, 0, s_res, s_res);
isMappingShad = true;
//DRAW
glEnableVertexAttribArray(sm_attr_coord3d);
glVertexAttribPointer(sm_attr_coord3d, 3, GL_FLOAT, GL_FALSE, 5*4, scene);
glDrawArrays(GL_TRIANGLES, 0, 14*3);
glDisableVertexAttribArray(sm_attr_coord3d);
isMappingShad = false;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
printf("Render Sbuf in %dms (GLerr: %d)\n", SDL_GetTicks() - ticks, glGetError());
return 0;
}
This is the full code for the POC shadow mapping project (C++)
(Requires SDL 1.2, SDL-image 1.2, GLEW (1.5) and GLM development headers.)
initShadowMap is called, followed by loadSMap, the scene is drawn from the camera POV and then dnitShadowMap is called.
I followed this tutorial originally (Along with another more comprehensive tutorial which has disappeared as this guy re-configured his site but used to be here (404).)
I've ensured that the scene is visible (as can be seen within the full project) to the light source (which uses an orthogonal projection matrix.) Shader utilities function fine in non-shadow-mapped projects. I should also note that at no point is the GL error state set.
What am I doing wrong here and why did this not cause problems on my AMD card?
(System: Ubuntu 12.04, Linux 3.2.0-49-generic, 64 bit, with the nvidia-experimental-310 driver package. All other games are functioning fine so it's most likely not a card/driver issue.)