Godot Version
4.3 beta
Question
Hello everyone, I’m having trouble implementing screen space shadows, also known as contact shadows. The reference I’m using is from the Spartan Engine blog Screen space shadows and the source code. When trying to replicate this in Godot, I’m having many issues as shown in the image:
I’ve tried many things but none have worked. I can’t figure out what the problem is, and if you’re wondering if it’s because of the Reverse Z, it seems that in Godot 4.2.2 looks the same.
This is the code, I hope someone can find the problems:
shader_type spatial;
render_mode unshaded;
uniform sampler2D depth_image : hint_depth_texture;
uniform vec3 sun_pos;
uniform int g_sss_max_steps = 32; // Max ray steps, affects quality and performance.
uniform float g_sss_ray_max_distance = 0.5f; // Max shadow length, longer shadows are less accurate.
uniform float g_sss_thickness = 0.05f; // Depth testing thickness.
uniform float g_sss_max_delta_from_original_depth = 0.005f; // The maximum allowed depth deviation from the original pixel (a big deviation decreased the probabilty that the pixel is the occluder).
float get_noise_interleaved_gradient(vec2 screen_pos)
{
vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f);
return fract(magic.z * fract(dot(screen_pos, magic.xy)));
}
vec2 view_to_uv(vec3 x, mat4 proj)
{
vec4 uv = proj * vec4(x, 1.0);
return (uv.xy / uv.w) * vec2(0.5f, -0.5f) + 0.5f;
}
float get_linear_depth(vec2 uv, mat4 proj)
{
float log_depth = textureLod(depth_image, uv, 0.0).r;
vec4 depth_view = inverse(proj) * vec4(uv * 2.0 - 1.0, log_depth, 1.0);
depth_view.xyz /= depth_view.w;
return depth_view.z;
}
float screen_fade(vec2 uv)
{
vec2 fade = max(vec2(0.0f), 12.0f * abs(uv - 0.5f) - 5.0f);
return clamp(1.0f - dot(fade, fade), 0.0, 1.0);
}
vec3 get_position(vec2 uv, mat4 proj)
{
float depth_tex = textureLod(depth_image, uv, 0.0).r;
vec3 ndc = vec3(uv.x * 2.0 - 1.0, (1.0 - uv.y) * 2.0 - 1.0, depth_tex);
//vec3 ndc = vec3(uv * 2.0 - 1.0, depth_tex);
vec4 depth_view_space = inverse(proj) * vec4(ndc, 1.0);
return depth_view_space.xyz / depth_view_space.w;
}
bool is_valid_uv(vec2 value) { return (value.x >= 0.0f && value.x <= 1.0f) && (value.y >= 0.0f && value.y <= 1.0f); }
float ScreenSpaceShadows(vec2 uv, mat4 view, mat4 proj)
{
vec3 ray_pos = get_position(uv, proj);
vec3 ray_dir = (view * vec4(-sun_pos, 0.0)).xyz;
// Compute ray step
float g_sss_step_length = g_sss_ray_max_distance / float(g_sss_max_steps);
vec3 ray_step = ray_dir * g_sss_step_length;
float offset = get_noise_interleaved_gradient(uv);
ray_pos += ray_step * offset;
float depth_original = ray_pos.z;
// Ray march towards the light
float occlusion = 0.0;
vec2 ray_uv = vec2(0.0);
for (uint i = uint(0); i < uint(g_sss_max_steps); i++)
{
// Step the ray
ray_pos += ray_step;
ray_uv = view_to_uv(ray_pos, proj);
if (!is_valid_uv(ray_uv))
return 1.0f;
// Compute the difference between the ray's and the camera's depth
float depth_z = get_linear_depth(ray_uv, proj);
float depth_delta = ray_pos.z - depth_z;
bool can_the_camera_see_the_ray = (depth_delta > 0.0f) && (depth_delta < g_sss_thickness);
bool occluded_by_the_original_pixel = abs(ray_pos.z - depth_original) < g_sss_max_delta_from_original_depth;
if (can_the_camera_see_the_ray && occluded_by_the_original_pixel)
{
// Mark as occluded
occlusion = 1.0f;
// Fade out as we approach the edges of the screen
occlusion *= screen_fade(ray_uv);
break;
}
}
// Convert to visibility
return 1.0f - occlusion;
}
void vertex(){
POSITION = vec4(VERTEX.xy, 1.0, 1.0);
}
void fragment(){
ALBEDO = vec3(ScreenSpaceShadows(SCREEN_UV, VIEW_MATRIX, PROJECTION_MATRIX));
}