Screen-Space Ambient Occlusion

The results of this effect are pretty awesome, and it seems like a self-contained post-processing effect. Has anyone done any work with this, or are there any GLSL examples around?:
http://www.gamedev.net/community/forums/topic.asp?topic_id=463075

Also read this for lots of useful links:
http://levelofdetail.wordpress.com/2008/02/10/2007-the-year-ssao-broke/

You can have a look at the Crysis Cg shader source if you have the game (or demo)

Yes, I’ve done some experimentation. I’ve got good results that work over 60 fps on a 8600 GT but with ugly outlines, so I stuck with the standard way of doing it. In my first experiment, I tried it with a gaussian blurred texture, taken from the bloom pass. If anyone has any success with a gaussian blurred texture without outlines of objects, please tell me how you removed the artifacts.

I’ve posted a small post about SSAO on my blog:
http://rajeshdmonte.blogspot.com/2008/04/experiments-with-ssao-screen-space.html

I’ve been made an implementation about this technique.
check out my blog:

http://gamedusa.blogspot.com

For some GLSL code you can go to RGBA homepage of Iñigo Quilez.

I’ve been made an implementation about this technique.
check out my blog:

http://gamedusa.blogspot.com

For some GLSL code you can go to RGBA homepage of Iñigo Quilez.

Iñigo’s code looks good, but he uses an extra render target to output a linear depth, which I think is completely unnecessary. I want to write a shader that will just work on any generic depth buffer…then people can just plug it into their engine, and BOOM! instant real-time GI (sort of). :wink:

Well, here’s code to convert the exponential depth to a linear value (thanks Humus):
// Convert exponential depth value to linear depth
float f=1000.0;
float n = 0.1;
float z = (2 * n) / (f + n - texture2D( texture0, texCoord ).x * (f - n));

I am afraid I do not understand the rest of his shader…so it’s either a matter of copying everything exactly, or implementing my own method from scratch.
http://rgba.scenesp.org/iq/computer/articles/ssao/ssao.htm

Well, right now this is basically just a soft outline shader:

uniform sampler2D texture0;
uniform vec2 camerarange;

varying vec2 texCoord;


float readDepth( in vec2 coord ) {
	return (2.0 * camerarange.x) / (camerarange.y + camerarange.x - texture2D( texture0, coord ).x * (camerarange.y - camerarange.x));	
}


void main(void)
{	
	float depth = readDepth( texCoord );
	float d;
	
	float pw = 1.0 / 800.0 * 2.0;
	float ph = 1.0 / 600.0 * 2.0;

	float ao = 0.0;
	
	float aoMultiplier=1000.0;

	float depthTolerance = 0.001;
	
	d=readDepth( vec2(texCoord.x+pw,texCoord.y+ph));
	ao+=max(0.0,d-depth-depthTolerance) * aoMultiplier;

	d=readDepth( vec2(texCoord.x-pw,texCoord.y+ph));
	ao+=max(0.0,d-depth-depthTolerance) * aoMultiplier;

	d=readDepth( vec2(texCoord.x+pw,texCoord.y-ph));
	ao+=max(0.0,d-depth-depthTolerance) * aoMultiplier;

	d=readDepth( vec2(texCoord.x-pw,texCoord.y-ph));
	ao+=max(0.0,d-depth-depthTolerance) * aoMultiplier;
	
	ao/=4.0;
	ao=min(ao,0.25);
	
	gl_FragColor = vec4(1.0-ao);
}

Here it is multiplied by 0.5. The problem is that in very corners, the sample right next to one surface is the same depth, so it doesn’t detect a difference, and it makes the corner bright.

Here is it, the shader you have been waiting for, perhaps dreaming of.

vert:

varying vec2 texCoord;

void main(void) {
	gl_Position = ftransform();
	texCoord=gl_MultiTexCoord0.xy;
	gl_FrontColor = gl_Color;
}

frag:

uniform sampler2D texture0;
uniform sampler2D texture1;

uniform vec2 camerarange;
uniform vec2 screensize;

varying vec2 texCoord;


float readDepth( in vec2 coord ) {
	return (2.0 * camerarange.x) / (camerarange.y + camerarange.x - texture2D( texture0, coord ).x * (camerarange.y - camerarange.x));	
}


void main(void)
{	
	float depth = readDepth( texCoord );
	float d;
	
	float pw = 1.0 / screensize.x;
	float ph = 1.0 / screensize.y;

	float aoCap = 1.0;

	float ao = 0.0;
	
	float aoMultiplier=1000.0;

	float depthTolerance = 0.0001;
	
	d=readDepth( vec2(texCoord.x+pw,texCoord.y+ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	d=readDepth( vec2(texCoord.x-pw,texCoord.y+ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	d=readDepth( vec2(texCoord.x+pw,texCoord.y-ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	d=readDepth( vec2(texCoord.x-pw,texCoord.y-ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);
	
	pw*=2.0;
	ph*=2.0;
	aoMultiplier/=2.0;

	d=readDepth( vec2(texCoord.x+pw,texCoord.y+ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	d=readDepth( vec2(texCoord.x-pw,texCoord.y+ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	d=readDepth( vec2(texCoord.x+pw,texCoord.y-ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	d=readDepth( vec2(texCoord.x-pw,texCoord.y-ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	pw*=2.0;
	ph*=2.0;
	aoMultiplier/=2.0;

	d=readDepth( vec2(texCoord.x+pw,texCoord.y+ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	d=readDepth( vec2(texCoord.x-pw,texCoord.y+ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	d=readDepth( vec2(texCoord.x+pw,texCoord.y-ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	d=readDepth( vec2(texCoord.x-pw,texCoord.y-ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	pw*=2.0;
	ph*=2.0;
	aoMultiplier/=2.0;

	d=readDepth( vec2(texCoord.x+pw,texCoord.y+ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	d=readDepth( vec2(texCoord.x-pw,texCoord.y+ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	d=readDepth( vec2(texCoord.x+pw,texCoord.y-ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	d=readDepth( vec2(texCoord.x-pw,texCoord.y-ph));
	ao+=min(aoCap,max(0.0,depth-d-depthTolerance) * aoMultiplier);

	ao/=16.0;
	
	gl_FragColor = vec4(1.0-ao) * texture2D(texture1,texCoord);
}

Uniforms:
vec2 camerarange - near and far camera range
vec2 screensize - screen width and height

mh all i get are black outlines around my meshes.
how do you render texture0? i pass -(gl_ModelViewMatrix*gl_Vertex) to the fragment shader and output its length.
what’s texture1 supposed to be? the original scene?
what about the normal map Quilez uses?
do i need to perform a blur pass afterwards?

Texture0 is the depth texture. Texture1 is the rendered scene.

You could get rid of the texture1 lookup, and just use this to render the SSAO effect. Then combine this in another pass with the rendered scene, blurring this texture on top of the rendered scene.

This one will taper the effect off over a certain depth, so you won’t have an object in the foreground casting black outlines on the sky behind it:

uniform sampler2D texture0;
uniform sampler2D texture1;

uniform vec2 camerarange;
uniform vec2 screensize;

varying vec2 texCoord;


float readDepth( in vec2 coord ) {
	return (2.0 * camerarange.x) / (camerarange.y + camerarange.x - texture2D( texture0, coord ).x * (camerarange.y - camerarange.x));	
}

float compareDepths( in float depth1, in float depth2 ) {
	float aoCap = 1.0;
	float aoMultiplier=10000.0;
	float depthTolerance=0.000;
	float aorange = 10.0;// units in space the AO effect extends to (this gets divided by the camera far range
	float diff = sqrt( clamp(1.0-(depth1-depth2) / (aorange/(camerarange.y-camerarange.x)),0.0,1.0) );
	float ao = min(aoCap,max(0.0,depth1-depth2-depthTolerance) * aoMultiplier) * diff;
	return ao;
}

void main(void)
{	
	float depth = readDepth( texCoord );
	float d;
	
	float pw = 1.0 / screensize.x;
	float ph = 1.0 / screensize.y;

	float aoCap = 1.0;

	float ao = 0.0;
	
	float aoMultiplier=10000.0;

	float depthTolerance = 0.001;
	
	float aoscale=1.0;

	d=readDepth( vec2(texCoord.x+pw,texCoord.y+ph));
	ao+=compareDepths(depth,d)/aoscale;

	d=readDepth( vec2(texCoord.x-pw,texCoord.y+ph));
	ao+=compareDepths(depth,d)/aoscale;

	d=readDepth( vec2(texCoord.x+pw,texCoord.y-ph));
	ao+=compareDepths(depth,d)/aoscale;

	d=readDepth( vec2(texCoord.x-pw,texCoord.y-ph));
	ao+=compareDepths(depth,d)/aoscale;
	
	pw*=2.0;
	ph*=2.0;
	aoMultiplier/=2.0;
	aoscale*=1.2;
	
	d=readDepth( vec2(texCoord.x+pw,texCoord.y+ph));
	ao+=compareDepths(depth,d)/aoscale;

	d=readDepth( vec2(texCoord.x-pw,texCoord.y+ph));
	ao+=compareDepths(depth,d)/aoscale;

	d=readDepth( vec2(texCoord.x+pw,texCoord.y-ph));
	ao+=compareDepths(depth,d)/aoscale;

	d=readDepth( vec2(texCoord.x-pw,texCoord.y-ph));
	ao+=compareDepths(depth,d)/aoscale;

	pw*=2.0;
	ph*=2.0;
	aoMultiplier/=2.0;
	aoscale*=1.2;
	
	d=readDepth( vec2(texCoord.x+pw,texCoord.y+ph));
	ao+=compareDepths(depth,d)/aoscale;

	d=readDepth( vec2(texCoord.x-pw,texCoord.y+ph));
	ao+=compareDepths(depth,d)/aoscale;

	d=readDepth( vec2(texCoord.x+pw,texCoord.y-ph));
	ao+=compareDepths(depth,d)/aoscale;

	d=readDepth( vec2(texCoord.x-pw,texCoord.y-ph));
	ao+=compareDepths(depth,d)/aoscale;
	
	pw*=2.0;
	ph*=2.0;
	aoMultiplier/=2.0;
	aoscale*=1.2;
	
	d=readDepth( vec2(texCoord.x+pw,texCoord.y+ph));
	ao+=compareDepths(depth,d)/aoscale;

	d=readDepth( vec2(texCoord.x-pw,texCoord.y+ph));
	ao+=compareDepths(depth,d)/aoscale;

	d=readDepth( vec2(texCoord.x+pw,texCoord.y-ph));
	ao+=compareDepths(depth,d)/aoscale;

	d=readDepth( vec2(texCoord.x-pw,texCoord.y-ph));
	ao+=compareDepths(depth,d)/aoscale;

	ao/=16.0;
	
	gl_FragColor = vec4(1.0-ao) * texture2D(texture1,texCoord);
}

Hello, your shader works for me and it looks pretty good.
I notice that there is an effect of diffuse lighting (looks like there was a light pointing from the camera to the object) and I would like to disable this effect. I tried to play whith some attributes in the compareDepth function but without removing it entirely.
Do you have the same observation in your application ? Do you think it’s possible to disable completly this effect ?
thanks

matcheu, I toyed with Leadwerks’ shader, and found the solution is only to clamp the “ao” value between 0.25 and 0.5 . This at least minimized the resulting flickering.
In the gamedev.net forum, a member of the Ogre3D community posted something that might be the real solution - requires having the surface-normals of the final image. I didn’t experiment with his code yet.

I actually found the camera angle artifact looks better because it provides some slight shading on curved surfaces, making the shape more apparent. When I “fixed” it people complained that it didn’t look as good.

I gave the shader a try but am not getting the expected results. The code for the shaders is identical so I figure I must have an error in my OpenGL state.

Check out the following images…

Here is a sample scene rendered without the SSAO shader.

Here is the same scene, rendered with the SSAO shader.

Notice the banding effect around the edges and the lack of ambient occlusion on any of the surfaces.

Here is the same scene rendered with a geometry based AO effect.

Notice the expected ambient occlusion on the plane below the sphere and/or cube and also between the sphere and cube. This is what the SSAO shader should look like (or at least approximate) but it doesn’t.

Anyone have any ideas?

Don’t forget that SSAO is a big image-space trick. Good parameters varies, depending on the scene rendered.

It looks like you want a larger search radius in this scene.
And you probably have to remove or move the gl light to better match your setup.

Incidentally NVIDIA now has a demo & paper of this that works quite well.

Hi guys,

I plan to implement this technique for my demo. Iam not that skilled, but I hope I can get this working. I have some questions though.

Shaders making SSAO are working with depth values, right ? So what I need is to pass depth buffer into my shader.

How do I do that ? I had an idea to use depth texture of my FBO and pass it via some texture unit. Will this work ?

And to note that this texture is rectangle texture, not ordinary squared texture. How should I get texture coordinates for this texture ? To load correct depth value for my fragment ?

Does someone have an demo example with source code that I can study ? (it has always been best for me to look at someone others code, than to code something from scratch)

Yes, you can render the scene into the depth texture attachment of FBO and pass it in a texture unit to SSAO shader.

The texture coordinates can be passed as vertex attributes. You need only 4 of them for the screen quad.