Variance Shadow Maps (VSM) is a pretty known technique. But I was unable to make it work properly with Cascaded Shadow Maps (CSM). I can’t get homogenous shadows in some places with CSM, and when cascade index changes shadow difference is visually noticable.
You can see artifacts in the middle of the image (shadow on the next cascade is lighter):
When I used standard shadow maps with CSM, the result was fine! So I came to consclusion to get rid of VSM usage until I find correct implementation with homogenous results usable for CSM.
VSM Vertex Shader
#version 330 core
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec3 a_normal;
uniform mat4 u_projection_view;
uniform mat4 u_model;
uniform mat4 u_depth_bias_projection_view;
out DATA
{
vec3 position;
vec3 normal;
vec4 shadow_coord;
} vs_out;
void main()
{
vec4 position_world = u_model * vec4(a_position, 1.0);
mat3 model = mat3(u_model);
vs_out.position = vec3(position_world);
vs_out.normal = model * a_normal;
vs_out.shadow_coord = u_depth_bias_projection_view * position_world;
gl_Position = u_projection_view * position_world;
}
VSM Fragment Shader
#version 330 core
struct Light {
vec3 position; // in world space
vec3 color;
};
out vec4 out_color;
uniform Light u_light;
uniform sampler2D u_shadow_sampler;
in DATA
{
vec3 position;
vec3 normal;
vec4 shadow_coord;
} fs_in;
const float kScreenGamma = 2.2; // Assume that monitor is in sRGB color space
vec4 shadow_coord_post_w;
float ChebyshevUpperBound(float distance)
{
// We retrive the two moments previously stored (depth and depth*depth)
vec2 moments = texture(u_shadow_sampler, shadow_coord_post_w.xy).rg;
// Surface is fully lit. as the current fragment is before the light occluder
if (distance <= moments.x)
return 1.0;
// The fragment is either in shadow or penumbra. We now use chebyshev's upperBound to check
// How likely this pixel is to be lit (p_max)
float variance = moments.y - (moments.x * moments.x);
variance = max(variance, 0.00002);
float d = distance - moments.x;
float p_max = variance / (variance + d*d);
return p_max;
}
void main()
{
// Compute ambient term
vec3 ambient = vec3(0.2);
// Compute diffuse term
vec3 normal = normalize(fs_in.normal);
vec3 diffuse = vec3(0.0);
vec3 light = normalize(u_light.position - fs_in.position);
float lambertian = clamp(dot(light, normal), 0.0, 1.0);
diffuse += lambertian * u_light.color;
// Shadows factor
shadow_coord_post_w = fs_in.shadow_coord / fs_in.shadow_coord.w;
float visibility = 1.0;
bool outside_shadow_map = fs_in.shadow_coord.w <= 0.0 ||
(shadow_coord_post_w.x < 0.0 || shadow_coord_post_w.y < 0.0) ||
(shadow_coord_post_w.x >= 1.0 || shadow_coord_post_w.y >= 1.0);
if (!outside_shadow_map)
{
visibility = ChebyshevUpperBound(shadow_coord_post_w.z);
}
vec3 color_linear = ambient + (diffuse) * visibility;
vec3 color_corrected = pow(color_linear, vec3(1.0/kScreenGamma));
out_color = vec4(color_corrected, 1.0);
}