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 4024 fois, 6 pages
 5 | Du filtrage un peu plus élaboré (plus proches voisins)
Jusqu’à présent, les différents effets que vous nous avons présentés affectaient chaque pixel d’une texture sans tenir compte des pixels voisins. Or, en traitement d’images, il est très courant d’utiliser des filtres qui prennent en compte les pixels avoisinants. Regardons s’il est possible de récupérer les valeurs des pixels voisins. A priori, disposant de la texture et des coordonnées du pixel courant, le mini-programme qu’est le pixel shader doit être en mesure de récupérer n’importe quelle valeur d’un autre pixel.
Il faut savoir que les coordonnées d’un pixel d’une texture se trouvent dans un repère normé. Les abscisses et les ordonnées sont comprises entre 0 et 1. Donc les coordonnées (1,1) de la texture correspondent à la taille (largeur, hauteur) en pixels. On comprend alors que la distance en abscisse entre deux pixels est de « 1/largeur », que la distance en ordonnée entre deux pixels est de « 1/hauteur ». Les développeurs DirectX ont pris l’habitude d’appliquer des textures 128 par 128 sur leurs objets 3D. Pour leur différent mini-programme, ils ont pris l’habitude de « coder en dur » la distance entre deux pixels : 0.0078125. Ils économisent ainsi un temps de calcul précieux puisqu’ils n’ont pas à passer en paramètre la taille de leur texture. Notez bien qu’à partir des versions ps_2_x des pixel shaders, il est possible d’utiliser la fonction HLSL ddx(x) et ddy(y) permettant de connaître les distances entre deux pixels. La version ps_2_0 retenue par l’équipe WPF pour une plus grande compatibilité matérielle ne permet pas d’utiliser ces fonctions. Il faut donc que la taille de la texture soit transmise par une constante au pixel shader. Pour cela, il existe deux possibilités : soit notre classe héritée de ShaderEffect comporte deux propriétés de dépendance prenant en compte la largeur et la hauteur de la texture afin de les transmettre au pixel shader, soit la mécanique sous-jacente de cette partie du Framework prend en charge la détermination de la taille de la texture. C’est bien évidemment cette deuxième possibilité que nous allons étudier puisqu’elle a été mise en œuvre par l’équipe WPF dans le Service Pack 1 du Framework 3.5.
L’utilisation est relativement simple. Côté WPF, elle ne nécessite qu’une seule ligne de code dans le constructeur de la classe héritée de ShaderEffect :

this.DdxUvDdyUvRegisterIndex = 1;

Cette propriété permet de spécifier l’index du registre où se trouvera la constante comportant les valeurs à exploiter pour déterminer la distance entre deux pixels aussi bien en abscisse qu’en ordonnée. Nous n’avons rien d’autre à faire du côté WPF. Etudions maintenant le code HLSL :

sampler2D ImplicitInput : register(s0);

float facteur : register(c0);

//constante permettant de connaître la distance entre deux pixels

float4 ddxDdy : register(c1);

La constante se trouve bien à l’index 1 du registre. Elle est de type float4. Récupérons la distance en abscisse et en ordonnée entre deux pixels (elle est contenue dans ddxDdy) :

float4 main(float2 uv : TEXCOORD) : COLOR

{

    //Récupération des distances entre deux pixels

    float ddx = length(ddxDdy.rg);

    float ddy = length(ddxDdy.ba);

Maintenant que nous avons la distance entre deux pixels, il est aisé d’opérer un filtrage. Nous allons construire un filtrage classique de type Laplacien. Le filtre doit s’opérer sur les 8 valeurs de pixels des plus proches voisins :

//Nombre de pixel sur lequel on applique un filtre (8 voisins du pixel central)

const int n = 9;

 

//tableau des distances des pixels voisins

const float2 k[n] = {

        float2(-ddx, ddy),

        float2( 0.0 , ddy),

        float2( ddx, ddy),

        float2(-ddx, 0.0 ),

        float2( 0.0, 0.0),

        float2( ddx, 0.0 ),

        float2(-ddx, -ddy),

        float2( 0.00 , -ddy),

        float2( ddx, -ddy),

};

Nous constatons qu’il est possible de définir un tableau regroupant les distances entre les pixels entourant le pixel en cours de traitement, ce qui facilitera les traitements. Nous allons utiliser deux tableaux, un contenant les coefficients de notre filtre de type Laplacien et un autre qui contiendra les valeurs de la couleur des pixels voisins :

//tableau des coefficients d'un filtre de type Laplacien (on multiplie par -8 le pixel central)

float coef[n] = {-1,0,-1,0,4,0,-1,0,-1};

 

//Déclaration du tableau des couleurs des pixels voisins

float4 pix[n];

Notez bien que le mini-programme que nous écrivons peut servir à d’autres filtres, il vous suffira alors de modifier les coefficients.
Récupérons les valeurs des couleurs des différents pixels en bouclant :

//Déclaration de la variable i pour effectuer les boucles  

int i;

 

//Récupération dans le tableau pix de toutes les valeurs des pixels voisins

 for (i=0; i < n; i++) {

  pix[i] = tex2D(ImplicitInput, uv + k[i]);

}

Nous pouvons constater que le langage HLSL est suffisamment évolué pour permettre de réaliser des boucles. Une fois récupérée toutes les valeurs des couleurs des pixels, nous appliquons le filtre de manière très simple (nous avons déjà mis en application la fonction « dot(x,y) » dans un précédent chapitre) :

//Déclaration de la couleur permettant de recueillir l'application du filtre de type laplacien

float4 couleur = 0;

 

//calcul de la valeur du pixel par filtrage

for (i = 0; i < n; i++) {

  couleur.rgb += dot(pix[i].rgb, coef[i]);

}

Nous avons appliqué le filtre Laplacien sur le pixel. Il peut être intéressant de donner un effet de transition entre la valeur du pixel original et la nouvelle valeur calculée du pixel. Pour cela, nous avons conservé dans notre exemple la constante « facteur » afin que la propriété de dépendance « FacteurProperty » puisse être reliée sous WPF à une DoubleAnimation par exemple. La seule contrainte pour notre effet est que la constante « Facteur » soit bornée en [0,1]. Comme nous l’avons vu précédemment, nous n’inclurons pas le contrôle des bornes dans le pixel shader pour des raisons de performance.

    //Renvoie la texture originale si facteur = 1

    //Renvoie le laplacien si facteur = 0

    //Entre 0 et 1, nous avons les intermédiaires (effet intéressant lors d'animations WPF)

    return pix[4] * facteur + (couleur * (1- facteur));

}

Pix[4] est la valeur de la couleur du pixel central.
Nous redonnons au lecteur le code de MyEffect afin qu’il puisse mettre en œuvre cet effet intéressant :

public class MyEffect : ShaderEffect

{

    public MyEffect()

    {

        this.PixelShader = _pixelShader;

        UpdateShaderValue(ImplicitInputProperty);

        UpdateShaderValue(FacteurProperty);

        this.DdxUvDdyUvRegisterIndex = 1;

    }

 

    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); }

    }

 

    public static readonly DependencyProperty FacteurProperty = DependencyProperty.Register(

        "Facteur", typeof(double), typeof(MyEffect),

            new UIPropertyMetadata(0.0, PixelShaderConstantCallback(0), CoerceFacteur)

            );

 

    private static object CoerceFacteur(DependencyObject dpdObj, object valeur)

    {

        MyEffect effect = dpdObj as MyEffect;

        double facteur = 0;

        if (effect != null && double.TryParse(valeur.ToString(), out facteur))

        {

            if (facteur < 0.0 || facteur > 1.0)

            {

                return effect.Facteur;

            }

        }

        return facteur;

    }

 
» Démarrer une discussion
 
Discussion démarée par tanuki le 11/05/2009 à 18:20, 1 commentaire(s).