Q1: What exactly is a shader?

A1: A shader can be best though of as a script that executes on your graphics card. There are currently two main kinds of shaders, vertex shaders, and pixel shaders (fragment shaders to the OpenGL crowd).


Vertex shaders take as their input the vertex position, texture coordinates for that vertex, and other data that may be sent in. As output, they give the vertex position in clip-space and whatever data the pixel shader needs. This could be texture coordinates, a color or two, lighting information, or just about anything else. There are restrictions on how much data the vertex shader can send to the pixel shader which depend on the GPU and also API being used.


Pixel shaders take as their input whatever the vertex shader passes them. As output, they write color data to the current render target(s) which is generally the application window, though it may be a texture, or textures as the case may be.


Q2: Can you show me an example of an HLSL shader?

A2: Yes.


Vertex Shader:


struct AppData
{
   float4 Pos : POSITION;
   float2 UV0 : TEXCOORD0;
};

struct VertexOut
{
   float4 Pos : POSITION;
   float2 Tex0 : TEXCOORD0;
};

VertexOut main(AppData IN, uniform float4x4 ModelViewProjMatrix)
{
   VertexOut OUT;
   OUT.Pos = mul(IN.Pos, ModelViewProjMatrix);
   OUT.Tex0 = IN.UV0;
   return OUT;
}


Fragment Shader:


struct VertexOut
{
   float4 Pos : POSITION;
   float2 Tex0 : TEXCOORD0;
};

struct PixelOut
{
   float4 Color : COLOR0;
};

PixelOut main(VertexOut IN, uniform sampler2D texture : TEXUNIT0)
{
   PixelOut OUT;
   OUT.Color = tex2D(texture, IN.Tex0);
   return OUT;
}


Q3: Can you explain what each line of that shader does?

A3: Yes.


struct AppData
{
   float4 Pos : POSITION;
   float2 UV0 : TEXCOORD0;
};


This struct tells us what the application is passing into the vertex shader. In this case, we have a float4 (ie 4 floats in a vector) named Pos which takes the vertex position, and a float2 (ie 2 floats in a vector) named UV0 which takes the first texture coordinate assigned to the vertex.


struct VertexOut
{
   float4 Pos : POSITION;
   float2 Tex0 : TEXCOORD0;
};


This struct tells us what the vertex shader is going to output. We obviously must output the vertex position in clip space, and we are also outputting a texture coordinate which will be assigned to TEXCOORD0 when it is passed into the pixel shader.


VertexOut main(AppData IN, uniform float4x4 ModelViewProjMatrix)


This defines the main() function in the vertex shaders. All shaders must have a main function. This function returns a VertexOut, and as input takes an AppData, and a uniform float4x4 holding (hopefully) the Modelview-Projection matrix.

A uniform value is a value set by the application. A float4x4 is a 4x4 matrix.


OUT.Pos = mul(IN.Pos, ModelViewProjMatrix);
OUT.Tex0 = IN.UV0;
return OUT;


This multiplies our input vertex position by the modelview projection matrix and writes that result to the output register for vertex position in clip space. It also writes the input texture coordinate into the output texture coordinate register without any modification. Finally, it returns OUT, ending the main function, and the vertex shader. On to the pixel shader.


struct VertexOut
{
   float4 Pos : POSITION;
   float2 Tex0 : TEXCOORD0;
};


Now defines what the input to the pixel shader is, and what registers this input is located in. Just copying the VertexOut struct was suitable for our needs, and saved a few seconds of typing up another struct.


struct PixelOut
{
   float4 Color : COLOR0;
};


This struct defines our output from the pixel shader. In this case, we are outputting only one color, most likely to the main window.


PixelOut main(VertexOut IN, uniform sampler2D texture : TEXUNIT0)


This defines the main function for our pixel shader. It returns a PixelOut, and as input takes a uniform sampler2D.

A sampler2D is a variable type which is used in texture lookups. In this case, we are taking the value stored in the TEXUNIT0 register and assigning it to our sampler2D.


OUT.Color = tex2D(texture, IN.Tex0);


This reads the texture defined by the sampler2D texture at the texture coordinates IN.Tex0. It writes the result into the Color member of the PixelOut struct. After this, OUT is returned, ending the pixel shader, and writing the color into the current active render target (probably the main window).


Q4: Can you show the same shader as a GLSL shader?

A4: Yes.

Vertex Shader:


void main()
{
   gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
   gl_TexCoord[0] = gl_MultiTexCoord0;
}


Fragment Shader:


uniform sampler2D texture;

void main()
{
   gl_FragColor = texture2D(texture, gl_TexCoord[0].st);
}


Q5: Whoa, the GLSL shader looks completely different. What's going on, and why is it so different?

A5: GLSL includes a very large number of built in variables that are set by OpenGL. This variables include the current ModelView matrix, the current projection matrix, the current ModelViewMatrix, the current active lights and their information, etc. In effect, almost any part of the OpenGL render state that could affect the vertex or fragment shader is available to the shader as a built in value, and does not need to be specified by the application.

GLSL also includes a number of built in input and output variables. For example, gl_Vertex contains the vertex position, and gl_Position is the output register for the vertex position in clip space. gl_TexCoord[0] is the output register for a texture coordinate, and gl_MultiTexCoord0 stores the texture coordinates assigned to the first texture unit.

In the fragment shader, gl_FragColor is the output variable to write color to the main render target (usually the main window, though it may be a texture).

One important thing to note is that GLSL does not allow main to return anything, or to accept any inputs.

Another important thing to note is that GLSL does not allow uniforms to be initialized to any values. In HLSL, we said
uniform sampler2D texture : TEXUNIT0 to set the texture sampler2D to the value stored in TEXUNIT0. In GLSL this must be set directly from the application. That would look something like this:


U32 textureLoc = glGetUniformLocation(myShader, "texture");
glUseProgram(myShader);
glUniform1i(textureLoc, 0);