High-Level Shading Language
Talk is cheap. Show me the code.
— L. Torvalds
The Basics
As we have learned in earlier tutorials, shaders are not programmed in C++. DirectX shaders are written in their own language called High-Level Shader Language, or HLSL, for short. The syntax of HLSL is similar to C, but there are a few major differences:
Variables
HLSL has three basic variable types: scalars, vectors and matrices.
Scalar variables work just like normal variables in C. HLSL has support for bool, int, unsigned int, half and float scalar variables. Since most GPUs are optimized to work with floats, it is probably a good idea to use floats most of the time.
Vector variables simply store up to four scalar variables, declared by placing a number at the end of the type:
Matrix variables are made up by up to sixteen scalar variables, stored in rows and columns:
Semantics
Another big difference are the keywords (semantics) to describe how variables are being used on the GPU.
When passing variables between the different stages of the graphics pipeline, it must always be clear what the variables actually represent, for example, as information is passed into the rasterizer stage, it must be clear what variables represent the position and colour of each vertex, and so forth.
To define the role of each variable, each parameter and even all return values must get a suffix, separated by a colon from the variable name:
The POSITION suffix tells the GPU that this variable is used to store the position of a vertex.
A standard function call thus looks like this:
This function takes two parameters, holding position and colour information, and outputs only colour information. To handle multiple output, one would use structures, like this:
While at first using semantics seems a bit cumbersome, an obvious benefit of semantics is that variables can be named differently between stages. The DirectX pipeline does variable matching via semantics instead of variable names.
For another less obvious advantage, consider the following example
Since the vertex shader output provides all the information the pixel shader needs, this is perfectly valid, the normal variable will simply be ignored, and DirectX knows which variable represents the needed information.
And with this implementation, it is possible to write a new pixel shader, using the normal variable, without having to write a new vertex shader.
Thus, semantics provide a way to encapsulate input and output between stages, effectively giving us the equivalent of pointer addresses.
Just a warning though: While using different names does not slow down the program, different layout positions will, as DirectX will reorganize the data then.
For further details about semantics, check the MSDN.
As another example, here are the vertex and pixel shaders from the previous tutorials:
Vertex Shader
Pixel Shader
Vector Variables
The individual components of a vector are accessed like structs, using predefined keys, namely x,y,z,w for positions and r,g,b,a for colours:
Please note that whether you use x,y,z,w or r,g,b,a is not relevant, they access the same components:
HLSL offers an easy way to create vectors of equal or smaller dimensions from existing vectors:
Note that the keys must be in the same name set, else the declaration is invalid. Also note that using a vector variable as a scalar always only accesses the first coordinate of the vector.
In the next tutorial, we will create a few special shader effects, just for fun!
References
- Microsoft Developer Network (MSDN)
- Wikipedia