# Am I missing something for ambient occlusion?

I'm trying to implement ambient occlusion in Python 3 and I'm seeing shadows beneath my reflective spheres but they seem very faint. I'm not sure if that means I've missed something, or if I just have a false impression of how much shadow results from ambient occlusion.

The result looks like this:

This is modeled as spheres (rather than triangle meshes). The 4 yellowish mirror spheres are hovering just above a very large sphere to approximate a plane, and the whole scene is surrounded by a very large white emissive sphere that provides the ambient sky light.

For each pixel sample rays are chosen with a Gaussian distribution around the pixel centre and more samples are chosen until the variance is sufficiently low. Rays reflect in the single specular reflection direction from the mirror spheres, and in any direction from the hemisphere of possible directions when hitting the floor. All rays eventually hit the white sky sphere and the colour is determined based on the losses along the way.

Is there some vital step I'm overlooking?

• Are you using path tracing or physically-accurate ray tracing? If so, then the ambient occlusion is already a built-in consequence of the algorithm and does not need to be specifically modelled. Intuitively, to me it seems correct that the shadow is faint: your spheres are mirrors and so diffuse rays shot from the ground near the sphere will tend to be reflected away and towards the skybox instead of back towards the ground as would happen with diffuse spheres. – yuriks Aug 12 '15 at 0:48
• do you know about srgb correction? If not that could be a factor. http.developer.nvidia.com/GPUGems3/gpugems3_ch24.html – Alan Wolfe Aug 12 '15 at 21:57
• I'm with Alan too, gamma can cause all sorts of contrast issues. But a uniformly emissive sphere will tend to produce very faint occlusions. You should try to use a falloff factor in the emission, such that vertical emissions are stronger than grazing emissions. Also your spheres are reflective, make then absorbing. but i guess your tracer does not take that into account otherwise we'd see more energy under your spheres. – v.oddou Oct 9 '15 at 1:24
• Your sky may be casting a little penumbra instead of a shadow. Reduce it to a small sphere for a check. Also, the spheres are indirectly illuminating the background. – Yves Daoust Oct 11 '15 at 18:45
• I found a real photo that proves you are missing some occlusion imgur.com/a/qcxmK – v.oddou Dec 26 '17 at 3:31

It's a bit hard to tell from your image, but it does look a bit faint. When debugging these kinds of things, it's always useful to strip down your scene as much as possible to remove any unnecessary complexity from the picture. In your case, try only creating a single diffuse sphere that touches the ground in one point. Give the ground and the sphere an albedo of 1. If ambient occlusion is implemented correctly, the point where the sphere touches the ground should have a pixel value of 0; the further away from the sphere you get, the closer the pixel values should go to 1 (not above - make sure nothing gets clamped when you output the image). Make sure to gamma correct your images.

The one thing that sticks out from your description is

Rays reflect [...] in any direction from the hemisphere of possible directions when hitting the floor

That by itself is fine, but you need to make sure to multiply the rays by the Lambertian BRDF (i.e. dot(normal, ray)/Pi). Even better is to directly sample from a cosine hemisphere, in which case all factors cancel out. This blog post has all the important info in one place.

• Wouldn't albedo values of 1 produce an image that never converges? Afaik, albedo should always be < 1 for convergence to happen. An albedo of 1 would specifically not cause a contact shadow because rays would be perfectly reflected until they reach a light source such as the sky. – yuriks Aug 12 '15 at 0:52
• In global illumination, there would be no contact shadow, yes. However, the question is about ambient occlusion, which only performs one bounce. Additionally, as long as the scene is not closed (i.e. the sky is visible), the scene would converge if global illumination was used even with albedo of 1. – Benedikt Bitterli Aug 12 '15 at 8:20
• The trick of putting the sphere on the ground and looking at the contact point is a great one. Notice it works for debugging your (spherical) lights too, but in the opposite direction! – imallett Oct 11 '15 at 5:04

I'll add some guidelines to help readers understand Benedikt Bitterli's statement "Make sure to gamma correct your images".

Gamma correcting images does not mean applying a power filter at the end. It means working in linear space during all calculations, and finally encoding the output into gamma space.

Gamma space is the color space into which all display machines of this age expect RGB values to be presented to them.

Linear space in opposition, is the fact of working with values which are proportional to physical emissions.

Therefore, when the artist edit the values of the albedos colors of the world surfaces, and the colors of the lights of the scene, he does it in gamma space because no edition software dare to modify what the user has input and just saves as-is. So the artist was looking at something that looked good for him, while stored in gamma space.

So the first step of all rendering engine, should be to convert all human edited input to linear space first. In your case that means all sphere colors and emissions values.

Then, you do your usual raytracing, brdf evaluation, monte carlo sampling, whatever process, in HDR if possible using float32 components if possible. (usually works nice with 128 bits SIMD as r,g,b,a vectors).

Finally you tone filter the HDR image according to the exposure value you choose, heuristically or manually. This operation could be very simple like a clamp. And then and only then, you encode the final image to gamma space.

The base is: a monitor will take an input value x and apply the formula x^2.2 to create the physical radiance on the output pixel.

Thus, the conversion formulas goes as follow:

linear to gamma: x^(1/2.2)
gamma to linear: x^2.2

Please note as trivia that sRGB space has a complex formula, a if, and some offset. But viewed from afar the curve is very very close to simple gamma.

Second thing to take into account, are you sure your sampling is fair ?
You could be sampling too much in the directions that privileges more rays to reach the sky. check the uniformity of your sampling. You said you use a gaussian that seems to me like you are going to ask for bias. Just use the classic Lambert distribution sampling: https://pathtracing.wordpress.com/ this way you reduce the number of samples necessary to get a variance equivalent with a purely uniform sphere. and you also save the cosine evaluation because its embedded in the distribution.

Lastly, if your spheres are really mirrors and your GI is supposedly actually working, it will accumulate reflected energy on the ground, therefore compensating your shadows from occlusion. I even expect to see more energy than darkening in such cases.

• Interesting - thank you. For the additional light reaching the floor from the spheres, I am expecting this to be less than the light that would reach the floor directly from the sky if the spheres were not there, since they are convex. That is, I can't picture caustics resulting from only convex surfaces when the light is coming evenly from the sky. Even in the extreme case of all light being reflected (no absorption), I would expect simply no shadow, rather than a caustic. – trichoplax Oct 9 '15 at 2:47
• I understand your intuition. I wanted to dare make an analytical computation for what energy we should expect on a pixel laying just under the vertical tangent of the sphere but I'm so slow at math I gave up lol. Indeed convex surfaces will reflect through only one path to any given singular emission source-point, so caustics seems implausible. – v.oddou Oct 16 '15 at 1:19