[PL] Shadery w WPF – moje pierwsze podejście

Artur Żarski u mnie w firmie, od jakiegoś czasu, a dokładniej odkąd zobaczył moje zabawy z Xna męczył mnie abym napisał Shader do WPF. W końcu znalazłem chwilkę.

Przy okazji zabawy obaliłem jedną tezę opublikowaną na poniższym blogu:
https://blogs.msdn.com/greg_schechter/archive/2008/09/16/introducing-multi-input-shader-effects.aspx

Co prawda nie mam takiego ładnego bindowania, ale wiele inputów (samplerów) jestem w stanie dołożyć już teraz. No ale do dzieła.

Założyłem sobie dwa projekty, jeden z aplikacją WPF, drugi z biblioteką Shaderów.
Finalnie wygląda to tak:

image

Na swoim oknie w WPF osadziłem jedną z kontrolek WPFKit czyli kalendarz, w Visual Studio wygląda to następująco:

image
Nic wielkiego, ale podobnie jak na wymienionym wyżej blogu w Xaml dodałem sobie dodatkowy obrazek:

    <Window.Resources>
        <ImageBrush x:Key="phongMap" x:Name="phongMap" ImageSource="phong.jpg" />
    </Window.Resources>

Mój phong.jpg wygląda dokładnie tak:
image 

W kodzie mojego okna dodałem jeszcze obsługę mojego Shadera:

        InversePhongShader inversePhongEffect;

        public EffectWindow()
        {
            InitializeComponent();

            inversePhongEffect = new InversePhongShader();
            inversePhongEffect.Input2 = (Brush)Resources["phongMap"];

calendar1.Effect = inversePhongEffect; 
calendar1.MouseMove += new MouseEventHandler(calendar1_MouseMove);

        }

        void calendar1_MouseMove(object sender, MouseEventArgs e)
        {
            Point mP = e.GetPosition(calendar1);
            mP.X/=calendar1.ActualWidth;
            mP.Y/=calendar1.ActualHeight;
            inversePhongEffect.MousePosition = mP;

        }

Co tutaj się wydarzyło? Stworzyłem instancję swojego Effektu (Shadera), który opiszę później. Linijkę niżej dodałem drugi Input w postaci właśnie wymienionego wyżej JPG.

Greg Schechter obiecuje, że w RTM będzie się dało ładnie takie parametry bindować w Xamlu, ja niestety sposiłkowałem się kodem. (ale działa już teraz!)

Podpiąłem też zdarzenie generowane, gdy mysz będzie poruszana na kontrolce kalendarza.
Bedzie mi to potrzebne w shaderze, gdyż to co ma on robić to Odwracać kolorystykę oryginalnej kontrolki, tak żeby była ciemna nie jasna (Inverse) oraz żeby dodawał efekt światełka z latarki w miejscu gdzie jest kursor myszy. Zdarzenie przekazuje parametry związane z pozycją myszy.

Jest tam wykonane dodatkowe dzielenie ponieważ shader rozumie współrzędne w skali 0.0-1.0 a nie w pixlach.

Aby te parametry (MousePosition, dodatkowy Sampler) zadziałały potrzebna była jeszcze modyfikacja w kodzie klasy efektu:

   public class InversePhongShader : ShaderEffect
    {

 …

  public InversePhongShader()
        {
            this.PixelShader = _pixelShader;

  UpdateShaderValue(InputProperty);
            UpdateShaderValue(Input2Property);
            UpdateShaderValue(MousePositionProperty);
        }

        public Brush Input
        {
    get { return (Brush)GetValue(InputProperty); }
            set { SetValue(InputProperty, value); }
        }

        public Brush Input2
        {
            get { return (Brush)GetValue(Input2Property); }
            set { SetValue(Input2Property, value); }
        }

        public static readonly DependencyProperty InputProperty =
            ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(InversePhongShader), 0);

        public static readonly DependencyProperty Input2Property =
            ShaderEffect.RegisterPixelShaderSamplerProperty("Input2", typeof(InversePhongShader), 1);

        public Point MousePosition
        {
            get { return (Point)GetValue(MousePositionProperty); }
            set { SetValue(MousePositionProperty, value); }
        }

        public static readonly DependencyProperty MousePositionProperty =
            DependencyProperty.Register("MousePosition", typeof(Point), typeof(InversePhongShader),
                    new UIPropertyMetadata(new Point(.5,.5), PixelShaderConstantCallback(0)));

}

Potem już tylko Pixel Shader w HLSL:

//-----------------------------------------------------------------------------------------
// Shader constant register mappings (scalars - float, double, Point, Color, Point3D, etc.)
//-----------------------------------------------------------------------------------------

float2 mousePosition : register(C0);

//--------------------------------------------------------------------------------------
// Sampler Inputs (Brushes, including ImplicitInput)
//--------------------------------------------------------------------------------------

sampler2D implicitInputSampler : register(S0);
sampler2D phongMapSampler : register(S1);

//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------

float4 main(float2 uv : TEXCOORD) : COLOR

   float4 color = 1 - tex2D(implicitInputSampler, uv);
   float4 colorPhong;
   float2 phongUV = mousePosition;     
   float dist = length(phongUV-uv);
   if (dist>.3) {
        colorPhong = (0,0,0,0);
        color/=4;// colorPhong;
   } else {
        colorPhong = tex2D(phongMapSampler, (.5+dist)); 
        color = color + colorPhong*(1.0-dist*2);
   }

   color.a=1.0;
   return color;
}

I taki mam efekt, gdy uruchomię swoją aplikację:

image

Oczywiście jak ruszam mychą to światełko za nią podąża.

Technorati Tags: Polish posts,WPF,coding,DirectX