1
\$\begingroup\$

After lot of efforts I have an almost nice shader doing SSR with HiZ. The shader is provided below. Some parameters are tunable and you can speed up things but the difficulty in my scene (picture) are the spheres that limit the coarsiness of the ray marching. So the benefit of HiZ is not as high as expected for this scene but there’s some (130 FPS instead of 110-120, 960x540).

I'm using a HiZ loop working only with increasing Mip level and a second one is used to get finer result as at that point I did not understand fully how the cell boundary calculations work.

Unfortunately there is a last annoying problem I was not able to solve in this SSR HiZ shader. The problem is shown in green on the left picture and on right what I have without HiZ. It is annoying because I did not have this problem with my regular SSR shader whitout HiZ. I have tried a thickness test like in the McGuirre method but it is not working.

 e.g : if abs ( ray.z-zbuff)< thickness.

enter image description here

So if someone can help below is my shader. enjoy if you're interested.

The shader is below

  //parameter for finer or coarser primary while results
  #define offset0 0.005
  #define offsetR 2
  //my initial resolution (Mip=0)
  static const int2 Resolution = {960, 540};
  //uv offset1 : critical to ensure correct uv sampling. modulate to see effect
  static const float2 offset1 = {1, 1.015};
  //parameter for finer or coarser secondary while results
  #define offset2  1.15
  //parameter for final dicotomic refinement
  #define refinemax 3
  //parameter for removing vertical artifact for objects not on ground
  //based on the normal of the object
  #define dot3falloff 0.00005
  #define falloff 0.00003

  float4 PS_PostDeferredReflex(PS_INPUTQUAD Input) : SV_TARGET
  {
      float D = txDepth1.SampleLevel(samPoint, Input.Tex, 0).r;
      if ( D == 1 ) return float4(0,0,0,0);
      float4 ColorOut = txDiffuse1.SampleLevel(samPoint, Input.Tex, 0);
      if ( ColorOut.a <0.9)  return ColorOut;
      float3 PosV = float3(InvProj.x*(Input.Tex.x*2-1),InvProj.y*(1-Input.Tex.y*2), 1)/(InvProj.z*D+1);
      if ( txDiffuse1.SampleLevel(samPoint, Input.Tex, 0).a == 0)  return ColorOut;

      float3 VSDir = normalize(reflect(normalize(PosV.xyz),txNormal1.SampleLevel(samPoint, Input.Tex, 0).rgb*2-1));    
      if ( VSDir.z < 0 ) return ColorOut;

      float4 SSEnd = mul(float4(PosV + VSDir*200, 1), Proj);
      SSEnd/= SSEnd.w;
      SSEnd.xy = SSEnd.xy*float2(0.5,-0.5)+float2(0.5,0.5); 
      float3 SSray = float3(Input.Tex, D);
      float3 SSDir = normalize(SSEnd.xyz-SSray);

      float dSS;
      if ( abs(SSDir.x) < abs(SSDir.y) ) dSS = abs(SSDir.y); else dSS = abs(SSDir.x);
      SSDir/=dSS;//"normalize" SSDir according to longest x or y
      SSDir*=offset0;//modulate step size

      int Mip = 0;//you can start at higher Mip level but I don't see improvements
      float ZBufferVal=1;
      float2 CurResol;
      float3 curSSDir = SSDir*pow(2,Mip)*offsetR;//pow useless at Mip=0
//primary while : move ray with Mip-dependant increasing steps
//until ray.z > zbuffer
      float rayZmin = 0;
      float3 poslast = SSray;
      int i=0;
      float4 Color ;
      while ((Mip<10)&&(i<20))//my resolution corresponds to 10 miplevel
      {
          i++;
          rayZmin = SSray.z;
          poslast = SSray;
          float2 MipSize = (Resolution>>Mip);
          SSray+=curSSDir;
          //use Load like below to have better sampling than 
          //SampleLevel(samPoint,SSray.xy,Mip).r;
          ZBufferVal = txDepth1.Load(int3(SSray.xy*MipSize*offset1, Mip)).r;
          if ( ZBufferVal==0 ) ZBufferVal=1;    //avoid disappearing reflexions at bottomscreen
          if (SSray.z > ZBufferVal)  break;
          if ( Mip < 9 )
          {
              Mip++;
              curSSDir*=2;
          }
      }
      if (poslast.z>=1) return ColorOut;
      //secondary while : move ray from last position (z<zbuffer) now only at level Mip = 0
      //until ray.z > zbuffer
      SSDir=normalize(SSDir)*0.02;
      i=0;
      SSray = poslast;
      while (i<40)
      {
          i++;
          ZBufferVal = txDepth1.SampleLevel(samPoint, SSray.xy, 0).r;   
          if (SSray.z > ZBufferVal) 
          {
              float3 MinRay = poslast;
              float3 MaxRay = SSray;
              for(int j = 0; j < refinemax; j++)
              {
                  poslast = (MinRay+MaxRay)*0.5f;
                  ZBufferVal = txDepth1.SampleLevel(samPoint, poslast.xy, 0).r;
                  if ( poslast.z > ZBufferVal) MaxRay = poslast; else MinRay = poslast;
              }
              float3 N = txNormal1.SampleLevel(samPoint, poslast.xy,0).rgb*2-1;
              float Dot3 = ( N.x+N.y+N.z>2.99f)?dot3falloff:(dot(-VSDir, N)<0)?0:dot3falloff;
              if (abs(poslast.z-ZBufferVal)<Dot3) 
              {
                  Color = float4(txDiffuse1.SampleLevel(samPoint, poslast.xy, 0).rgb, 1-abs(poslast.z-D)*500);
                  return lerp(Color,ColorOut,1-Color.a);
              }
              return ColorOut;
          }
          poslast = SSray;
          SSray += SSDir;
      }
      return ColorOut;
  }
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

After some refinements I have found the solution (see picture). The vertical holes were due to the line "return ColorOut" just after the refinemax loop. This line must be removed to let the while loop continue to find an accurate position. In turn this slows down the shader. I add in this answer a corrected shader. This new shader is more complete and you can set or not HiZ. It is also intended for showing how I managed the deferred multilayers used to fill the last holes due to overlapping objects. How these layers are created is explained in one of my previous questions. After the Hiz search a second loop is used to fill these holes as the HiZ algorithm I used is no table to do it. At the end the speed is still not there but it is possibly because I want to filter off the black background as much as possible.

The corrected shader is below. The HiZ loop comes from (here . It was easy to implement and give fine results) .

   Texture2D txDiffuse[3] : register(t0);//0 main scene; 1 object occluded once 2 object occluded >1
   Texture2D txNormal[3] : register(t3);//normals for the 3 layers
   Texture2D txDepth1 : register(t6);//main depth (zbuf) of layer 0. Has mip levels
   Texture2D txDepth23 : register(t7);//rg contains depth fo layers 1 and 2. No mip levels
   Texture2D txDepth4 : register(t8);//not used
   SamplerState samPoint : register(s2);
   
   cbuffer cbMeshQuad : register(b5)
   {
       matrix Proj;
       float4 InvProj;
       float4 Resol_HiZ_Lay;
       //xy is the resolution of Mip=0; z =0/1 to (de)activate HiZ; w =0/1 to (de)activate multilayers
   }
   
   struct VS_INPUTQUAD
   {
       float3 Pos : POSITION;
       float2 Tex : TEXCOORD0;
   };
   
   struct PS_INPUTQUAD
   {
       float4 Pos : SV_POSITION;
       float2 Tex : TEXCOORD0;
   };

   PS_INPUTQUAD VS_PostDeferredReflex(VS_INPUTQUAD Input)
   {
       PS_INPUTQUAD Output;
       Output.Pos = float4(Input.Pos.xyz, 1);
       Output.Tex = Input.Tex;
       return Output;
   };
   #define refinemax 4
   #define dot3falloff 0.00005
   #define Zthickness 0.00005

   void GetZLayer(float3 SSray, inout float3 ZBufferVal, inout float hit)
   {
   //for additional layer no Hierarchical Z is made so sample Mip 0
       ZBufferVal.gb = txDepth23.Load(int3(SSray.xy *Resol_HiZ_Lay.xy, 0)).rg+float2(0.000005f,0.000005f);
       ZBufferVal *= float3(1,ZBufferVal.g<1.000005,ZBufferVal.b<1.000005);
       float3 Delta = abs(SSray.zzz-ZBufferVal);            
       hit=1;
       if ( Delta.b  <= Delta.r ) {Delta.r = Delta.b;ZBufferVal.r = ZBufferVal.b;hit=3;}
       if ( Delta.g  <= Delta.r ) {ZBufferVal.r = ZBufferVal.g;hit=2;}
   }

   float4 GetColorSingle(float3 SSray, float Z, float D, float3 VSDir)
   {
       float3 Normal = txNormal[0].SampleLevel(samPoint, SSray.xy,0).rgb;
       float3 Color = txDiffuse[0].SampleLevel(samPoint, SSray.xy, 0).rgb;
       Normal=Normal*2-1;
       float Dot3 = ( Normal.x+Normal.y+Normal.z>2.99f)?dot3falloff:(dot(-VSDir, Normal)<0)?0:dot3falloff;
       if (abs(SSray.z-Z)<Dot3) return float4(Color,1-abs(SSray.z-D)*500);
       return float4(0,0,0,0);
   }

   float4 GetColorLayer(float3 SSray, float hit, float Z, float D, float3 VSDir)
   {
       float3 Normal  = float3(1,1,1);
       float3 Color  = float3(1,1,1);
       if ( hit==1) 
       {
           Normal = txNormal[0].SampleLevel(samPoint, SSray.xy,0).rgb;
           Color = txDiffuse[0].SampleLevel(samPoint, SSray.xy, 0).rgb;
       }
       if ( hit==2)
       {
           Normal = txNormal[1].SampleLevel(samPoint, SSray.xy,0).rgb;
           Color = txDiffuse[1].SampleLevel(samPoint, SSray.xy, 0).rgb;
       }
       if ( hit==3)
       {
           Normal = txNormal[2].SampleLevel(samPoint, SSray.xy,0).rgb;
           Color = txDiffuse[2].SampleLevel(samPoint, SSray.xy, 0).rgb;
       }
       Normal=Normal*2-1;
       float Dot3 = ( Normal.x+Normal.y+Normal.z>2.99f)?dot3falloff:(dot(-VSDir, Normal)<0)?0:dot3falloff;
       if (abs(SSray.z-Z)<Dot3) return float4(Color,1-abs(SSray.z-D)*500);
       return float4(0,0,0,0);
   }
   
   //NOTE the blend state used is classic opaque (no blend, src one dest zero blend add)
   //destination render target is cleared to 0 at start
   float4 PS_PostDeferredReflex(PS_INPUTQUAD Input) : SV_TARGET
   {
       float D = txDepth1.SampleLevel(samPoint, Input.Tex, 0).r;
       float4 ColorOut = txDiffuse[0].SampleLevel(samPoint, Input.Tex, 0);
       if ( D == 1 ) return ColorOut;
       float3 PosV = float3(InvProj.x*(Input.Tex.x*2-1),InvProj.y*(1-Input.Tex.y*2), 1)/(InvProj.z*D+1);
       if ( ColorOut.a <0.9)  return ColorOut;

       float3 VSDir = normalize(reflect(normalize(PosV),txNormal[0].SampleLevel(samPoint, Input.Tex, 0).rgb*2-1));    
       if ( VSDir.z < 0 ) return ColorOut;

       float4 SSEnd = mul(float4(PosV + VSDir*200, 1), Proj);
       SSEnd/= SSEnd.w;
       SSEnd.xy = SSEnd.xy*float2(0.5,-0.5)+float2(0.5,0.5); 
       float3 SSray = float3(Input.Tex, D);
       float3 SSDir = normalize(SSEnd.xyz-SSray);
       int Mip = 0;//you can start at higher Mip level but I don't see improvements
       float3 ZBufferVal=float3(1,1,1);
       float3 poslast = SSray;
       int i=0;
       int maxi=45;
       float DirSize = 0.02;
       float3 Delta= float3(0,0,0);
       float hit = 1;
       float4 Color ;
   //  mipmax is used to see the maximum mip level reached. see below
   //   int MipMax=0;
       if ( Resol_HiZ_Lay.z )
       {
           float4 X_stepoffset = float4(1,1,Zthickness,Zthickness);
           if (SSDir.x<0) X_stepoffset.x = -1.0;
           if (SSDir.y<0) X_stepoffset.y = -1.0;
           X_stepoffset.zw *= X_stepoffset.xy;
           X_stepoffset.xy = saturate(X_stepoffset.xy);
           float zDir = SSDir.z;//store dir.z for later use
           SSDir/=SSDir.z;// the algorithm needs normalization to SSDir.z = 1
           float2 CurResol;
           float3 tmp_ray;
           SSray+=SSDir*Zthickness;
           while((Mip > -1)&&(i < 40))
           {
               i++;
               CurResol = (int2(Resol_HiZ_Lay.xy)>>Mip);
               float2 old_cell_id = floor(SSray.xy * CurResol);
               ZBufferVal.r = txDepth1.Load(int3(old_cell_id, Mip)).r;
               if ( Resol_HiZ_Lay.w ) GetZLayer(SSray, ZBufferVal, hit);
               if (ZBufferVal.r==0)ZBufferVal.r=1; 
               float min_minus_ray = ZBufferVal.r - SSray.z;
               tmp_ray = SSray;
               if ( min_minus_ray > 0) 
               {
                   tmp_ray = SSray + SSDir*min_minus_ray;
                   float2 new_cell_id = floor(tmp_ray.xy * CurResol);
                   if ((new_cell_id.x != old_cell_id.x) || (new_cell_id.y != old_cell_id.y))
                   {
           //repositionne tmp_ray to match cell
                       float2 planes = (old_cell_id+ X_stepoffset.xy)/CurResol;
                       float2 solutions = (planes - SSray.xy)/SSDir.xy;
                       tmp_ray = SSray + SSDir * min(solutions.x, solutions.y);
                       tmp_ray.xy += (solutions.x < solutions.y) ? float2(X_stepoffset.z, 0) : float2(0, X_stepoffset.w);
           //going higher than mip 7 generate hole artifacts in my scene on the vertical plane
           //when at bottom screen.
           //help need to solve it if possible
                       Mip = min(7, Mip + 2.0f);
                       //if you wan tto see the maxmip level reached use this line
                       //if ( Mip > MipMax ) MipMax = Mip;
                   }
               }
               Mip--;
               SSray = tmp_ray;
               if ((SSray.x<0 )||(SSray.x >1 )||(SSray.y<0)||(SSray.y>1)) return ColorOut;
           }
           SSDir*=zDir;//restores appropriate SSDir increment for refinement
           if ( Resol_HiZ_Lay.w )
               Color = GetColorLayer(SSray, hit, ZBufferVal.r, D, VSDir);
           else
               Color = GetColorSingle(SSray, ZBufferVal.r, D, VSDir);
           if ( Color.a > 0 ) return lerp(Color,ColorOut,1-Color.a);
         //use these lines instead of previous one to see the maxmip level reached 
         /*     float4 MipColor = float4(MipMax==0||MipMax==3||MipMax==4||MipMax==6||MipMax==7, MipMax==1||MipMax==3||MipMax==5||MipMax==6||MipMax==8,                        MipMax==2||MipMax==4||MipMax==5||MipMax==6||MipMax==9,1);
          if ( Color.a > 0 ) return lerp(MipColor,ColorOut,1-Color.a);*/

          //for secondary loop with HiZ parameters maxi can be lower and DirSize coarser
          maxi=25;
          DirSize = 0.05;
          i=0;
      }
  //to skip next while showing vertical holes due to the HiZ algorithm
  //turn on return ColorOut;
  //Any better solution welcome as this makes the shader slower
  //  return ColorOut;
      SSDir*=DirSize;
      while (i<maxi)
      {
          i++;
          poslast = SSray;
          SSray += SSDir;
          if ((SSray.x<0 )||(SSray.x >1 )||(SSray.y<0)||(SSray.y>1)) break;
          ZBufferVal.r = txDepth1.SampleLevel(samPoint, SSray.xy, 0).r;
          if ( Resol_HiZ_Lay.w ) GetZLayer(SSray, ZBufferVal, hit);
          if (SSray.z > ZBufferVal.r) 
          {
              float3 MinRay = poslast;
              float3 MaxRay = SSray;
              for(int j = 0; j < refinemax; j++)
              {
                  poslast = (MinRay+MaxRay)*0.5f;
                  if( hit ==1 )
                          ZBufferVal.r = txDepth1.SampleLevel(samPoint, poslast.xy, 0).r;
                  else
                  if (Resol_HiZ_Lay.w)
                  {
                      if( hit ==2 ) ZBufferVal.r = txDepth23.SampleLevel(samPoint, poslast.xy, 0).r;
                      else ZBufferVal.r = txDepth23.SampleLevel(samPoint, poslast.xy, 0).g;
                  }
                  if ( poslast.z > ZBufferVal.r) MaxRay = poslast; else MinRay = poslast;
              }
              if ( Resol_HiZ_Lay.w )
                  Color = GetColorLayer(poslast, hit, ZBufferVal.r, D, VSDir);
              else
                  Color  = GetColorSingle(poslast, ZBufferVal.r, D, VSDir);
              if ( Color.a >0 ) return lerp(Color,ColorOut,1-Color.a);
            
              //to visualize where refinement apply use this line insteadof previous one
              //if ( Color.a >0 ) return lerp(float4(1,1,1,1),ColorOut,1-Color.a);
          }
      }
      return ColorOut;
  }

enter image description here

\$\endgroup\$
1
  • \$\begingroup\$ Don't forget to click the checkmark icon in the top left of the Answer to mark it as "accepted" if it worked for you. \$\endgroup\$
    – DMGregory
    Commented Sep 19, 2022 at 11:17

You must log in to answer this question.

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