Saturday, April 23, 2011

Anatomy of an effect file

To be able to render geometry to the screen using our own shader programs we must create an effect file. Effect files contain our vertex and pixel shaders and all other variables they require to run.

First we need to create a new effect file. For this we just have to right click on the content project and select Add->New Item. From the list we will select a new effect file.

By simply browsing through the file we can see that we already have a functional example. It has all the basic parts of any effect. I will go from top to bottom and explain what each part is used for.

First of all we can see this:

float4x4 World;
float4x4 View;
float4x4 Projection;

These 3 are parameters, we will set them from the game at runtime when we will draw. As you can see they are of type float4x4. This means they are float matrices of size 4 by 4. Types like this are built into HLSL so you can use them with no problem. HLSL supports most common scalar types like bool, int, uint, float, double but it also one more called half (a 16 bit floating point number). In case you want more information about them check out the HLSL scalar types reference on msdn. Because in graphics we use vectors and matrices so often they are part of the language. Vectors can have from 1 to 4 elements of the same scalar type. The syntax is quite simple, just put the number of elements you want your vector to have at the end of the type name. For example int3 is a vector with 3 integers. To create a matrix you use the same syntax as vectors only that you set both dimensions with an x between them. For example int3x3 is a matrix of integers with 3 rows and 3 columns. The limit for both dimensions is 4.

The next thing you will see is 2 simple structure declarations, as a programmer these should be VERY familiar to you, except for one little detail:

struct VertexShaderInput
{
    float4 Position : POSITION0;
};

What is that : POSITION0? Well that is a semantic, it is used so the graphics card knows what the intended use of this variable is. Also, the output from the vertex shader will go into the pixel shader, but they don't have to be the same structure/type or in the same order so the graphics card will use semantics to determine what vertex shader output goes to what pixel shader input (and anywhere else we have a connection in the rendering pipeline). Some semantics have a more special meaning, for example POSITION0 will be used to determine the position on screen of our objects. I will not go into further detail here as you will understand them better as we work with them, but if you really feel the need to read more about semantics go and read the HLSL semantics documentation on msdn.

Following the structures we will find 2 functions, these are our vertex and pixel shaders, you can have as many functions you want in a single file, just remember that HLSL functions are inlined. Now let's look at our vertex program:

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;
 
    float4 worldPosition = mul(input.Position, World);
    float4 viewPosition = mul(worldPosition, View);
    output.Position = mul(viewPosition, Projection);
 
    return output;
}

As you can see the input and output have the type of the above structures, because semantics are written in the structures we don't need any here. Inside the function we can see we create a temporary instance of our structure that we will later return. Accessing the elements of a structure is very straightforward, just use the '.' operator as you would in c#. this function only does a few vector-matrix multiplications using the intrinsic function mul(). This function receives 2 matrix parameters (or a matrix and a vector) and multiplies them according to the normal mathematical method. This way we have to write very little code and is also rather fast as it uses hardware instructions. The multiply operator '*' does componentwise (element by element) multiplication so we have to use this function for the regular mathematical multiplication. The language has a lot of built-in functions that we will explore throughout these tutorials. If you want to read more about them go the HLSL Intrinsic functions reference on msdn.

The last function is the pixel shader function. This little function only return the color red with full opacity:

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    return float4(1001);
}

This time the output is not one of our structures so we must tell it what semantic it has, you cand see the COLOR0 semantic at the end of the same line, this is the output semantic for our pixel shader function. COLOR0 tells the graphics card that this will be the color the pixel will have on screen. Here a constructor is used to create a new float4 variable from 4 scalars. The inputs to the constructor are the color channels in this order: RED, GREEN, BLUE and ALPHA where alpha is the opacity of our color and the values range from 0 to 1.

Finally we have our technique. A technique is a set of passes, a pass is a set of settings for the graphics card, in a pass we can tell the graphics card how to draw. Within the pass we tell it things like if it should blend (combine colors) of transparent objects or not, and what pixel shader and vertex shader to use. This techniques has only 1 pass and only sets the shaders, nothing else:

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

The name of this technique is 'Technique1' and the name of it's (single) pass is 'Pass1'. You can use these names to set the current technique and drawing pass from the game. The 2 lines of code just tell the compiler what Vertex and Pixel shader to use. As you can see we have to specify to what shader we are setting the function, after that tell it to compile using a version parameter and after that the name of the function we want to use for that shader. The shader version used here is 2.0. You can set a different vertex and pixel shader versions. In XNA you can only use shader model 2 and 3 (with custom extensions for XBox 360) but only if the project is in HiDef profile, if you use Reach then you are limited to shader model 2.

No comments:

Post a Comment