9
\$\begingroup\$

I have implemented basic shadow mapping for the first time in OpenGL using shaders and I'm facing some problems. Below you can see an example of my rendered scene:

enter image description here

The process of the shadow mapping I'm following is that I render the scene to the framebuffer using a View Matrix from the light point of view and the projection and model matrices used for normal rendering.

In the second pass, I send the above MVP matrix from the light point of view to the vertex shader which transforms the position to light space. The fragment shader does the perspective divide and changes the position to texture coordinates.

Here is my vertex shader,


#version 150 core

uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
uniform mat4 MVPMatrix;
uniform mat4 lightMVP;

uniform float scale;

in vec3 in_Position;
in vec3 in_Normal;
in vec2 in_TexCoord;

smooth out vec3 pass_Normal;
smooth out vec3 pass_Position;
smooth out vec2 TexCoord;
smooth out vec4 lightspace_Position;

void main(void){
    pass_Normal = NormalMatrix * in_Normal; 
    pass_Position = (ModelViewMatrix * vec4(scale * in_Position, 1.0)).xyz;
    lightspace_Position = lightMVP * vec4(scale * in_Position, 1.0);
    TexCoord = in_TexCoord;

    gl_Position = MVPMatrix * vec4(scale * in_Position, 1.0);
}

And my fragment shader,


#version 150 core

struct Light{
    vec3 direction;
};

uniform Light light;
uniform sampler2D inSampler;
uniform sampler2D inShadowMap;

smooth in vec3 pass_Normal;
smooth in vec3 pass_Position;
smooth in vec2 TexCoord;
smooth in vec4 lightspace_Position;

out vec4 out_Color;


float CalcShadowFactor(vec4 lightspace_Position){

    vec3 ProjectionCoords = lightspace_Position.xyz / lightspace_Position.w;

    vec2 UVCoords;
    UVCoords.x = 0.5 * ProjectionCoords.x + 0.5;
    UVCoords.y = 0.5 * ProjectionCoords.y + 0.5;

    float Depth = texture(inShadowMap, UVCoords).x;
    if(Depth < (ProjectionCoords.z + 0.001)) return 0.5;
    else return 1.0;
}

void main(void){

    vec3 Normal = normalize(pass_Normal);
    vec3 light_Direction = -normalize(light.direction);
    vec3 camera_Direction = normalize(-pass_Position);
    vec3 half_vector = normalize(camera_Direction + light_Direction);

    float diffuse = max(0.2, dot(Normal, light_Direction));
    vec3 temp_Color = diffuse * vec3(1.0);

    float specular = max( 0.0, dot( Normal, half_vector) );

    float shadowFactor = CalcShadowFactor(lightspace_Position);

    if(diffuse != 0 && shadowFactor > 0.5){
        float fspecular = pow(specular, 128.0);
        temp_Color += fspecular;
    }

    out_Color = vec4(shadowFactor * texture(inSampler, TexCoord).xyz * temp_Color, 1.0);
}

One of the problems is self shadowing as you can see in the picture, the crate has its own shadow cast on itself. What I have tried is enabling polygon offset (i.e. glEnable(POLYGON_OFFSET_FILL), glPolygonOffset(GLfloat, GLfloat) ) but it didn't change much. As you see in the fragment shader, I have put a static offset value of 0.001 but I have to change the value depending on the distance of the light to get more desirable effects , which not very handy. I also tried using front face culling when I render to the framebuffer, that didn't change much too.

The other problem is that pixels outside the Light's view frustum get shaded. The only object that is supposed to be able to cast shadows is the crate. I guess I should pick more appropriate projection and view matrices, but I'm not sure how to do that. What are some common practices, should I pick an orthographic projection?

From googling around a bit, I understand that these issues are not that trivial. Does anyone have any easy to implement solutions to these problems. Could you give me some additional tips?

Please ask me if you need more information on my code.

Here is a comparison with and without shadow mapping of a close-up of the crate. The self-shadowing is more visible.

\$\endgroup\$
2
  • \$\begingroup\$ Seems like you should describe your problem(s) in more detail. You have one sentence about your problems and the next sentence suggests a fix for one. Tell us exactly what the problems are and what you've done already to try and fix it. \$\endgroup\$
    – House
    May 31, 2012 at 23:50
  • \$\begingroup\$ I edited my post, I hope it's better now. \$\endgroup\$ Jun 1, 2012 at 0:16

3 Answers 3

6
\$\begingroup\$

You should not shadow the polygons that are back faced from the light.

I changed your fragment shader to below code.

float CalcShadowFactor(vec4 lightspace_Position)
{

    vec3 ProjectionCoords = lightspace_Position.xyz / lightspace_Position.w;

    vec2 UVCoords;
    UVCoords.x = 0.5 * ProjectionCoords.x + 0.5;
    UVCoords.y = 0.5 * ProjectionCoords.y + 0.5;

    float Depth = texture(inShadowMap, UVCoords).x;
    if(Depth < (ProjectionCoords.z + 0.001)) return 0.5;
    else return 1.0;
}

void main(void)
{

    vec3 Normal = normalize(pass_Normal);
    vec3 light_Direction = -normalize(light.direction);
    vec3 camera_Direction = normalize(-pass_Position);
    vec3 half_vector = normalize(camera_Direction + light_Direction);

    float fndotl = dot(Normal, light_Direction);
    float shadowFactor = CalcShadowFactor(lightspace_Position);
    float diffuse = max(0.0, fndotl) * shadowFactor + 0.2;
    vec3 temp_Color = vec3(diffuse);

    float specular = max( 0.0, dot( Normal, half_vector) );

    if(shadowFactor > 0.9){
        float fspecular = pow(specular, 128.0);
        temp_Color += fspecular;
    }

    out_Color = vec4(shadowFactor * texture(inSampler, TexCoord).xyz * temp_Color, 1.0);
}
\$\endgroup\$
0
8
\$\begingroup\$

Regarding the self-shadowing, I'm not sure just what you're referring to - I don't see any "shadow acne" on the crate in your screenshot. If you mean that the faces facing away from the light are dark, well, of course they are - they should be! :) The side face does look a little weird, split down the diagonal into half lit and half shadowed. If you're using N dot L for lighting, and you have good normals (i.e. hard normals on this cube, not smoothed normals) that shouldn't happen.

Using polygon offset and/or drawing the back faces into the shadow map are the standard solutions (well...workarounds really) for shadow acne.

As for the projection matrix, you should think of the light as a camera. If it's a point light, it would be a perspective camera and if it's a directional light it would be like an orthographic camera. Construct the projection matrix just the same way you would for a camera, using the light's field of view, aspect ratio, etc.

To restrict the pixel shader to only draw the pixels in the light's view frustum, once you calculate the shadow map UVs just check if they're between 0 and 1 and discard if not (or set the light's color to zero). Another way is to set the shadow map texture to "clamp to border" mode and set a border color of zero, which will ensure everything outside the shadow map gets fully shadowed.

\$\endgroup\$
5
  • \$\begingroup\$ Here is a closeup of the box and comparison with and without shadow mapping link . The self-shadowing is more visible here. Could you explain why you would use a perspective camera for point lights and orthographic for directional? \$\endgroup\$ Jun 1, 2012 at 2:32
  • \$\begingroup\$ Okay, in that shot it does look like shadow acne. Drawing back faces into the shadow map (and not front faces) should fix that. I know you said you tried that, but perhaps something went wrong? It's just glCullFace(GL_FRONT). As for point vs directional, it's all about the rays. Camera rays converge to a point in a perspective camera and light rays converge to a point in a point light; camera rays are parallel for an orthographic camera and light rays are parallel for a directional light. \$\endgroup\$ Jun 1, 2012 at 2:56
  • \$\begingroup\$ I see, that makes sense. Here is another comparison with and without face culling. The one in the bottom has face culling enabled. I will try to use orthographic projection for my light View matrix, that shouldn't affect the self-shadowing problem though, right? Maybe my clip range is too large (i.e. I have 0.1 to 100)? \$\endgroup\$ Jun 1, 2012 at 3:11
  • \$\begingroup\$ Those look exactly the same. You're using back-face culling for the rest of the scene, right? Did you do glEnable(GL_CULL_FACE)? \$\endgroup\$ Jun 1, 2012 at 3:28
  • \$\begingroup\$ They are not exactly the same, look closely at the box. In the picture above, the shadow line is convex while the one in the bottom is concave. \$\endgroup\$ Jun 1, 2012 at 3:57
3
\$\begingroup\$

The pixels outside of the lights view are dark because they're being sampled from outside the lights depth texture.

When you transform the vertex by the projection matrix you get the clip coordinates [lightspace_Position] of the fragment. You are then converting that into texture coordinates [UVCoords]. However, clip space coordinates of a fragment can still be outside the -1.0 to 1.0 range (off the screen and hence clipped) and therefore the converted texture coordinates can be outside of the 0.0 to 1.0 range of your texture.

Try this:

if(UVCoords.x >= 0.0 && UVCoords.x <= 1.0 && UVCoords.y >= 0.0 && UVCoords.y <= 1.0 && (Depth < (ProjectionCoords.z + 0.001)))
    return 0.5;

You can also multiply a bias matrix into your [lightMVP] and avoid doing the coordinate conversion in your fragment shader.

\$\endgroup\$
1
  • \$\begingroup\$ The bias matrix is usually the way people go. It's a lot faster than branching code in a fragment shader. \$\endgroup\$
    – Ian Young
    Feb 20, 2018 at 11:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .