アヒルのある日

株式会社AHIRUの社員ブログです。毎週更新!社員が自由に思いついたことを書きます。

【WPF】VisualをShaderEffectにSamplerとして渡す

こんにちは!いかついプログラマです。

今回は、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つのサンプラーを渡すことができました。

それではまた!