Shadow mapping implementation not working?

| | August 6, 2015

I am trying to implement Shadow Mapping in my program using Java/LWJGL/OpenGL/GLSL, but I cannot get it to work properly. Let’s start off with a screenshot first, then the relevant code and lastly some remarks I have on them:

My scene without shadows:

Scene: No shadows

My scene with shadows:

Scene: With shadows

What you see in point pictures is the light source (yellow point), a randomized terrain and the floor. I am implementing shadows on only the floor at the moment. In the light pass all the objects, but the light, get rendered with the light shader. At the draw pass, all objects get rendered with their own shaders.

The artifact that happens is that I see (1) no shadow and (2) exactly half of my plane is missing.

Geometry locations are (in form (x, y, z) with y the up vector):

  • Light located at (0, 7.5, 0)
  • Floor located in a rectangle from (-400, -10, -400) to (400, -10, 400)
  • Terrain located between (0, 0, 0) to (48, 0, 48)
  • The near plane is located at 0.1
  • The far plane is located at 1000

The relevant code, most should be self explanatory:

Initialiazing OpenGL:

@Override
protected void init() {
    super.init();

    //shadow FBO and texture
    depthFBO = new FrameBufferObject().create().bind();
    depthTexture = new Texture2D().create().bind()
            .storage2D(11, GL_DEPTH_COMPONENT32F, 4096, 4096)   //11 is log2(4096)
            .minFilter(GL_LINEAR)
            .magFilter(GL_LINEAR)
            .compareMode(GL_COMPARE_REF_TO_TEXTURE)
            .compareFunc(GL_LEQUAL);
    depthFBO.texture(GL_DEPTH_ATTACHMENT, depthTexture, 0)
            .unbind();
    depthTexture.unbind();

    //drawable buffer
    buffer = new DynamicDrawArrayBuffer().create().bind().fillData(Drawable.putAllData(drawables));
    vertexArray = new VertexArrayObject().create().bind();
    vertexArray.setAttribute(buffer, VS_POSITION, 3, GL_FLOAT, false, 0, 0);
    vertexArray.enableAttributes(VS_POSITION);

    //set model matrix
    modelMatrix.identity().translate(0f, 0f, 0f);
    Uniforms.setUniformMatrix4(UNIFORM_MODEL_MATRIX, false, modelMatrix.writeToFloatBuffer(modelMatrixBuffer));

    //set light MVP
    lightMVPMatrix.identity()
            .frustum(-1f, 1f, -1f, 1f, nearPlane, farPlane)
            .lookAt(new Vector3f(0f, 7.5f, 0f), Vector3f.O, Vector3f.Y)
            .multiply(modelMatrix);
    Uniforms.setUniformMatrix4(UNIFORM_LIGHT_MVP_MATRIX, false, lightMVPMatrix.writeToFloatBuffer(lightMVPMatrixBuffer));

    //set shadow matrix for the light
    shadowMatrix.identity()
            .multiply(Matrix4f.SCALE_BIAS)
            .multiply(lightMVPMatrix);
    Uniforms.setUniformMatrix4(UNIFORM_SHADOW_MATRIX, false, shadowMatrix.writeToFloatBuffer(shadowMatrixBuffer));

    //general settings
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glPatchParameteri(GL_PATCH_VERTICES, 16);
}

Rendering:

@Override
protected void render(final double msDelta) {
    super.render(msDelta);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);

    //Light pass
    depthFBO.bind();
    glViewport(0, 0, 4096, 4096);
    lightProgram.use();
    glDrawBuffer(GL_NONE);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(1f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    Drawable.drawAllLightPass(drawables, lightProgram);
    depthFBO.unbind();

    //Draw pass
    glViewport(0, 0, screenWidth, screenHeight);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(1f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glActiveTexture(GL_TEXTURE0);
    depthTexture.bind();
    glDrawBuffer(GL_BACK);
    Drawable.drawAll(drawables);
    depthTexture.unbind();
}

Then the light.vs.glsl vertex shader:

#version 440 core

layout(location = 4) uniform mat4 light_mvp;

layout(location = 0) in vec4 position;

void main(void) {
    gl_Position = light_mvp * position;
}

The light.fs.glsl fragment shader:

#version 440 core

layout(location = 0) out float depth;

void main(void) {
    depth = gl_FragCoord.z;
}

The floor.vs.glsl vertex shader:

#version 440 core

layout(location = 0) in vec4 position;

layout(location = 0) uniform mat4 model_matrix;
layout(location = 1) uniform mat4 view_matrix;
layout(location = 2) uniform mat4 proj_matrix;
layout(location = 3) uniform mat4 shadow_matrix;

out VS_OUT {
    vec3 N;
    vec3 L;
    vec3 V;
    vec4 shadow_coord;
} vs_out;

uniform vec4 light_pos = vec4(0.0, 7.5, 0.0, 1.0);

void main(void) {
    vec4 local_light_pos = view_matrix * light_pos;
    vec4 p = view_matrix * model_matrix * position;

    //normal
    vs_out.N = vec3(0.0, 1.0, 0.0);

    //light vector
    vs_out.L = local_light_pos.xyz - p.xyz;

    //view vector
    vs_out.V = -p.xyz;

    //light space coordinates
    vs_out.shadow_coord = shadow_matrix * position;

    gl_Position = proj_matrix * p;
}

The floor.fs.glsl fragment shader:

#version 440 core

out vec4 color;

in VS_OUT {
    vec3 N;
    vec3 L;
    vec3 V;
    vec4 shadow_coord;
} fs_in;

layout(binding = 0) uniform sampler2DShadow shadow_tex;

uniform vec3 light_ambient_albedo = vec3(1.0);
uniform vec3 light_diffuse_albedo = vec3(1.0);
uniform vec3 light_specular_albedo = vec3(1.0);

uniform vec3 ambient_albedo = vec3(0.1, 0.1, 0.2);
uniform vec3 diffuse_albedo = vec3(0.4, 0.4, 0.8);
uniform vec3 specular_albedo = vec3(0.0, 0.0, 0.0);
uniform float specular_power = 128.0;

void main(void) {
    //normalize
    vec3 N = normalize(fs_in.N);
    vec3 L = normalize(fs_in.L);
    vec3 V = normalize(fs_in.V);

    //calculate R
    vec3 R = reflect(-L, N);

    //calcualte ambient
    vec3 ambient = ambient_albedo * light_ambient_albedo;

    //calculate diffuse
    vec3 diffuse = max(dot(N, L), 0.0) * diffuse_albedo * light_diffuse_albedo;

    //calcualte spcular
    vec3 specular = pow(max(dot(R, V), 0.0), specular_power) * specular_albedo * light_specular_albedo;

    //write color
    //color = vec4(ambient + diffuse + specular, 0.5);
    color = mix(textureProj(shadow_tex, fs_in.shadow_coord), 1.0, 0.5) * vec4(ambient + diffuse + specular, 0.5);
}

The strange things I have discovered so far:

  • If in the init() method, I change .frustum(-1f, 1f, -1f, 1f, nearPlane, farPlane) to .frustum(-1f, 1f, -1f, 1f, -nearPlane, farPlane), then the opposite of the floor is considered to be in the shadow and vica versa.

  • I should note that I have used OpenGL SuperBible: Sixth Edition as learning resource, if anyone is concerned, especially the shadowmapping example.

  • I understand most of what I am doing, however the shadow mapping part is still very new to me, I understand that I need to use a frustum as projection matrix to simulate a directional light, and that the view matrix is there to put every point in camera coordinates. However I do not understand why in .lookAt(new Vector3f(0f, 7.5f, 0f), Vector3f.O, Vector3f.Y) the Vector3f.O is there as second argument, formally for a lookAt matrix it is there to denote the at point, but how does that coincide with my lightMVPMatrix?

  • The camera projection is a first person view, and I am rotating my camera via that, maybe it is relevant.

I have really done my utmost best to clearly explain what is going on, but I am pretty much at a loss here, and do not know anymore what else could be wrong, obviously something is going wrong as I expecting that the terrain will cast a shadow on the floor.

Leave a Reply