Shadow mapping a spot light

I’m using OpenGL 4.1 core profile on MacOS El Capitan with an Intel HD Graphics 5000 GPU.

I just started learning about OpenGL a few weeks ago and now I’m trying to make shadows. I used this tutorial Tutorial 16 : Shadow mapping. I can get their code to work but I can’t get mine to work properly. I understand the theory behind shadow maps but I just can’t get the code to work. I’ve managed to get the depth buffer in the frame buffer correct and I was able to put it on a quad and draw it on the screen. So here it is


Figure 0 - Depth buffer

Close pixels are dark and far pixels are light which is what they should be. I was able to transform the fragment position into light space and sample from the depth texture.


Figure 1 - Depth buffer sampled in fragment shader

When I draw the distance from the light to the fragment (the z component of the light space coordinates) it looks similar to the depth texture and that’s the problem.


Figure 2 - Distance from light to fragment

The value in the depth texture is not similar enough to the distance from the light. So I do (sort of) get a shadow. These 3 images should be identical from this perspective (the light’s perspective) but they aren’t. I don’t get “shadow acne” because I’ve done something wrong. Here it is.


Figure 3 - Left side of cube on plane

You’ll notice that there is no diffuse or specular lighting. I removed that to simplify the code as much as I could. At first glance it may seem that the near cliping plane of the light projection matrix is too large but I set it to 0.1 and still got the same result. I’m pretty sure the shadow looks like this because either the depth texture or the light distance (or both) are more “wrong” as they approach the light. Figure 2 differs from figure 1 close to the light but but becomes more similar away from the light.

Now for the shaders.

The vertex shader transforms the coordinates into screen space and light space. I don’t think the problem is there. Here’s the fragment shader.


#version 410

uniform sampler2D shadowMap;

in vec4 shadowPos;

out vec4 outColor;

void main() {
  //the / 2 + 0.5 scales from range -1-1 to 0-1
  vec2 samplePos = (shadowPos.xy / shadowPos.w) / 2 + 0.5;
  //           figure 1                         figure 2
  if (texture(shadowMap, samplePos).x < shadowPos.z / shadowPos.w) {
    //in shadow
    outColor = vec4(0.0, 0.0, 0.0, 1.0);
  } else {
    //not in shadow
    outColor = vec4(1.0, 0.0, 0.0, 1.0);
  }
}

It compares the distance on the depth texture to the distance from the light. I’m not sure what dividing w means. I just read somewhere that if w is non-zero then I should divide xyz by it.

Here’s the C++ file. I tried to leave out as much irrelevant code as I could.


int main(int argc, char* args[]) {
  //open the window with GLFW, 512 by 512
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  glFrontFace(GL_CCW);
  
  assert(glGetError() == GL_NO_ERROR);
  
  //load main program, compile shaders, glGetAttribLocation,
  //glGetUniformLocation, glLinkProgram

  //load mesh into GL_ARRAY_BUFFER
  //the mesh is hardcoded into the file
  
  assert(glGetError() == GL_NO_ERROR);
  
  glm::mat4 modelMat = glm::scale({}, glm::vec3(3.0f, 3.0f, 3.0f));
  glm::mat4 viewMat = glm::translate({}, glm::vec3(0.0f, 1.0f, 5.0f));
  glm::mat4 projMat = glm::perspective(1.5f, 1.0f, 0.1f, 100.0f);
  //notice that projMat and lightProj are the same
  glm::mat4 lightView = glm::translate({}, glm::vec3(0.0f, 1.0f, 5.0f));
  glm::mat4 lightProj = glm::perspective(1.5f, 1.0f, 0.1f, 100.0f);
  
  assert(glGetError() == GL_NO_ERROR);
  
  //load light program, compile shaders, glGetAttribLocation,
  //glGetUniformLocation, glLinkProgram
  
  assert(glGetError() == GL_NO_ERROR);
  
  GLuint frameBuffer;
  glGenFramebuffers(1, &frameBuffer);
  glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
  
  GLuint depthTexture;
  glGenTextures(1, &depthTexture);
  glBindTexture(GL_TEXTURE_2D, depthTexture);
  
  glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, 1024, 1024, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
  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);
  
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
  
  glDrawBuffer(GL_NONE);

  GLenum completeness = glCheckFramebufferStatus(GL_FRAMEBUFFER);
  if (completeness != GL_FRAMEBUFFER_COMPLETE) {
    std::cout << glGetString(completeness) << '
';
  }
  
  glBindFramebuffer(GL_FRAMEBUFFER, 0);
  
  while(!glfwWindowShouldClose(window)) {
    glfwPollEvents();
    //read keyboard input and move camera
    
    assert(glGetError() == GL_NO_ERROR);
    
    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
    
    glViewport(0, 0, 1024, 1024);
    glClear(GL_DEPTH_BUFFER_BIT);
    
    //call glUseProgram on the light program 

    //send the light MVP matrix to the light program
    //lightProj * glm::inverse(lightView) * modelMat
    
    //prepare the mesh to be drawn with the light program,
    //glEnableVertexAttribArray, glVertexAttribPointer
    
    //draw the mesh with the light program
    //glDrawElements

    assert(glGetError() == GL_NO_ERROR);
    
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    
    glViewport(0, 0, 512, 512);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //call glUseProgram on the main program
    
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    //set shadowMap to 0
    //set lightMVP to (lightProj * glm::inverse(lightView) * modelMat)
    //set MVP to (projMat * glm::inverse(viewMat) * modelMat)
    
    //prepare the mesh to be drawn with the main program,
    //glEnableVertexAttribArray, glVertexAttribPointer
    
    //draw the mesh with the main program
    //glDrawElements
    
    assert(glGetError() == GL_NO_ERROR);
    glfwSwapBuffers(window);
  }
  
  glfwTerminate();
  
  return 0;
}

I’ve noticed that if I substitute a perspective matrix for the orthogonal matrix in the tutorial code, I doesn’t work and if I substitute an orthogonal matrix for the perspective matrix in my code, it also doesn’t work. So directional lights and spot lights need to be treated differently in some other way but I don’t know what that is.

Please let me know if I’ve missed any important code.

Thank you in advance for any help.