The DirectX Graphics Processing Pipeline
To be able to understand how shaders work and what each type of shader does, you need to see how 3D objects are processed and displayed. The Direct3D 9 Graphics Pipeline (also used by XNA) is outlined in the following figure.
Each block in the figure has a specific purpose in the rendering process. Data comes in through the vertex data, primitive data and textures. The vertex data contains untransformed model vertices. These are stored in vertex buffers. The Primitive data specifies geometric primitives, like points, lines, triangles, and polygons. These are defined by groups of vertices from the vertex data, indexed in index buffers. In theory, the tessellation unit converts high-order primitives (like N-patches or displacement maps) to vertices and stores these in vertex buffers. However, there are few (if any) GPUs that actually implement this functionality. In the newer versions of Direct3D (Direct3D 11), this stage of the pipeline got reborn and is able to do wonderful stuff, but for now, this functionality is not available in XNA.
The vertex processing stage is one of interest. Here, the vertices stored in the vertex buffer are read, and various transformations are applied to them, before sending them forward to the geometry processor. This is where the first type of shaders comes into play: the vertex shaders. When you will be writing vertex shaders, your task will be to transform vertices given in object coordinates into vertices in screen-projected coordinates. You can learn about the different coordinate spaces in the Creator’s Club site’s Education section. For now, remember that vertices read from a vertex buffer are multiplied by some matrices, and end up being projected on the 2D screen. Together with the projected position, other attributes are usually passed, like color, normals, texture coordinates.
In the geometry processing stage, several algorithms are applied on the geometry formed by the transformed vertices. These algorithms include clipping (geometry which is off the screen is removed), back face culling (geometry facing away from the view direction is culled), rasterization (triangles defined by vertices are transformed into pixels). The rasterizer takes three points of a triangle together with their attributes, and interpolates these values for each pixel inside those triangles. These values are passed forward to the pixel processors.
The pixel processing stage is the home of the second type of shader you will learn about, namely the pixel shader. A pixel shader receives as input data coming from the rasterizer. This includes texture coordinates, normals, binormals, and many others. Inside the pixel shader, you need to use this data, together with data read from textures, to output a single color. This color is taken by the pixel rendering stage, and it is modified by alpha blending, depth, and stencil testing, before finally being written into the framebuffer.
The two blocks we haven’t talked about are textures and texture samplers. Textures are memory blocks of data, usually representing arrays of colors. These are accessed by pixel shaders (and in some cases by vertex shaders) through texture samplers which specify the addressing mode and filtering that are used when accessing a certain texture.
This is the general view of the graphics pipeline. It is good to have it in mind, in order to understand where and when each shader is executed. Now that we have this in place, let us look at the two types of shaders we have seen.
Vertex Shaders
As you saw earlier, vertex shaders are programs that are executed in the “vertex processing” stage of the pipeline. Thus, a vertex shader is responsible for a number of things:
- Coordinate Space Transformations. This is normally composed of three transformations. The world transformation positions and rotates objects in space, relative to the world coordinate system. The view transformation moves all vertices in view space, i.e. the space relative to the camera. In view space, the camera is at the origin of the system. The last transformation is the projection transformation, which converts the 3D triangles and polygons in view space into 2D triangles and polygons, which can be rendered on the screen.
- Some animation techniques happen in the vertex shader. While this is actually part of the world transformation, it is worth mentioning it separately, to keep it in mind.
- Light and color computations can also be done in a vertex shader
- Vertex shaders should output any variables and values that might be needed by the pixel shader for advanced affects.
Vertex shaders are implemented as functions containing a list of instructions. The maximum length of a shader is determined by the shader version used, and the code that results after the compilation of the HLSL code into assembly. The shader version also determines the instructions that are available inside a vertex shader. As a general rule, higher shader versions offer more features, but lower versions are supported on more system configurations. The Xbox 360 shader version is a super-set of vs_3_0, which means that it has the features of vs_3_0, and a few more besides them.
The simplest example of a vertex shader written in HLSL can be seen below.
float4x4 WorldViewProjection;
float4 VertexShaderFunction(float4 inputPosition : POSITION) : POSITION
{
return mul(inputPosition, WorldViewProjection);
}
This shader transforms the vertex from model space into projection space, by multiplying it with the WorldViewProjection matrix. The application is responsible of setting this parameter to an appropriate value.
As you can see, the code is similar to C, and looks much better than the assembly one. Don’t worry if there are some things you don’t yet understand. Some of them will be explained later, and others you’ll understand with more practice. The data types float4x4 and float4 are some of the data types of HLSL; mul is an intrinsic function, and the two instances of POSITION are called semantics. There are also other semantics, for outputting different types of information, but we will get to them later. For now, we move forward to take a look at pixel shaders.
Pixel Shader
As vertex shaders work on vertices, pixel shaders work on pixels. Before a pixel is written to the frame buffer, it is first passed through the pixel shader. This is represented by the pixel processing state. As a rule, a pixel shader needs to output a single color. The value of this color may be computed in several ways, taking into account the texture, ambient light, directional lights, shadows, material type, etc. The inputs used for this by the shader are attributes coming from the vertex texture, shader parameters coming from the application, and texture data coming from the texture samplers. Pixel shader length and complexity is also limited by the shader version used when compiling.
Take a look at a very simple example of a pixel shader.
float4 PixelShaderFunction() : COLOR0
{
return float4(1, 0, 0, 1);
}
This very simple shader makes each pixel red. The returned value, float4(1, 0, 0, 1), represents the color of the pixel, stored as an array of floats, each representing one element of the RGBA (Red-Green-Blue-Alpha) color mix. In a real example, a pixel shader is rarely this simple. It usually has some input parameters, and more complex computations, including reading from textures and computing lighting.


by Dennis Brandis, on 11.19.09 @ 12:22 am
On page 4:
float4 PixelShaderFunction(float2 TexCoords : TEXCOORD0) : COLOR0
{
return tex2D(TextureSampler, input.TexCoord);
}
may be it should be:
return tex2D(TextureSampler, TexCoord);
by Catalin Zima, on 11.19.09 @ 2:16 pm
I fixed it now. Thanks!
by Crash Course in HLSL « optic rust, on 01.02.10 @ 4:43 pm
[...] What does HLSL stand for? Why was it created? How does an HLSL effect file look like? What can you do with HLSL? Catalin Zima answers these questions and more in a brilliant article introducing HLSL over at her site. Check it out here [...]
by Hassan Aly Selim, on 01.31.10 @ 8:42 pm
Thanks alot for this HLSL Crash Course =)
Now I can start writing my own Custom Shaders in XNA !
by Catalin’s XNA Experiments » Re-awarded XNA/DirectX MVP for 2010, on 04.03.10 @ 6:10 pm
[...] Crash Course in HLSL (also published [...]
by Enio, on 05.01.10 @ 3:12 pm
Thanks for your helpful explanations. Which book would be more suitable for a beginner who needs to learn everything from scratch (shaders, algebra, algorithms, and physical)? Thanks!
by Enio, on 05.02.10 @ 12:41 am
Why not develop the second part of the course with several practical examples (Blur, DOF, Sepia, etc.)?
by Crash Course in HLSL, on 06.17.10 @ 8:54 am
[...] Can be found at: http://www.catalinzima.com/?page_id=575 [...]
by Devrunner, on 07.27.10 @ 11:50 am
You’re the best.