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.
This is fixed by adding a bias to the depth map check.
However, this causes the shadow to 'detach' from the object when the light is moved to a different angle.
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);
}
glDepthOffset
. Know that this will break certain kinds of depth buffer optimizations during rendering, but that may not be all that important since you're just filling the depth buffer and not running a complicated fragment shader. – Andon M. Coleman