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