Adding ambient lighting – processing in 2D screen coordinates

Welcome to this last chapter in the HLSL series. Maybe I should have put this chapter before the other chapters on lighting, as ambient lighting is a lot easier: it is done by adding a constant amount of light to the scene. As a little extra, we’ll be adding the glow around the light bulbs of our lampposts.

There is very little we have to do in our DirectX code. We’ll start by setting the amount of diffuse lighting, so add this line to your UpdateLightData method:

effect.SetValue("xAmbient", 0.4f);

We’ll also be needing the 3D position of our Camera, so add this line to the bottom of the SetUpCamera method:

Let’s start by defining the interfaces between our vertex and pixel shader. As always, we will need to provide the 2D screen position of every vertex. Because the glow around the lampposts will be added to the final 2D image, we will also need these 2D coordinates in our pixel shader, so we’ll need to pass them in the Position2D variable. As we’re doing lighting, we’ll also need to pass in the texture coodinates.:

We simply multiply the color found in the texture by the ambient lighting factor. This seems too simple to be a real pixel shader, so let’s add the glow around the light bulbs.

The concept is very easy, but it shows calculations in 2D screen space, so it’s worth to be included in a tutorial. We will calculate the 2D screen position of the light. Then we calculate the distance between the 2D screen pos of the light, and the 2D screen pos of the current pixel. If this distance is smaller than a certain amount, we add some white.

First we should calculate the 2D screen pos. We have done this before, so here is the code:

For the remainder of the code, we have to iterate through our 2 lampposts:

for (int CurrentLight=1; CurrentLight<3; CurrentLight++) {}

Next we will calculate the 2D position of the light. This is done exactly the same way: first we multiply the 3D position by the worldviewprojection matrix, after which we map the coordinates to the [0 1] region.

Now we have both the 2D positions of the light and the pixel, we can calculate the distance between them. We can use the ‘distance’ method of HLSL:

float dist = distance(ScreenPos, LightScreenPos);

Before we move on, we have to define the maximal radius of the glow around our lights. If the light is further away from our camera, the radius of the glow around this light has to decrease, so it has to be inversely related to the distance between our camera and the light:

float radius = 3.5f/distance(xCameraPos, xLightPos[CurrentLight]);

Note that this time the distance between 2 3D coordinates is being calculated, while in the previous case we were dealing with 2 2D coordinates. Adjust the 3.5f value to size you glows.

Now we have all data we need! We can check if the distance between the pixel and the light is smaller than the maximal radius. If this is the case, we will add an amount of light that is related to this distance:

if (dist < radius){ Output.Color.rgb += (radius-dist)*8.0f;}

That’s it! Running this code should already give you the image you can see the final image of the series. However, if we take a look at the calculations done by the last pixel shader, we notice a lot of constant calculations are being done, such as the 2D position of our lights, the dist and the radius values. These are being calculated for every pixel, while they will always remain the same!

Not completely true. Remember the chapter on preshaders? Our compiler was able to identify blocks of code inside our vertex shader that remained constant for each vertex. The same is true for constant code in pixel shaders! This means this code will be stripped out and calculated once on the CPU. If you don’t believe me, you can always have a look at the assembler output, as I showed you in the chapter on preshaders.

This concludes this Series on HLSL. We started with a small theoretic introduction to HLSL, and up to chapter 10 all basic HLSL stuff was explained. From then on, we’ve seen a lot of more advanced topics, such as preshaders and setting individual depth values. With all this, you were able to construct a technique that casts shadows of multiple lights on your scene.

Our final DirectX code:

using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; using D3D = Microsoft.DirectX.Direct3D;