Skip to main content
deleted 9668 characters in body
Source Link
philB
  • 159
  • 9

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). Unfortunately

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.

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;
  }

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). 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.

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.

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;
  }
deleted 9668 characters in body
Source Link
philB
  • 159
  • 9

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;
  }

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;
  }
added 4446 characters in body
Source Link
philB
  • 159
  • 9

In contrast to other codes available on websitesThe corrected shader is below. The HiZ loop comes from (this onehere . It was easy to implement and give fine results) I have decoupled the increasing/decreasing Mip level parts. A first while advances the ray with increasing Mip and raystep until ray.z>zbuff. Then starting from the last position where ray.z<zbuff a second loop works with only Mip=0. Finally a refinement step is done. Doing it this way avoids the single inc/dec mip while loop found elsewhere that I found not so fast enought for me at the end. Also other codes are using the concept of boundary cells and I had difficulties to understand how they calculate the ray increase from these cells.

 //parameter for finer or coarser primary while results
 #define offset0 0.02
 #define offsetR 2
 //my initial resolution (Mip=0)
 static const int2 Resolution = {960, 540};
 //uv offset1 : critical to ensure correct uv sampling. modulate the values higher than 1 to see effect
 static const float2 offset1 = {1, 1.035};
 //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

 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);
     //only the ground mesh is alpha=1 to receive reflections
     if ( txDiffuse1.SampleLevel(samPoint, Input.Tex, 0).a == 0)  return float4(0,0,0,0);
     //calculalate raydir in view space
     float3 PosV = float3(InvProj.x*(Input.Tex.x*2-1),InvProj.y*(1-Input.Tex.y*2), 1)/(InvProj.z*D+1);

     float3 VSDir = normalize(reflect(normalize(PosV.xyz),txNormal1.SampleLevel(samPoint, Input.Tex, 0).rgb*2-1));    
     if ( VSDir.z < 0 ) return float4(0,0,0,0);//filter off ray towards camera

the ray dir initilization method in screen space comes from here

     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
     //also starting with higher Mip level produces a loss of reflection at the bottom screen 
     float ZBufferVal;
     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

     while (Mip<10)//my resolution corresponds to 10 miplevel
     {
             //using the offset1 ensure correct uv sampling. remove to see what i mean
             //use Load as below or SampleLevel
             //CurResol = (Resolution>>Mip)*SSray.xy*offset1;
             //ZBufferVal = txDepth1.Load(int3(CurResol, Mip)).r;
             ZBufferVal = txDepth1.SampleLevel(samPoint, SSray.xy*offset1, Mip).r; //or use Load as above
             if ( ZBufferVal==0 ) ZBufferVal=1; //avoid disappearing reflexions at bottom screen
             if (SSray.z > ZBufferVal) break;
             SSray += curSSDir;
             if ( Mip <10 ) 
             {
                 Mip++;
                 curSSDir*=2;
             }
    }
    if (SSray.z>=1) return float4(0,0,0,0);

secondary while : move ray from last position (z<zbuffer) now only at level Mip = 0 until ray.z > zbuffer

         float3 poslast = SSray;
         SSDir*=offset2;
         while (ZBufferVal!=0)//otherwise you should be outbound according to DX specs
         {
             SSray += SSDir;
             ZBufferVal = txDepth1.SampleLevel(samPoint, SSray.xy, 0).r;    
             if (SSray.z > ZBufferVal) break;
             poslast = SSray;
         }

refinement step

         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;
         }

some alpha checking before returning the color. remember D is the depth at start

         float3 N = txNormal1.SampleLevel(samPoint, poslast.xy,0).rgb*2-1;
         //N.x+N.y+N.z>2.99f is for the spot lights (red/blue..) not lited. If femoved the spotlight are not reflected
         float Dot3 = ( N.x+N.y+N.z>2.99f)?dot3falloff:(dot(-VSDir, N)<0)?0:dot3falloff;
         if (abs(poslast.z-ZBufferVal)<Dot3) 
             return float4(txDiffuse1.SampleLevel(samPoint, poslast.xy, 0).rgb, 1-abs(poslast.z-D)*500);
         return float4(0,0,0,0);
     }
   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;
  }

In contrast to other codes available on websites (this one was easy to implement and give fine results) I have decoupled the increasing/decreasing Mip level parts. A first while advances the ray with increasing Mip and raystep until ray.z>zbuff. Then starting from the last position where ray.z<zbuff a second loop works with only Mip=0. Finally a refinement step is done. Doing it this way avoids the single inc/dec mip while loop found elsewhere that I found not so fast enought for me at the end. Also other codes are using the concept of boundary cells and I had difficulties to understand how they calculate the ray increase from these cells.

 //parameter for finer or coarser primary while results
 #define offset0 0.02
 #define offsetR 2
 //my initial resolution (Mip=0)
 static const int2 Resolution = {960, 540};
 //uv offset1 : critical to ensure correct uv sampling. modulate the values higher than 1 to see effect
 static const float2 offset1 = {1, 1.035};
 //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

 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);
     //only the ground mesh is alpha=1 to receive reflections
     if ( txDiffuse1.SampleLevel(samPoint, Input.Tex, 0).a == 0)  return float4(0,0,0,0);
     //calculalate raydir in view space
     float3 PosV = float3(InvProj.x*(Input.Tex.x*2-1),InvProj.y*(1-Input.Tex.y*2), 1)/(InvProj.z*D+1);

     float3 VSDir = normalize(reflect(normalize(PosV.xyz),txNormal1.SampleLevel(samPoint, Input.Tex, 0).rgb*2-1));    
     if ( VSDir.z < 0 ) return float4(0,0,0,0);//filter off ray towards camera

the ray dir initilization method in screen space comes from here

     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
     //also starting with higher Mip level produces a loss of reflection at the bottom screen 
     float ZBufferVal;
     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

     while (Mip<10)//my resolution corresponds to 10 miplevel
     {
             //using the offset1 ensure correct uv sampling. remove to see what i mean
             //use Load as below or SampleLevel
             //CurResol = (Resolution>>Mip)*SSray.xy*offset1;
             //ZBufferVal = txDepth1.Load(int3(CurResol, Mip)).r;
             ZBufferVal = txDepth1.SampleLevel(samPoint, SSray.xy*offset1, Mip).r; //or use Load as above
             if ( ZBufferVal==0 ) ZBufferVal=1; //avoid disappearing reflexions at bottom screen
             if (SSray.z > ZBufferVal) break;
             SSray += curSSDir;
             if ( Mip <10 ) 
             {
                 Mip++;
                 curSSDir*=2;
             }
    }
    if (SSray.z>=1) return float4(0,0,0,0);

secondary while : move ray from last position (z<zbuffer) now only at level Mip = 0 until ray.z > zbuffer

         float3 poslast = SSray;
         SSDir*=offset2;
         while (ZBufferVal!=0)//otherwise you should be outbound according to DX specs
         {
             SSray += SSDir;
             ZBufferVal = txDepth1.SampleLevel(samPoint, SSray.xy, 0).r;    
             if (SSray.z > ZBufferVal) break;
             poslast = SSray;
         }

refinement step

         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;
         }

some alpha checking before returning the color. remember D is the depth at start

         float3 N = txNormal1.SampleLevel(samPoint, poslast.xy,0).rgb*2-1;
         //N.x+N.y+N.z>2.99f is for the spot lights (red/blue..) not lited. If femoved the spotlight are not reflected
         float Dot3 = ( N.x+N.y+N.z>2.99f)?dot3falloff:(dot(-VSDir, N)<0)?0:dot3falloff;
         if (abs(poslast.z-ZBufferVal)<Dot3) 
             return float4(txDiffuse1.SampleLevel(samPoint, poslast.xy, 0).rgb, 1-abs(poslast.z-D)*500);
         return float4(0,0,0,0);
     }

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;
  }
Source Link
philB
  • 159
  • 9
Loading