Advertisement

Screen Space Reflections with DDA are distorted

Started by December 17, 2024 03:39 PM
0 comments, last by snoken 5 days ago

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

Advertisement