Montag, 29. März 2010

Silverlight: Custom Effects with DirectX HLSL

Um eine Silverlight Applikation aufzupeppen kann man neben den beiden bereits vorhandenen Effekten BlurEffect (Verwischen) und DropShadowEffect (Schatten) auch noch Custom Effects einsetzen.

Da Silverlight selbstdefinierte Effekte unterstützt, welche mit der High Level Shading Language (HLSL) erstellt wurden, kann man das nachfolgend genannte Shazzam-Tool nutzen, um solche Effekte zu kreieren. Die High Level Shading Language wurde mit DirectX 8 eingeführt und ist eine Sprache zur Definition von Shading-Effekten.

Um Effekte selbst zu definieren, wird die DirectX-SDK und Walt Ritschers’s Shazzam Shader Editing Tool (WPF ClickOnce-Anwendung) benötigt:

Beispiel Emboss-Effekt

Folgendes Beispiel zeigt den vordefinierten Emboss-Effekt aus dem Shazzam-Tool:

image

Das Shazzam-Tool kann dazu verwendet werden, Effekte zu kompilieren (aus .fx shader wird eine .rs-Datei) und die dazugehörigen Klassen zu erzeugen. Das Shazzam-Tool verfügt über sehr schöne bereits vordefinierte Effekte (Embossed, Bloom, InvertColor, Monochrome, ContrastAdjust, usw.). Wichtig ist, das man die Build-Action der zum Visual Studio Projekt hinzugefügten rs-Datei auf “Resource” stellt. Weil Shazzam uns WPF-Code liefert, müssen noch ein paar Änderungen von Hand vorgenommen werden (siehe Was muss man tun? unten).

Das Shazzam-Tool generiert zur Nutzung des Effekts den nachfolgenden Code (C# und Visual Basic):

Referenziert wird hierbei die kompilierte DirectX-Shader-Datei Embossed.ps.

   1: namespace Ch9UsingGraphicsAndVisuals.Effects
   2: {
   3:    using System.Windows;
   4:    using System.Windows.Media;
   5:    using System.Windows.Media.Effects;
   6:    using System;
   7:  
   8:    public class EmbossedEffect : ShaderEffect
   9:    {
  10:       public static DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(EmbossedEffect), 0);
  11:       public static DependencyProperty AmountProperty = DependencyProperty.Register("Amount", typeof(double), typeof(EmbossedEffect), new System.Windows.PropertyMetadata(new double(), PixelShaderConstantCallback(0)));
  12:       public static DependencyProperty WidthProperty = DependencyProperty.Register("Width", typeof(double), typeof(EmbossedEffect), new System.Windows.PropertyMetadata(new double(), PixelShaderConstantCallback(1)));
  13:       public EmbossedEffect()
  14:       {
  15:          Uri u = new Uri(@"/Ch9UsingGraphicsAndVisuals;component/Shaders/Embossed.ps", UriKind.Relative);
  16:          PixelShader = new PixelShader() { UriSource = u };
  17:          this.UpdateShaderValue(InputProperty);
  18:          this.UpdateShaderValue(AmountProperty);
  19:          this.UpdateShaderValue(WidthProperty);
  20:       }
  21:       public virtual System.Windows.Media.Brush Input
  22:       {
  23:          get
  24:          {
  25:             return ((System.Windows.Media.Brush)(GetValue(InputProperty)));
  26:          }
  27:          set
  28:          {
  29:             SetValue(InputProperty, value);
  30:          }
  31:       }
  32:       public virtual double Amount
  33:       {
  34:          get
  35:          {
  36:             return ((double)(GetValue(AmountProperty)));
  37:          }
  38:          set
  39:          {
  40:             SetValue(AmountProperty, value);
  41:          }
  42:       }
  43:       public virtual double Width
  44:       {
  45:          get
  46:          {
  47:             return ((double)(GetValue(WidthProperty)));
  48:          }
  49:          set
  50:          {
  51:             SetValue(WidthProperty, value);
  52:          }
  53:       }
  54:    }
  55: }

Im XAML-Code muss man dann nur noch dem entsprechendem Objekt (jedes Objekt, was von UIElement erbt hat eine Effect-Eigenschaft) die Effect-Eigenschaft zuweisen:

<Image Source="Images/sampleImage.jpg" Stretch="Fill" Width="154" VerticalAlignment="Top" Height="116" HorizontalAlignment="Left" Margin="180,0,0,0">
  <Image.Effect>
     <localEffects:EmbossedEffect x:Name="EmbossedEffect" Width="0.004" Amount="1" />
  </Image.Effect>
/Image>
Was muss man tun?

Hier noch mal die notwendigen Schritte um eigene Effekte mit dem Shazzam-Tool zu integrieren:

  1. Testen und kompilieren des Shaders in Shazzam
  2. Kompilierte .ps-Datei zum Visual Studio-Projekt hinzufügen und Build Action auf Resource setzen
  3. Neue Effekt-Klasse zum Projekt hinzufügen
  4. Den generierten Shazzam-Code in die Klasse einfügen
  5. Namespace anpassen
  6. Refactor->Klassenname umbenennen
  7. Ändern von UIPropertyMetaData in PropertyMetaData
  8. Erzeugen einer Uri, welche die .ps-Datei referenziert (System wird benötigt)
  9. Hinzufügen eines parameterlosen Konstruktors für unsere Effekt-Klasse, der die Pixel-Shader-Eigenschaft initialisiert

Weitere Effekte sind in der Open Source Shader Bibliothek von Codeplex zu finden.

Keine Kommentare:

Kommentar veröffentlichen