# Screen space reflections bug

I try to implement screen space reflections in my graphics engine using ray marching algorithm. After a lot of trying I got this result:

https://youtu.be/yzkgpcliBVQ

As you can see, the reflections are cut off if you get closer and stretched if you move further away. All vectors seem to be in view space, the transformation function between spaces is correct.

void main()
{
vec4 texelNormalWS = getNormal(TexCoords);

vec4 texelNormalVS = inverse(transpose(view)) * texelNormalWS;
vec4 texelPositionWS = getPosition(TexCoords);

vec3 viewDirVS = (view * texelPositionWS).xyz;
vec3 reflectDirVS = normalize(reflect(normalize(viewDirVS.xyz),normalize(texelNormalVS.xyz)));

...

color = vec4(ssrVS(viewDirVS, reflectDirVS),1.0f);
}

vec2 viewSpaceToSS(vec3 position)
{
vec4 pVP = proj * vec4(position,1.0f);
pVP.xy /=pVP.w;
pVP.xy = pVP.xy * 0.5f + 0.5f;
return pVP.xy;
}

vec2 BinarySearchVS(vec3 ray, vec3 dir)
{
vec2 projectedCoords;
float depth;

for(int i = 0; i < 5; i++)
{
projectedCoords = viewSpaceToSS(ray);
depth = getPosition(projectedCoords).z;

float dDepth = ray.z - depth;

dir *= 0.5f;
if(dDepth > 0.0f)
ray += dir;
else
ray -= dir;
}

ray.z-=depth;
return viewSpaceToSS(ray);
}

vec3 ssrVS(vec3 ray, vec3 dir)
{
float stepSize = 0.2f;
const int maxSteps = 100;
vec2 projectedCoords;

dir*=stepSize;

for(int i = 0; i < maxSteps; i++)
{
ray+=dir;

projectedCoords = viewSpaceToSS(ray);
float depth = getPosition(projectedCoords).z;

if (depth > 100.0f)
continue;

float dDepth = ray.z - depth;
if(dDepth <= 0 && dir.z - dDepth < 1.2)
projectedCoords = BinarySearchVS(ray,dir);
}

return getColor(projectedCoords).xyz;
}



Any idea how to fix this distortion?

UPD:

I figured out that

if(dDepth <= 0 && dir.z - dDepth < 1.2)


doesn't hit most of the time.

I added after hit checking branch:

return vec3(1.0f,0.0f,0.0f);


got this:

• Why did you need this algorithm? I'm using simple technique for this like rendering the reflected scene to a texture and using the screeen projected coordinates as we do for shadows. You can also use stencil buffer as for mirrors. May 2 at 7:03
• Since this is inefficient, you need to render the scene twice. May 2 at 13:52

If your are still interested by an answer, after working around myself with ssr, I came back to your code and ported for DX. I made some changes and below is the result. I use the ZMin/ZMax test inspired from the McGuire method instead of your dDepth test. At the end this modified code is the faster I have for now. Regarding the offset mirroring at horizon in the picture can it be a problem of normal used for ray direction not in the [-1,1] range? I saw problems like this when normal are saved in texture in the [0,1] range and not converted back to [-1,1] range.

I introduces a test on world position to skip self reflection on ground.

I hope this will have some insterest for you.

float2 BinarySearchVS(float3 ray, float3 dir)
{
float4 PCoords;
int3 TexCoords;
float SX = (ScreenX-1)*0.5f;
float SY = (ScreenY-1)*0.5f;
float ZMax = ray.z;
float ZMin = ray.z;
float ZCur = ray.z;
for(int i = 0; i < 5; i++)
{
PCoords = mul(float4(ray, 1), Proj);//retrieve coords in [-1,1] range
PCoords.xy /=PCoords.w;
if (( PCoords.x<-1 ) || ( PCoords.x >1 ) || ( PCoords.y<-1) || ( PCoords.y>1)) break;// exit if out of screen
TexCoords = int3(int(SX+PCoords.x*SX), int(SY-PCoords.y*SY), 0);//get coords in screen size for DX Tex.Load method
ZCur = txDepth1.Load(TexCoords).r;//get Depth from regular ZBuffer
if ( ZCur == 0 ) break;
ZCur = 1/(InvProj.z*ZCur+1);//retrieve Pos.z only in view space
dir *= 0.5f;
ZMin = ray.z; //set ZMin as old ray.z
if (ZCur>ZMax) ray+=dir; else ray-=dir;//testing for dir sign
if ((ZCur<ZMax)&&(ZCur>ZMin)) break;//If current depth=Pos.z in the range min/max stop
ZMax = ray.z; //set ZMax as new ray.z
}
PCoords = mul(float4(ray, 1), Proj);
return PCoords.xy /=PCoords.w;//retrieve coords in [-1,1] range
}
//some defined values
#define stepSize 4
#define maxSteps 35
#define thickness 1
#define UseBinary 1

float4 ssrVS(float3 ray, float3 dir)
{
dir*=stepSize;
float4 PosV;
float4 PosW;
float4 PCoords;
int3 TexCoords;
float SX = (ScreenX-1)*0.5f;
float SY = (ScreenY-1)*0.5f;
float ZMax = ray.z;
float ZMin = ray.z;
float ZCur = ray.z;
for(int i = 0; i < maxSteps; i++)
{
ZMin = ray.z;
ray+=dir;
ZMax = ray.z;
PCoords = mul(float4(ray, 1), Proj);
PCoords.xy /=PCoords.w;
if (( PCoords.x<-1 ) || ( PCoords.x >1 ) || ( PCoords.y<-1) || ( PCoords.y>1)) break;
TexCoords = int3(int(SX+PCoords.x*SX), int(SY-PCoords.y*SY), 0);
if ( ZCur == 0 ) break;
if ( ZCur < 1 )
{
PosV = float4(PCoords.xy, ZCur, 1);//here we need full PosV to get PosW
PosV.xyz = float3(InvProj.x*PosV.x,InvProj.y*PosV.y, 1)/(InvProj.z*PosV.z+1);
PosW=mul(PosV,InvView);
if ( PosW.y>0) //don’t self-reflect the ground who’s pos.y = 0
{
ZCur = PosV.z;
if ((ZCur<ZMax+thickness)&&(ZCur>ZMin))
{
if ( UseBinary > 0 )
{
PCoords.xy = BinarySearchVS(ray,dir);