I add shadows to the scene in OpenGL, making two passes of passage, one to the depth map and one to the regular frame buffer.
Without using offset, when using a depth map, there are many shadow eels.

This is fixed by adding an offset to the depth map check.

However, this causes the shadow to "separate" from the object when the light moves at a different angle.

I believe this effect is called peter panning and is caused by the large offset used for different angles.
The usual fix for this seems to be to drop backward triangles when drawing a shadow map, however, since the floor plane is a two-dimensional object, I don't believe that this will work properly.
The actual terrain I am using is procedurally generated, and therefore it is not as easy to create a 3D version as in this simple example.
How can I lock peter pan on a 2D object like 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); }