Adding the Effect files

Let’s jump right in there and start by structuring our Content project. Create two new folders as follows:

If you are unfamiliar with HLSL, Microsoft wrote a brilliant guide about programming in HLSL. And, again, Catalin Zima wrote a good article too. It is definitely worth reading these articles if you are new to HLSL and I’d recommend starting with Catalin’s article first.

Let’s add our first Shader.  Right mouse click the Shaders folder and highlight “Add” and then click “New Item”.  Select “Effect File” and name it “Clear”:

Click the “Add” button and it will create a template Effect file.  Select all of the code in the file and delete it as we won’t need any of it. The purpose of this Effect file is to clear our G-Buffer. Enter the following code:

struct VertexShaderInput
{
    float3 Position : POSITION0;
};

struct VertexShaderOutput
{
    float4 Position : POSITION0;
};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;

    output.Position = float4(input.Position,1);
    return output;
}

struct PixelShaderOutput
{
    float4 Color : COLOR0;
    float4 Normal : COLOR1;
    float4 Depth : COLOR2;
};

PixelShaderOutput PixelShaderFunction(VertexShaderOutput input)
{
    PixelShaderOutput output;

    // Black color
    output.Color = 0.0f;
    output.Color.a = 0.0f;

    // When transforming 0.5f into [-1,1], we will get 0.0f
    output.Normal.rgb = 0.5f;

    // No specular power
    output.Normal.a = 0.0f;

    // Max depth
    output.Depth = 0.0f;
    return output;
}

technique ClearGBuffer
{
    pass Pass1
    {
        VertexShader = compile vs_2_0 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

Excellent, we now have our first shader for our G-Buffer. We won’t hook this up in code just yet as we’ll add the next shader. Again, follow the same process as above and name the new Effect file “Final”. Remove all of the code and enter the following:

texture colorMap;
texture lightMap;
float2 halfPixel;

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

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

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);
    output.TexCoord = input.TexCoord - halfPixel;
    return output;
}

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    float3 diffuseColor = tex2D(colorSampler,input.TexCoord).rgb;
    float4 light = tex2D(lightSampler,input.TexCoord);
    float3 diffuseLight = light.rgb;
    float specularLight = light.a;
    return float4((diffuseColor * diffuseLight + specularLight),1);
}

technique CombineFinal
{
    pass Pass1
    {
        VertexShader = compile vs_2_0 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

This shader is used to combine the Color RenderTarget with the Light RenderTarget to create our final scene. Now, let’s add them to the “DeferredRenderer” class. Declare two new variables:

private Effect clearBufferEffect, finalEffect;

And we’ll instantiate them in the constructor:

public DeferredRenderer(GraphicsDevice device, ContentManager content)
{
    // After the RenderTargets initialize

    clearBufferEffect = content.Load<Effect>("Shaders/Clear");
    finalEffect = content.Load<Effect>("Shaders/Final");
}

Do a quick build to make sure that we don’t have any errors (you shouldn’t ;-)). Now we have instantiated them, we can finally start to use these shaders. Let’s add some more code. Find your ClearGBuffer method and add the following:

public void ClearGBuffer()
{
    clearBufferEffect.CurrentTechnique.Passes[0].Apply();
}

What that line does is apply the current technique from the Clear Effect file. We only have the one pass in our shader file, which is why we use the index 0. If we had multiple passes, we’d need to use a foreach loop in order to iterate through each pass.

Find your CombineGBuffer method and add the following code:

void CombineGBuffer()
{
    finalEffect.Parameters["colorMap"].SetValue(colorRT);
    finalEffect.Parameters["lightMap"].SetValue(lightRT);
    finalEffect.Parameters["halfPixel"].SetValue(halfPixel);
    finalEffect.CurrentTechnique.Passes[0].Apply();
}

In this code we are assigning some parameters which are in our shader file. If you look back at the Final Effect file, declared at the top are:

texture colorMap;
texture lightMap;
float2 halfPixel;

These are the parameters that we are assigning in the CombineGBuffer method. You’ll notice that we’ve not declared a variable for halfPixel, yet we are using it in the CombineGBuffer method. Let’s go back and declare this new variable. Add the following with your other variables:

private Vector2 halfPixel;

Great. We now have to instantiate this variable, so let’s go to the constructor and do this. After your backbufferWidth and backbufferHeight declarations, and before the instantiation of the RenderTargets, add in the following snippet:

halfPixel = new Vector2()
{
    X = 0.5f / (float)backbufferWidth,
    Y = 0.5f / (float)backbufferHeight
};

Building the solution won’t give you any errors, but trying to run the application will possibly bring a few to the table. Before we run the application, we need to make sure that we are using the Hi-Def profile rather than the Reach profile. We need to do this for both projects, so right click the ProjectVanquish library project and click “Properties”. If the Hi-Def profile isn’t selected, select it:

Do the same for the ProjectVanquishTest project and then rebuild the solution. Once built, run the application. Your code is now using your new shaders and you should see something like this:

This is a good place to end this part. In the next part, we’ll continue by adding a Quad Renderer.