こんにちは!いかついプログラマです。
今回は、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つのサンプラーを渡すことができました。
それではまた!