こんにちは!いかついプログラマです。
今回は、WPFアプリケーションでShaderEffectに対してSamplerとしてVisualを渡す方法を紹介します。
シェーダー
まずは使用するシェーダーです。 入力サンプラーを2つ使用するため、サンプラーレジスタ(s0、s1)を宣言します。
sampler2D input : register(s0);
sampler2D mask : register(s1);
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 color = tex2D(input, uv);
float4 maskColor = tex2D(mask, uv);
color.a = maskColor.a;
return color;
}
inputとmaskの2つのサンプラーを用意し、maskのアルファ値をinputのアルファ値とします。 このhlslファイルは、fxc.exe等を使用してコンパイルしておきます。
ShaderEffect
次に、ShaderEffectクラスを継承したクラスを作成します。
class OpacityMaskEffect : ShaderEffect
{
public OpacityMaskEffect()
{
PixelShader ps = new PixelShader();
string path = System.IO.Path.GetFullPath(@$"..\..\..\OpacityMask.ps");
ps.UriSource = new Uri(path);
this.PixelShader = ps;
UpdateShaderValue(InputProperty);
UpdateShaderValue(MaskProperty);
}
public Brush Input
{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}
public static readonly DependencyProperty InputProperty
= ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(OpacityMaskEffect), 0);
public Brush Mask
{
get { return (Brush)GetValue(MaskProperty); }
set { SetValue(MaskProperty, value); }
}
public static readonly DependencyProperty MaskProperty
= ShaderEffect.RegisterPixelShaderSamplerProperty("Mask", typeof(OpacityMaskEffect), 1);
このとき、入力サンプラーを二つ用意するためにInputプロパティとMaskプロパティを用意します。 また、Input,Maskプロパティをサンプラーレジスタ0、1に関連させるため、RegisterPixelShaderSamplerPropertyの第三引数はそれぞれ0,1とします。
サンプルコントロール
つづいて、上記のShaderEfectを適用するためのサンプルコントロールです。
public partial class ShaderEffectTest : UserControl
{
public ShaderEffectTest()
{
InitializeComponent();
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
// 塗りつぶし
drawingContext.DrawRectangle(new SolidColorBrush(Color.FromRgb(255, 255, 100)), null, new Rect(0, 0, 640, 480));
// マスクを用意
var maskVisual = new DrawingVisual();
var maskDrawingContext = maskVisual.RenderOpen();
maskDrawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 255, 255, 100)), null, new Rect(0, 0, 640, 480));
maskDrawingContext.DrawRectangle(new SolidColorBrush(Color.FromRgb(255, 0, 0)), null, new Rect(240, 160, 160, 160));
maskDrawingContext.Close();
var maskBrush = new VisualBrush(maskVisual);
// ShaderEffectを用意
var shaderEffect = new OpacityMaskEffect() { Mask = maskBrush };
this.Effect = shaderEffect;
}
}
OnRenderメソッドをoverrideし、コントロールに直接描画していきます。 また、ここで一緒にMaskとしてShaderEffectに渡すVisualも描画していきます。
塗りつぶした画面の上にマスクとして描画したVisualをそのまま重ねて表示すると、次の画像のように表示されます。

var maskBrush = new VisualBrush(maskVisual); var shaderEffect = new OpacityMaskEffect() { Mask = maskBrush };
によって、OpacityMaskEffectのインスタンスを作成します。 また、MaskにはmaskVisualをもとにして作成したVisualBrushを渡します。 これによって、OpacityMaskEffectのMaskとして、描画したVisualが使用されます。 ちなみに。ShaderEffectのサンプラーとして渡せるBrushですが、Brushを継承したクラスなら何でも渡せるというわけではありません。 渡せるものは、
・BitmapCacheBrush
・VisualBrush
・ImageBrush
の3つのクラスです。
それ以外のBrush(SolidColorBrushなど)を渡そうとすると、実行時エラーで怒られます。(コンパイルは通ります。)

さて、 this.Effect = shaderEffect;
で、ShaderEffectTestコントロールにEffectとしてOpacityMaskEffectを渡します。
上記のShaderEffectが適用された画面は以下のようになります。

無事、ShaderEffectに2つのサンプラーを渡すことができました。
それではまた!