1.1 Fx Developer Tools
// 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);
}
//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);
}
//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
//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
//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.
// 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.
//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:
//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.