2

I am trying to implement SSR (screen space reflections). I managed to get a basic effect, but there are some artifacts as you can see from the images and the video below. First of all I would like to solve these visual problems and then I would like to try to improve the performance of the algorithm if possible.

SSR artifacts: https://youtu.be/LFBAJHa_mFM

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

My SSR implementation is based on this tutorial.

Below you can find the code of the pixel shader that generates the reflection map (or reflected UV map) and here you can find the full implementation. As you can see it is practical identical to the code of the tutorial (screen-space-reflection.frag) except for few changes:

  • I don't store the 3D position of each pixel in an off-screen texture, but I reconstruct the 3D position from the view space depth in the function GetFullViewPosition.
  • When I transform the fragment NDC into texture coordinates, I multiply by float2(+0.5f, -0.5f) instead of float2(+0.5f, +0.5f).
  • I added a check that stops ray tracing if the current position is out of screen space.

SSRReflectionsMapComputePS.hlsl

cbuffer ConstantBuffer : register(b0)
{
    float4   gFrustumFarCorner[4];
    float4x4 gProj;
};

struct VertexOut
{
    float4 PositionH  : SV_POSITION;
    float3 ToFarPlane : TEXCOORD0;
    float2 TexCoord   : TEXCOORD1;
};

Texture2D gNormalDepthMap : register(t0);
SamplerState gNormalDepthSamplerState : register(s2);

float3 GetFullViewPosition(float2 uv, float z)
{
    // bilinear interpolation
    float4 p0 = lerp(gFrustumFarCorner[0], gFrustumFarCorner[3], uv.x);
    float4 p1 = lerp(gFrustumFarCorner[1], gFrustumFarCorner[2], uv.x);
    float3 ToFarPlane = lerp(p0.xyz, p1.xyz, 1 - uv.y);

    // reconstruct full view space position (x,y,z)
    // find t such that p = t*pin.tofarplane
    // p.z = t*pin.tofarplane.z ==> t = p.z / pin.tofarplane.z
    return (z / ToFarPlane.z) * ToFarPlane;
}

float3 GetFullViewPosition(float2 uv)
{
    float4 NormalDepth = gNormalDepthMap.SampleLevel(gNormalDepthSamplerState, uv, 0);
    return GetFullViewPosition(uv, NormalDepth.w);
}

// https://lettier.github.io/3d-game-shaders-for-beginners/screen-space-reflection.html
// https://github.com/lettier/3d-game-shaders-for-beginners/blob/master/demonstration/shaders/fragment/screen-space-reflection.frag

float4 main(VertexOut pin) : sv_target
{
    // view space normal and depth (z-coord) of this pixel
    float4 normaldepth = gNormalDepthMap.SampleLevel(gNormalDepthSamplerState, pin.TexCoord, 0);

    float3 n = normaldepth.xyz;
    float pz = normaldepth.w;

    //float3 p = (pz / pin.tofarplane.z) * pin.tofarplane;
    float3 p = GetFullViewPosition(pin.TexCoord, pz);

    float maxdistance = 100;
    float resolution = 1;
    int   steps = 10;
    float thickness = 0.5f;

    float2 texsize;
    gNormalDepthMap.GetDimensions(texsize.x, texsize.y);
    float2 texcoord = pin.PositionH.xy / texsize;

    float3 positionfrom = p;
    float3 positionfromunit = normalize(positionfrom);
    float3 normal = normalize(n);
    float3 pivot = normalize(reflect(positionfromunit, normal));

    float3 positionto = positionfrom;
    float4 uv = 0;

    float3 viewstart = positionfrom + pivot * 0;
    float3 viewend = positionfrom + pivot * maxdistance;

    float4 fragstart = float4(viewstart, 1);
    fragstart = mul(gProj, fragstart);
    fragstart.xy /= fragstart.w;
    fragstart.xy = fragstart.xy * float2(+0.5f, -0.5f) + 0.5f;
    fragstart.xy *= texsize;

    float4 fragend = float4(viewend, 1);
    fragend = mul(gProj, fragend);
    fragend.xy /= fragend.w;
    fragend.xy = fragend.xy * float2(+0.5f, -0.5f) + 0.5f;
    fragend.xy *= texsize;

    float2 frag = fragstart.xy;
    uv.xy = frag / texsize;

    float deltax = fragend.x - fragstart.x;
    float deltay = fragend.y - fragstart.y;
    float usex = abs(deltax) >= abs(deltay) ? 1 : 0;
    float delta = lerp(abs(deltay), abs(deltax), usex) * clamp(resolution, 0, 1);
    float2 increment = float2(deltax, deltay) / max(delta, 0.001f);

    float search0 = 0;
    float search1 = 0;

    int hit0 = 0;
    int hit1 = 0;

    float viewdistance = viewstart.z;
    float depth = thickness;

    for (int i = 0; i < int(delta); ++i)
    {
        frag += increment;
        uv.xy = frag / texsize;

        // do not sample outside the screen space
        if (any(uv.xy < float2(0, 0)) || any(uv.xy > float2(1, 1))) break;

        positionto = GetFullViewPosition(uv.xy);

        search1 = lerp((frag.y - fragstart.y) / deltay, (frag.x - fragstart.x) / deltax, usex);
        search1 = clamp(search1, 0, 1);

        viewdistance = (viewstart.z * viewend.z) / lerp(viewend.z, viewstart.z, search1);
        depth = viewdistance - positionto.z;

        if (depth > 0 && depth < thickness)
        {
            hit0 = 1;
            break;
        }
        else
        {
            search0 = search1;
        }
    }

    search1 = search0 + ((search1 - search0) / 2);

    steps *= hit0;

    for (int i = 0; i < steps; ++i)
    {
        frag = lerp(fragstart.xy, fragend.xy, search1);
        uv.xy = frag / texsize;

        // do not sample outside the screen space
        if (any(uv.xy < float2(0, 0)) || any(uv.xy > float2(1, 1))) break;

        positionto = GetFullViewPosition(uv.xy);

        viewdistance = (viewstart.z * viewend.z) / lerp(viewend.z, viewstart.z, search1);
        depth = viewdistance - positionto.z;

        if (depth > 0 && depth < thickness)
        {
            hit1 = 1;
            search1 = search0 + ((search1 - search0) / 2);
        }
        else
        {
            float temp = search1;
            search1 = search1 + ((search1 - search0) / 2);
            search0 = temp;
        }
    }

    float visibility = hit1 *
        (1 - max(dot(-positionfromunit, pivot), 0)) *
        (1 - clamp(depth / thickness, 0, 1)) *
        (1 - clamp(length(positionto - positionfrom) / maxdistance, 0, 1)) *
        (uv.x < 0 || uv.x > 1 ? 0 : 1) *
        (uv.y < 0 || uv.y > 1 ? 0 : 1);

    return float4(uv.xy, 0, saturate(visibility));
}
Arctic Pi
  • 669
  • 5
  • 19

1 Answers1

0

I had the same issues with artifacts when I implemented SSR. I had all my g-buffer textures set up as LINEAR min and mag filtering. Changing them to NEAREST fixed the artifacts you’re seeing at the back of the skull.