Hi all,
I've been looking to improve my SSR implementation from just traditionally marching in world space and projecting back to screen space.
I camr across a paper entitled “Efficient GPU Screen-Space Ray Tracing” by Morgan Mcguire https://jcgt.org/published/0003/04/04/ which uses DDA to march in screen space and keeps track of the view-space position while doing so.
I have implemented the algorithm however I am getting a weird curved-like reflections and I am not entirely sure why and I cannot seem to understand where I am going wrong. I was wondering if anyone has had a crack at implementing this paper or similar and might know where I am going wrong. I'm using Vulkan + GLSL
vec3 ssr()
{
float maxDistance = debugRenderer.maxDistance;
ivec2 c = ivec2(gl_FragCoord.xy);
float stepSize = debugRenderer.stepSize;
float stride = debugRenderer.stepCount;
float jitter = float((c.x+c.y)&1)*0.5;
// World-Space
vec3 WorldPos = texture(gBuffPosition, uv).rgb;
vec3 WorldNormal = (texture(gBuffNormal, uv).rgb);
// View-space
vec4 viewSpacePos = ubo.view * vec4(WorldPos, 1.0);
vec3 viewSpaceCamPos = vec4(ubo.view * vec4(ubo.cameraPosition.xyz, 1.0)).xyz;
vec3 viewDir = normalize(viewSpacePos.xyz - viewSpaceCamPos.xyz);
vec4 viewSpaceNormal = normalize(ubo.view * vec4(WorldNormal, 0.0));
vec3 viewReflectionDirection = normalize(reflect(viewDir, viewSpaceNormal.xyz));
float nearPlaneZ = -debugRenderer.nearplane;
float rayLength = ((viewSpacePos.z + viewReflectionDirection.z * maxDistance) > nearPlaneZ) ?
(nearPlaneZ - viewSpacePos.z) / viewReflectionDirection.z : maxDistance;
vec3 viewSpaceEnd = viewSpacePos.xyz + viewReflectionDirection * rayLength;
// Screen-space start and end points
vec4 ssStart = ubo.projection * vec4(viewSpacePos.xyz, 1.0);
vec4 ssEnd = ubo.projection * vec4(viewSpaceEnd, 1.0);
float rcpK0 = 1.0 / ssStart.w;
float rcpK1 = 1.0 / ssEnd.w;
// Camera-space positions scaled by rcp
vec3 Q0 = viewSpacePos.xyz * rcpK0;
vec3 Q1 = viewSpaceEnd.xyz * rcpK1;
// Perspective divide to get into screen space
vec2 P0 = ssStart.xy * rcpK0;
vec2 P1 = ssEnd.xy * rcpK1;
P0.xy = P0.xy * 0.5 + 0.5;
P1.xy = P1.xy * 0.5 + 0.5;
// If the distance squared between P0 and P1 is smaller than the threshold, adjust P1
P1 += vec2((distanceSquared(P0, P1) < 0.001) ? 0.01 : 0.0);
vec2 delta = P1 - P0;
// check which axis is larger. We want move in the direction where axis is larger first for efficiency
bool permute = false;
if(abs(delta.x) < abs(delta.y))
{
// the y is larger than x therefore we want to step in the larger y axis
permute = true;
delta = delta.yx;
P0 = P0.yx;
P1 = P1.yx;
}
float stepDir = sign(delta.x); // Direction for stepping in screen space
float invdx = stepDir / delta.x; // Inverse delta.x for interpolation
vec3 dQ = (Q1 - Q0) * invdx; // Camera-space position interpolation
float dk = (rcpK1 - rcpK0) * invdx; // Reciprocal depth interpolation
vec2 dP = vec2(stepDir, delta.y * invdx); // Step in screen space
dP *= stride;
dQ *= stride;
dk *= stride;
P0 = P0 + dP * jitter;
Q0 = Q0 + dQ * jitter;
rcpK0 = rcpK0 + dk * jitter;
float prevZMaxEstimate = viewSpacePos.z;
vec2 hitPixel = vec2(-1, -1);
vec3 Q = Q0;
float k = rcpK0;
float stepCount = 0.0;
float end = P1.x * stepDir;
float maxSteps = debugRenderer.steps;
for (vec2 P = P0; ((P.x * stepDir) <= end) && (stepCount < maxSteps); P += dP, Q.z += dQ.z, k += dk, stepCount += 1.0){
// Reconstruct screen-space pixel
hitPixel = permute ? P.yx : P;
float rayZMin = prevZMaxEstimate;
float rayZMax = (dQ.z * 0.5 + Q.z) / (dk * 0.5 + k);
prevZMaxEstimate = rayZMax;
if(rayZMin > rayZMax) {
float temp = rayZMin;
rayZMin = rayZMax;
rayZMax = temp;
}
// compare ray depth to current depth at pixel
float sceneZMax = LinearizeDepth(texelFetch(depthTex, ivec2(hitPixel), 0).x);
//sceneZMax = ubo.view * (vec4(sceneZMax.xyz, 1.0));
float sceneZMin = sceneZMax - debugRenderer.thickness;
if (((rayZMax >= sceneZMin) && (rayZMin <= sceneZMax))) {
break;
}
}
Q.xy += dQ.xy * stepCount;
vec3 hitPoint = Q * (1.0 / k);
// Transform the hit point to screen-space
vec4 ss = ubo.projection * vec4(hitPoint, 1.0); // Apply the projection matrix
ss.xyz /= ss.w; // Perspective divide to get normalized screen coordinates
ss.xy = ss.xy * 0.5 + 0.5; // Convert from NDC to screen-space
if(!inScreenSpace(vec2(ss.x, ss.y)))
{
return vec3(0.0);
}
vec2 csZBufferSize = textureSize(depthTex, 0);
if(all(lessThanEqual(abs(hitPixel - (csZBufferSize * 0.5)), csZBufferSize * 0.5)))
{
return texture(albedo, ss.xy).rgb;
}
return vec3(0.0);
}
Current output with stride = 1, steps = 25, maxDist = 0.051