XNA Shader Programming – Tutorial 1, Ambient light

XNA Shader Programming
Tutorial 1 – Ambient light
Hi, and welcome to tutorial 1 of my XNA Shader Tutorial series.
My name is Petri Wilhelmsen and is a member of Dark Codex Studios. We usually participate in various competitions regarding graphics/game development, at The Gathering, Assembly, Solskogen, Dream-Build-Play, NGA and so on.
 
The XNA Shader Programming series will cover many different aspects of XNA, and how to write HLSL shaders using XNA and your GPU. I will start with some basic theory, and then move over to a more practical approach to shader programming.
The theory part will not be very detailed, but should be enough for you to get started with Shaders and be able to experiment for yourself. It will cover the basics around HLSL, how the HLSL language works and some keywords that is worth knowing about.
 
Today I will cover XNA and HLSL, as well as a simple ambient lighting algorithm.
 
Prerequisites
Some programming in XNA, as I wont go much into details about loading textures, 3d models, matrices and some math.
 
A short history about shaders
Before DirectX8, GPU’s had a fixed way to transform pixels and vertices, called "The fixed pipeline". This made it impossible to developers to change how pixels and vertices was transformed and processed after passing them to the GPU, and made games look quite similar graphics wise.
DirectX8 introduced the vertex and pixel shaders, that were a method developers could use to decide how the vertices and pixles should be processed when going through the pipeline, giving them a lot of flexibility.
An assembly language was used to program the shaders, something that made it pretty hard to be a shader developers, and shader model 1.0 was the only supported version. But this changed once DirectX9 was released, giving developers the opportunity to develop shaders in a high level language, called High Level Shading Language( HLSL ), replacing the assmely shading language with something that looked more like the C-language. This made shaders much easier to write, read and learn.
DirectX10.0 introduced a new shader, the Geometry Shader, and was a part of Shader Model 4.0. But this required a new state-of-the-art graphics card, and Windows Vista.
XNA supports Shader Model 1.0 to 3.0, but works on XP, Vista and XBox360!
 
Shaders?
Well, enough history.. Really, what is a shader?
As I said, shaders can be used to customize steps in the pipeline to make it up to the developer to implement how pixels/vertices should be processed.
As we can see from the figure below, an application got initiates and uses a shader when rendering, the vertex buffer works with the pixelshader by sending required data from the vertex shader to the pixel shader, working together to create an image to the framebuffer.
 
One important fact to note behind your ears is that many GPUs does not support all shader models. This should be accounted for when developing shaders. One shader should have alternate methods to archive similar/simpler effects, making the application work on older computers.
 
Vertex Shaders
Vertex shaders is used to manipulate vertex-data, per vertex. This can for example be a shader that makes a model “fatter” during rendering by moving vertexes along their normals to a new position for every vertex in the model( deform shaders ).
Vertex shaders got input from a vertex structure defines in the application code, and loads this from the vertex buffer, passed into the shader. This describes what properties each vertex will have during shading: Position, Color, Normal, Tangent++.
The vertex shader sends its output to for later use in the pixel shader. Do define what data the vertex shader will pass to the next stage can be done by defining a struct in the shader, containing the data you want to store, and make the vertex shader return this instance, or by defining parameters in the shader, using the out keyword. Output can be Position, Fog, Color, Texture coordinates, Tangets, Light position and so on.
 
struct VS_OUTPUT
{
    float4 Pos: POSITION;
};
VS_OUTPUT VS( float4 Pos: POSITION )
{
    VS_OUTPUT Out = (VS_OUTPUT) 0;
    …
    return Out;
}
// or
float3 VS(out float2 tex : TEXCOORD0) : POSITION
{
      tex = float2(1.0, 1.0);
      return float3(0.0, 1.0, 0.0);
}
 
Pixel Shaders
The Pixel shader manipulates all pixels( per pixel ) on a given model/object/collection of vertices. This can be a metal box, where we want to customize the lighting algorithm on, colors and so on. The pixel shader gets data from the vertex shaders output values, like position, normals and texture coordinates:
 
float4 PS(float vPos : VPOS, float2 tex : TEXCOORD0) : COLOR
{
      …
      return float4(1.0f, 0.3f, 0.7f, 1.0f);
}
 
The pixel shader can have two output values, Color and Depth.
 
 
HLSL
High Level Shading Language is used to develop shaders. In HLSL, you can declare variables, functions, datatypes, testing( if/else/for/do/while+) and much more, in order to create a logic for vertices and pixels. Below is a table of some keywords that exists in HLSL. This is not all of them, but some of the most important ones.
 
Examples of datatypes in HSLS
bool true or false
int 32-bit integer
half 16bit integer
float 32bit float
double 64bit double
 
Examples of vectors in HSLS
float3 vectorTest
float vectorTest[3]
vector vectorTest
float2 vectorTest
bool3 vectorTest
 
Matrices in HSLS
float3x3: a 3×3 matrix, type float
float2x2: a 2×2 matrix, type float
 
We also have a lot of helper functions in HSLS, which help us archive complex mathematical expressions.
cos( x ) Returns cosine of x
sin( x) Returns sinus of x
cross( a, b ) Returns the cross product of two vectors a and b
dot( a,b ) Returns the dot product of two vectors a and b
normalize( v ) Returns a normalized vector v ( v / |v| )
 
 
HSLS offers a huge set of functions just waiting for you to use! Learn them, so you know how to solve different problems.
 
Effect files
Effect files ( .fx ) makes shader developing in HSLS easier, and you can store almost everything regarding shaders in a .fx file. This includes global variables, functions, structures, vertex shader, pixel shader, different techniques/passes, textures and so on.
We have already seen how to declare variables and structures in a shader, but what is this technique/passes thing?  It’s pretty simple. One Shader can have one or more techniques. Each technique can have a unique name and from the game/application, we can select what technique in the shader we want to used, by setting the CurrentTechnique property of the Effect class.
effect.CurrentTechnique = effect.Techniques["AmbientLight"];
 
Here, we set “effect” to use the technique “AmbientLight”. One technique can have one or more passes, and we must remember to process all passes in order to archive the result we want.
This is an example of a shader containing one technique and one pass:
technique Shader
{
    pass P0
    {
        VertexShader = compile vs_1_1 VS();
        PixelShader = compile ps_1_1 PS();
    }
}
 
This is an example of a shader containing one technique and two passes:
technique Shader
{
    pass P0
    {
        VertexShader = compile vs_1_1 VS();
        PixelShader = compile ps_1_1 PS();
    }
pass P1
    {
        VertexShader = compile vs_1_1 VS_Other();
        PixelShader = compile ps_1_1 PS_Other();
    }
}
 
This is an example of a shader containing two techniques and one pass:
technique Shader_11
{
    pass P0
    {
        VertexShader = compile vs_1_1 VS();
        PixelShader = compile ps_1_1 PS();
    }
}
technique Shader_2a
{
    pass P0
    {
        VertexShader = compile vs_1_1 VS2();
        PixelShader = compile ps_2_a PS2();
    }
}
We can see that a technique got two functions, one for the pixel shader and one for the vertex shader.
VertexShader = compile vs_1_1 VS2();
PixelShader = compile ps_1_1 PS2();
This tells us that the technique will use VS2() as the vertex shader, PS2 as the pixel shader, and will support shader model 1.1 or higher. This makes it possible to have a different and more complex shader for GPUs supporting higher shader model versions.
 
 
Implementing Shaders in XNA
Its really easy to implement shaders in XNA. In fact, only a few lines of code is needed to load and use a shader. Here is a list of steps that can be followed when making a shader:
1. Make the shader
2. Put the shaderfile( .fx ) in “Contents”
3. Make an instance of the Effect class
4. Initiate the instance of the Effect class.
5. Select what technique you want to use
6. Begin the shader
7. Pass different parameters to the shader
8. Draw the scene/object
9. End the shader
 
The steps in a bit more detail:
1.When making a shader, several programs like notepad, visual studio editor and so on can be used. There are also some shader IDEs available, and personally I like to use nVidias FX Composer: http://developer.nvidia.com/object/fx_composer_home.html
 
2. When the shader is created, drag it into the ”Content” colder, so it gets an asset name:
 
3. XNA Framework includes a Effect class that is used to load and compile the shaders. To make an instance of this class, write the following line of code:
Effect effect;
Effect is a part of the “Microsoft.Xna.Framework.Graphics” library, so remember to add this line of code to the using statement block:
using Microsoft.Xna.Framework.Graphics
4. To initiate the shader, we can use Content to either load if from the project or from a file:

effect = Content.Load<Effect>("Shader");
Shader is the asset name of the shader you added to the Contents folder.
5. Select what technique you want to use:
effect.CurrentTechnique = effect.Techniques["AmbientLight"];
6. To start using an Effect, call the Begin() function:
effect.Begin();
Also, you must start all the passes in the shader.
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
    // Begin current pass
    pass.Begin();
 
7. There are many ways to set a Shader parameter, but the following is sufficient for the tutorial. Note: This is not the fastest way of doing this, and I will come back to this in a later tutorial:
effect.Parameters["matWorldViewProj"].SetValue( worldMatrix * viewMatrix * projMatrix);
where "matWorldViewProj" is defined in the shader: float4x4 matWorldViewProj; and worldMatrix * viewMatrix * projMatrix er is a matrix that matWorldViewProj is set to.
 
SetValue sets a value to the parameter and sends it to the shader, and GetValue<Type> retrives a value from the shader, where Type is the datatype to retrive. For example, GetValueInt32() gets an integer from the shader.
 
8. Render the scene/object you want this shader to process/transform.
 
9. To stop the pass, call pass.End() and to stop the shader, call the End() method of Effect:
pass.End();
effect.End();
To understand this better, open the source code provided and see the steps in action.
Ambient light
Ok, we are finally at the last step, implementing the shader! Not bad eh?
First of all, what is an "Ambient light"?
Ambient light is the basic light in a scene that’s just there. If you go into a complete dark room, the ambient light is typically zero, but when walking outside there is almost always some light that makes it possible to see. This light got no direction and is here to make sure objects that are not lit, will have a basic color.
 
The formula for Ambient light is:
I = Aintensity x Acolor ( 1.1)
 
where L is the light, Aintensity is the intensity of the light( usually between 0.0 and 1.0, and Acolor is the color of the ambient light. This color can be a hardcoded value, a parameter or a texture.
 
Ok, lets start implementing the shader. First of all, we need a matrix that represents the world matrix:
float4x4 matWorldViewProj;
 
Declare this in the top of the shader as a global variable.
Then, we need to know what values the vertex shader will pass to the pixel shader. This is done by creating a structure( you can name it to whatever you want):
 
struct OUT
{
    float4 Pos: POSITION;
};
 
We create a structure named OUT that contains a variable of the type float4 with the name Pos. The :POSITION in the end tells the GPU what register to put this value in. So, what is a register? Well, a register is simply just a container in the GPU that contains data. The GPU got different registers to put position data, normal, texture coordinates and so on, and when defining a variable that the shader will pass to the pixel shader, we must also decide where in the GPU this value is stored.
 
Lets take a look at the vertex shader:
 
OUT VertexShader( float4 Pos: POSITION )
{
    OUT Out = (OUT) 0;
    Out.Pos = mul(Pos, matWorldViewProj);
    return Out;
}
 
We create the vertex shader function of the OUT type, where it takes in the parameter float4 Pos: POSITION. This is the position of the vertex defined in the model file/application/game.
 
Then, we make an instance of the OUT structure name Out. This structure must be filled and returned from the function for later processing.
The position we have in the input parameter is not processed, and needs to be multiplied with the worldviewprojection matrix in order to be placed correctly on the screen.
As this is the only variable in OUT, we are ready to return it and move on.
 
Now, its the pixel shaders turn to make a move. We declare this as a float4 function, returning a float4 value stored in the COLOR register of the GPU.
It’s in the pixel shader we will compute the ambient light algorithm:
float4 PixelShader() : COLOR
{
    float Ai = 0.8f;
    float4 Ac = float4(0.075, 0.075, 0.2, 1.0);
   
    return Ai * Ac;
}
 
Here we use 1.1 to calculate what the color of the current pixel will be. Ai is the ambient intensity, and Ac is the ambient color.
 
Last, we must define the technique and bind the pixelshader and certex shader function to the technique:
technique AmbientLight
{
    pass P0
    {
        VertexShader = compile vs_1_1 VertexShader();
        PixelShader = compile ps_1_1 PixelShader();
    }
}
 
 
Ok, thats it!
Now, i recommend you to look at the source code and play around with the values in order to understand how to setup and implement a shader using XNA.
 
NOTE:
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.
Download: Executable + Source
 
This entry was posted in XNA Shader Tutorial. Bookmark the permalink.

18 Responses to XNA Shader Programming – Tutorial 1, Ambient light

  1. Sally says:

    http://www.mtboutlets.com/ MBT Shoes in fashionhttp://www.us-dioroutlet.com/ Cheap Dior shoeshttp://www.newbalanceshoes-outlet.com/ Discount New Balance shoeshttp://www.us-pumaoutlet.com/ Discount PUMA Outlet

  2. Unknown says:

    Helo, your blog is really good, I like it very much!By the way, if you like nike chaussures tn you can come here to have a look!http://www.tnchaussurescom.comhttp://www.sunglassesol.net

  3. Windy says:

    <a href="http://www.huluvideoconverter.net">Hulu Video Converter</a> is the professional Hulu converter to download video from Hulu, Google Video, Yahoo Video, and many others and also convert them to most common videos like MP4, H.264, AVI, MPEG automatically for you to enjoy on iPod, iPhone, PSP, Cell Phone, Windows Media, etc. So that you can enjoy your videos anywhere and anytime. Similar software: <a href="http://www.iphonetomactransfer.net">iPhone to Mac Transfer</a>, <a href="http://www.iphonetocomputertransfer.net">iPhone to Computer Transfer</a>, <a href="http://www.videoconverterforipad.com">Video Converter for iPad</a>, <a href="http://www.flvtoiphone.com">FLV to iPhone</a>.

  4. Brent says:

    Is there a chance that these HLSL tutorials will be updated to suit 4.0?

  5. Brent says:

    In reguards as to implementing them into XNA 4.0 syntax… (i know that the .fx doesn’t need to change)

  6. digitalerr0r says:

    Hi! Yes, the tutorials are in the process of being updated to XNA 4.0, but it will take some time. I know someone is also translating my tutorials into 4.0 as well.

  7. Pingback: XNA 4 tutorial: Custom shader Effects » André Jeworutzki

  8. Mats says:

    Hi!
    Just reporting some typos which you might want to fix:
    The headings for the datatypes vectors and matrices spells HLSL as HSLS.

  9. Hi there,
    first of all thanks for the great tutorials.
    I tried playing around a little with your examples.So I added a sphere.x to the scene, declare load the model. Then I simply copy-pasted the foreach statement in Draw and replaced all the model entities with my sphere object. But this is what happens.

    http://imageshack.us/photo/my-images/838/ambientd.png/

    So how can I make XNA draw both complete models. I mean, shouldn’t they move through each other?
    Thanks in advance

  10. Sifire says:

    Hi there,

    I’ve been working on shaders for a project and was wondering if you had updated these tutorials to work with Visual Studio 2010/XNA 4.0. I’ve given it a go myself, but truth be told the errors are gibberish to me as I am unfamiliar with Shaders within XNA. I’m currently recieving errors regarding the VertexShader when I attempt to compile. Any ideas? Thanks, though, some good tutorials here!

  11. Pingback: Föreläsning 28/1 samt startprojekt inför laborationerna. - Shaderprogrammering

  12. Pingback: Föreläsningar - Shaderprogrammering

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.