Skip to main content
GameDev.net gamedev.net
🔒 Locked

Screen Space Shadows

Started by __MONTE2020 Sep 22, 2021 at 4:50 PM 7 replies 14.7k views
Original Post
__MONTE2020
__MONTE2020

Has anyone tried implementing screen space shadows? You can see what I am talking about https://panoskarabelas.com/posts/screen_space_shadows/​​ . It seems simple so I tried to implement it and here is my HLSL code:

static const uint SSS_MAX_STEPS = 16; // Max ray steps, affects quality and performance.
static const float SSS_RAY_MAX_DISTANCE = 0.05f; // Max shadow length, longer shadows are less accurate.
static const float SSS_THICKNESS = 0.1f; // Depth testing thickness.
static const float SSS_STEP_LENGTH = SSS_RAY_MAX_DISTANCE / (float) SSS_MAX_STEPS;

float ScreenSpaceShadows(float2 uv)
{
    float depth = depthTx.Sample(linear_wrap_sampler, uv);
    float3 ray_position = GetPositionVS(uv, depth);
    
    float3 ray_direction = normalize(-current_light.direction.xyz);
    float3 ray_step = ray_direction * SSS_STEP_LENGTH;
    ray_position += ray_step * dither(uv);
    
     // Ray march towards the light
    float occlusion = 0.0;
    float2 ray_uv = 0.0f;
    
    for (uint i = 0; i < SSS_MAX_STEPS; i++)
    {
        // Step the ray
        ray_position += ray_step;

        float4 ray_projection = mul(float4(ray_position, 1.0), projection);
        ray_projection.xyz /= ray_projection.w;
        ray_uv.xy = 0.5 * ray_projection.xy + 0.5;
        ray_uv.y = 1.0 - ray_uv.y;

        [branch]
        if (IsSaturated(ray_uv))
        {
            float depth_z = depthTx.Sample(linear_wrap_sampler, ray_uv);
            
            // Compute the difference between the ray's and the camera's depth
            float depth_linear = ConvertZToLinearDepth(depth_z);
            float depth_delta = ray_projection.z - depth_z;

            // Check if the camera can't "see" the ray (ray depth must be larger than the camera depth, so positive depth_delta)
            if (depth_delta > 0 && (depth_delta < SSS_THICKNESS))
            {
                // Mark as occluded
                occlusion = 1.0f;

                // screen edge fade:
                float2 fade = max(12 * abs(ray_uv - 0.5) - 5, 0);
                occlusion *= saturate(1 - dot(fade, fade));

                break;
            }
        }
    }

    // Convert to visibility
    return 1.0f - occlusion;
}

The problem is it's full of artifacts. I am not sure if I have bug in my code or this method works on simple scenes (I tested it using Sponza, I can send screenshots if needed). Any thoughts?

hplus0603
hplus0603

I did not read the article or code, but I have this to offer as help:

Screen space shadows don't work. There are at least three problems:

  1. Performance – tracing a line from the lit-pixel through the screen to each light source is a lot of texture samples
  2. Screen space – things (or part of things) will stop casting shadows when they are clipped at the edge of the screen
  3. Occlusion – light cannot pass behind things on the screen. A tree in the foreground will cast a shadow as if it's a wall extending from its position, back to infinity
enum Bool { True, False, FileNotFound };
Frantic PonE
Frantic PonE

Screenshots? The type of artifact is really important here, a decent amount of things you simply can't get rid of with screenspace hacks like shadows. Others you can, so screenshots/video would help a lot.

turanszkij
turanszkij

Post screenshot of artifact. As previous comments said, screen space shadows will always have some issues, but they can also work fine for very short range detail shadows, the kind of thing that shadow maps cannot capture.

__MONTE2020
__MONTE2020

Sorry for late response, here is the screenshot. My intention wasn't to use screen space shadows instead of shadow maps but to combine them.

EDIT: I can't upload image for some reason, here is the link https://ibb.co/sKSLWm0

Eternal
Eternal
ray_position += ray_step * dither(uv);

This looks suspicious to me, is it possible that the ray start gets moved back into the geometry? Have you tried without this step (and on a simpler scene)? Also please include the code for “dither”.

Frantic PonE
Frantic PonE

Eternal said:

ray_position += ray_step * dither(uv);

This looks suspicious to me, is it possible that the ray start gets moved back into the geometry? Have you tried without this step (and on a simpler scene)? Also please include the code for “dither”.

Self occlusion problems are definitely a classic shadowing artifact… it looks possible and there's that odd pattern of the artifact disappearing and reappearing in a pattern so…

__MONTE2020
__MONTE2020

It should actually be

ray_position += ray_step * dither(uv * frame_cbuf.screen_resolution);

but the artifacts are still there. dither returns Bayer matrix element (a positive number) so I don't think dithering is a problem. Anyway, this is low priority problem for me, I was just wondering if someone else had similar experience with them.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.