Until very recently, I thought making a Matchbox for Flame would be a long and painful task. I had looked into it a couple years ago when the documentation was very limited, and gave up without really giving it a chance.

I looked into it again a couple of weeks ago, and discovered that I was very wrong. Making a matchbox was actually pretty straightforward once I got started breaking it down.

Let’s look together at a way to get started making your own shaders.

1 – Useful resources

Matchbox shaders are using the GLSL language, which stands for OpenGL Shading Language, and uses a syntax very close to C and C++. If you have bases in these languages, you should be pretty comfortable jumping straight into it.
If you’re not familiar with C languages, do not despair, as it will still be relatively easy to get started making your own Shaders.

The website of OpenGL offers A LOT of resources and documentation about GLSL, but I think it’s overkill for beginners, and you shouldn’t need to look into it until you’re getting into really complex stuff.
I prefer to use the very compact documentation put together by the makers of the Shaderific App (a GLSL app for iOS), it’s a very easy to read simplified documentation which contains enough to get you started. It’s available here: http://www.shaderific.com/glsl/.
Next, logik-matchbook.org provides you with dozens of Matchboxes already made. and for me was the most useful source of information. I built my first shader by breaking down somebody else’s shader. If there is something you would like your shader to do but you don’t know how to achieve it, don’t hesitate to find another shader that achieves that result, and dissect it to understand how it’s done. Of course I don’t recommend ripping off other peoples work and redistributing it as your own, be respectful of people’s work and give credit when needed.
The LOGIK facebook group is great at giving support for anything Flame related, so I would head over there if I had any questions about making matchboxes.
The official autodesk documentation (PDF) is of course a good place to look as well.
Last but not least, there are a few websites that provide pre-made GLSL shaders that are not Matchboxes, but are good sources of inspiration or can sometimes be converted directly into a matchbox. Example: shadertoy.comglslsandbox.com

2 – Decide what will your shader do

If you’re reading this tutorial, I would assume there is a Matchbox you’d like to make. It’s a good time now to think about what you will be making and get an idea of the math implied to achieve the results you’re after.

I will be making what many people seem to call an additive keyer. I’m not sure why it’s called like this since it’s not a keyer at all, but rather a way to blend a Front image with a Background image in order to restore small details lost in a key, but I’ll stick to the given name.

The idea and math behind an additive keyer is very basic, and would be a good shader for a total beginner like myself:
Subtract a reference color of your choice from your Front image, then add the result to your Background image.
When you subtract the reference color, you end up with either positive or negative values. As a result, when you add that on your background, the negative values will make your background darker, and the positive values will make it lighter.

We can push it a bit further by giving the user more control, to maybe adjust the amount of darks and brights to reveal, or adding saturation controls.

I already made that shader, which I will redo now slightly differently but here are some results obtained with the first version of it.

addkey_1

Original plate (cropped and zoomed in)

addkey_2

Pulling a 3 click key in a Master Keyer

addkey_3

Result with a Comp node over a Blue Background

Blend over a blue BG using matchbox,

The UI of the Matchbox Additive Keyer

The UI of the Matchbox Additive Keyer

3 – Start coding

Open the text editor of your choice (or an IDE if you’re a more serious programmer). I find that Gedit on linux does the job perfectly. You can set it up to highlight C language, and turn on line numbering to make it a bit easier on your eyes.

Most Matchbox shaders follow pretty much the same structure. It can be mixed up a bit but it should more or less match up with something that looks like this:

[cpp] // Define Inputs

// Define Uniform Variables (that we will be able to control in the user interface)

// Define some functions if needed

// Main loop (Where most of the magic happens)[/cpp]

A bit on GLSL syntax:

  • Comments are defined by adding “//” in front of a line. Anything after these slashes will be ignored by the interpreter.
  • Variables are defined in a few different ways, the most common being “varType varName = varValue”. varType would be the type of the variable, it could be float, int, vec2, vec3, vec4 (vectors of 2,3,4 dimensions), … varName would be the name of your variable, it can be pretty much whatever you want, but try to give it a meaningful name. Finally, varValue would be the value of your variable. Make sure the value is of the same type as you assigned your variable. Example: vec3 color = (0.75, 1, 0.5);
  • Functions can be defined as well. A function is defined like so: “resultType functionName (arguments) {function content}”. We will see a more detailed example in a bit.
  • Uniforms are global variables that are used as parameters. These are the variables that will be exposed to the user in the Flame interface. They are your controls. They are defined by typing “uniform varType varName”.
  • Semicolons (;) are needed after most commands in GLSL code. For me that was the main source of error, as I’m not used to that requirement coming from Python.

Let’s get started. If we follow the main layout provided above, the first thing we need to worry about is “What inputs should we accept?”.
Front and Back would be the only really necessary inputs needed for the additive process, however, as we would like to be able to handle the blend operation in the shader as well, we also need a matte.

One thing to keep in mind, is that GLSL on linux is currently using version 130 of GLSL, while MacOS only supports up to version 120 so far. So if order to make the shader compatible with Mac users, we’ll add a line at the top. As well as a needed line to obtain the image resolution from Flame.

[cpp] // version 120 is to make it work on Mac
#version 120

//Image resolution
uniform float adsk_result_w, adsk_result_h;

// Define Inputs
uniform sampler2D front, back, matte;[/cpp]

Next, we need to fill in the uniform variables, the controls that we will give the user.
When I explained the math behind the shader, I told you all values brighter than a reference color would get added to the background, and all the ones darker than that color would darken our background. Which mean our first needed variable is our reference color.
That would actually be enough to make a very simple additive keyer, but I’d like to give a bit more control to the user.
I would like the user to be able to control how much of darks and brights to add to the background, and allow them to control the saturation of the darks and brights separately. I also want to add an option to output the whole blended result or only the new Background.

[cpp] // Define Uniform Variables (that we will be able to control in the user interface)
uniform vec3 reference;
uniform float mix_highs;
uniform float mix_darks;
uniform float desat_highs;
uniform float desat_darks;
uniform int bg_only;[/cpp]

That should work. I’m using a vec3 for the color as I want to be able to control R, G and B values separately. Floats for most variables as I want to be able to finely control the result, and finally an int should do it for the bg_only, as I will just take in 0 or 1 from a dropdown menu. I could also use a “bool” wich is a simple Boolean (True or False), but I like an int as it could allow me to have more than 2 options in the dropdown menu later on if needed.

The next step is to define functions. You could skip that entirely, but functions are useful for operations that you may need more than once, so that you don’t need to re-write the whole code every time you need to do that operation in the code.
In my case, I know that I want to be able to control the saturation of my darks and brights separately, so that would mean writing the code for a saturation control at least twice. It is much more interesting to make it into a function. That will make your life easier, your code clearer, lighter, and faster.

[cpp] // Define some functions if needed
vec3 desaturate(vec3 color, float amount)
{
vec3 gray = vec3(dot(vec3(0.2126,0.7152,0.0722), color));
return vec3(mix(color, gray, amount));
}[/cpp]

Let’s break down that part a bit.
On the first line, I indicate that I want my function to return a vec3 (rgb value). My function name is “desaturate”, and it accepts two arguments: a color, that needs to be of type vec3, and an amount that needs to be a float.
I then open the body of the function with a {, and fill out my code.
The next line is a bit tough to read at first, but after a bit of explanation it should be clear enough for you to keep going. We define a vec3 that we call “gray”. That would be an entirely desaturated image. In order to desaturate an image, multiple algorithms are available. This one consists in doing a dot product between our original color and a predefined vector. To simplify the math of what is happening here and make it understandable by a primary school student, you could write it like this: gray = r*0.2126 + g*0.7152 + b*0.0722.
This operation however returns a single float value, so it is run again into a vec3() to copy that float value back into r,g and b.
Finally, we return the result of a mix operation, which is a built in function that mixes 2 values controlled by a third value. In our case, a mix between the gray-scale image and the fully colored one.

The final part is to write the shader itself. This happens inside a loop, as it will apply the same mathematical operation to every single pixel of your image one by one. Luckily, with today’s GPUs, millions of operations can be done in a fragment of a second, so the code I’m writing today would be executed pretty much in real time.
I won’t break down too much what’s going on in the main loop, instead the explanation will be included in the code as comments.

[cpp] // Main loop (Where most of the magic happens)
void main(void)
{
//The next line is required for the shader to work. It tells the shader which pixel we’re now working on.
vec2 uv = gl_FragCoord.xy / vec2( adsk_result_w, adsk_result_h);

//Get our inputs (notice it refers to the previous line "uv" to indicate which pixel we’re looking at)
vec3 f = texture2D(front, uv).rgb;
vec3 b = texture2D(back, uv).rgb;
vec3 m = texture2D(matte, uv).rgb;

// I’m also going to recall my uniform "reference" as a faster way to type "r"
vec3 r = reference;

// subtract operation (front minus reference)
vec3 sub = f – r;

// Max 0.0 to get all the positives
vec3 highs = max(sub, 0.0);
// And min for the negatives
vec3 darks = min(sub, 0.0);

//Give an option to desaturate the highlights and the darks separately, each with a SLIDER (0 to 1)
highs = desaturate(highs, desat_highs);
darks = desaturate(darks, desat_darks);

//Finally, let them decide how much of the darks and highlights they want to add, there are multiple ways to do that, the easiest I think is to put a simple multiplier, 0 would kill the values, 1 would be normal, above 1 would make it more intense. If you want to go overkill you could separate that in R,G and B gain.
highs = highs * mix_highs;
darks = darks * mix_darks;

//Now we add both the darks and highlights to the BG plate (since the darks are negative values, it gets darker)
b = b + highs + darks;
// risk of negative values, maybe need a neg clamp
if (neg_clamp == true) {
b = max(b, 0.0);
}

// Blend all together for the final comp on top of new back.
vec3 result = vec3(m * f + (1.0 – m) * b);

//Output the final result.
if (bg_only == 1) {
//If the user chose to output the BG only, output the BG:
gl_FragColor = vec4(b, m);
} else {
//else Output the result
gl_FragColor = vec4(result, m);
}
}[/cpp]

You will notice that I added an clamp option that I did not plan beforehand, so I will need to add an extra uniform variable to my earlier code.

4 – The whole code

[cpp] // version 120 is to make it work on Mac
#version 120

//Image resolution
uniform float adsk_result_w, adsk_result_h;

// Define Inputs
uniform sampler2D front, back, matte;

// Define Uniform Variables (that we will be able to control in the user interface)
uniform vec3 reference;
uniform float mix_highs;
uniform float mix_darks;
uniform float desat_highs;
uniform float desat_darks;
uniform int bg_only;
uniform bool neg_clamp;

// Define some functions if needed
vec3 desaturate(vec3 color, float amount)
{
vec3 gray = vec3(dot(vec3(0.2126,0.7152,0.0722), color));
return vec3(mix(color, gray, amount));
}

// Main loop (Where most of the magic happens)
void main(void)
{
//The next line is required for the shader to work. It tells the shader which pixel we’re now working on.
vec2 uv = gl_FragCoord.xy / vec2( adsk_result_w, adsk_result_h);

//Get our inputs (notice it refers to the previous line "uv" to indicate which pixel we’re looking at)
vec3 f = texture2D(front, uv).rgb;
vec3 b = texture2D(back, uv).rgb;
vec3 m = texture2D(matte, uv).rgb;

// I’m also going to recall my uniform "reference" as a faster way to type "r"
vec3 r = reference;

// subtract operation (front minus reference)
vec3 sub = f – r;

// Max 0.0 to get all the positives
vec3 highs = max(sub, 0.0);
// And min for the negatives
vec3 darks = min(sub, 0.0);

//Give an option to desaturate the highlights and the darks separately, each with a SLIDER (0 to 1)
highs = desaturate(highs, desat_highs);
darks = desaturate(darks, desat_darks);

//Finally, let them decide how much of the darks and highlights they want to add, there are multiple ways to do that, the easiest I think is to put a simple multiplier, 0 would kill the values, 1 would be normal, above 1 would make it more intense. If you want to go overkill you could separate that in R,G and B gain.
highs = highs * mix_highs;
darks = darks * mix_darks;

//Now we add both the darks and highlights to the BG plate (since the darks are negative values, it gets darker)
b = b + highs + darks;
// risk of negative values, maybe need a neg clamp
if (neg_clamp == true) {
b = max(b, 0.0);
}

// Blend all together for the final comp on top of new back.
vec3 result = vec3(m * f + (1.0 – m) * b);

//Output the final result.
if (bg_only == 1) {
//If the user chose to output the BG only, output the BG:
gl_FragColor = vec4(b, m);
} else {
//else Output the result
gl_FragColor = vec4(result, m);
}
}[/cpp]

5 – Testing the code and generating the XML.

In a terminal console, navigate to the following folder: “/usr/discreet/flame_2016.0.1/bin“. Of course it should be adapted to your current version or system. If you’re not familiar with command line use, cd is the command to open a directory, ls will list the content of the current directory.
so in my case, I need to open a terminal and type “cd /usr/discreet/flame_2016.0.1/bin“. Once I did, I can type ls to double check the file shader_builder* is in there.

If it is, type the following command: “shader_builder -m -x /path/to/shader.glsl“.
-m” indicates this should be read as a matchbox shader.
-x” says we should generate an XML is there are no errors.
In my case I type “shader_builder -m -x /usr/discreet/presets/2016.0.1/matchbox/shaders/EL/simple_additive_key.glsl“.
My terminal runs for a second and outputs:
compiling shader file /usr/discreet/presets/2016.0.1/matchbox/shaders/EL/simple_additive_key.glsl …

[OK]
all shaders compiled … [OK]
generating XML interface …
creating XML file (/usr/discreet/presets/2016.0.1/matchbox/shaders/EL/simple_additive_key.xml) … [OK]

Luckily everything was OK in this case. If there was an error in your code, it would be highlighted here. For example, the first time I run it, I had this error message:
0(57) : error C0000: syntax error, unexpected floating point constant, expecting identifier or template identifier or type identifier at token “<float-const>”

It was because on line 57, I typed a “.” instead of a “,”.

Once there are no more errors and the XML is generated properly, you can go on and try your shader in flame. It should work pretty much fine, but the interface should look quite messy.

Automatically generated UI.

Automatically generated UI.

6 – Cleanimg up the XML.

In order to make out preset much more user friendly, we need to edit the XML.
If we open the XML file in a text editor, it will look like this:

[xml] <ShaderNodePreset SupportsAdaptiveDegradation="False" SupportsAction="False" SupportsTransition="False" SupportsTimeline="False" TimelineUseBack="False" MatteProvider="False" CommercialUsePermitted="True" ShaderType="Matchbox" SoftwareVersion="2016.0.0" LimitInputsToTexture="True" Version="1" Description="" Name="Preset Name">
<Shader OutputBitDepth="Output" Index="1">
<Uniform Index="0" NoInput="Error" Tooltip="" DisplayName="back" InputColor="67, 77, 83" Mipmaps="False" GL_TEXTURE_WRAP_T="GL_CLAMP_TO_EDGE" GL_TEXTURE_WRAP_S="GL_CLAMP_TO_EDGE" GL_TEXTURE_MAG_FILTER="GL_LINEAR" GL_TEXTURE_MIN_FILTER="GL_LINEAR" Type="sampler2D" Name="back">
</Uniform>
<Uniform Max="1000000" Min="-1000000" Default="0" Inc="1" Tooltip="" Row="0" Col="0" Page="0" Type="int" DisplayName="bg_only" Name="bg_only">
</Uniform>
<Uniform ResDependent="None" Max="1000000.0" Min="-1000000.0" Default="0.0" Inc="0.01" Tooltip="" Row="1" Col="0" Page="0" Type="float" DisplayName="desat_darks" Name="desat_darks">
</Uniform>
<Uniform ResDependent="None" Max="1000000.0" Min="-1000000.0" Default="0.0" Inc="0.01" Tooltip="" Row="2" Col="0" Page="0" Type="float" DisplayName="desat_highs" Name="desat_highs">
</Uniform>
<Uniform Index="1" NoInput="Error" Tooltip="" DisplayName="front" InputColor="67, 77, 83" Mipmaps="False" GL_TEXTURE_WRAP_T="GL_CLAMP_TO_EDGE" GL_TEXTURE_WRAP_S="GL_CLAMP_TO_EDGE" GL_TEXTURE_MAG_FILTER="GL_LINEAR" GL_TEXTURE_MIN_FILTER="GL_LINEAR" Type="sampler2D" Name="front">
</Uniform>
<Uniform Index="2" NoInput="Error" Tooltip="" DisplayName="matte" InputColor="67, 77, 83" Mipmaps="False" GL_TEXTURE_WRAP_T="GL_CLAMP_TO_EDGE" GL_TEXTURE_WRAP_S="GL_CLAMP_TO_EDGE" GL_TEXTURE_MAG_FILTER="GL_LINEAR" GL_TEXTURE_MIN_FILTER="GL_LINEAR" Type="sampler2D" Name="matte">
</Uniform>
<Uniform ResDependent="None" Max="1000000.0" Min="-1000000.0" Default="0.0" Inc="0.01" Tooltip="" Row="3" Col="0" Page="0" Type="float" DisplayName="mix_darks" Name="mix_darks">
</Uniform>
<Uniform ResDependent="None" Max="1000000.0" Min="-1000000.0" Default="0.0" Inc="0.01" Tooltip="" Row="4" Col="0" Page="0" Type="float" DisplayName="mix_highs" Name="mix_highs">
</Uniform>
<Uniform Row="0" Col="1" Page="0" Default="False" Tooltip="" Type="bool" DisplayName="neg_clamp" Name="neg_clamp">
</Uniform>
<Uniform Inc="0.01" Tooltip="" Row="1" Col="1" Page="0" IconType="None" ValueType="Position" Type="vec3" DisplayName="reference" Name="reference">
<SubUniform ResDependent="None" Max="1000000.0" Min="-1000000.0" Default="0.0">
</SubUniform>
<SubUniform ResDependent="None" Max="1000000.0" Min="-1000000.0" Default="0.0">
</SubUniform>
<SubUniform ResDependent="None" Max="1000000.0" Min="-1000000.0" Default="0.0">
</SubUniform>
</Uniform>
</Shader>
<Page Name="Page 1" Page="0">

<Col Name="Column 1" Col="0" Page="0">
</Col>

<Col Name="Column 2" Col="1" Page="0">
</Col>

</Page>
</ShaderNodePreset>[/xml]

There is quite a lot to look at. I’m actually going to start towards the bottom on the “Page” options.
Your interface can be separated in multiple pages, each page with up to 4 columns containing each up to 5 options.
You might be able to fit more columns if they contain less options but I haven’t really tried yet.
I want to separate my options in 4 columns: Color reference, Additive details, Saturation, Options.

[xml] <Page Name="Additive Keyer" Page="0">

<Col Name="Color Reference" Col="0" Page="0">
</Col>

<Col Name="Additive details" Col="1" Page="0">
</Col>

<Col Name="Saturation" Col="2" Page="0">
</Col>

<Col Name="Options" Col="3" Page="0">
</Col>

</Page>[/xml]

As you can see each column has an index and page, which will be useful for us later.

Let’s get back to the top. On the very first line, most of the info should be pretty much filled out, but we can complete the Description and the name.
Then starting from line 3, you get all the uniforms that we defined previously. Some are our uniforms, some are our input.
I want to group my 3 inputs together to make it easier to read.
On input controls, you can set the color of the input. However, if the input should behave like a built in “Front, back or matte” it is easier to set it as an input type rather than an input color. I also edit the DisplayName to make it a bit more pretty. Finally I edit the index number to make them appear in the order I want (Front first, then back, then matte)

[xml] <Uniform Index="0" NoInput="Error" Tooltip="" DisplayName="Front" InputType="Front" Mipmaps="False" GL_TEXTURE_WRAP_T="GL_CLAMP_TO_EDGE" GL_TEXTURE_WRAP_S="GL_CLAMP_TO_EDGE" GL_TEXTURE_MAG_FILTER="GL_LINEAR" GL_TEXTURE_MIN_FILTER="GL_LINEAR" Type="sampler2D" Name="front">
</Uniform>
<Uniform Index="1" NoInput="Error" Tooltip="" DisplayName="Back" InputType="Back" Mipmaps="False" GL_TEXTURE_WRAP_T="GL_CLAMP_TO_EDGE" GL_TEXTURE_WRAP_S="GL_CLAMP_TO_EDGE" GL_TEXTURE_MAG_FILTER="GL_LINEAR" GL_TEXTURE_MIN_FILTER="GL_LINEAR" Type="sampler2D" Name="back">
<Uniform Index="2" NoInput="Error" Tooltip="" DisplayName="Matte" InputType="Matte" Mipmaps="False" GL_TEXTURE_WRAP_T="GL_CLAMP_TO_EDGE" GL_TEXTURE_WRAP_S="GL_CLAMP_TO_EDGE" GL_TEXTURE_MAG_FILTER="GL_LINEAR" GL_TEXTURE_MIN_FILTER="GL_LINEAR" Type="sampler2D" Name="matte">
</Uniform>[/xml]

Then, I get to my other uniforms. I need to give them a nicer DisplayName (the name that will appear in the UI) and position them in my row/columns/page. You can also edit HOW they appear, for example my vec3 color reference is for now just known as a vec3. So far, it is being interpreted as 3 float values. It could also be a 3d position, or in my case, an RGB color. I need to let the XML know about that. Same thing for my bg_only, now it’s a integer field, I’d like to have a dropdown menu.

The Final XML:

[xml] <ShaderNodePreset SupportsAdaptiveDegradation="False" SupportsAction="False" SupportsTransition="False" SupportsTimeline="False" TimelineUseBack="False" MatteProvider="False" CommercialUsePermitted="True" ShaderType="Matchbox" SoftwareVersion="2016.0.0" LimitInputsToTexture="True" Version="1" Description="Performs an additive operation to restore details in a key" Name="Simple additive keyer">
<Shader OutputBitDepth="Output" Index="1">
<Uniform Index="0" NoInput="Error" Tooltip="" DisplayName="Front" InputType="Front" Mipmaps="False" GL_TEXTURE_WRAP_T="GL_CLAMP_TO_EDGE" GL_TEXTURE_WRAP_S="GL_CLAMP_TO_EDGE" GL_TEXTURE_MAG_FILTER="GL_LINEAR" GL_TEXTURE_MIN_FILTER="GL_LINEAR" Type="sampler2D" Name="front">
</Uniform>
<Uniform Index="1" NoInput="Error" Tooltip="" DisplayName="Back" InputType="Back" Mipmaps="False" GL_TEXTURE_WRAP_T="GL_CLAMP_TO_EDGE" GL_TEXTURE_WRAP_S="GL_CLAMP_TO_EDGE" GL_TEXTURE_MAG_FILTER="GL_LINEAR" GL_TEXTURE_MIN_FILTER="GL_LINEAR" Type="sampler2D" Name="back">
</Uniform>
<Uniform Index="2" NoInput="Error" Tooltip="" DisplayName="Matte" InputType="Matte" Mipmaps="False" GL_TEXTURE_WRAP_T="GL_CLAMP_TO_EDGE" GL_TEXTURE_WRAP_S="GL_CLAMP_TO_EDGE" GL_TEXTURE_MAG_FILTER="GL_LINEAR" GL_TEXTURE_MIN_FILTER="GL_LINEAR" Type="sampler2D" Name="matte">
</Uniform>
<Uniform Inc="0.01" Tooltip="" Row="0" Col="0" Page="0" IconType="Pick" ValueType="Colour" Type="vec3" DisplayName="Color Reference" Name="reference">
<SubUniform ResDependent="None" Max="1000000.0" Min="-1000000.0" Default="0.0">
</SubUniform>
<SubUniform ResDependent="None" Max="1000000.0" Min="-1000000.0" Default="0.0">
</SubUniform>
<SubUniform ResDependent="None" Max="1000000.0" Min="-1000000.0" Default="0.0">
</SubUniform>
</Uniform>
<Uniform ResDependent="None" Max="3.0" Min="0.0" Default="1.0" Inc="0.01" Tooltip="Reveal details darker than the reference color" Row="0" Col="1" Page="0" Type="float" DisplayName="Add Darks" Name="mix_darks">
</Uniform>
<Uniform ResDependent="None" Max="3.0" Min="0.0" Default="1.0" Inc="0.01" Tooltip="Reveal details brighter than the reference color" Row="1" Col="1" Page="0" Type="float" DisplayName="Add Highlights" Name="mix_highs">
</Uniform>
<Uniform ResDependent="None" Max="1.0" Min="0.0" Default="0.0" Inc="0.01" Tooltip="Desaturate Dark details" Row="0" Col="2" Page="0" Type="float" DisplayName="Desaturate Darks" Name="desat_darks">
</Uniform>
<Uniform ResDependent="None" Max="1.0" Min="0.0" Default="0.0" Inc="0.01" Tooltip="Desaturate Bright details" Row="1" Col="2" Page="0" Type="float" DisplayName="Desaturate Highlights" Name="desat_highs">
</Uniform>
<Uniform Row="0" Col="3" Page="0" Default="False" Tooltip="Clamp negative values" Type="bool" DisplayName="Clamp Negative Values" Name="neg_clamp">
</Uniform>
<Uniform Default="0" Inc="1" Tooltip="" Row="3" Col="3" Page="0" DisplayName="Output" Type="int" Name="bg_only" ValueType="Popup">
<PopupEntry Title="Blend" Value="0">
</PopupEntry>
<PopupEntry Title="Background Only" Value="1">
</PopupEntry>
</Uniform>
</Shader>
<Page Name="Additive Keyer" Page="0">

<Col Name="Color Reference" Col="0" Page="0">
</Col>

<Col Name="Additive details" Col="1" Page="0">
</Col>

<Col Name="Saturation" Col="2" Page="0">
</Col>

<Col Name="Options" Col="3" Page="0">
</Col>

</Page>
</ShaderNodePreset>[/xml]

And a screenshot of the final interface:
addkey_final_UI

The next step would be to generate an image preview for your shader, but I haven’t had the chance to do that yet, so that will be in the next tutorial.

That’s it. Another tutorial that looks much more impressive than it actually is. Hope you guys made it to the end, and don’t hesitate to ask questions.