In this post I’ll go over the modifications I’ve made to my ambient light shader to have more realistic “global illumination”.
First let’s look at some of the problems:
What’s wrong in this image? The sun is shining on the bright orange cliff on the right, the but the shadowed cliff wall on the left is ignorant of this.
The article here describes a cheap way to mimic this effect: add another light that points in the opposite direction of the sun.
So let’s try this out. One place this quickly falls apart is when the sun is shining directly down. The “bounce light” will be coming from below. What happens to our birch trees, which were nicely lit during sunrise? Have a look, the bottom image is with the sun shining down:
The trunks are unrealistically dark because the bounce lighting isn’t really doing anything here: It’s coming from below, and so vertical surfaces are not lit. Granted, the article mentioned above (which is careful to note that its techniques are quick and dirty for demos) says that you should make the bounce light horizontal. When the sun is mostly overhead though, which horizontal direction do you choose?
But ok – let’s see what it looks like when the sun is at a lower angle (where a horizontal bounce direction makes more sense). Here’s a directional (sun) light only, near sunrise:
Now let’s put another directional light from the opposite direction with (Suncolor * SceneAlbedo). This is our “bounce light”:
And now combined:
Well that’s nice, except we see there is a dark area on the tree trunk where it is affected by neither light. That might be accurate if you were holding a mirror up to the sun, but not when the sun reflected off the whole scene. One option is to have the bounce light “wrap around” past 90 degrees a bit. That does do a decent job of fixing the above two issues.
But let’s take a closer look at what is actually happening: Sun is shining down, and lights up the scene. Let’s make a simple assumption that the scene is flat ground:
Assuming the sun is lighting up an “infinite” area around the tree, the bounce light affecting the tree will basically be a hemispheric light. It’s like the tree is lit by a cubemap, the bottom half of which is (SunColor * SceneAlbedo) modulated by the angle at which the sun is hitting the ground, and the top half of which is black (when considering only the bounce lighting, not the original sunlight).
This provides us with a little bit more real physical basis for the “wrap around” I mentioned for the bounce light:
float3 bounceLightColor = sunlightColor * sceneAlbedo * saturate(dot(sunDirection, groundUpVector)); float3 bounceLight = bounceLightColor * dot(surfaceNormal, groundUpVector) * 0.5 + 0.5;
Now the trunks are lit up by sunlight bouncing off the ground:
Now back to the canyon image at the beginning of this article. We wanted the light reflected off the opposite canyon wall to light up the shadowed wall a bit. Well, this hemispheric light isn’t going to do much for that – our light is coming from the ground hemisphere, and we’re essentially ignoring the sun direction (or rather, it’s only being used to determine the strength of the sunlight reflecting off the ground).
To get around this problem, we can use a modified ground plane. We’ll tilt it slightly towards the sun. This arguably has a physical basis – the sun isn’t just reflected off flat ground, it’s reflected off of “above ground” objects like other trees and rocks. Yes, there are objects on both sides of the point we’re lighting, but only the ones on the opposite side of the sun really contribute to bounce lighting.
The tilted ground normal is calculated like so:
Vector3 tiltedGroundNormal = Vector3.Normalize(actualGroundNormal * C_VALUE + directionToSun);
I use a value of 0.75 for C_VALUE currently.
So here’s one of those cliffs again, showing the directional light and with and without the bounce light (I’ve also turned off the overhead hemispheric ambient light, so that’s why the top image is completely dark in the shadowed areas):
I’ve used the term scene albedo a number of times so far. What is it? Well, I started off using a brownish green color with an albedo of about 0.2. 0.2 is a typical value for the grass, dirt, cliffs, etc.
I knew using a fixed scene albedo would be wrong, but the bounced light is a fairly minor component compared to the sun itself, so it’s not terribly noticeable. Except when the actual ground has a very different albedo – such as snow.
So I decided it was time to make a world albedo map. This is done by rendering the world from above, chunk by chunk, stitching it all together, and blurring it significantly. Snowfall is dynamic in my world though (what a problem that is!), so I also need to make a rough estimation of how much a particular piece of ground is affected by snow (e.g. water and cliffs aren’t), and then combine that with the current snowpack in that area. The result of doing this is that the trees are much better lit in bright snow:
Just for kicks, let’s compare some test objects: one out in the water vs one in the snow:
These are backlit, so most of the light we’re seeing on them comes from our ambient term (which includes scene albedo). If we compare them side by side, we can see the significant different in brightness:
So this is a very very rough approximation of objects inheriting some color from their surroundings. If I paint the ground bright colors, it’s more noticeable:
It’s important to note that this is not directional though. The cylinder in the middle is a mix of green and magenta even though from this angle it would be greenish in the real world. For a directional solution we could use spherical harmonics. I describe my experience with that here.
The other ambient term
So far we’ve been talking about the sunlight bounce term. There’s also the ambient term that describes how the object is lit by the sky (as opposed to the sun/moon). With everything we’ve talked about so far, this one becomes pretty obvious. For this I use a hemispheric light that goes from the SkyColor on top to SkyColor * SceneAlbedo on the bottom. This is basically what I did before, except now we have the benefit that it gets some color from its surroundings.
Let’s finish with a foggy nighttime scene with the dark sides of the conifers lit by bounced moonlight:
Oh wait, one more. The following image shows our three main lighting terms, and the final image with them composed together: