Introduction
In this tutorial, we will take our first look at an important component of game development: shaders. This tutorial is the first in a series of tutorials about shaders on this website. I will be starting from the basics of shaders, so you aren't expected to know anything about them in advance. However, shaders are, in themselves, an advanced topic in making games. It is an extremely good idea to have gone through the other tutorials on this website, especially the 3D tutorials, which will give you the experience you need to get going with shaders. In this tutorial, we will outline what a shader is and what it does. In future tutorials, we will start creating shaders from the ground up.
The Rise of the Programmable Graphics Pipeline and Shaders
Back in the day, when you were doing stuff with 3D games, you would take all of your model data and textures and send them over to the graphics card. You would then call any of a number of functions from a fixed set of functions to configure how the objects should be drawn. By 'fixed set', I mean that the DirectX people (or OpenGL people) create the list of available functions, and you can only use what they have created. This graphics model is called the Fixed Function Pipeline (FFP). This model limits what game developers can do, though, because they can only use the functions that were in the DirectX or OpenGL libraries. Don't get me wrong. These libraries were pretty comprehensive, and they could do almost anything you could dream up. And graphics programmers were (and still are) very resourceful, and could manipulate the Fixed Function Pipeline to do almost anything that it wasn't designed to do. Almost.
The Fixed Function Pipeline still has its limitations, though. This led to the creation of the next major step in the design of graphics libraries: programmable graphics pipelines. In this model, parts of the pipeline are programmable. You get to decide exactly how things should be performed, so you get the effect you want. Basically, you create a simple program in a special programming language, which is then compiled and sent over to the graphics card and executed. the programmable graphics pipeline allows you to completely rewrite parts of the pipeline, and the trend is to allow you to rewrite even more parts in the future. These small programs are called shaders, mainly because their original goal was to simply shade the model you are drawing, based on the lighting in the scene. Of course, shaders can do just about anything you can dream up. You aren't just limited to shading. You can do texturing, coloring, or just about anything else you can think of.
The Programmable Graphics Pipeline
Before going on, it is probably worth a small discussion on how the programmable graphics pipeline and shaders work. This pipeline has the responsibility of taking commands to draw a group of vertices (usually from your 3D model) with all of the correct texturing, lighting, and positioning. The original fixed-function pipeline looked something like the image below:
On the left, vertices are passed into the pipeline. At this point, the vertices are in "model coordinates", meaning that the locations of each vertex are relative to the model. In fact, these vertices all have the exact same locations as they do in the model file. Our first step is to transform these vertices to the right location on the screen, which is usually determined by the usual world, view, and projection matrices. This transformation is done in the Vertex Transformation step. The result of this is vertices that are located in the correct location (transformed vertices). During this step, each triangle (or other primitive) is handled separately.
The next step is to put the pieces back together and begin rasterization, which is the process of taking a geometric shape and converting it into the pixels that will be drawn for it in the end. This is done in the second step, called Primitive Assembly and Rasterization.
After this step, we have what are called "fragments", but which essentially, are pixels with some more information to help us determine (in the next step) how the pixel should be colored. For instance, texturing and coloring information will be passed along with the fragment.
The third step is Fragment Texturing and Coloring, where the exact color of the pixel is determined, based on the texture being used and the color information associated with the fragment. After that, a few other pixel operations are done during the Raster Operations stage, including possibly fog and alpha blending, and the final value for a pixel is determined and placed in the render target, which is then drawn on the screen.
The programmable graphics pipeline is fairly similar to this, with a couple of differences. The programmable graphics pipeline is shown below:
The major differences come in the first and third steps. Rather than having vertex transformations performed by the graphics library, you get to write your own program to do this. This is called a programmable vertex processor, or Vertex Shader. You will write a simple program that will be compiled and sent to the graphics card and run for every vertex you draw. The rasterization step is similar to the one in the Fixed Function Pipeline. However, fragment coloring and texturing are also done with your own program. Once again, you will write a simple program that will be compiled and sent over to the graphics card, and then it will be executed on every pixel that is drawn.
The programmable graphics pipeline allows you to do anything you can dream up during these two steps. In the future, it is likely that more and more of this pipeline will become programmable. In fact, DirectX 10 as support for another type of shader called a geometry shader, which allows you to generate more geometry (more vertices) on the graphics card, which means that you will be able to get away with sending even less information over to the graphics card in some cases.
The big drawback is that now you have to write all of the functions that you want to use, including texturing and lighting calculations. This can be a daunting task for anyone. The XNA framework supplies you with the BasicEffect class, which, hopefully, you have used quite a bit by now. In this class, they have implemented all of the basic shading stuff for you, and give you an easy interface to use it with.
In fact, the BasicEffect class ends up feeling fairly similar to the original Fixed Function Pipeline. The BasicEffect class is quite powerful, and it is easy to use, and for many simpler games, this might be enough. In these HLSL tutorials, though, we are going to go on and create our own shaders. For any sophisticated game, you will likely need to use a big variety of shaders to get the effects that you want. For instance, I browsed through the directory structure for Microsoft Flight Simulator X, and discovered that there was a directory that contained hundreds, if not thousands, of shader files! In the tutorials to come, we will go through the basics of creating and using your own shaders in an XNA game.
HLSL
HLSL, which is short for High-Level Shader Language, is the programming language that we will use. It is pretty much the standard language when you are using DirectX. (OpenGL uses a language called GLSL, which is similar, but different.) HLSL is fairly similar in structure to the programming language C. If you have done much with C or C++, this is probably a relief to you. Even C# and Java are somewhat similar to it. But if you are still a little worried about HLSL, don't be. We will take it fairly slowly, and we will go through it from the ground up.
What's Next?
Well, I realize that this tutorial might be a little confusing at first glance. I don't really expect you to have understood everything here all at once. It might be useful to re-read this tutorial again or look up some other references online about the graphics pipeline. But in the rest of the tutorials in this set, I don't expect you to completely understand all of this information. We will go back through things as needed in the later tutorials. There is nothing wrong with simply continuing on to the next tutorial right away! The next tutorial will take you through the process of creating your first shader, which is a simple ambient light shader.
Having problems with this tutorial? Try the troubleshooting page! |