Article: Crash Course in HLSL
Effect Files
So far we have talked about shaders. After having the ability to write vertex and pixel shaders, the next logical step is to combine these two in one place. The combination of these types of shaders, together with a set of other states that control how the graphic pipeline functions is called an effect.
If you look again at the graphics pipeline, you can imagine an effect as a block that replaces or controls a few of the blocks in the diagram: Vertex Processing, Geometry Processing, Pixel Processing, Texture Samplers, and a part of Pixel Rendering. The Vertex Processing and Pixel Processing are implemented by writing programmable shaders. The other blocks can be controlled by setting the pipeline state, i.e. setting values to a set of specific variables.
Effects also provide a convenient way to write shaders for multiple hardware versions. For this, effects have the concept of techniques. A technique is a collection of one or more passes. Each pass defines a certain way of rendering the object. To do this, a technique and its passes encapsulate global variables, pipeline states, texture sampler states and shader states. This way, you can write different shader versions, which rely on different functionalities from the hardware, and encapsulate them in techniques. Then, at runtime, select the proper technique to be used, based on the hardware the application is running. On a high-end machine you will probably want to use a technique that contains more complex shaders, and on a low-end machine, the simpler and faster one. The shaders are defined as functions, and assigned to two pipeline variables, named VertexShader and PixelShader.
The general layout of an effect file can be seen below.
//parameter declarations […] //data type declarations […] //function declarations […] //technique declarations […]
This probably is not very descriptive, but in the rest of the article, I will explain each element.
Data Types
Let us take a quick look at the data types available in HLSL. After this, in the next session, you will see hoe to declare variables and parameters using these data types. The simplest types are the scalar types, listed below.
| Type | Value |
| bool | true or false |
| int | signed integer |
| half | 16-bit floating point |
| float | 32-bit floating point |
The float type is the native type used inside GPUs, and this is what you will most often use. Because of this, some GPUs that might not support other types (int, half, double) will emulate them using float. Integers usually fall into this category, and because of this emulation, the full range of 32-bit integers will not be covered by a 32-bit floating-point representation.
Right after scalar types, vector types come into play. A vector contains between one and four components of a scalar type. A vector type is composed by a scalar type immediately followed by a number that indicates the number of components of the vector. Some examples include: float3, half2, int4, double2. An advantage of vector types, besides the ease of use, is the fact that operations done on them are applied simultaneously on all components. For example, adding two float4 variables will add the corresponding components in just a single instruction. To access individual components of a vector, you can use two sets of accessors: rgba and xyzw. As an example, for a variable of type float4 named var, writing var.x, or var.r gives access to the first component of var; var.y and var.g to the seconds, and so on. Usually, when using a variable as a position, you would use the .xyzw accessors, and when using it as a color, the .rgba accessors.
The next data structure that is common in computer graphics is the matrix type. A matrix type is defined similar to vectors, but it uses two dimensions. In the definition, the scalar type is followed by the number of rows, an x and the number of columns: int4x2, double1x3, float4x4. Usually, you’ll just use matrices in multiplications, but if you ever need to access a certain component of the matrix, there are a few ways of doing this: a zero based notation, which has the form of an underscore, followed by the letter m, the row number, and then the column number; a one based notation, which has the form of an underscore, followed by the row and column number; and finally access can be done as in an array, by specifying the row and column numbers in square brackets, zero-based. Assuming we have a variable mvar of type float4x4, all the following notations will access the same component, the one on the second row and third column: mvar._m12, mvar._23, mvar[1][2].
A texture type is the data type that represents a texture object. The keyword for this data type is simply texture. Another data type linked to textures is the sampler, which contains a sampler state, i.e. the texture to be sampled, and the filters that should be used. The common sampler types are sampler, sampler1D, sampler2D, sampler3D and samplerCUBE. We will go into more details about using textures and samplers later.
The last important data type is the structure. A structure is a user-defined data type that has a collection of members, of other data types. The keyword struct is used to define a structure, after which the structure name may be used. To access components of a structure, the structure operator (.) is used. An example of a structure that contains two members, a position and a color, can be seen below.
struct demoStruct
{
float3 position;
float4 color;
}
These are the most important data types you will get to use when writing HLSL code.
Intrinsic Functions
HLSL comes with a large set of intrinsic functions (functions defined by the language) that offer access to commonly used functionality. The parameters for these functions depend on what the function does, but they are always matrices, samplers, scalars or vectors. You can see the full set of intrinsic functions here.
March 25th, 2010 - 07:42
Very interesting and well written article! I always has a bit of trouble getting the hang of shaders, but this has helped me out! Perhaps I should start looking into XNA again, with the new 4.0 release and all
March 25th, 2010 - 18:23
I’m always glad when a tutorial of mine helps someone
Shaders are really nice and learning to use them will help you a lot. Just remember that if you target the Windows Phone, XNA 4.0 has no support for shaders on that platform.
September 8th, 2010 - 13:09
Oh I wish I new about the NoInterpolation keyword much sooner… thanks Catalin; as always, a very nice read.