So I was working on my own SSAO method, and I read this thread. Then I realized I could improve mine in a number of ways. In particular, I based my sampling pattern on ArKano's and used the notion of occluding discs. However, I use occluding spheres whose radius is limited in such a way as to prevent the self-occlusion of planes. I require this, because I'm doing a pool table, and I need interacting spheres and flat surfaces to look correct. Incidentally, it also diminishes the banding when under-sampling due to extreme close-ups.
I also work directly with the non-linear depth buffer. I have an efficient way of reconstructing the world space position from the non-linear depth, presuming the near plane is symmetrical.
I have yet to code an edge aware blur, so please excuse the noise. The essential code follows the images.
All together now:
Reconstructing world space position:
//Darryl Barnhart//www.dbarnhart.cavec3 viewDelta(vec2 screenSpaceCoords) { screenSpaceCoords = 2*screenSpaceCoords-vec2(1); return vec3(screenSpaceCoords/vec2(projection[0][0], projection[1][1]), -1);}float worldDepth(float nonlinearDepth) { return projection[3][2]/(1.0-2.0*nonlinearDepth-projection[2][2]);}vec3 worldPosition(vec2 coords, float nonlinearDepth) { return viewDelta(coords)*-worldDepth(nonlinearDepth);}
Note that -z is the forward direction.
The SSAO:
//Darryl Barnhart//www.dbarnhart.caconst float sampleRadius = 0.011;float sample(vec2 coords, float offsetFactor, vec3 basePosition, vec3 baseNormal) { float nonlinearDepth = texture2D(sceneDepth, coords).r; if(nonlinearDepth==1) return 0; vec3 position = worldPosition(coords, nonlinearDepth); vec3 displacement = position - basePosition; float distanceSqr = dot(displacement, displacement); float distance = sqrt(distanceSqr); vec3 direction = displacement/distance; float radiusFactor = dot(direction, baseNormal); if(radiusFactor<0.001) return 0; //Ignore samples behind the surface float radius = radiusFactor*sampleRadius*offsetFactor; float occlusion = 1-distance*inversesqrt(distanceSqr + radius*radius); return occlusion;}//Take for samples rotated by 90 degrees eachfloat fourSamples(vec2 baseCoords, vec2 baseOffset, float offsetFactor, vec3 basePosition, vec3 baseNormal) { baseOffset*=offsetFactor; float result=0; result+=sample(baseCoords+vec2( baseOffset.x, baseOffset.y), offsetFactor, basePosition, baseNormal); result+=sample(baseCoords+vec2( baseOffset.y, -baseOffset.x), offsetFactor, basePosition, baseNormal); result+=sample(baseCoords+vec2(-baseOffset.x, -baseOffset.y), offsetFactor, basePosition, baseNormal); result+=sample(baseCoords+vec2(-baseOffset.y, baseOffset.x), offsetFactor, basePosition, baseNormal); return result;}void main() { vec2 coords = gl_FragCoord.xy*screenSizeInv; float nonlinearDepth = texture2D(sceneDepth, coords).r; vec3 position = worldPosition(coords, nonlinearDepth); vec3 normal = normalize(texture2D(sceneNormals, coords).xyz); vec2 randomDirection = normalize(2*texture2D(randomTexture, coords/screenSizeInv.x/64.0).xy-vec2(1)); vec2 axisOffset = randomDirection*sampleRadius/-position.z; vec2 angleOffset = vec2(axisOffset.x-axisOffset.y, axisOffset.x+axisOffset.y)*(sqrt(2.0)/2); float occlusion = 0; occlusion+=fourSamples(coords, angleOffset, 0.25, position, normal); occlusion+=fourSamples(coords, axisOffset, 0.5, position, normal); occlusion+=fourSamples(coords, angleOffset, 0.75, position, normal); occlusion+=fourSamples(coords, axisOffset, 1.0, position, normal); gl_FragColor.r = clamp(1-occlusion/(occlusion+1), 0, 1);}
I still have one issue that you can see in the screenshots. When looking directly into very narrow crevices, like those between the ball and the table, the occlusion lightens at the narrowest part. I think that's what's referred to as "halo", and best I can tell, nothing can be done about it except more sampling.
Anyways, I have another idea for SSAO that I want to try. A lot of the slowness comes from reading from far away parts of the depth texture, not cache friendly. Also, proper ambient occlusion should take into account the entire scene, so an alleyway off a street becomes darker. I figured you could build a mipmap pyramid of the depth and normals, compute SSAO starting somewhere near the top, then progressively refine it by using the lower resolution and more global AO levels to modulate the higher resolution levels. You only need to sample the neighbouring pixels on each pass, so the cache will like it, and both global and local occlusion are captured. I doubt it would require any blur afterwards either. Still, I'm not sure how fast it would be in the end.
Any thoughts?
Darryl Barnhart
www.dbarnhart.ca
[s] [/s]
I can see the fnords.