easy · useful · affordable · amazing ·
visual effects plug-ins

Available for...
Premiere Pro (Windows)
After Effects (Windows)
Photoshop (Windows)
CyberLink PowerDirector (14/ltr)
Magix Movie Studio (2022/later)
Magix Video Pro X (X11/later)
VEGAS Pro (14/later)
VEGAS Movie Studio (14/later)
DaVinci Resolve (19/later)


1.1 Fx Developer Tools

Navigate to Start > Pixelan AnyFx Shared and run the Effect Developer Tools. You will see the effect's user interface window ("Fx Interface") on the left and the FX Developer Toolbox ("Fx Toolbox") on the right. FX Developer Tools can compile effects for our engine from .pafx files (Pixelan AnyFX file format) -- a subset of the CgFX format -- and test them with various renderers, frame sizes, bit depths etc. FX Toolbox interface includes:
Project section
  • Load/Save buttons – Load a new project or save the current one
  • More button – Access a menu for less frequent commands like Save As and New Project.
  • Project Settings button - Open the Settings dialog. Currently, this includes the .pafx source file attached to the project. Click the ... button to select a different source file.
  • Compile button - Compiles your source, then builds and installs the effect into the AnyFX Shared plugin. Successfully compiled effects will load in the FX Interface. Right-click this button to open the source file in a text editor (Edit Source).
Input
  • Image Set radio button – Provides four pairs of images to test effects with different sources.
  • Image icons – Click an icon to select a new image.
  • Frame Size menu – Select different frame sizes for testing; the FX Toolbox scales inputs to match these dimensions.
Host
  • Host – Simulates host behavior, such as Alpha mode (Straight vs. Premultiplied).
  • Fields – Renders the full frame, two separate fields, or a single field.
  • Color Depth – Select between 8-bit, 16-bit, or 32-bit float channels.
  • Frames – Sets the effect or transition duration in frames.
  • Timeline – How the effect or transition is applied in host's timeline.Use over CB (Checkerboard) to view transparent areas clearly.
Render
The compiler prepares your source for four renderers: CgFX (OpenGL), pure OpenGL, Direct3D Effect, and pure Direct3D.
  • Renderer – Select active renderer.
  • Tiling – Enables tiling if the frame size exceeds the selection. Note: This only affects the FX Toolbox, not the final plugin or AnyFX Shared plugin.
1.2 Workflow

1. Run FX Developer Tools and create a new project.
2. Open the source file in a text editor, modify it, and save.
3. Switch back to FX Developer Tools, then compile and test your changes.
4. Repeat steps 2 and 3 until the effect is complete.


Tutorial: Creating a Simple Zoom & Rotate Filter

In this guide, we will build a functional Zoom and Rotate filter. This example illustrates how to handle multiple texture addressing modes, implement angle and menu controls, and use automatic variables to maintain aspect ratio. To keep things concise, we will only show the additions or changes made to a default "New Project"(Filter) source.

1. Implementing Basic Zoom

First, we need a Zoom slider control in the effect's UI section. In the shader, we create a local float2 variable for the texture coordinates and assign the input coordinates to it. To zoom the image, we multiply this variable by g_fZoom and sample the texture.

// TODO: Add your user interface controls here
float	g_fZoom : PARAM
<
	string	Name				= "Zoom In/Out";
	string	UIType				= "slider";
	float2	UIRange				= {0.0f,5.0f};
> = 1.0;

//Pixel/fragment shader
float4 Example_PS( VS_OUTPUT vsin ) : COLOR0
{
	float2 tex = vsin.Tex;
	
	tex = tex * g_fZoom;
	float4 color=tex2D( samplerSrc, tex );

	return saturate(color);
}

However, because the (0,0) coordinate is located at the upper-left corner, the image will zoom toward that corner by default. To zoom toward the center of the image, we must translate the image center to 0.0, apply the zoom, and then translate it back. Since texture coordinates range from 0.0 to 1.0, the center point is (0.5, 0.5).

//Pixel/fragment shader
float4 Example_PS( VS_OUTPUT vsin ) : COLOR0
{
	float2 tex = vsin.Tex;
	
	tex = tex - 0.5;	
	tex = tex * g_fZoom;
	tex = tex + 0.5;	
	
	float4 color=tex2D( samplerSrc, tex );

	return saturate(color);
}


2. Simulating Address Modes

Next, we can simulate Wrap, Mirror, and Clamp modes to see how they affect the image. We will add a menu control to switch between these modes.

//Add new control in UI section
int g_nAddress:PARAM 
<
	string	Name				= "Address";
	string	UIType				= "menu";
	string	UIItems[]			= {"Wrap","Mirror","ClampToEdge","Transparent"};
> = 0;

//Pixel/fragment shader
float4 Example_PS( VS_OUTPUT vsin ) : COLOR0
{
	float2 tex = vsin.Tex;
	
	tex = tex - 0.5;	
	tex = tex * g_fZoom;
	tex = tex + 0.5;	
	if(g_nAddress==0)	tex = frac(tex);			// Wrap
	if(g_nAddress==1)	tex = 1.0-abs(frac(tex*0.5)*2.0-1.0);	//Mirror
	if(g_nAddress==2)	tex = saturate(tex);			//Clamp
	
	float4 color = tex2D( samplerSrc, tex );

	return saturate(color);
}
The CLAMP_TO_EDGE mode is highly useful in filters like blurs, though it is included here primarily for demonstration. For this specific filter, it is better to have modes where the area outside the image is transparent. Since checking if a point is "inside or outside" the image is a common task, we will create a dedicated function for it:

//Add this above the first shader where it is called or above all shaders.
float IsOut(float2 textc)
{
	return distance(textc,saturate(textc));
}

//Pixel/fragment shader
float4 Example_PS( VS_OUTPUT vsin ) : COLOR0
{
	float2 tex = vsin.Tex;
	
	tex = tex - 0.5;	
	tex = tex * g_fZoom;
	tex = tex + 0.5;	
	// Updated Pixel Shader logic for Transparent mode
	if(g_nAddress==3){
		if(IsOut(tex)) return 0;
	}
	if(g_nAddress==0)	tex=frac(tex);
	if(g_nAddress==1)	tex=1.0-abs(frac(tex*0.5)*2.0-1.0);
	if(g_nAddress==2)	tex=saturate(tex);
	
	float4 color=tex2D( samplerSrc, tex );

	return saturate(color);
}
3. Adding Rotation

Now, let's add rotation relative to the center. While shader languages offer matrix multiplication for rotation, we will use the standard sin/cos formula here to illustrate the underlying math.

//Add new control in UI section
float	g_fAngle : PARAM 
<
	string	Name				= "Rotate";
	string	UIType				= "angle";
	float2	UIRange				= {-1.0f,1.0f};
	float2	Bounds				= { -10.0f,10.0f };
> = 0.0;

//Pixel/fragment shader
float4 Example_PS( VS_OUTPUT vsin ) : COLOR0
{
	float2 tex = vsin.Tex;
	float2 tex2;
	
	float ang = -g_fAngle * 6.2831;
	tex = tex - 0.5;
	tex2.x = tex.x * cos( ang ) - tex.y * sin( ang );
	tex2.y = tex.x * sin( ang ) + tex.y * cos( ang );
	tex = tex2 * g_fZoom;
	tex = tex + 0.5;
	
	if(g_nAddress==3){
		if(IsOut(tex)) return 0;
	}
	if(g_nAddress==0)	tex=frac(tex);
	if(g_nAddress==1)	tex=1.0-abs(frac(tex*0.5)*2.0-1.0);
	if(g_nAddress==2)	tex=saturate(tex);
	
	float4 color=tex2D( samplerSrc, tex );

	return saturate(color);
}
One common issue is that rotation often ignores the frame aspect ratio because texture coordinates always range from 0 to 1. To fix this, we use the a_f2AutoRatio automatic variable to adjust the coordinates before rotation and restore them afterward.

//Add this in automatic variables section
float2	a_f2AutoRatio = { 1.0 , 1.0 };
const float twoPi = 6.283185307179586476925286766559f;


//Pixel/fragment shader
float4 Example_PS( VS_OUTPUT vsin ) : COLOR0
{
	float2 tex = vsin.Tex;
	float2 tex2;
	
	float ang = -g_fAngle * twoPi;
	tex = tex - 0.5;
	tex = tex * a_f2AutoRatio;
	tex2.x = tex.x * cos( ang ) - tex.y * sin( ang );
	tex2.y = tex.x * sin( ang ) + tex.y * cos( ang );
	tex = tex2 / a_f2AutoRatio;
	tex = tex * g_fZoom;
	tex = tex + 0.5;
	
	if(g_nAddress==3){
		if(IsOut(tex)) return 0;
	}
	if(g_nAddress==0)	tex=frac(tex);
	if(g_nAddress==1)	tex=1.0-abs(frac(tex*0.5)*2.0-1.0);
	if(g_nAddress==2)	tex=saturate(tex);
	
	float4 color=tex2D( samplerSrc, tex );

	return saturate(color);
}
4. Final Polishing: Anti-Aliasing for Zoom Out

Setting MagFilter = Linear; in the texture sampler makes Zoom In look smooth, but Zoom Out can still suffer from pixel flickering. To fix this, we must average the pixels proportionally to the zoom level. By using a Linear sampler with specific offsets, we can average 4 pixels per sample. With just 8 samples and proper offsets, we can average up to 32 pixels for a much smoother result.

//Add this in automatic variables section
float2	a_f2PixelSize = { 0.001 , 0.001 };

//Pixel/fragment shader
float4 Example_PS( VS_OUTPUT vsin ) : COLOR0
{
	float2 tex = vsin.Tex;
	float2 tex2;
	
	float ang = -g_fAngle * twoPi;
	tex = tex - 0.5;
	tex = tex * a_f2AutoRatio;
	tex2.x = tex.x * cos( ang ) - tex.y * sin( ang );
	tex2.y = tex.x * sin( ang ) + tex.y * cos( ang );
	tex = tex2 / a_f2AutoRatio;
	tex = tex * g_fZoom;
	tex = tex + 0.5;
	
	if(g_nAddress==3){
		if(IsOut(tex)) return 0;
	}
	if(g_nAddress==0)	tex=frac(tex);
	if(g_nAddress==1)	tex=1.0-abs(frac(tex*0.5)*2.0-1.0);
	if(g_nAddress==2)	tex=saturate(tex);
	
	float4 color=tex2D( samplerSrc, tex );
	
	if(g_fZoom>1.0){
		float blur = saturate((g_fZoom-1.0)*0.25);
		color=color+tex2D( samplerSrc, tex+a_f2PixelSize*blur*float2(0.5,-1.5));
		color=color+tex2D( samplerSrc, tex+a_f2PixelSize*blur*float2(1.5,0.5));
		color=color+tex2D( samplerSrc, tex+a_f2PixelSize*blur*float2(-0.5,1.5));
		color=color+tex2D( samplerSrc, tex+a_f2PixelSize*blur*float2(-1.5,-0.5));
		color=color+tex2D( samplerSrc, tex+a_f2PixelSize*blur*float2(2.5,-1.5));
		color=color+tex2D( samplerSrc, tex+a_f2PixelSize*blur*float2(1.5,2.5));
		color=color+tex2D( samplerSrc, tex+a_f2PixelSize*blur*float2(-2.5,1.5));
		color=color+tex2D( samplerSrc, tex+a_f2PixelSize*blur*float2(-1.5,-2.5));
		color=color/9.0;
	}

	return saturate(color);
}
The Zoom Out effect should now appear significantly smoother. Note that minor flickering may still occur in the UI preview if the preview size does not match the frame size; however, the zoom should look smooth once rendered in your NLE.
Congratulations! You have created a fully functional Zoom/Rotate filter.



Tutorial: Creating a Simple Zoom & Rotate Transition

Many effects can also be implemented as transitions — by gradually applying the effect to the end of the first clip and blending into the next clip (either clean or with a decreasing effect). In this tutorial, we’ll start with the already‑created Zoom & Rotate effect and focus only on the transition‑specific aspects. As before, we will show only the additions or modifications made to the default “New Project” (Transition) template.

1. Converting Simple Zoom & Rotate filter into a transition

Copy all controls, automatic variables, the IsOut function, and the shader code (excluding the averaging section) from the SimpleZoom filter into your transition source.

// TODO: Add your user interface controls here 
float g_fZoom : PARAM 
< 
	string Name = "Zoom In/Out"; 
	string UIType = "slider"; 
	float2 UIRange = {0.0f,5.0f}; 
> = 1.0; 
int g_nAddress:PARAM 
< 
	string Name = "Address"; 
	string UIType = "menu"; 
	string UIItems[] = {"Wrap","Mirror","ClampToEdge","Transparent"}; 
> = 0; 
float g_fAngle : PARAM 
< 
	string Name = "Rotate"; 
	string UIType = "angle"; 
	float2 UIRange = {-1.0f,1.0f}; 
	float2 Bounds = { -10.0f,10.0f }; 
> = 0.0; 

// TODO: Add the used automatic variables here 
float a_fCompletion = 0;
float2 a_f2PixelSize = { 0.001 , 0.001 }; 
float2 a_f2AutoRatio = { 1.0 , 1.0 }; 
const float twoPi = 6.283185307179586476925286766559f; 

float IsOut(float2 textc)
{
	return distance(textc,saturate(textc));
}

//Pixel/fragment shader
float4 Example_PS( VS_OUTPUT vsin ) : COLOR0
{
	float4 colorA=tex2D( samplerSrcA, vsin.Tex );
	float4 colorB=tex2D( samplerSrcB, vsin.Tex );
	float4 colorOut=0;

	//Zoom Filter code start
	float2 tex=vsin.Tex,tex2;
	
	float ang=-g_fAngle*twoPi;
	tex=tex-0.5;
	tex=tex*a_f2AutoRatio;
	tex2.x = tex.x * cos( ang ) - tex.y * sin( ang );
	tex2.y = tex.x * sin( ang ) + tex.y * cos( ang );
	tex=tex2/a_f2AutoRatio;
	tex=tex*g_fZoom;
	tex=tex+0.5;

	if(g_nAddress==3){
		if(IsOut(tex)) return 0;
	}
	if(g_nAddress==0)	tex=frac(tex);
	if(g_nAddress==1)	tex=1.0-abs(frac(tex*0.5)*2.0-1.0);
	if(g_nAddress==2)	tex=saturate(tex);
	float4 color=tex2D( samplerSrc, tex );
	//Zoom Filter code end
	
	colorOut=colorA*(1.0-a_fCompletion)+colorB*a_fCompletion;
	return saturate(colorOut);
}
However, this code will not compile yet. The default transition template uses slightly different variable names. We need to remove the first line, rename color to colorA, and replace samplerSrc with samplerSrcA. Additionally, in Transparent mode the shader must not return early, because we still need to blend Clip A and Clip B. Instead of returning, we assign a transparent black value to colorA.
Here is the corrected version:

//Pixel/fragment shader
float4 Example_PS( VS_OUTPUT vsin ) : COLOR0
{
	float4 colorB=tex2D( samplerSrcB, vsin.Tex );
	float4 colorOut=0;

	//Zoom Filter code start
	float2 tex=vsin.Tex,tex2;
	
	float ang=-g_fAngle*twoPi;
	tex=tex-0.5;
	tex=tex*a_f2AutoRatio;
	tex2.x = tex.x * cos( ang ) - tex.y * sin( ang );
	tex2.y = tex.x * sin( ang ) + tex.y * cos( ang );
	tex=tex2/a_f2AutoRatio;
	tex=tex*g_fZoom;
	tex=tex+0.5;

	if(g_nAddress==0)	tex=frac(tex);
	if(g_nAddress==1)	tex=1.0-abs(frac(tex*0.5)*2.0-1.0);
	if(g_nAddress==2)	tex=saturate(tex);
	float4 colorA=tex2D( samplerSrcA, tex );
	if(g_nAddress==3){
		if(IsOut(tex)) colorA=0;
	}	
	//Zoom Filter code end
	
	colorOut=colorA*(1.0-a_fCompletion)+colorB*a_fCompletion;
	return saturate(colorOut);
}
Transitions based on effects typically apply the effect gradually. To achieve this, we’ll make the zoom and rotation start at their default values (so the user initially sees the original image) and increase them gradually. We will use for this a very useful lerp function. Lerp, or linear interpolation, is a mathematical function used to smoothly transition between two values, positions, or colors.

//Pixel/fragment shader
float4 Example_PS( VS_OUTPUT vsin ) : COLOR0
{
	float4 colorB=tex2D( samplerSrcB, vsin.Tex );
	float4 colorOut=0;

	//Zoom Filter code start
	float2 tex=vsin.Tex,tex2;
	
	float zoom = lerp( 1.0, g_fZoom, a_fCompletion );
	float ang=-g_fAngle*twoPi*a_fCompletion;
	tex=tex-0.5;
	tex=tex*a_f2AutoRatio;
	tex2.x = tex.x * cos( ang ) - tex.y * sin( ang );
	tex2.y = tex.x * sin( ang ) + tex.y * cos( ang );
	tex=tex2/a_f2AutoRatio;
	tex=tex*zoom;
	tex=tex+0.5;

	if(g_nAddress==0)	tex=frac(tex);
	if(g_nAddress==1)	tex=1.0-abs(frac(tex*0.5)*2.0-1.0);
	if(g_nAddress==2)	tex=saturate(tex);
	float4 colorA=tex2D( samplerSrcA, tex );
	if(g_nAddress==3){
		if(IsOut(tex)) colorA=0;
	}	
	//Zoom Filter code end
	
	colorOut=lerp(colorA, colorB, a_fCompletion);
	return saturate(colorOut);
}
The transition should work properly now, but there is one common transitions problem:
In many transitions, one clip performs an action (like a slide or rotate) to reveal the next, or vice versa one clip moves to cover the previous one. While this works seamlessly between two clips, a challenge arose when NLE vendors introduced the ability to apply transitions to the start or end of a single clip. To maintain compatibility with existing SDKs, vendors made this process "transparent" for the plugin. When applied to a single clip, the transition receives one video clip and one transparent frame instead of a second piece of footage.
At the start: The transition treats Clip A as transparent and Clip B as the actual footage.
At the end: The transition treats Clip A as the footage and Clip B as transparent.
While this seems logical, it creates visual issues when the clip tasked with "doing something" (sliding, flying out, or rotating) is the transparent one. Instead of a moving video clip, the user sees a moving "hole" that reveals static background layers underneath. This often leads users to believe the transition is broken. While plugins could theoretically handle this by automatically reversing the logic (e.g., sliding Clip B in instead of sliding A out), most NLE SDKs do not inform the plugin of its placement (start, end, or between). Adding a manual "Reverse" toggle is an option, but it remains unintuitive for users why one direction works automatically while the other requires a manual fix. A practical solution is to design transitions where both clips perform an action, and optionally provide controls like “Zoom Clip A Only” or “Zoom Clip B Only”.

//Pixel/fragment shader
float4 Example_PS( VS_OUTPUT vsin ) : COLOR0
{
	float4 colorOut=0;

	float2 tex=vsin.Tex,tex2;
	
	float zoom;
	float ang;
	zoom = lerp( 1.0, g_fZoom, a_fCompletion );
	ang = -g_fAngle*twoPi*a_fCompletion;
	tex=tex-0.5;
	tex=tex*a_f2AutoRatio;
	tex2.x = tex.x * cos( ang ) - tex.y * sin( ang );
	tex2.y = tex.x * sin( ang ) + tex.y * cos( ang );
	tex=tex2/a_f2AutoRatio;
	tex=tex*zoom;
	tex=tex+0.5;

	if(g_nAddress==0)	tex=frac(tex);
	if(g_nAddress==1)	tex=1.0-abs(frac(tex*0.5)*2.0-1.0);
	if(g_nAddress==2)	tex=saturate(tex);
	float4 colorA=tex2D( samplerSrcA, tex );
	if(g_nAddress==3){
		if(IsOut(tex)) colorA=0;
	}	
	
	tex=vsin.Tex;
	
	zoom = lerp( g_fZoom, 1.0, a_fCompletion );
	ang=-g_fAngle*twoPi*(1.0-a_fCompletion);
	tex=tex-0.5;
	tex=tex*a_f2AutoRatio;
	tex2.x = tex.x * cos( ang ) - tex.y * sin( ang );
	tex2.y = tex.x * sin( ang ) + tex.y * cos( ang );
	tex=tex2/a_f2AutoRatio;
	tex=tex*zoom;
	tex=tex+0.5;

	if(g_nAddress==0)	tex=frac(tex);
	if(g_nAddress==1)	tex=1.0-abs(frac(tex*0.5)*2.0-1.0);
	if(g_nAddress==2)	tex=saturate(tex);
	float4 colorB=tex2D( samplerSrcB, tex );
	if(g_nAddress==3){
		if(IsOut(tex)) colorB=0;
	}	
	
	colorOut=lerp(colorA, colorB, a_fCompletion);
	return saturate(colorOut);
}
Let's also add controls to reverse the direction of rotation and zoom for the input clips:

// TODO: Add your user interface controls here 
int g_nIZoom:PARAM 
< 
	string Name = "Opposite Zoom"; 
	string UIType = "option"; 
> = 0; 

int g_nIAngle:PARAM 
< 
	string Name = "Opposite Angle"; 
	string UIType = "option"; 
> = 0;

//Pixel/fragment shader
float4 Example_PS( VS_OUTPUT vsin ) : COLOR0
{
	float4 colorOut=0;

	float2 tex=vsin.Tex,tex2;
	
	float zoom;
	float ang;
	zoom = lerp( 1.0, g_fZoom, a_fCompletion );
	ang = -g_fAngle*twoPi*a_fCompletion;
	tex=tex-0.5;
	tex=tex*a_f2AutoRatio;
	tex2.x = tex.x * cos( ang ) - tex.y * sin( ang );
	tex2.y = tex.x * sin( ang ) + tex.y * cos( ang );
	tex=tex2/a_f2AutoRatio;
	tex=tex*zoom;
	tex=tex+0.5;

	if(g_nAddress==0)	tex=frac(tex);
	if(g_nAddress==1)	tex=1.0-abs(frac(tex*0.5)*2.0-1.0);
	if(g_nAddress==2)	tex=saturate(tex);
	float4 colorA=tex2D( samplerSrcA, tex );
	if(g_nAddress==3){
		if(IsOut(tex)) colorA=0;
	}	
	
	tex=vsin.Tex;
	zoom=g_fZoom;	
	
	if(g_nIZoom){	
	zoom = lerp(zoom, 1.0, a_fCompletion );
	}
	else{
	if(zoom<0.001) zoom=0.001;
	zoom = lerp(1.0/zoom, 1.0, a_fCompletion );
	}
	if(g_nIAngle){	
	ang=-g_fAngle * twoPi * ( 1.0 - a_fCompletion);
	}
	else{
	ang=g_fAngle * twoPi * ( 1.0 - a_fCompletion);
	}
	tex=tex-0.5;
	tex=tex*a_f2AutoRatio;
	tex2.x = tex.x * cos( ang ) - tex.y * sin( ang );
	tex2.y = tex.x * sin( ang ) + tex.y * cos( ang );
	tex=tex2/a_f2AutoRatio;
	tex=tex*zoom;
	tex=tex+0.5;

	if(g_nAddress==0)	tex=frac(tex);
	if(g_nAddress==1)	tex=1.0-abs(frac(tex*0.5)*2.0-1.0);
	if(g_nAddress==2)	tex=saturate(tex);
	float4 colorB=tex2D( samplerSrcB, tex );
	if(g_nAddress==3){
		if(IsOut(tex)) colorB=0;
	}	
	
	colorOut=colorA*(1.0-a_fCompletion)+colorB*a_fCompletion;
	return saturate(colorOut);

}
Congratulations! You have created a fully functional Zoom/Rotate transition.