Jump to content
  • Advertisement
Sign in to follow this  

Alchemy Ambient Occlusion

This topic is 2122 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello all,


I've recently ported over some Alchemy AO code to glsl into my engine and get decent results:




The only issue seems to be the thin black edge around each object, which is exactly 1 pixel thick. Does anyone who has implemented Alchemy AO know what the cause of it is and how I can minimize it?


Here's my code:

const vec2 focalLength = vec2(1.0/tan(45.0*0.5)*screenRes.y/screenRes.x,1.0/tan(45.0*0.5));

float linearDepth(float d, float near, float far)
	d = d*2.0 - 1.0;
	vec2 lin = vec2((near-far)/(2.0*near*far),(near+far)/(2.0*near*far));
	return -1.0/(lin.x*d+lin.y);

vec3 UVtoViewSpace(vec2 uv, float z)
	vec2 UVtoViewA = vec2(-2.0/focalLength.x,-2.0/focalLength.y);
	vec2 UVtoViewB = vec2(1.0/focalLength.x,1.0/focalLength.y);
	uv = UVtoViewA*uv + UVtoViewB;
	return vec3(uv*z,z);

vec3 GetViewPos(vec2 uv)
	float z = linearDepth(texture(depthTex, uv).r,0.1,100.0);
	return UVtoViewSpace(uv, z);

vec2 RotateDir(vec2 dir, vec2 cosSin)
	return vec2(dir.x*cosSin.x - dir.y*cosSin.y, dir.x*cosSin.y + dir.y*cosSin.x);

vec2 tapLocation(int sampleNumber, float spinAngle, out float ssR)
	float alpha = float(sampleNumber + 0.5) * (1.0 / NUM_SAMPLES);
	float angle = alpha * (NUM_SPIRAL_TURNS * 6.28) + spinAngle;
	ssR = alpha;
	return vec2(cos(angle), sin(angle));

void main()
	float ao = 0.0;
	float aoRad = 0.5;
	float aoBias = 0.002;
	vec2 aoTexCoord = gl_FragCoord.xy/vec2(screenRes.x,screenRes.y);
	vec2 aoRes = vec2(screenRes.x,screenRes.y);
	vec3 aoRand = texture(aoRandTex, aoTexCoord*aoRes/1).xyz;
	vec3 aoC = GetViewPos(aoTexCoord);
	vec3 aoN_C = normalize(cross(dFdx(aoC),dFdy(aoC)));
	float uvDiskRadius = 0.5*focalLength.y*aoRad/aoC.z;
	vec2 pixelPosC = gl_FragCoord.xy;
	pixelPosC.y = screenRes.y-pixelPosC.y;
	float randPatternRotAngle = (3*int(pixelPosC.x)^int(pixelPosC.y)+int(pixelPosC.x)*int(pixelPosC.y))*10.0;
	float aoAlpha = 2.0*pi/NUM_SAMPLES + randPatternRotAngle;
	for(int i = 0; i < NUM_SAMPLES; ++i)
		float ssR;
		vec2 aoUnitOffset = tapLocation(i, randPatternRotAngle, ssR);
		ssR *= uvDiskRadius;
		vec2 aoTexS = aoTexCoord + ssR*aoUnitOffset;
		vec3 aoQ = GetViewPos(aoTexS);
		vec3 aoV = aoQ - aoC;
		float aoVv = dot(aoV,aoV);
		float aoVn = dot(aoV, aoN_C);
		float aoEp = 0.01;
		float aoF = max(aoRad*aoRad - aoVv, 0.0);
		ao += aoF*aoF*aoF*max((aoVn - aoBias)/(aoEp+aoVv),0.0);
	float aoTemp = aoRad*aoRad*aoRad;
	ao /= aoTemp*aoTemp;
	ao = max(0.0,1.0 - ao*(1.0/NUM_SAMPLES));

	color = vec4(vec3(ao),1);
Edited by gboxentertainment

Share this post

Link to post
Share on other sites

Okay, so I've resolved that issue of the edge problem.

It turned out that it is quite inaccurate when I reconstruct the camera space normals from the camera space position (which is constructed from depth).

Sampling from an actual normal texture eliminated the problem:

vec3 aoN_C = camSpaceNorm;//normalize(cross(dFdx(aoC),dFdy(aoC)));



Now I've just got to add bilateral filtering. I'll provide updates when I've done that.

Share this post

Link to post
Share on other sites

Glad you got it working. If you don't like the extra textures, you can also sample your position texture at offsets and do the derivatives manually, which removes the 2x2 pixel artifacts.

Share this post

Link to post
Share on other sites

Glad you got it working. If you don't like the extra textures, you can also sample your position texture at offsets and do the derivatives manually, which removes the 2x2 pixel artifacts.


But I thought that was what was causing the problem in the first place?

vec3 aoC = GetViewPos(aoTexCoord);
vec3 aoN_C = normalize(cross(dFdx(aoC),dFdy(aoC)));

Or are you saying that I offset aoTexCoord when sampling my depth texture? by how much?

Share this post

Link to post
Share on other sites

Actually I mean you can manually compute the derivatives instead of using dFdx and dFdy, which are the cause of the artifacts (they operate on 2x2 pixel blocks).


Essentially you just need to recompute your position at 4 different locations (sides of the pixel) and get the difference between them and the center. It's obviously more expensive than dFdx/dFdy (I'd assume :P) because you're sampling 4 more times, but it's artifact free and doesn't require storing a normal texture (if you're not using one for anything else).


Here's some pseudo code (not really tested but should work if I'm remembering this right):

vec3 aoC = GetViewPos( aoTexCoord );

vec3 aoC0 = GetViewPos( aoTexCoord + vec2( pixSize.x, 0.0 ) );
vec3 aoC1 = GetViewPos( aoTexCoord - vec2( pixSize.x, 0.0 ) );
vec3 aoC2 = GetViewPos( aoTexCoord + vec2( 0.0, pixSize.y ) );
vec3 aoC3 = GetViewPos( aoTexCoord + vec2( 0.0, pixSize.y ) );

vec3 dx = GetDerivative( aoC, aoC0, aoC1 );
vec3 dy = GetDerivative( aoC, aoC2, aoC3 );

vec3 aoN_C = normalize( cross( dx, dy ) );

pixSize is the inverse of your resolution (1 / res). The GetDerivative function looks something like this:

vec3 GetDerivative( vec3 p0, vec3 p1, vec3 p2 )
    vec3 v1 = p1 - p0;
    vec3 v2 = p0 - p2;
    return ( dot( v1, v1 ) < dot( v2, v2 ) ) ? v1 : v2;

Try it out and see if it works for you. If you're not going to use your normal texture elsewhere then the cost of generating it from geometry might be a bit much. smile.png

Edited by Styves

Share this post

Link to post
Share on other sites

Thanks for the advice - I haven't tried it yet though because I'm reusing a normal map from a previous pass.


Anyways, here are my results of cross-bilateral filtering:





Its reasonable, but the main concern that I've got is the blurring does not completely hide the noise. I have to use more samples, which is slower, but even if I do, it seems that the random sampling directions of this method is too uniform - i.e. the screen-space artifacts of "hovering" occlusion is still apparent - which is not really much better than that of standard ssao.

Another disadvantage seems to be the occluder distance falloff - which I can only increase when I increase the sampling radius (there must be some way to make this independent).

However, the main advantage of Alchemy AO is that flat surfaces are not incorrectly occluded.


I've also implemented the method by Arkano22 from these forums (http://www.gamedev.net/topic/550699-ssao-no-halo-artifacts/)

Here's a comparison below (left - Alchemy AO; right - Arkano AO):




Looking at these two methods, Arkano's method provides the ability to independently adjust occluder distance falloff - so I can start having occlusion when the occluder is a distance away from the occluded surface. It is also faster because it does not require any blur filtering. However, it suffers from some slight incorrect occlusion of flat surfaces.

Alchemy AO avoids the incorrect occlusion of flat surfaces but the blurred noise artifacts are still apparent.


Here's another comparison (left - Alchemy AO; right - Arkano AO):




This is an emissive object with specularity/reflection turned to its highest setting.

The Alchemy AO in the left picture captures all the subtle details, but the blurring makes it look like a diffuse material.

Arkano's method, does not capture all the details, but still retains the specular nature of the material.


Here's a picture that emphasizes the only problem which bugs me about Arkano's method:



Everything else looks really good, except for the per vertex occlusion artifacts.

With Arkano's AO, I am only using a depth buffer and nothing else. So it must be the depth buffer causing this.

Share this post

Link to post
Share on other sites


Good results so far. For the noise/blur problem - on console (where we use fewer samples), we use a regular (4x4) random rotation pattern (with 16 random angles in a texture), rather than completely random at every pixel. That helps tremendously - you get slightly worse (theoretical) total information extracted, but the noise is regular (and limited to a small window), so the blur is able to hide it much better.

Share this post

Link to post
Share on other sites
Sign in to follow this  

  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!