Jump to content
  • Advertisement
  • entries
  • comments
  • views

Critique my crappy ass shader article

Sign in to follow this  


Its less of a shader article and more of a quick run through on some of the topics that involve shaders. I'm going to write a different one once I get some free time, but for now I'm off to brainstorm before Raymond gets out of work.

@HopeDagger - You asked for it so its not my fault if it sucks really bad =TH.

Shader Tutorial - HLSL version

By Donald Beals

Sorry about excessively switching between the terms shader and effect, they're synonymous to me.

Part 1 - Introduction

I'm writing this thing because HopeDagger said I should, so if it sucks yell at him =TH (but if its good, I'll take the credit[grin].) Anyway, I have a few shader articles planned as of right now (this one, one that uses more advanced stuff, and then a series that covers a single topic (I'll be writing these as I'm learning the shader stuff).) Note that I'm using the effects framework since it simplifies stuff tenfold. That is all.

Part 2 - Variables

Ok, variables are the part of effects. There are tons of them - float, int, texture, sampler, vector, etc. For this article I'm sticking with floats, textures, and samplers because the rest of them are for more advanced stuff that I haven't gotten into.

Ok, floats come in 4 versions: float, float2, float3, and float4. There are a few ways to access the elements of the extra versions. First is the good ol' array index operator -[], which is nice if you're using them as arrays and such. But, you'll also be using them as vectors and colors, and [0] doesn't really stick out as being the X component. So, for when you're using them as vectors you can use .x, .y, .z, or .w (respectively of course.) And finally, for colors you have .a .r .g .b.

Example for you:

float3 Position = (float3)0;
float3 Direction = float3(0.7f, 0.7f, 0.0f);
float Speed = 21.0f;

Position = Position + (Direction * Speed);
Position += Direction * Speed;
Position.x += Direction.x * Speed;
Position.y += Direction.y * Speed;
Position.z += Direction.z * Speed;

// or, an better approach IMO:
Position.xyz += (Direction * Speed);

The top part shouldn't need any explanation, since it is the same as C++ (BUT, do notice that operations are component wise. Direction * Speed multiplies each of Direction's components (x, y, and z) by Speed.) The bottom part on the other hand is a little different. You can specify multiple components instead of just one. In the above example its kind of pointless, but instead of doing .xyz you could do .xy or .xz or .yz (I haven't tried mixing them up (i.e. .yx), but I have to leave something for you to do.)

Ok, samplers and textures need each other AFAIK. You create a texture object and then assign it to a sampler, which controls the sampler states, or something like that. Anyway, you'll declare a texture and then you'll declare a sampler like so:

texture YourTextureNameHere<>;

sampler YourSamplerNameHere = sampler_state
Texture = ;
AddressU = CLAMP;
AddressV = CLAMP;

(Notice the <> that are wrapping YourTextureNameHere.
These are required (I've also read that () can be used,
but I haven't tried them), so don't forget them
(or God just might kill a kitten.)

Ok, for now you can ignore the <> after YourTextureNameHere (if you want to go off on your own to figure this out, you'll have to look up "annotations".) So, your texture is declared and you won't do anything else with it (atleast, not in the scope of this article.) The sampler state is meat of our shaders for this article. We'll only be using it to get data from our texture for processing, but I'm sure there are other uses. Also notice that you can specify different sampler states in the declaration (you'll have to play around to figure out the state names and values. Supposedly the names are the same as the Direct3D ones, just without the D3D prefixes.)

Thats about all I can tell you about variables, so we'll move onto built in functions.

Part 3 - Built-in functions

Ok, HLSL supports most of your math operations such as cos() and sin(), but it also has a plethora of other crap (by crap I mean good stuff.) The 2 functions that I'll be focusing on are lerp() and tex2D(). We'll use lerp() in our last example to do some blending.

tex2D() is the function that we'll be using the most. It takes 2 variables - a sampler and some texture coordinates and returns the data from our texture. You may be wondering - how? I have no friggin clue, but it works. Here is a nice little sample for you (note that this isn't compileable since its not a full file):

texture ObjTex<>;
texture ObjMask<>;

sampler ObjSamp = sampler_state
Texture = ;
AddressU = CLAMP;
AddressV = CLAMP;

sampler MaskSamp = sampler_state
Texture = ;
AddressU = WRAP;
AddressV = WRAP;

float4 ObjTexColor = tex2D(ObjSamp, Input.TexCoord0);
float4 ObjMaskColor = tex2D(ObjMask, Input.TexCoord1);
float4 Result = ObjTexColor;
Result.a = ObjMaskColor.r;

I'm sorry =(, I tried my best to stay away from an example that had stuff in it that I hadn't explained, but it just didn't work. We'll return to this example when I explain structures (which will demystify the Input.TexCoord#.)

Ok, so ObjTexColor now holds the data from our ObjSamp texture and ObjMaskColor holds the data from our ObjMask texture. The last two lines are the magic of the example. It sets Result to ObjTexColor and then sets Result.a to the red component of our ObjMaskColor (I use only the red component that way you could specify more than one mask and also to show how this can be useful.) So, if our mask texture's red channel is 0, then that section will not be drawn at all. If its 128 then that pixel will be blended halfway. You get the idea, moving on.

Part 4 - Structures

Structures are really important, since they'll be containing your texture coordinates and such. Structures are defined just like C++ stuctures, with one exception - semantics. Semantics is a system that allows you to tell the compiler what the member is for. The hardest thing about semantics is learning what the compiler assumes. For example, one of the semantics you'll be using is POSITION. The compiler will assume that this is POSITION0 which might indicate you can have more than one position... I have no clue. I only know a few of the semantics:

TEXCOORD# (0 to 7 I believe)

// here is the format for a semantic:
VariableType VariableName : SEMANTIC;

So, you can probably figure out how to write a structure to represent our input pixel, but here's an example just in case:

struct InputPixel
float4 m_Diffuse : DIFFUSE;
float4 m_TexCoords0 : TEXCOORD0;
float4 m_TexCoords1 : TEXCOORD1;

You'll notice that I didn't include a position and that would be because I'm focusing on pixel shaders (since I don't have much 3D math practice.) Although, all of the stuff I've gone over is true for vertex shaders too. The only other thing that I can tell you about structures at the moment is no, they do not support methods.

Part 5 - Our first file

Ok, we're ready to make an .fx file. I'm going to try showing you the code first and then explaining it afterwards:

struct InputPixel
float4 m_Diffuse : DIFFUSE;

float4 ps_main(InputPixel Input) : COLOR
return 1.0f - m_Diffuse;

technique Technique0
pass Pass0
PixelShader = compile ps_1_1 ps_main();

Ok, this is a very simple example that inverts our input's color. The main things to notice are that the function uses a semantic (since it essentially represents the output color), that the function's name can be anything you want, and that their can be more than one pass and/or technique (sorry, but I'm not going to cover multiples of these since I haven't gotten that far.)

The line inside of Pass0 is the only thing that hasn't been covered. PixelShader is a variable that belongs to Pass0. Compile is a special keyword or something, and it takes 2 arguments: the version to compile to and the pixel shader function.

Thats about all there is to say about the above example, sorry if I missed something!

Part 6 - Our second file

Onto our next, awesomer example. In this one I'm going to include a single texture and we're going to use the sin() function to make it look kind of wavy. After this example I'll show the C++ code that is required. Later after I get the chance to ask Raymond if I can use his water texture I'll show a really cool water example that will use a very similar shader to the one we define here.

Ok, this example is going to use the following algorithm:

Input.TexCoord0.x + (sin((Input.TexCoord0.y + fSlowTimer)*30)*0.05);

Learn it, love it, and then explain it to me because I have no clue what its doing =/. The only thing I can explain is the fSlowTimer, which is a timer that slowly grows (so that our output isn't static.)

This part is mostly going to be a code dump, since I've already explained everything thats in use:

// Our input pixel structure
struct InputPixel
float4 m_Diffuse : DIFFUSE;
float4 m_TexCoords0 : TEXCOORD0;

// Our texture and sampler
texture DemoTex<>;
sampler DemoSamp = sampler_state
Texture = ;
AddressU = WRAP;
AdresssV = WRAP;

// Our timer
float fSlowTimer = 1.0f;

// Our entry point
float4 ps_main(InputPixel Input) : COLOR
float2 fTexCoords = Input.m_TexCoords0;
fTexCoords.x = fTexCoords.x + (sin((fTexCoords.y + fSlowTimer) * 30) * 0.05f);
return tex2D(DemoSamp, fTexCoords);

// our other stuff
technique Technique0
pass Pass0
PixelShader = compile ps_1_1 ps_main();

Now for the hard part - compiling and loading and such. We're going to need a custom vertex structure:

class Vertex
D3DXVECTOR4 m_Position;
int m_nDiffuse;
D3DXVECTOR2 m_TexCoords0;

static const int FVF;
static const int SIZE;

const int Vertex::SIZE = sizeof(Vertex);

We're going to need several variables:

ID3DXEffect* g_pEffectComPtr = 0;
D3DXHANDLE g_DemoTexHandle;
D3DXHANDLE g_SlowTimerHandle;
IDirect3DTexture9* g_pTextureComPtr = 0;
float g_fSlowTimer = 1.0f;

Loading the shader consists of a single function call with like 20 0's:

D3DXCreateEffectFromFile(pDirect3DDeviceComPtr, Filename, 0, 0, 0, 0, &g_pEffectComPtr, 0);

// Before we do anything else we need to set the technique as well:
D3DXHANDLE hTechnique;
g_pEffectComPtr->FindNextValidTechnique(0, &hTechnique);

// Okay, to do debugging with this thing, you need to do like so:
ID3DXBuffer* pError = 0;
D3DXCreateEffectFromFile(pDirect3DDeviceComPtr, Filename, 0, 0, 0, 0, &g_pEffectComPtr, &pError); // I put pError as the last argument
// output info however you want
int nLength = pBuffer->GetBufferSize();
std::string Text = (char*)pBuffer->GetBufferPointer();

Ok, so 5 zeroes, whatever. This compile and loads the effect file for us and the second bit of code selects Technique0 as our current technique. The third bit is how you handle debugging this call. Next is getting the handles to our variables; the handles are pretty much the pointers to our variables.

g_DemoTexHandle = g_pEffectComPtr->GetParameterByName(0, "DemoTex");
g_SlowTimerHandle = g_pEffectComPtr->GetParameterByName(0, "fSlowTimer");

Easy enough. The first argument of GetParameterByName() is a handle to a top level parameter. Sorry, but thats out of the scope of the article, the DX documents do a pretty good job of summing it up though. The second is the name of our variable in the shader. Now that we have the handle to our variables, we need to set them:

g_pEffectComPtr->SetTexture(g_DemoTexHandle, g_pTextureComPtr);
g_pEffectComPtr->SetFloat(g_SlowTimerHandle, g_fSlowTimer);

Next we need to render:

int nPassCount = 0;
int nPassIndex = 0;
g_pEffectComPtr->Begin(&nPassCount, 0);
// draw your shat here

It is important to note that if you call any of the Set() functions within the Begin()/End() calls, you need to call ID3DXEffect's CommitChanges() method.

And last but not least, we need to release our com pointers:


I didn't cover most of the C++ code since, if you're reading this then you should know how to create a Direct3D device and texture as well as render and release them.

This is pretty much the basics of shaders using the effects system. Using the shader systems separately is a lot more of a pain, so I wouldn't recommend it. As I said earlier, once I get a chance to talk to Raymond about using his water texture, I'll show you a really cool/simple example that makes some ok water.

EDIT: OMG, I just realized I forget the timer code. During the games main loop you're going to want to update the timer (so that the output isn't static.) To do so, we need to modify g_fSlowTimer. My demo updated the timer by 0.1f every 100ms. Here's a simple example:

// outside of game loop
int nTimeStart = 0;
int nTimeElapsed = 0;

// in game loop
g_pEffectComPtr->SetFloat(g_SlowTimerHandle, g_fSlowTimer);

int nTime = timeGetTime();
nTimeElapsed += nTime - nTimeStart;
nTimeStart = nTime;
if(nTimeElapsed > 100)
nTimeElapsed = 0;
g_fTimer += 0.01f;
Sign in to follow this  


Recommended Comments

Just because I love to tease people, I made a movie that demonstrates what part 2 of the article will look like =D. Thanks to Raymond for letting me use his water texture since mine sucked ass:

Edit: For reference, these are the image that are used to make that:



The top one is used for the whole thing and the bottom one is used for each chunk. The whole thing is made up of 16 256x256 chunks (4x4.)

Share this comment

Link to comment
Holy smokes! When I asked for a mini-article I sure didn't expect this! [smile]

You've covered a lot of stuff here, and I'm still trying to digest a lot of it. After seeing the sine-wavy code and then the cool movie it produces, I'm fairly convinced that shaders aren't quite as difficult as I had thought. However, there's still a few areas that I'm a little fuzzy on, which I think some experimentation will aid. I'll be sure to use this article as a reference as I leap forward!

(It might also be worth throwing the word "HLSL" in the title somewhere so casual readers will know that we're in D3D-land :P)

Again, really extensive and informative article. I'm really glad you took my suggestion to heart! Thanks!

Share this comment

Link to comment
No problem, if anybody has any questions or comments about it, don't hesitate.

Edit: also, this weekend I'll be doing part 2 as well as a article on making an orthographic multi-layer tile engine. The latter will be finished next weekend because I really want to focus on Project2.

Share this comment

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!