Pixel shader - création, utilisation, et binding

Description

Ce code a pour but de montrer comment créer un pixel shader, et comment l'utiliser dans une application wpf (ou silverlight).

Tout d'abord, c'est quoi un pixel shader ? La réponse est ici : http://fr.wikipedia.org/wiki/Pixel_shader
Pour ceux qui ont la flemme de lire, un pixel shader est un mini programme qui permet de minipuler une partie du procésus de rendu d'une image. Exemple : dans un monde en 3D, on a une vitre et derrière un cube. On regarde le cube à travers la vitre. Si on veut un aspect bosselé pour notre carreau, et bien on va utiliser un pixel shader. De même, si on veut appliquer un effet de flou sur une image, la déssaturer, ne garder que la composante rouge, ect... et bien on va utiliser un pixel shader. C'est très rapide, ça se parallélise bien dans les cartes graphiques, bref... ça déchire.
D'ailleurs, vous en avez peut-être déjà utilisé, si vous avez déjà manipulé les BlurEffect en wpf (non, je ne parle pas du BlurBitmapEffect, ce truc pourri dont le rendu n'était pas généré par la carte graphique, mais par le procésseur principal).

Première étape : Création du pixel shader.
Pour en créer, je vous conseil l'outil suivant : shazzam (non, rien à voir avec le truc qui reconnait la musique qui passe en boite de nuit, et qui est installé sur votre téléphone Android ou votre IPhone... bande de bobos !).
Pour le télécharger, c'est ici : http://shazzam-tool.com/
Le principe, c'est qu'on a une fonction, et en entrée de celle-ci, on nous passe un point nommé uv, donc les variables x et y varient entre 0 et 1. (0,0) étant le bord supérieur gauche de l'image, et (1,1) étant le bord inférieur droit... Ca, c'est bien, ça nous change pas de d'habitude, hormis qu'on travaille plus en pixels, mais en unités allant de 0 à 1.
Ce que nous demande le pipe line graphique, c'est de lui dire de qu'elle couleur est le point se trouvant aux coordonnées (uv.x, uv.y) (rouge, vert, bleu... et alpha... ouais, faut pas l'oublier, celui-là).
Le code de mon pixel shader se trouve dans la source, juste après cet article. On peut faire certainement mieux que ce que je fais, mais bon, comme je suis une bille en maths...

Seconde étape : compiler le shader.
Shazzam le fait tout seul, pour peu que vous ayez téléchargé le SDK de directX et configuré correctement votre shazzam. Quoi, faut encore que je vous donne le lien vers le SDK, bande de faignasses ? Et bien il est là :
http://www.microsoft.com/downloads/details.aspx?FamilyID=b66e14b8-8505-4b17-bf80-edb2df5abad4&displaylang=en#dx

Troisième étape : Inclure le pixel shader dans le projet et l'utiliser.
Première chose à faire, il faut mettre le .ps généré par le SDK DirectX dans votre projet, et surtout, NE PAS OUBLIER DE METTRE "RESOURCE" POUR LE BUILD ACTION DE CE FICHIER (sinon, vous êtes marrons).
Il faut aussi créer une classe dans votre projet pour wrapper les différents paramètres d'entrée de votre pixel shader avec votre code c#. Shazzam (quel magnifique outil), génère cette classe pour nous ! Si si, regardez bien, le petit onglet "Generated Shader - C#". Il y a même un onglet pour les loosers qui en sont restés à VB ;-)
Hop, ni vu ni connu, un petit copier coller depuis shazzam, et voilou. Le seul truc à modifier dans la classe, c'est le chemin d'accès vers le fichier .ps :
pixelShader.UriSource = new Uri(@"pack://application:,,,/StrechShader;component/StretchPointEffect.ps");
Ca va, c'est pas trop violent.

Quatrième étape, un peu de binding pour assaisonner le tout. Dans l'exemple présenté ici, il y a une augmentation diminution de la force d'aspiration avec la molette de la souris (qui ne fonctionne d'ailleurs plus une fois que l'on a cliqué sur l'un des boutons de l'interface, mais bon...). Ainsi qu'une modification du point d'aspiration lorsque l'on clique sur la fenêtre. Je vous laisse admirer la source qui n'a rien de bien compliqué. Et je suis d'accord, mon contrôleur n'est pas commenté, mais bon, pour ce qu'il y a dedans...

Petite info : ce code a été compilé avec Visual Studio Express 2010 Beta 2 (qui porte très bien son nom de "beta").

Lâchez vos com' !

Source / Exemple :


/// <class>ImageMover</class>
/// <namespace>StrechShader</namespace>
/// <description>Effet d'aspiration de l'image en un point et une force donnée.</description>

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

/// <summary>Le point d'aspiration</summary>
/// <minValue>0</minValue>
/// <maxValue>0.2</maxValue>
/// <defaultValue>0.5,0.5</defaultValue>
float2 Center : register(C0);

/// <summary>La force d'aspiration</summary>
/// <minValue>0</minValue>
/// <maxValue>1</maxValue>
/// <defaultValue>0.01</defaultValue>
float Strenght : register(C1);

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

sampler2D  implicitInputSampler : register(S0);

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

float4 main(float2 uv : TEXCOORD) : COLOR
{
	// Si la force est à 0, on a rien à faire.
	if(Strenght == 0) return tex2D(implicitInputSampler, uv);
	// récupération du coin de l'image approprié
	float2 corner = 0;
	if(uv.x > Center.x) corner.x = 1;
	if(uv.y > Center.y) corner.y = 1;
	// calcul de la distance entre le pixel en cours, et le point d'aspiration
	float distCenter = sqrt(pow(uv.x-Center.x, 2)+pow(uv.y-Center.y, 2));
	// Récupération du pixel qui vient en remplacement du pixel actuel
	float2 newPoint = uv;
	float Strenght2 = 1 - Strenght;
	if(corner.x == 1) newPoint.x -= ((Center.x - uv.x) / (distCenter * Strenght2)/100);
	else newPoint.x += ((uv.x - Center.x) / (distCenter * Strenght2)/100);
	// Si le pixel de remplacement est en dehors de l'image, on retourne un point vide
	if(newPoint.x < 0 || newPoint.x > 1) return 0;
	if(corner.y == 1) newPoint.y -= ((Center.y - uv.y) / (distCenter * Strenght2)/100);
	else newPoint.y += ((uv.y - Center.y) / (distCenter * Strenght2)/100);
	// Si le pixel de remplacement est en dehors de l'image, on retourne un point vide
	if(newPoint.y < 0 || newPoint.y > 1) return 0;
	// On retourne le pixel récupéré.
	return tex2D(implicitInputSampler, newPoint);
}

Conclusion :


Ha oui, au fait... on peut aussi faire des trucs jolis avec les pixel shaders... enfin pas comme là quoi !

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.