0
\$\begingroup\$

I've transposed from OpenGL to DX11 a SSR ray marching shader (code found here : https://community.khronos.org/t/screen-space-reflections/69987). It seems to work well but I have had an initial problem of final scale for the mirrored stuff (see picture left). The first shader used is below. enter image description here

(NOTE: i'm reconstructing at the moment the position from the depthmap (txPosition) which is slowing down a lot and I calculate on the fly the view normal as being camera position - pixel world position (InvLightProj.xyz - dPL.xyz in PS_PostDeferredReflex) . Also the mirrored stuff is just the albedo (txDiffuse) for now):

 static const float rayStep = 0.25f;
 static const float minRayStep = 0.1f;
 static const int maxSteps = 5;
 static const float searchDist = 5.0f;
 static const float searchDistInv = 0.2f;
 static const int numBinarySearchSteps = 5;
 static const float maxDDepth = 1.0f;
 static const float maxDDepthInv = 1.0f;

 float3 BinarySearch(float3 dir, inout float3 hitCoord, out float dDepth)
 {
    float depth;
     float4 projectedCoord;
     for(int i = 0; i < 3; i++)
     {
         projectedCoord = mul(float4(hitCoord, 1.0), ViewProj);//use the projection matrix
         projectedCoord.xy /= projectedCoord.w;
         projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;
        float D = txPosition.Sample(samPoint, projectedCoord.xy).r; if ( D == 1 ) return float3(0,0,0);
        float4 dPL = float4(projectedCoord.x*2-1, (1-projectedCoord.y)*2-1, D, 1);
        dPL.xyz = float3(InvProj.x*dPL.x,InvProj.y*dPL.y, 1)/(InvProj.z*dPL.z+1);
        dPL = mul(dPL, InvView);depth = dPL.z;
        dDepth = hitCoord.z - depth;
         hitCoord += (dDepth > 0.0)?dir:0;
         dir *= 0.5;
         hitCoord -= dir;    
     }
     projectedCoord = mul(float4(hitCoord, 1.0),ViewProj); 
     projectedCoord.xy /= projectedCoord.w;
     projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;
     return float3(projectedCoord.xy, depth);
 }

 static const float reflectionSpecularFalloffExponent = 3.0;

 float4 RayCast(float3 dir, inout float3 hitCoord, out float dDepth)
 {
     dir *= rayStep;
     float depth;float4 projectedCoord;
     float4 Ray = float4(0.0f, 0.0f, 0.0f, 0.0f);
     for(int i = 0; i < maxSteps; i++)
     {
         hitCoord += dir;
         projectedCoord = mul(float4(hitCoord, 1.0),ViewProj);
         projectedCoord.xy /= projectedCoord.w;
         projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;
        float D = txPosition.Sample(samPoint, projectedCoord.xy).r; if ( D == 1 ) return float4(0,0,0,0);;
        float4 dPL = float4(projectedCoord.x*2-1, (1-projectedCoord.y)*2-1, D, 1);
        dPL.xyz = float3(InvProj.x*dPL.x,InvProj.y*dPL.y, 1)/(InvProj.z*dPL.z+1);
        dPL = mul(dPL, InvView);depth = dPL.z;
         dDepth = hitCoord.z - depth;
        Ray = float4(BinarySearch(dir, hitCoord, dDepth), 1.0);
        if(dDepth < 0.0) return Ray;
     }
     return Ray;
 }

 float4 PS_PostDeferredReflex(PS_INPUTQUAD Input) : SV_TARGET
 {
     float3 specular = txDiffuse.Sample(samPoint, Input.Tex).rgb;
     if(specular.r+specular.g+specular.b == 0.0) return float4(0.0, 0.0, 0.0, 0.0);
     float D = txPosition.Sample(samPoint, Input.Tex).r;    if ( D == 1 ) return float4(0,0,0,0);
     float4 dPL = float4(Input.Tex.x*2-1, (1-Input.Tex.y)*2-1, D, 1);
     dPL.xyz = float3(InvProj.x*dPL.x,InvProj.y*dPL.y, 1)/(InvProj.z*dPL.z+1);
     dPL = mul(dPL, InvView);
     if ( dPL.y > 1 ) return float4(0,0,0,0);
      float3 viewPos = InvLightProj.xyz;
      float3 viewNormal = (InvLightProj.xyz-dPL.xyz);
     // Reflection vector
     float3 reflected = normalize(reflect(normalize(viewPos), normalize(viewNormal)));
     // Ray cast
     float3 hitPos = viewPos;
     float dDepth;
     float4 coords = RayCast(reflected * max(minRayStep, -viewPos.z), hitPos, dDepth);
     float2 dCoords = abs(float2(0.5, 0.5) - coords.xy);
     float screenEdgefactor = clamp(1.0 - (dCoords.x + dCoords.y), 0.0, 1.0);
     // Get color
     float3 Color = txDiffuse.Sample(samPoint, coords.xy).rgb;
     float Spec = 1;//pow(specular, reflectionSpecularFalloffExponent);
     float EFactor = screenEdgefactor * clamp(-reflected.z, 0.0, 1.0);
     float Search = clamp((searchDist - length(viewPos - hitPos)) * searchDistInv, 0.0, 1.0) * coords.w;
     return float4(Color, Spec*EFactor*Search);
 }

new code for my mirror shader

static const float UVInc = 0.015f;
static const float UVIncL = UVInc*0.45f;

float4 PS_PostDeferredReflex(PS_INPUTQUAD Input) : SV_TARGET
{
     float D = txPosition.Load(int3(int(Input.Tex.x*ScreenX), int(Input.Tex.y*ScreenY),0)).r;
     if ( D == 1 ) return float4(0,0,0,0);
      float2 TexProj = float2(Input.Tex.x*2-1,1-2*Input.Tex.y);
      float4 PosVRef = float4(TexProj, D, 1);
      PosVRef.xyz = float3(InvProj.x*PosVRef.x,InvProj.y*PosVRef.y, 1)/(InvProj.z*PosVRef.z+1);
      float4 PosWRef = mul(PosVRef, InvView);
      if ( PosWRef.y > 1 ) return float4(0,0,0,0);
      float2 TexHit = Input.Tex;
      float2 Tex;
      float hit = 0;
      float2 TexProjCur = TexProj;
      TexProj = Input.Tex;
      float A = 0.5f;
      for (int i = 0; i < 150; i++)
      {
         TexProjCur.y+=UVInc;
         if ((TexProjCur.y < -1) || (TexProjCur.y > 1)) break;
         Tex = mad(TexProjCur, float2(0.5f, -0.5f), 0.5f);
         float D = txPosition.Load(int3(int(Tex.x*ScreenX), int(Tex.y*ScreenY),0)).r;
         if ( D < 1 )
         {  
            float4 PosV = float4(TexProjCur, D, 1);
            PosV.xyz = float3(InvProj.x*PosV.x,InvProj.y*PosV.y, 1)/(InvProj.z*PosV.z+1);
            float4 PosWorldPix = mul(PosV, InvView);
            if ( PosWorldPix.y > 0 )
            {
                float4 MirrorPix = mul(PosWorldPix, Reflex_View_World);
                {
                     MirrorPix/=MirrorPix.w;
                     MirrorPix.xy = mad(MirrorPix.xy, float2(0.5f, -0.5f), 0.5f);
                     MirrorPix.y-=0.011f;//correction for PosWorldPix.y = 0 alignment default
                     if (abs(MirrorPix.y - TexProj.y) <= UVIncL)
                     {
                        hit = 1;
                        TexHit = Tex;   
  //                    A = 0.5f-clamp(PosWorldPix.y/100, 0, 1);//activate to fade with height
                        break;
                     }
                 }    
             }
          }
    }
    if ( hit < 1 ) return float4(0,0,0,0);
    float3 Color = txDiffuse.Load(int3(int(TexHit.x*ScreenX), int(TexHit.y*ScreenY),0)).rgb;
     return float4(Color,A);
 }
\$\endgroup\$
1
  • \$\begingroup\$ After many attemps I think my problem comes from the way the Ray is calculated. clearly what i have is only and y-inverted scene. We should reflect with the normal of the object. For the ground it is (0,1,0) in world space. In view space the variaous calculation i found are not working either. AS my view is moving around the scene with angle RotY and at some height should I consider this in the calculation of the reflected ray and how? \$\endgroup\$
    – philB
    Commented May 20, 2022 at 15:06

1 Answer 1

0
\$\begingroup\$

After some time spent to work around this I have some results shown in the picture below. What I learnt was

1 : don’t forget GL uses projUV*(0.5, 0.5) + 0.5 when DX uses projUV*(0.5, -0.5) + 0.5

2 : how to produce a normalmap in view space (see lighting in camera space https://docs.microsoft.com/en-us/windows/win32/direct3d9/camera-space-transformations)

3 : the most important. Use texture.load instead of texture.sample to be able to iterate inside loop with dynamic branch as required for the ray marching algorithm (discussion on this here : https://gamedev.net/forums/topic/636473-load-vs-samplegrad/5015249/). As a collateral result it’s also much faster allowing longer loop sampling.

A first solution I found with ray marching (https://community.khronos.org/t/screen-space-reflections/69987) was difficult to set and at the end produced these “tubular” artifacts I cant’ remove (picture A). I found the code from McGuire better looking (picture C) but with some holes for overlapping objects when z does not match.

While working on that I had this simple idea to write a shader to reconstruct the mirrored position (panel B). The idea is that the mirrored uv coordinates of the mirrored pixel must match the uv coordinates of the current pixel that receive this mirrored pixel. Interestingly this code produces a result similar to the McGuire’ one but with some artifacts like the distorded orange plane and an approximate results when positionworld y=0 that I compensated. The code above was change to this mirror shader to show how I proceed. I’m interested if someone can help me improve it as for the binarysearch of ray marching. My attemps for this were moderately succesfull.

Finally the code from GitHub (https://github.com/RoundedGlint585/ScreenSpaceReflection/blob/master/shaders/SSRFragment.glsl) was identical to McGuire’ except it produces “vertical holes” (panel D). It's advantage is that it is the faster one.

enter image description here

\$\endgroup\$
1
  • \$\begingroup\$ "Can someone help me in some ways to resolve the various artifacts these methods have." - this looks like something that belongs in a question post, not in an answer post. \$\endgroup\$
    – DMGregory
    Commented May 25, 2022 at 15:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .