HLSL Tutorial
HLSL Tutorial
HLSL Tutorial
PAUL HARTZER
C ONTENTS
Overview 1
Background 2
HLSL 2
Color representations 3
Basic software 5
A simple shader 5
A more complex shader 7
Shaders that relocate pixels 10
Using HLSL filters in VC# 11
Conclusion 13
End notes 13
O VERVIEW
HLSL is a C-related language designed to allow for manipulation of images in the
DirectX model. One of its functions is to create filters, from simple color tinting to
more advanced image manipulations.
Next, I’ll go through three simple examples taken from the sample shaders provided
by Shazzam. These examples are intended to give you an idea of some of the basics of
HLSL as applied to pixel shading.
I’ll wrap up by briefly detailing how I implemented a shader within VC# 2010. The
biggest part of the work, though, is creating the shader within Shazzam; once that’s
been done, applying it within VC# is fairly easy.
B ACKGROUND
HLSL
HLSL stands for High Level Shader Language. In general, a shader language provides a
means for describing how the computer (specifically, the GPU, or Graphics Processing
Unit) is to manipulate basic input images for various effects. This consists of three
basic components:
C OLOR REPRESENTATIONS
The color of a specific pixel in an image file is described by four parameters. Three of
these relate to color (red, green, and blue), and the fourth to the alpha channel. These
values are typically referred to by their initials (r, g, b, and a, respectively).
In HLSL representations, RGB values vary from 0 (no color) to 1 (full color). For
instance, if r = 1, g = 0, and b = 0, the pixel is bright red. If r = 0.75, g = 0.5, and b =
0.25, the pixel is orange. An HLSL value of 1.0 corresponds to a hexadecimal value of
#FF.
The alpha channel represents the degree of transparency; as Webopedia2 describes, “it
specifies how the pixel’s colors should be merged with another pixel when the two are
overlaid, one on top of the other.” Images that are completely opaque (that is, nothing
shows through when placed over other objects) have an alpha value of 1. Invisible
images have an alpha value of 0. Figure 9 illustrates applying variable levels of
transparency to the original image (alpha = min{red * green * blue * 5, 1}), and then
superimposing it onto a second image:
DirectX SDK: The most recent DirectX SDK can be obtained from Microsoft3. This
is required for HLSL.
Shazzam: While not required, Shazzam4 greatly simplifies the process of learning,
developing, and compiling pixel shaders in HLSL. It also comes with a large
collection of sample shaders, which can then be easily further modified as desired.
A SIMPLE SHADER
Because the pixel shader applies the same formula to each pixel in an image, there is
no looping required. Here is the code for InvertColor, which creates the “negative”
image in figure 3 (line numbers are provided for reference only and should not be
included in the actual code; the variable names have been changed from Shazzam’s
code for expository purposes):
Line 01 labels the input image, referenced in line 04. InputImage is a variable name,
and can be replaced with any suitable name. sampler2D simply indicates that the
program will be operating in a two-dimensional image file. Registers will be discussed
below.
Line 02 indicates that the input will consist of a two-dimensional set of pixel
coordinates, to be referenced by the variable named xy. The output will be a four-
value array representing a color.
In HLSL pixel shaders, these are the most common variable types:
float3: A three-dimensional array, used for rgb values (hence, excluding the alpha
channel)
Line 04 takes the color information of a specific pixel in the image and places it into a
variable called InputColor.
Line 05 calculates the new value of the pixel. HLSL allows a special shorthand that can
be confusing. float4() takes four arguments, but these can be grouped together. In
this case, InputColor.a - InputColor.rgb is equivalent to InputColor.a -
InputColor.r, InputColor.a - InputColor.g, InputColor.a - InputColor.b. In
fact, all of these lines are equivalent:
Note that order is important, so this line, while valid, is not equivalent to the ones
above:
Since the default value for the alpha channel is 1.0, line 05 will usually be equivalent to
this:
In other words, for the red, green, and blue channels, take the inverse of the value.
Using InputColor.a instead of 1 in line 05 takes any transparency into account.
If the formula is adjusted, the output image changes. For instance, you could take just
the inverse of the red channel while swapping the blue and green, or you could apply
different formulas to each channel (pow() is the HLSL function for powers;
pow(x,0.5) takes the square root of x):
Fig. 10: Red inverted, blue/green swapped Fig. 11: Various simple tweaks
Finally, line 06 returns the value. The procedure is then repeated on each pixel in the
image.
As you can see, you can create some fairly interesting filters by using code snippet 1 as
a template and simply changing line 05; Microsoft maintains a complete list of
intrinsic HLSL functions6. More complex filters, such as the distortion in figure 4
above, require more programming.
Lines 01-04 represent user-set variables. To see how each of these work, select the
ColorTone sample shader in Shazzam, and then select the Change Shader Settings tab.
Each piece of user input requires a register, including the input image itself.
Obviously, since these are user settings, any of them could be replaced with a fixed
value in the code itself. For instance, if you want to have a fixed desaturation of 0.5,
delete line 01 and insert this before line 08:
Additionally, you could create other user settings, set as allowing for variable levels of
gray (line 10) rather than using the standard values.
Lines 09 to 13 represent the transformation applied to the image. This uses two
common functions, dot() and lerp(). dot() calculates the dot product of two vector
arrays; this is a technical way of saying that each value in the first array is multiplied
by the corresponding value in the second array, with the results being added together.
Thus, line 10 is a shorthand version of this:
The values (0.3, 0.59, 0.11) are standard values for making a color image gray, meant to
account for the varying brightnesses of red, green, and blue. These are not the only
standard values; Wikipedia7, for instance, uses (0.2126, 0.7152, 0.0722). Feel free to
adjust as desired.
The function lerp() returns the linear interpolation of two points at a specified
distance. That is to say, the function draws a line between the two points, and then
calculates a new point somewhere on that line, the specified distance (as a
percentage) away from the first point. The formula is x + s(y-x), where x and y
represent the endpoints and s is the distance. For instance, if you merely wanted to
make an image look washed out, you could use this code:
which results in the image in figure 12. The Color Tone effect creates a more complex
desaturation and colorization, seen in figure 3 (repeated here for comparison):
As with dot(), lerp() can be replaced with the underlying formula with the same
result, as with line 11:
If you choose, you can even expand it fully, still with the same result:
float3 muted;
muted.r = scnColor.r + Desaturation*(gray.x - scnColor.r);
muted.g = scnColor.g + Desaturation*(gray.x - scnColor.g);
muted.b = scnColor.b + Desaturation*(gray.x - scnColor.b);
scnColor: a copy of the image tinted strongly towards the lighter of the two
specified colors. For instance, if LightColor is yellow, then whites and lighter
pixels in the original image will become yellow, but darker colors will be less
affected. To see the effect, comment out line 13 and reapply it using F5.
Note that the function uses predominantly float3 rather than float4 values, because
this effect does not affect the degree of transparency (i.e., alpha channel).
Lines 08-10 determine the location of the pixel in relation to the center point specified
in the user setting. The y coordinate is adjusted if something other than 1 is specified
for AspectRatio.
Line 11 calculates the actual distance between the current pixel and the center point.
The important difference between this shader and the previous examples is in the last
line of the function, line 14. Instead of returning the adjusted color of each pixel in the
image, the function returns information for the appropriate pixel in the original image
based on the adjustments. The function tex2d() returns the pixel of the specified
image at the specified co-ordinate, which may or may not be the same as the co-
ordinates currently under consideration. This function appears in the first line of the
previous two examples because no pixels are moved in those cases.
Here is the shader with different values specified for the user-specified variables. Note
that in figure 13, an aspect ratio of 1 creates an oval rather than a circle; this is because,
as noted earlier, the image is remapped virtually onto (0,1) scales in each direction, so
effects with an aspect ratio of 1.0 will create ovals, not circles, unless the base image is
square.
These are the steps I followed to add a red-orange filter to selected cards in my
Canfield8 program:
pixelShader.UriSource = new
Uri("/Shazzam.Shaders;component/ColorTone.ps", UriKind.Relative);
to this:
d. Within the Solution Explorer, I set the Build Action for ColorTone.ps to
Resource.
The above steps added the appropriate file to the project’s resources and provided the
basic code for applying the filter. Additionally, certain code was added to the main
project code file.
using Shazzam.Shaders;
To the beginning of the function that redraws the screen after a card selection, I
added:
Note that the extra color conversion is needed within the WPF framework in which
the program was written, because WPF treats graphics differently than traditional
VC# projects. Recall that the Color Tone effect takes four variables; since only two are
defined, the other two will take their default values.
Finally, I directed the program to apply the filter to the appropriate image, based on
which card has been selected. For instance, this code applies the filter to the top of the
waste pile if that card is selected; otherwise, all effects are turned off.
Once you’ve used Shazzam to do the heavy lifting, the actual implementation of a
pixel shader within VC# 2010 is fairly straightforward.
C ONCLUSION
This article is intended as a basic overview on using HLSL to create pixel shader
effects, also known as filters, and to implement these effects within VC#. HLSL goes
far beyond the examples discussed above, but hopefully this basic tutorial has
provided enough foundation for you to now explore the other sample effects provided
by Shazzam, as well as generating your own filters.
E ND NOTES
URIs to links provided in text (as of September 10, 2010):
1
http://en.wikipedia.org/wiki/Shader
2
http://www.webopedia.com/TERM/A/alpha_channel.html
3
http://msdn.microsoft.com/en-us/directx/aa937788.aspx
4
http://shazzam-tool.com/
5
http://www.microsoft.com/express/Downloads/
6
http://msdn.microsoft.com/en-us/library/ff471376(v=VS.85).aspx
7
http://en.wikipedia.org/wiki/Luminance_(relative)
8
http://paulhartzer.com/programming/csharp/canfield-wpf/