Whilst my day job continues on its ever-accelerating decline :-) I thought I'd entertain myself by experimenting with multi-input pixel shaders in WPF following on from the post I wrote over here.
Now, luckily for me Greg over here has already talked about it and it turns out to be not much of a stretch over what I did previously - you define a 2nd input for your shader of type brush and then you can sample from either input in order to produce the resultant effect.
I took my previously created "Swirl" effect and added a second brush to make a sort of "BlendAndSwirl" effect;
float factor : register(C0);
float blend : register(C1);
sampler2D input : register(S0);
sampler2D input2 : register(S1);
float4 main(float2 uv : TEXCOORD) : COLOR
{
float angle = -3.14;
float yDist = uv.y - 0.5;
float xDist = uv.x - 0.5;
float dist = sqrt(xDist * xDist + yDist * yDist);
angle *= dist * factor;
float xN = cos(angle) * xDist - sin(angle) * yDist;
float yN = sin(angle) * xDist + cos(angle) * yDist;
uv.x = xN + 0.5;
uv.y = yN + 0.5;
float4 img1Colour = tex2D(input, uv);
float4 img2Colour = tex2D(input2, uv);
img1Colour *= blend;
img2Colour *= (1.0 - blend);
img2Colour += img1Colour;
return(img2Colour);
}
and then wrote the C# to go hand in hand with it;
public class BlendSwirlEffect : ShaderEffect
{
static BlendSwirlEffect()
{
_pixelShader.UriSource = Global.MakePackUri("effect1.ps");
}
public BlendSwirlEffect()
{
this.PixelShader = _pixelShader;
UpdateShaderValue(InputProperty);
UpdateShaderValue(Input2Property);
UpdateShaderValue(SwirlProperty);
}
public Brush Input
{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}
public static readonly DependencyProperty InputProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(BlendSwirlEffect), 0);
public Brush Input2
{
get { return ((Brush)GetValue(Input2Property)); }
set { SetValue(Input2Property, value); }
}
public static readonly DependencyProperty Input2Property =
ShaderEffect.RegisterPixelShaderSamplerProperty("Input2", typeof(BlendSwirlEffect), 1);
public double Swirl
{
get { return (double)GetValue(SwirlProperty); }
set { SetValue(SwirlProperty, value); }
}
public static readonly DependencyProperty SwirlProperty =
DependencyProperty.Register("Swirl", typeof(double), typeof(BlendSwirlEffect),
new UIPropertyMetadata(0.0, PixelShaderConstantCallback(0)));
public double Blend
{
get { return (double)GetValue(BlendProperty); }
set { SetValue(BlendProperty, value); }
}
public static readonly DependencyProperty BlendProperty =
DependencyProperty.Register("Blend", typeof(double), typeof(BlendSwirlEffect),
new UIPropertyMetadata(0.0, PixelShaderConstantCallback(1)));
private static PixelShader _pixelShader = new PixelShader();
}
there's nothing too magical going on in here - we just have a 2nd brush to play with and sample and then I've got 2 factors to control the "level" of Blending of one picture with another and the "factor" applied to the Swirl.
I then built a little UI to use the effect;
<Grid x:Name="mainGrid">
<Grid.Resources>
<ImageBrush
x:Key="brush1"
ImageSource="c:\windows\web\wallpaper\img1.jpg"
Stretch="Fill" />
<Storyboard
x:Key="sbAnimate" Storyboard.TargetName="blendEffect" FillBehavior="HoldEnd">
<DoubleAnimation
Storyboard.TargetProperty="Blend"
From="0"
To="1"
BeginTime="00:00:00.5"
Duration="00:00:00.5"/>
<DoubleAnimation
Storyboard.TargetProperty="Swirl"
From="0"
To="4"
Duration="00:00:01"/>
<DoubleAnimation
Storyboard.TargetProperty="Swirl"
From="4"
To="0"
Duration="00:00:01"
BeginTime="00:00:02"/>
</Storyboard>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition
Height="4*" />
<RowDefinition />
</Grid.RowDefinitions>
<Image
Source="c:\windows\web\wallpaper\img2.jpg"
Stretch="Fill"
Margin="10">
<Image.Effect>
<local:BlendSwirlEffect
x:Name="blendEffect"
Input2="{DynamicResource brush1}"
Swirl="{Binding ElementName=swirlSlider,Path=Value}"
Blend="{Binding ElementName=blendSlider,Path=Value}" />
</Image.Effect>
</Image>
<StackPanel Grid.Row="1">
<Grid
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel
Margin="10">
<Slider
x:Name="blendSlider"
Minimum="0"
Maximum="1"
MinWidth="192"
Margin="5"/>
<TextBlock
FontSize="14"
Text="Blend"
TextAlignment="Center" />
</StackPanel>
<StackPanel
Margin="10"
Grid.Column="1">
<Slider
x:Name="swirlSlider"
Minimum="0"
Maximum="4"
MinWidth="192"
Margin="5"/>
<TextBlock
FontSize="14"
Text="Swirl"
TextAlignment="Center" />
</StackPanel>
</Grid>
<Button
FontSize="14"
Content="Animate"
Click="OnAnimate"
MinHeight="48"
MinWidth="192"
HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
Which just defines an Image that's using the BlendSwirlEffect and a second image brush that it's using as the 2nd image to sample in the pixel shader ( Input2 in the code and the XAML ).
This gives a UI like thi;s
where I can then go and blend/swirl a little;
and I stuck a little animation on there that animates both properties at the same time. Here's the project code if you want to play with it - note that to compile it you'll need the directX SDK so I've included the binary files in the project in case you just want to run it.