Frédéric Mélantois
WPF : Effets et Pixel Shaders
Le SP1 du Framework .NET 3.5 apporte à WPF un certain nombre d’améliorations dont les effets sur les images. Une « fenêtre » pour l’utilisation des pixel shaders de DirectX a été ouverte.
Par Frédéric Mélantois publié le 12/01/2009 à 09:06, lu 4014 fois, 6 pages
 2 | Développement de l’effet à partir de ShaderEffect
Le Service Pack 1 du Framework 3.5 propose une nouvelle classe abstraite « ShaderEffect » permettant de mettre en œuvre des effets sur tout élément du Framework dérivant de la classe de base « UIElement ». A noter que ceci ne fonctionne pas pour les classes dérivant de la classe abstraite « UIElement3D ».
En effet, toutes les classes dérivant d’UIElement ont une propriété « Effect » ajoutée depuis le SP1 qu’il est recommandé désormais d’utiliser au lieu de « BitmapEffect ».

public static readonly DependencyProperty EffectProperty;

 

public Effect Effect

{

    get

    {

        return (Effect)base.GetValue(EffectProperty);

    }

    set

    {

        base.SetValue(EffectProperty, value);

    }

}

Pour plus de détails, vous pouvez consulter la documentation de la MSDN :
Il est essentiel de comprendre que l’on va pouvoir « brancher » un effet sur tout élément visuel dérivant d’UIElement.
Apparu avec le Service Pack 1, la classe « Effect » est une classe abstraite dont dérivent :
 
/content/13791ad3-9dcd-44e1-8694-84f713c4a868/image2.jpeg
 
Avant de s’intéresser plus particulièrement à la classe « ShaderEffect », qui va nous permettre de créer des effets personnalisés utilisant les Pixel Shaders, étudions très superficiellement la classe « Effect ». Elle contient un membre statique important « ImplicitInput » :

public static Brush ImplicitInput

{

    get;

    set;

}

La documentation MSDN explique que l’effet s’applique au visuel d’un objet dérivant d’UIElement par cette entrée de type « Brush ».
Pour information, les classes dérivant de « System.windows.Media.Brush » sont :
 
/content/13791ad3-9dcd-44e1-8694-84f713c4a868/image3.png
 
Prenons un exemple Xaml de mise en œuvre d’un des nouveaux effets fournis avec le Service Pack 1 du Framework 3.5 :

< Canvas Name ="canvas1">

        < Canvas.Effect >

            < BlurEffect RenderingBias ="Performance" Radius ="5" KernelType ="Gaussian"/>

        </ Canvas.Effect >

Le Framework fournit à l’heure actuelle très peu d’effets. Il est donc indispensable de connaître la façon d’en créer de nouveaux. La classe abstraite « ShaderEffect » permet cette construction. Essayons de mettre en œuvre un exemple basique :

public class MyEffect : ShaderEffect

{

    public MyEffect()

    {

        this.PixelShader = _pixelShader;

    }

 

    private static PixelShader _pixelShader = new PixelShader()

                { UriSource = new Uri(@"pack://application:,,,/MyEffect;component/MyEffect.ps") };

}

Dans le constructeur de notre classe héritant de ShaderEffect, nous devons fournir à la propriété « PixelShader » une instance de la classe « PixelShader » qui permettra de prendre en charge le code compilé du pixel shader que nous construirons par la suite avec le langage HLSL (High Level Shader Language). L’utilisation d’une référence statique évite une nouvelle instanciation à chaque appel de l’effet. L’instance reçoit en paramètre le chemin de la ressource du code compilé du Pixel Shader que nous écrirons un peu plus tard en langage HLSL.
Une fois que l’on pointe sur le mini-programme pixel shader, il nous faut lui fournir une valeur d’entrée pour le registre 0, correspondant à l’échantillon :

public static readonly DependencyProperty ImplicitInputProperty =

                    ShaderEffect.RegisterPixelShaderSamplerProperty("ImplicitInput", typeof(MyEffect), 0);

    public Brush ImplicitInput

    {

        get { return (Brush)GetValue(ImplicitInputProperty); }

        set { SetValue(ImplicitInputProperty, value); }

    }

Le système de Dependency Properties est ici utilisé. Si cette notion ne vous est pas familière, lisez l’excellent article de David Catuhe :
Dans le code ci-dessus, la méthode statique « RegisterPixelShaderSamplerProperty » permet d’associer une propriété de dépendances à un registre du Pixel Shader.
Pour une meilleure compréhension, détaillons tous les paramètres possibles de cette méthode :
 
/content/13791ad3-9dcd-44e1-8694-84f713c4a868/image4.png
 
  • dpName correspond au nom de la propriété de dépendance
  • ownerType est le type de l’effet personnalisé (ici, typeof(MyEffect))
  • samplerRegisterIndex correspond à l’index du registre du Pixel Shader sur lequel on passera la source de la texture à échantillonner. On comprend que l’on peut avoir plusieurs sources de texture en entrée d’un Pixel Shader, chaque index correspondant à une source différente. Le nombre de source dépasse les 8 entrées sous la version 2.0 des Pixel Shaders.
  • samplingMode correspond à une énumeration dont les valeurs sont Auto, Bilinear et NearestNeighbor.
Concernant le mode d’échantillonnage (SamplinMode) de la source fournie au Pixel Shader, vous trouverez des explications complémentaires dans la documentation MSDN :
J’apporterai toutefois des précisions supplémentaires très succinctes afin de compléter celle-ci, pour les personnes n’ayant jamais étudié l’infographie. En regardant l’énumération, on comprend bien que l’on a le choix entre un échantillonnage de type NearestNeighbor (plus proche voisin) ou de type Bilinear (bilinéaire) :
 
/content/13791ad3-9dcd-44e1-8694-84f713c4a868/image5.png
 
 
/content/13791ad3-9dcd-44e1-8694-84f713c4a868/image6.png
 
La méthode d’interpolation bilinéaire donne un meilleur rendu mais comporte de nombreux calculs et est donc moins performante que la méthode du plus proche voisin. Vous pouvez donc faire le choix entre qualité et performance. Si vous ne voulez pas effectuer ce choix, la documentation MSDN précise que l’énumération « Auto » choisit le mode d'échantillonnage approprié en fonction du mode de rendu activé (matériel ou logiciel), de la texture de destination (pivotée ou non) et d'autres facteurs.
Une fois la propriété de dépendance définie, il faut faire la liaison dans le constructeur, grâce à la méthode « UpdateShaderValue ».

public MyEffect()

{

    this.PixelShader = _pixelShader;

    UpdateShaderValue(ImplicitInputProperty);

}

Concernant l’ensemble du développement c#, nous avons fait l’essentiel pour un exemple simple. Il nous faut développer le mini-programme pixel shader en language HLSL. Nous étudierons la manière de procéder dans le prochain chapitre.
En attendant, voici un récapitulatif du code présenté précédemment :

public class MyEffect : ShaderEffect

{

    public MyEffect()

    {

        this.PixelShader = _pixelShader;

        UpdateShaderValue(ImplicitInputProperty);

    }

 

    private static PixelShader _pixelShader = new PixelShader()

                { UriSource = new Uri(@"pack://application:,,,/MyEffect;component/MyEffect.ps") };

 

    public static readonly DependencyProperty ImplicitInputProperty =

                    ShaderEffect.RegisterPixelShaderSamplerProperty("ImplicitInput", typeof(MyEffect), 0);

    public Brush ImplicitInput

    {

        get { return (Brush)GetValue(ImplicitInputProperty); }

        set { SetValue(ImplicitInputProperty, value); }

    }

}

Notez bien qu’après avoir lu l’article en entier, et seulement après, les plus curieux d’entre vous pourront tester ce code en mettant en commentaire tout le code concernant ImplicitInput. Vous constaterez que votre effet fonctionne ! C’est donc que dans la mécanique sous-jacente, il est prévu de détecter une texture en entrée par défaut. Attention, à partir de deux textures en entrée, vous devrez spécifier. Bien que l’on puisse omettre une texture en entrée, je vous déconseille néanmoins de le faire pour garder de la lisibilité dans votre code.
 
» Démarrer une discussion
 
Discussion démarée par tanuki le 11/05/2009 à 18:20, 1 commentaire(s).