Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenGL shadow peter-panning

I'm adding shadows to a scene in OpenGL by doing two draw passes, one to a depth map and one to the normal frame buffer.

Without using a bias when using the depth map, there is a lot of shadow acne.

shadow acne without bias

This is fixed by adding a bias to the depth map check.

shadow with bias

However, this causes the shadow to 'detach' from the object when the light is moved to a different angle.

peter panning

I believe this effect is called peter-panning and is caused by a larger bias being used for different angles.

The usual fix for this seems to be to cull back facing triangles when drawing the shadow map, however as the floor plane is a 2D object, I don't believe this would work properly.

The actual terrain I am using is procedurally generated and so it is not as simple to create a 3D version of it as in this simple example.

How can the peter-panning be fixed on a 2D object such as this?


Vertex Shader

#version 400

layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
layout(location = 2) in vec2 texture_coords;

out VS_OUT {
    vec4 position;
    vec3 normal;
    vec2 texture_coords;
    vec4 shadow_position;
} vs_out;

uniform mat4 model;
uniform mat4 model_view;
uniform mat4 model_view_perspective;
uniform mat3 normal_matrix;
uniform mat4 depth_matrix;

void main() {
    vec4 position_v4 = vec4(position, 1.0);

    vs_out.position = model_view * position_v4;
    vs_out.normal = normal_matrix * normal;
    vs_out.texture_coords = texture_coords;
    vs_out.shadow_position = depth_matrix * model * position_v4;

    gl_Position = model_view_perspective * position_v4;
}

Fragment Shader

#version 400

in VS_OUT {
    vec4 position;
    vec3 normal;
    vec2 texture_coords;
    vec4 shadow_position;
} fs_in;

out vec4 colour;

uniform mat4 view;
uniform mat4 model_view_perspective;
uniform vec3 light_position;
uniform vec3 emissive_light;
uniform float shininess;
uniform int textured;
uniform sampler2D tex;
uniform sampler2DShadow shadow_texture;

void main() {
    const vec3 specular_albedo = vec3(1.0, 0.8, 0.6);

    colour = vec4(0.8, 0.8, 0.8, 0.8);
    if(textured != 0) {
        colour = texture(tex, fs_in.texture_coords);
    }

    vec3 light_direction = normalize(light_position);
    vec3 normal = normalize(fs_in.normal);

    float visibility = 1.0;
    if(fs_in.shadow_position.z <= 1.0) {
        float bias = max(0.05 * (1.0 - dot(normal, light_direction)), 0.005);
        if(fs_in.shadow_position.z > texture(shadow_texture, fs_in.shadow_position.xyz, 0.0) + bias){
            visibility = 0.0;
        }
    }

    /* Ambient */
    vec3 ambient = colour.xyz * 0.1;

    /* Diffuse */
    vec3 diffuse = visibility * (clamp(dot(normal, light_direction), 0, 1) * colour.xyz);

    /* Specular */
    vec3 specular = vec3(0.0);
    if(dot(normal, light_direction) > 0) {
        vec3 V = normalize(-fs_in.position.xyz);
        vec3 half_dir = normalize(light_direction + V);
        specular = visibility * (pow(max(dot(normal, half_dir), 0.0), shininess) * specular_albedo.xyz);
    }

    colour = vec4(((ambient + diffuse) * colour.xyz) + specular + emissive_light, 1.0);
}
like image 964
flau Avatar asked Nov 18 '16 07:11

flau


1 Answers

https://msdn.microsoft.com/en-us/library/windows/desktop/ee416324(v=vs.85).aspx

Calculating tight near planes and far planes also helps avoid Peter Panning.

Slope-Scale Depth Bias

As previously mentioned, self-shadowing can lead to shadow acne. Adding too much bias can result in Peter Panning. Additionally, polygons with steep slopes (relative to the light) suffer more from projective aliasing than polygons with shallow slopes (relative to the light). Because of this, each depth map value may need a different offset depending on the polygon's slope relative to the light.

Direct3D 10+ hardware has the ability to bias a polygon based on its slope with respect to the view direction. This has the effect of applying a large bias to a polygon that is viewed edge-on to the light direction, but not applying any bias to a polygon facing the light directly. Figure 10 illustrates how two neighboring pixels can alternate between shadowed and unshadowed when testing against the same unbiased slope.

http://www.sunandblackcat.com/tipFullView.php?l=eng&topicid=35

The problem is to determine optimal offset for each depth in shadow map. If you apply not enough offset, z-fighting will be still present. If you apply very large offset then Peter Panning will become noticeable. Offset should depend on precision of the shadow map, and on slope of the surface relative to direction to light source.

OpenGL can automatically compute and add offset to values which are stored in Z-Buffer. You can setup offset with glPolygonOffset function. Two parameters are available: multiplier for offset that depends on slope of the surface, and value that determines amount of additional smallest possible offsets (depends on format of shadow map):

https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glPolygonOffset.xhtml

like image 164
ErsatzStoat Avatar answered Oct 09 '22 21:10

ErsatzStoat