As already stated, I am not an expert in shader development - very far from it, in fact. Much of what I write is likely to be technically inaccurate - partly because I'm intentionally leaving out large chunks of information which I don't think you need to know, and partly because I don't actually know what I'm talking about myself. It would be great if someone who does know what they are talking about could write an article on the subject, but until that happens, this is what you're stuck with.

Note: This article is also available as a downloadable rich text file, with more reader-friendly formatting, from here: http://sdrv.ms/14tKNrK

What are pixel shaders?
A pixel shader is essentially a small computer program which manipulates the individual pixels of an image (in MMF2 terms, that means either an object, a layer or an entire frame), to produce a wide range of highly customizeable special effect filters.
The great thing about pixel shaders is that they use the computer's graphics card instead of the CPU, which can dramatically improve performance, since the graphics card is designed specifically for this, and the CPU is then freed up for other things (this is what's meant by hardware acceleration or HWA).

How do pixel shaders work?
A pixel shader works by individually scanning every pixel in a texture (image), retreiving information about the pixel such as its position, color and opacity - this is known as sampling. The shader code then performs calculations using this information - as well as information sampled from other pixels (including pixels in other textures), if required - and outputs a new pixel color and opacity, which is what appears on screen.

Shader programming languages
MMF2 only supports Microsoft DirectX pixel shaders (up to DX9.0c), which means shaders must be written in something called high level shader language (HLSL). MMF2 does not support other kinds of shader (eg. vertex shaders) or shaders written in other languages (eg. GLSL, which is used by OpenGL).
There is an excellent online language reference here: http://msdn.microsoft.com/en-us/library/windows/desktop/bb509615(v=vs.85).aspx (more MMF2-specific information can also be found in the MMF2 help file and on the clickteam forums).
The rest of this article will attempt to condense just the most important parts into something more manageable.

Variables are just like MMF2's alterable values and strings - but HLSL includes a much greater variety of types. Before a variable can be used in calculations, its type must first be defined (after which, there is no need to define it again). It is possible to define a variable and set its value at the same time, but this is not compulsory.
The main variable types are as follows:

An int is simply an integer (whole number).
eg. int myValue = 1;
Note that a single = sets the variable on the left to the value on the right.
Also, note the ; at the end of each line - in HLSL, every statement must end with a semicolon.

A float is simply a floating point number (fraction).
eg. float myValue = 1.0;

A float2 is a pair of floating point numbers (x and y coordinates), grouped together as one variable.
eg. float2 myValue = float2( 1.0, 1.0 );
Note that float2( x , y ) is a function (the equivalent of an expression in MMF2) used to set the individual x and y components.
When retrieving or setting one of the individual x or y components, the float2's name is followed by .x or .y.
eg. float myXValue = myValue.x;
eg. myValue.y = myYValue;

A float3 is three floating point numbers (r, g and b color components), grouped together as one variable.
eg. float3 myValue = float3( 1.0, 1.0, 1.0 );
eg. float myRedValue = myValue.r;
eg. float myGreenValue = myValue.g;
eg. float myBlueValue = myValue.b;

A float4 is four floating point numbers (r, g, b and a color components), grouped together as one variable.
eg. float4 myValue = float3( 1.0, 1.0, 1.0, 1.0 );
eg. float myAlphaValue = myValue.a;
A pixel shader will always output a float4 color at the end.

Statements in HLSL are just like actions in MMF2, with the usual mathematical operators such as +, -, / and *.
eg. myValue = (myValue + 1.0) * myOtherValue;
Some operators are different (for example, % is the equivalent of MMF2's mod operator), and there are a whole host of intrinsic functions available for use, so it may be necessary to consult the online reference.

The most common way to compare variables is using a simple if condition.
The syntax looks like this:
if (myValue == 1.0) {
myValue = 0;

Note that the condition itself is contained within brackets, and that == is used in place of a single =, which is used to assign values rather than to compare them. The code block that is to be executed if the condition is true, is contained within curly braces ({ and }) and comes immediately afterwards. The code example above simply checks to see if the variable myValue has a value of 1, and if so, sets it to 0.
The available comparators are:
== equal to
!= not equal to
>= greater than or equal to
> greater than
< less than
<= less than or equal to

An if condition may be followed by a similar else condition, although this is optional.
if (myValue == 1.0) {
...do this if the condition is true...
} else {
...do this if the condition is not true...

Alternatively, something called the ternary operator may be used.
The syntax looks like this:
myValue = myOtherValue == 0 ? 1 : 2;
The code example above will check if the variable myOtherValue is equal to 0. If so, it will set myValue to 1; if not, it will set myValue to 2.
Note the use of ? after the condition, and : to separate the two possible results.

It is also possible to construct conditions combining multiple comparators, using || (which means or) and && (which means and).
eg. if (myValue % 2 == 0 && myValue > 1) { ... }
The code example above will check if the value of the variable myValue is an even number, greater than 1.

Finally, it is possible to check whether any or all of the individual components of a float2, float3 or float4 satisfy the condition.
eg. if (any( myValue.rgb == myOtherValue.rgb )) { ... }
The code example above will check if any of the red, green or blue components are the same for both the variables myValue and myOtherValue.
eg. if (all( myValue.rg == myOtherValue.rg )) { ... }
The code example above will check if both the red and green components are the same for both variables.

HLSL allows for two different types of comments to be added code. These are ignored by the compiler, so can be used to explain what a piece of code does (particularly helpful if the shader is going to be shared with other people), or to temporarily disable a piece of code for debugging purposes.

Single line comments use // to denote the start of a comment (everything that follows on the same line is ignored).
eg. float myValue = 0.5; // <-- this sets myValue to 0.5

Multi-line comments use /* to denote the start of a comment, and */ to denote the end of a comment (everything in between is ignored).
eg. float myValue = 0.5; /*
^ this sets myValue to 0.5

Colors and dimensions
Pixel shaders use the familiar RGB (red, green, blue) component color model, plus an alpha (opacity) component. However, while in most computer graphics applications each of these components ranges from 0 to 255, in pixel shaders they are floating point values ranging from 0 to 1.
eg. float4( 1, 1, 1, 1 ) = white (255, 255, 255 with 100% opacity)
eg. float4( 1, 0.5, 1, 0.75 ) = pale pink (255, 128, 255 with 75% opacity)

Similarly, image coordinates are also specified using floating point values ranging from 0 to 1.
eg. float2( 0, 0 ) = top-left corner
eg. float2( 0, 1 ) = top-right corner
eg. float2( 1, 0 ) = bottom-left corner
eg. float2( 1, 1 ) = bottom-right corner
eg. float2( 0.5, 0.5 ) = centre

Should it be necessary to know the actual pixel dimensions of a texture, these must be specified as variables - they cannot be found automatically.

The structure of a pixel shader (.fx) file
A simple pixel shader is composed of 4 main parts, each of which will be covered in greater detail later on:

- Texture samplers
This is a list of textures which are used by the shader. These can include the object's own image, the screen directly behind the object, and other internal images and external image files.

- Predefined variables
Variables which only need to be set once, with the same value being used for all pixels, should be defined outside of the main pixel shader function. This includes all variables which are set from within MMF2.

- Main shader function
This is the function containing all of the actual shader code.

- Shader model
This tells MMF2 which shader model to use when compiling the shader. More recent shader models provide more advanced features (making more complex effects possible), but will only function on computers with more modern graphics cards.

Texture samplers
The first part of any shader is simply a list of the textures which the shader will use.
There are 2 special types of texture:
img - This is the object's own image.
bkg - This is the object's background (ie. what is directly behind it).

It's also possible to define additional custom textures, which makes it possible to load images from external files at runtime, among other things. This will be explained in greater detail later on (in the section relating to .xml files).

The syntax for registering a texture looks like this:
sampler2D img : register(s0);
sampler2D bkd : register(s1);
sampler2D myTexture : register(s2);

Only those textures which the shader actually uses should be included, and the register value must be different for each.

Predefined variables
Predefined variables are variables which need to be set only once, when the shader starts, and then do not change - the same value is used in the calculations for all pixels. This includes constants and any variable which is to be accessible from within MMF2.
eg. float pi = 3.14159265;
eg. float myValue;
Note that in the second example, myValue is defined as a variable of type float, but no value is assigned to it. In this case, the value will be set from within MMF2 - which means that the variable must be added as a parameter in the .xml file (see below).

Predefined variables should be defined before (and outside of) the main shader function (see below).
Variables which do not stay the same throughout, should be placed inside the main shader function instead.

The main shader function
The main shader function is where the majority of the shader code is placed, including all of the complex calculations.
The syntax looks like this:
float4 ps_main(in float2 texCoord : TEXCOORD0) : COLOR0 { ...insert your pixel shader code here... }

The code example above is for a function called ps_main, which sets a float2 called texCoord to the coordinates of the current pixel, and outputs a float4.

To retrieve the actual color information from the current pixel, it would need to be sampled:
eg. float4 myColor = tex2D( img, texCoord );

tex2D is the special function used to sample a pixel. It requires two parameters - a texture (this example uses img, which is the object to which the shader is applied - see above) and a float2 containing the coordinates of the pixel to be sampled.

The main shader function must output a float4 variable containing the new pixel color, in rgba format.
This is done simply by using the keyword return, followed by the variable's name.
eg. return myColor;

More detailed information on how to write this part of the shader will be provided later on.

Shader models
DirectX has provided support for pixel shaders since version 8.0, which was released back in 2000. Over the years, the technology has improved, giving rise to a number of different pixel shader versions, known as models. As a result, it is important to specify the model for which the shader is designed.
The syntax looks like this:
technique tech_main { pass P0 { PixelShader = compile ps_2_a ps_main(); } }

In the code example above, ps_main is the name of the main shader function, and ps_2_a is the shader model.
The following shader models are available:
ps_1_1 - DirectX 8.0
ps_1_3 - DirectX 8.0a
ps_1_4 - DirectX 8.1
ps_2_0 - DirectX 9.0
ps_2_a - DirectX 9.0a
ps_2_b - DirectX 9.0b
ps_3_0 - DirectX 9.0c

Note that versions 10 and 11 of DirectX, and therefore pixel shader models 4.0 and 5.0, are not supported by MMF2 at present.
Newer shader models allow for more complex shaders, but require more modern hardware in order to function, so it's good practice to use older models where possible (although realistically, DirectX 9.0c has been around since 2004, so compatibility shouldn't be a problem).
A comparison of the different shader models is available here: http://en.wikipedia.org/wiki/HLSL

The .XML file
Along with the .fx file, MMF2 also requires a .xml file. This is not part of the pixel shader itself, but is used to create an interface between the shader and MMF2 - including the edit boxes, slider controls, etc used to set shader variables.
The easiest way to get an understanding of the structure of an xml file is to open one up and take a look - a basic text editor such as notepad will suffice.

The first element is the <effect> element, which serves as a container for all the others, and is required.
<effect> ... </effect>

Inside that are additional elements containing information about the shader, which is displayed within MMF2. None are strictly required, though it's a good idea to include at least the <name> and <author> elements.
<name> ... </name>
<description> ... </description>
<author> ... </author>
<company> ... </company>
<copyright> ... </copyright>
<website> ... </website>
<email> ... </email>

Also included within the <effect> element, are <parameter> elements - one for each pre-defined variable used in the shader .fx file, listed one after the other. Each contains a number of child elements:

<name> ... </name>
<code> ... </code>
<description> ... </description>
<type> ... </type>
<property> ... </property>
<value> ... </value>
<min> ... </min>
<max> ... </max>
<delta> ... </delta>
<preview_value> ... </preview_value>

The <name> and <c/ode> elements give the variable's name, as it appears in MMF2 object properties panel and in the .fx shader file (also when setting the parameter at runtime), respectively.

The <type> element gives the variable type in the .fx shader file, and may be one of the following:

The first two should be self-explanatory by now.
int_float4 is a float4 (ie. rgba) color, and should be used with the color property (see below).

image is an image texture, and should be used with the image property (see below). This can be edited from the properties panel, or loaded at runtime from an external image file. For each image parameter used, a corresponding sampler must be added to the .fx shader file.
eg. sampler2D myTexture : register(s2);

The <property> element gives the GUI control type used to set the variable from within MMF2, and may be one of the following:
edit - an edit box, into which a value may be typed.
spin - a numeric control with up and down buttons, as well as a box into which a value may be typed.
slider - a drag-able slider bar.
checkbox - a simple checkbox, which sets the variable to either 0 or 1.
color - MMF2's standard color picker.
image - MMF2's standard picture editor.

The remaining few elements are optional, and not particularly important:
The <value>, <min> and <max> elements give the variable's default, minimum and maximum values, respectively.
Finally, <delta> is used to set the step size of slider controls, and <preview_value> to set the value used in the preview window (the small picture of a yellow fish).

Final words...
Firstly, well done for getting this far.
I've hopefully now covered all the theoretical stuff that you need to know in order to write your own shaders, but I fully appreciate that at this point, you're probably feeling more than a little lost.
So, in an attempt to remedy that, I will finish off this article with a handful of examples, which should hopefully make things a bit clearer.
All the example shaders are included in a zip file, available here: http://sdrv.ms/1aR9OSq

Example 1 - Greyscale Shader
This example will convert an image to greyscale.

// Start of .fx file
// Firstly, we need to include a sampler to get the object's own image.
sampler2D img : register(s0);

// This shader won't require any predefined variables, so we can move straight on to the main shader function.
float4 ps_main(in float2 texCoord : TEXCOORD0) : COLOR0 {

// Next, we need to get the pixel's original color and opacity.
float4 pixelColor = tex2D( img, texCoord );

// We can then calculate the average of the red, green and blue color components.
float pixelAverage = (pixelColor.r + pixelColor.g + pixelColor.b) / 3.0;

// The pixel's new color will have red, green and blue components all equal to the average that we just calculated, while the opacity remains unchanged.
float4 newColor = float4( pixelAverage, pixelAverage, pixelAverage, pixelColor.a );

// Now we just output the new color, and close the main shader function with a closed curly brace.
return newColor;

// The final part of the .fx file simply identifies the shader model as version 1.1 (this is a very basic shader, so does not demand a more modern shader model).
technique tech_main { pass P0 { PixelShader = compile ps_1_1 ps_main(); } }
// End of .fx file

As this example is only very basic, with no variables that are set from within MMF2, the .xml file is very simple:
<description>Converts an image to greyscale.</description>

To test the shader, first copy both the .fx and .xml files into the ...\Multimedia Fusion 2\Effects folder.
Next, from within MMF2, ensure that the application's display mode is set to Direct3D 9.
Finally, select either an object or a layer, and select your shader from the display options panel.
You may notice that there is an option to edit the shader from within MMF2 - unfortunately, if MMF2 is installed to the program files folder, you may not be able to resave the files after editing, due to to problems with permissions in Windows 7, unless you run MMF2 as an admin.

Example 2 - Flip Shader
This example will flip an image horizontally and/or vertically.

// Start of .fx file
// Firstly, we need to include a sampler to get the object's own image.
sampler2D img : register(s0);

// This shader will provide predefined variables (and checkboxes) so that the user can select whether to flip the image horizontally or vertically or both.
int flipX;
int flipY;

// Now on to the main shader function...
float4 ps_main(in float2 texCoord : TEXCOORD0) : COLOR0 {

// We'll start by creating a new set of coordinates, which is where we'll sample to get the new pixel color.
float2 newCoord = texCoord;

// If the variable flipX is equal to 1 (ie. if the checkbox has been checked), we will invert the x axis - remember that coordinates are not measured in actual pixels, but range from 0 to 1 (the left- and right-most edges), so to invert the axis we just subtract from 1.
if ( flipX == 1 ) {
newCoord.x = 1.0 - texCoord.x;

// And now the same for the y axis...
if ( flipY == 1 ) {
newCoord.y = 1.0 - texCoord.y;

// Next we sample a pixel from the new coordinates.
float4 newColor = tex2D( img, newCoord );

// And then we just output the new color and close the main shader function.
return newColor;

// The final part of the .fx file simply identifies the shader model as version 1.4 (this shader is a little more complex, and will give an error message with shader models below version 1.4).
technique tech_main { pass P0 { PixelShader = compile ps_1_4 ps_main(); } }
// End of .fx file

This time, the shader uses two predefined variables which need to be included in the .xml file:
<description>Flips an image horizontally and/or vertically.</description>

<name>Flip horizontally</name>
<description>Check to flip the image horizontally.</description>

<name>Flip vertically</name>
<description>Check to flip the image vertically.</description>

Example 3 - Zoom Shader
This example will zoom in on the centre of an image.

// Start of .fx file
// As usual, we start with the standard sampler...
sampler2D img : register(s0);

// This shader will include a predefined variable to control the zoom level.
float zoom;

// Again, we have the standard main shader function...
float4 ps_main(in float2 texCoord : TEXCOORD0) : COLOR0 {

// Next, we calculate the coordinates from which we will sample. Since coordinates range from 0 to 1, we can scale an image simply by dividing texCoord by the zoom factor. The other parts are there to ensure we zoom in on the centre of the image.
float2 newCoord = ((texCoord-0.5)/zoom)+0.5;

// Again, we sample the new pixel, and output the result.
float4 newColor = tex2D( img, newCoord );
return newColor;

// The final part of the .fx file simply identifies the shader model as version 2.0.
technique tech_main { pass P0 { PixelShader = compile ps_2_0 ps_main(); } }
// End of .fx file

The .xml file is relatively simple, with just one parameter:
<description>Zooms in on the centre of an image.</description>

<description>Zoom level.</description>

Example 4 - Triangle Shader
This example will draw a colored triangle, and is based on the following article: http://www.blackpawn.com/texts/pointinpoly/
It demonstrates that resources readily avialable on the internet, can easily be used as the basis for a pixel shader - even with no understanding whatsoever of the underlying theory.
As this shader is less straight-forward to use, an example .mfa is included with shaders here: http://sdrv.ms/1aR9OSq

// Start of .fx file
float Ax;
float Ay;
float Bx;
float By;
float Cx;
float Cy;
float4 drawColor;

float4 ps_main(in float2 texCoord : TEXCOORD0) : COLOR0 {

float2 A = float2(Ax, Ay);
float2 B = float2(Bx, By);
float2 C = float2(Cx, Cy);

float2 v0 = C - A;
float2 v1 = B - A;
float2 v2 = texCoord - A;

float dot00 = dot(v0, v0);
float dot01 = dot(v0, v1);
float dot02 = dot(v0, v2);
float dot11 = dot(v1, v1);
float dot12 = dot(v1, v2);

float invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
float v = (dot00 * dot12 - dot01 * dot02) * invDenom;

float4 newColor = float4(0,0,0,0);

if ((u >= 0) && (v >= 0) && (u + v < 1)) {
newColor = drawColor;
return newColor;

technique tech_main { pass P0 { PixelShader = compile ps_2_0 ps_main(); } }

// End of .fx file

...And now the accompanying .xml file:

<description>Draws a colored triangle.</description>

<description>X coordinate of point A.</description>

<description>Y coordinate of point A.</description>

<description>X coordinate of point B.</description>

<description>Y coordinate of point B.</description>

<description>X coordinate of point C.</description>

<description>Y coordinate of point C.</description>

<name>Fill color</name>
<description>The color of the triangle.</description>