0
votes

Managed to get Shadow Mapping to work in my OpenGL rendering engine, but it is producing some weird artifacts that I think are "shadow acne". However, I am using shadow2DProj to get the shadow value from the shadow depth map, which for me has proven to be the only way to get shadows to show up at all. Therefore, looking around at various tutorial at learnopengl, opengl-tutorials and others have yielded no help. Would like some advice as to how I could mitigate this problem.

Here is my shader that I use to draw the shadow map with:

#version 330 core

out vec4 FragColor; 

struct Light {
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    vec3 attenuation;
};

in vec3 FragPos;  
in vec3 Normal;  
in vec2 TexCoords;
in vec4 ShadowCoords;

uniform vec3 viewPos;
uniform sampler2D diffuseMap;
uniform sampler2D specularMap;
uniform sampler2DShadow shadowMap;
uniform Light lights[4];
uniform float shininess;

float calculateShadow(vec3 lightDir)
{
    float shadowValue = shadow2DProj(shadowMap, ShadowCoords).r;
    float shadow = shadowValue;
    return shadow;
}

vec3 calculateAmbience(Light light, vec3 textureMap)
{
    return light.ambient * textureMap;
}

void main()
{
    vec4 tex = texture(diffuseMap, TexCoords);

    if (tex.a < 0.5)
    {
        discard;
    }

    vec3 ambient = vec3(0.0);
    vec3 diffuse = vec3(0.0);
    vec3 specular = vec3(0.0);

    vec3 norm = normalize(Normal);
    vec3 viewDir = normalize(viewPos - FragPos);

    for (int i = 0; i < 4; i++)
    {
        ambient = ambient + lights[i].ambient * tex.rgb;

        vec3 lightDir = normalize(lights[i].position - FragPos);
        float diff = max(dot(norm, lightDir), 0.0);
        diffuse = diffuse + (lights[i].diffuse * diff * tex.rgb);

        vec3 reflectDir = reflect(-lightDir, norm);
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess);
        specular = specular + (lights[i].specular * spec * tex.rgb);

        float dist = length(lights[i].position - FragPos);
        float attenuation = lights[i].attenuation.x + (lights[i].attenuation.y * dist) + (lights[i].attenuation.z * (dist * dist));
        if (attenuation > 0.0)
        {
            ambient *= 1.0 / attenuation;
            diffuse *= 1.0 / attenuation;
            specular *= 1.0 / attenuation;
        }
    }

    float shadow = calculateShadow(normalize(lights[0].position - FragPos));
    vec3 result = (ambient + (shadow) * (diffuse + specular));
    FragColor = vec4(result, 1.0);
} 

This is the result I get. Notice the weird stripes on top of the cube:

Shadow Mapping Error

Reading the description about shadow acne, this seems to be the same phenomenon (source: https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping).

According to that article, I need to check if the ShadowCoord depth value, minus a bias constant, is lower then the shadow value read from the shadow map. If so, we have shadow. Now... here comes the problem. Since I am using shadow2DProj and not texture() to get my shadow value from the shadow map (through some intricate sorcery no doubt), I am unable to "port" that article's code into my shader and get it to work. Here is what I have tried:

float calculateShadow(vec3 lightDir)
{
    float closestDepth = shadow2DProj(shadowMap, ShadowCoords).r;
    float bias = 0.005;
    float currentDepth = ShadowCoords.z;
    float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;
    return shadow;
}

But that produces no shadows at all, since the "shadow" float is always assigned 1.0 from the depth & bias check. I must admit that I do not fully understand what I am getting from using shadow2DProj(...).r as compared to texture(...).r, but it sure is something completely different.

1

1 Answers

0
votes

This question has a misunderstanding of what shadow2DProj does. The function does not return a depth value, but a depth comparison result. Therefore, apply the bias before calling it.

Solution 1

Apply the bias prior to running the comparison. ShadowCoords.z is your currentDepth value.

float calculateShadow(vec3 lightDir)
{
  const float bias = 0.005;
  float shadow = shadow2DProj(shadowMap, vec3(ShadowCoords.uv, ShadowCoords.z - bias)).r;
  return shadow;
}

Solution 2

Apply the bias while performing the light-space depth pass.

glPolygonOffset(float factor, float units)

This function offsets Z-axis values by factor * DZ + units where DZ is the z-axis slope of the polygon. Setting this to positive values moves polygons deeper into the scene, which acts like our bias.

During initialization:

glEnable(GL_POLYGON_OFFSET_FILL);

During Light Depth Pass:

// These parameters will need to be tweaked for your scene 
// to prevent acne and mitigate peter panning
glPolygonOffset(1.0, 1.0); 

// draw potential shadow casters

// return to default settings (no offset)
glPolygonOffset(0, 0); 

Shader Code:

// we don't even need the light direction for slope bias
float calculateShadow() 
{
  float shadow = shadow2DProj(shadowMap, ShadowCoords).r;
  return shadow;
}