Directional lighting

In this post we’ll add a Directional light to our engine. We’ll start by creating a new folder in the “ProjectVanquishTestContent” project under the “Shaders” folder called “Lights”. We’ll add all of our lighting shaders in here. Create a new Effect file called “DirectionalLight” and remove all of the files content. We’ll start the shader off with some parameter declarations:

// Direction of the light
float3 lightDirection;
// Color of the light 
float3 Color; 
// Position of the camera, for specular light
float3 cameraPosition; 
// This is used to compute the world-position
float4x4 InvertViewProjection; 
// Diffuse color, and SpecularIntensity in the alpha channel
texture colorMap; 
// Normals, and SpecularPower in the alpha channel
texture normalMap;
// Depth
texture depthMap;
float2 halfPixel;

Next we’ll define our Sampler States:

sampler colorSampler = sampler_state
{
    Texture = (colorMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = LINEAR;
    MinFilter = LINEAR;
    Mipfilter = LINEAR;
};

sampler depthSampler = sampler_state
{
    Texture = (depthMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = POINT;
    MinFilter = POINT;
    Mipfilter = POINT;
};

sampler normalSampler = sampler_state
{
    Texture = (normalMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = POINT;
    MinFilter = POINT;
    Mipfilter = POINT;
};

We’ll create our Vertex Shader Input and Output structs, plus our VertexShaderFunction:

struct VertexShaderInput
{
    float3 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
};

struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;
    output.Position = float4(input.Position,1);
    // Align texture coordinates
    output.TexCoord = input.TexCoord - halfPixel;
    return output;
}

The only things left to do is create our Pixel Shader Function and to add our technique:

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    // Get normal data from the normalMap
    float4 normalData = tex2D(normalSampler,input.TexCoord);
    // Transform normal back into [-1,1] range
    float3 normal = 2.0f * normalData.xyz - 1.0f;
    // Get specular power, and get it into [0,255] range]
    float specularPower = normalData.a * 255;
    // Get specular intensity from the colorMap
    float specularIntensity = tex2D(colorSampler, input.TexCoord).a;
    
    // Read depth
    float depthVal = tex2D(depthSampler,input.TexCoord).r;

    // Compute screen-space position
    float4 position;
    position.x = input.TexCoord.x * 2.0f - 1.0f;
    position.y = -(input.TexCoord.x * 2.0f - 1.0f);
    position.z = depthVal;
    position.w = 1.0f;
    // Transform to world space
    position = mul(position, InvertViewProjection);
    position /= position.w;
    
    // Surface-to-light vector
    float3 lightVector = -normalize(lightDirection);

    // Compute diffuse light
    float NdL = max(0,dot(normal,lightVector));
    float3 diffuseLight = NdL * Color.rgb;

    // Reflection vector
    float3 reflectionVector = normalize(reflect(-lightVector, normal));
    // Camera-to-surface vector
    float3 directionToCamera = normalize(cameraPosition - position);
    // Compute specular light
    float specularLight = specularIntensity * pow( saturate(dot(reflectionVector, directionToCamera)), specularPower);

    // Output the two lights
    return float4(diffuseLight.rgb, specularLight) ;
}

technique DirectionalLight
{
    pass Pass0
    {
        VertexShader = compile vs_2_0 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

Great! Build the solution to make sure that you don’t have any errors. Back in our “DeferredRenderer” class, let’s define a new variable:

private Effect directionalLightEffect;

In the constructor, let’s instantiate it:

directionalLightEffect = content.Load<Effect>("Shaders/Lights/DirectionalLight");

In the “Draw” method, we have one comment left, and that is to do with the lights. Change the comment too:

DrawLights();

Now, let us create this new method:

void DrawLights()
{
}

In this method, we’ll set the Light RenderTarget, the BlendState and the DepthStencilState, draw the lights and reset the BlendState, DepthStencilState and the Light RenderTarget.

void DrawLights()
{
    device.SetRenderTarget(lightRT);
    device.Clear(Color.Transparent);
    device.BlendState = BlendState.AlphaBlend;
    device.DepthStencilState = DepthStencilState.None;

    // Draw lights
    DrawDirectionalLight(new Vector3(0, -1, 0), Color.Blue);

    device.BlendState = BlendState.Opaque;
    device.DepthStencilState = DepthStencilState.None;
    device.RasterizerState = RasterizerState.CullCounterClockwise;
    device.SetRenderTarget(null);
}

There is a new method in there which we’ll need to create:

void DrawDirectionalLight(Vector3 lightDirection, Color color)
{
}

This is really a nasty hack in order to create a Directional light for testing purposes. Outside of the “DeferredRenderer” class, you cannot modify the Color or Position of this light. This is where a Light Manager will come in handy and all of the Light rendering will move in there, much like the Scene Manager. Anyway, let’s continue with the code. In this method we’ll be assigning the parameter values in the “DirectionalLight” effect:

void DrawDirectionalLight(Vector3 lightDirection, Color color)
{
    directionalLightEffect.Parameters["colorMap"].SetValue(colorRT);
    directionalLightEffect.Parameters["normalMap"].SetValue(normalRT);
    directionalLightEffect.Parameters["depthMap"].SetValue(depthRT);
    directionalLightEffect.Parameters["lightDirection"].SetValue(lightDirection);
    directionalLightEffect.Parameters["Color"].SetValue(color.ToVector3());
    directionalLightEffect.Parameters["cameraPosition"].SetValue(sceneManager.Camera.Position);
    directionalLightEffect.Parameters["InvertViewProjection"].SetValue(
                                           Matrix.Invert(sceneManager.Camera.View * 
                                                         sceneManager.Camera.Projection));
    directionalLightEffect.Parameters["halfPixel"].SetValue(halfPixel);
    directionalLightEffect.Techniques[0].Passes[0].Apply();
    fullscreenQuad.Draw();
}

Now, if I haven’t missed anything out, you’ll be able to build this without any errors. If you run the application, you should see the following:

If you feel like changing the colour of the light, change the line in the “DrawLights” method:

DrawDirectionalLight(new Vector3(0, -1, 0), Color.Blue);

In the next part, we’ll look at implement Point lights.

Advertisements