Weird SSAO halo

I was goofing around with SSAO and I realized something.

Firstly, here’s my SSAO fragment shader code:


#version 400 core

in vec2 passTextureCoords;

out vec4 outColour;

uniform sampler2D gPositionDepth;
uniform sampler2D gNormals;
uniform sampler2D noiseTexture;
uniform vec3 samples[32];
uniform mat4 projMatrix;

const vec2 noiseScale = vec2(1280.0/4.0, 780.0/4.0);

void main(void){
	
	vec3 fragPos = texture(gPositionDepth, passTextureCoords).xyz;
	vec3 normal = texture(gNormals, passTextureCoords).xyz;
	vec3 randomVec = texture(noiseTexture, passTextureCoords * noiseScale).xyz;
	
	vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal)).xyz;
	vec3 bitangent = cross(normal, tangent);
	mat3 TBN = mat3(tangent, bitangent, normal);
	
	float occlusion = 0;
	int radius = 2;
	for(int i = 0; i < 32; i++){
		vec3 samp = TBN * samples[i];
		samp = fragPos + samp * radius;
		
		vec4 offset = vec4(samp, 1.0);
		offset = projMatrix * offset;
		offset.xyz /= offset.w;
		offset.xyz = offset.xyz * 0.5 + 0.5;
		
		float sampleDepth = texture(gPositionDepth, offset.xy).z;
		float rangeCheck = abs(samp.z - sampleDepth) < radius ? 1.0 : 0.0;
		occlusion += (sampleDepth >= samp.z ? 1.0 : 0.0) * rangeCheck;
	}
	
	occlusion = 1 - occlusion/32;
	outColour = vec4(occlusion);
	
}

This works perfectly fine. No halos, nothing.

However, take a look at this part:


        float sampleDepth = texture(gPositionDepth, offset.xy).z;
        float rangeCheck = abs(samp.z - sampleDepth) < radius ? 1.0 : 0.0;
	occlusion += (sampleDepth >= samp.z ? 1.0 : 0.0) * rangeCheck;
}
	
occlusion = 1 - occlusion/32;

I thought it didn’t make sense. I mean, I should be checking if there’s any geometry occluding the sample in the sample kernel. Following logic, I should be checking if sampleDepth is less than samp.z

But then I noticed that I was doing

occlusion = 1 - occlusion/32;

. Because of the subtraction of the normalized occlusion factor from 1, it was working properly.

So I tried changing it to the following:


        float sampleDepth = texture(gPositionDepth, offset.xy).z;
        float rangeCheck = abs(samp.z - sampleDepth) < radius ? 1.0 : 0.0;
	occlusion += (sampleDepth <= samp.z ? 1.0 : 0.0) * rangeCheck;
}
	
occlusion = occlusion/32;

As you can see, I changed sampleDepth >= samp.z to sampleDepth <= samp.z, which makes more sense.

And I also removed the "1 - " part. But now there’s a black halo around the mesh, and it doesn’t look very nice. I see no reason why it should be like this. Maybe it’s just me being dumb, I dunno :stuck_out_tongue:

Initial results:

https://anuj-rao.tinytake.com/media/3e6002?filename=1474366755903_20-09-2016-06-19-00.png&sub_type=thumbnail_preview&type=attachment&width=700&height=427&_felix_session_id=a8b84e98fb6c0e7088ab4e1cbef1bc40&salt=OTgyNDQ0XzQwODc4MTA

After change:

https://anuj-rao.tinytake.com/media/3e5fe9?filename=1474366614222_20-09-2016-06-16-36.png&sub_type=thumbnail_preview&type=attachment&width=700&height=425&_felix_session_id=a8b84e98fb6c0e7088ab4e1cbef1bc40&salt=OTgyNDMxXzQwODc3ODU

Your algorithm places a couple of samples in 3D around the pixel in question, then compares the depth of the sample with the depth in the depth buffer. For the white background the depth is 1.0, so if anything is nearby it’ll obviously fail the depth test. You can prevent this by not applying SSAO when the depth is exactly 1.0. However, this problem occurs when you have any large differences in depth, as an object 10km away will get occluded by an ant 10cm from the camera. This this is an inherent flaw of your algorithm.

I didn’t set the background colour though. The background colour for both scenes is supposed to be white. I don’t get why the colour of the background changes as I move the camera around.

I may have misunderstood but here goes.

For the background, fragPos, normal and hence tangent and bitangent will (persumably) all be zero since they’ve been set when clearing the G-buffer. This causes all the samples to end up at (0, 0, 0), which is turned into the texture coordinates (0.5, 0.5, 0.5). In other words, every single background pixel will read the center of the screen and compare the depth of that pixel with 0.5. As you move the camera around, the background will change/flicker as the center pixel it’s compared to changes depth.

Hmm…actually, since I’m using the colour white to clear the G-buffer, the fragPos and normals for the background pixels should all be vec3(1,1,1), right?
But your theory still makes sense, it’s just that it’s being compared to a different pixel, not the center pixel. That’s actually ingenious, I never would’ve thought of that.