XNA Shader Programming
Tutorial 19, Hemispheric ambient light
Hi, and welcome to Tutorial 19 of the XNA Shader Programming tutorial!
Today I’m going to change the way we calculate our ambient light, so it gets a little more realistic than a flat light.
The source can be found on the bottom of the article.
Hemispheric ambient light
Usually we use ambient intensity and ambient color to calculate our ambient color. This works in most situations, but we might want to add some realism to it. In the real world, a lot of the light we see comes from reflections in the environment, and not only light sources. Light from the sky, the grass below you, from the car on your left are all parts of what color gets reflected from a surface.
Hemispheric ambient light represents a simple and easy solution to this. By making a vector that represents the direction to the sky( color above the surface ), and calculating the angle between this vector and the normal of the surface, we can find what color gets reflected by the surface. So, if the surface is having a normal pointing down, and the sky vector us up ( 0, 1, 0 ), the surface will get the color of the ground below the object.
The red vectors are pointing to the sky, and the black vectors are the normals to different parts of a surface.
Implementing the shader
First of all, we need to declare some global variables. This is the direction to the sky, the color of the sky, the color of the ground and the intensity of the hemispherical light.
// Set the direction to the sky
float3 SkyDirection = float3(0.0f,1.0f,0.0f);
// Set ground color
float4 Gc = float4(0.5f,1.0f,0.5f,1.0f);
// Set sky color
float4 Sc = float4(0.5f,0.5f,1.0f,1.0f);
// Set the intensity of the hemisphere color
float Hi = 0.7f;
The sky color and the ground color could be fetched from a cubemap or another form of a lookup table, making it more realistic. But in this case, I want to keep it simple so you learn the lesson. 😉
Next, we must calculate our new ambient term:
float vecHemi = (dot(N, SkyDirection) * 0.5f ) + 0.5f;
float4 HemiFinal = Hi* lerp(Gc, Sc, vecHemi);
The first thing we do is to take the dot-product of our SkyDirection vector and the surface normal N.
Then we use this to lerp between the ground color and sky color, based on the angle between SkyDirection and N, and multuiply this with the intensity of our hemispherical ambient light.
Now, we add this to our ambient calculation. I want to keep the old ambient light to have some more control on the ambient light color itself, and multiplying this with the hemispherical light.
To only use the hemispherical ambient light, we could return this equation instead:
Thats it for the shader. The only thing we do with it is to take the angle between our surface normal and the sky, and then calculate if that surface will be colored by the sky, the ground or something between them( lerp ).
Using the shader
Nothing new here. The parameters are coded into the shader, but can be set from outside the shader as the variables are defined in global space in the shader.
You might have noticed that I have not used effect.commitChanges(); in this code. If you are rendering many objects using this shader, you should add this code in the pass.Begin() part so the changed will get affected in the current pass, and not in the next pass. This should be done if you set any shader paramteres inside the pass.