Améliorer le rendu avec le High Level Shader Language
Les shaders et XNA
Un shader est un programme directement exécutable par la carte graphique. Il permet de transférer la gestion des lumières, ombres, textures, etc., au processeur graphique et de soulager de cette charge de travail le processeur de l’ordinateur. Ainsi, à partir du même contenu de base, mais en utilisant divers shaders, vous pouvez obtenir des rendus complètement différents. =Labat FM.book Page 303 Vendredi, 19. juin 2009 4:01 16 Améliorer le rendu avec le High Level Shader Language Développement XNA pour la XBox et le PC 304 Vertex shaders et pixel shaders On distingue deux types de shaders : les vertex shaders et les pixels shaders. Les vertex shaders sont exécutés sur tous les vertices d’un objet. Si vous dessinez un simple triangle à l’écran, le vertex shader sera exécuté pour les trois vertices qui composent ce triangle. Mais les vertex shaders peuvent également être exécutés sur des sprites en 2D : en effet, ceux-ci sont constitués de 4 vertices (un dans chaque coin du sprite). Quant aux pixels shaders, ils sont exécutés sur tous les pixels visibles à l’écran de l’objet que vous dessinez, qu’il s’agisse d’un objet en 3D ou d’un sprite en 2D. Chronologiquement, le processus est le suivant : 1. Le GPU (processeur graphique) reçoit des paramètres et des vertices (leur position, couleur, texture, etc., tout dépend du choix que vous avez fait). 2. Le vertex shader est d’abord exécuté. En sortie, vous disposez des vertices modifiés (ou non, tout dépend du shader). 3. Ces données passent ensuite à l’étape de rastérisation. Au cours de cette étape, les données vectorielles sont converties en données composées de pixels pouvant être affichés à l’écran. 4. Le résultat de la rastérisation est ensuite envoyé au pixel shader. Après l’exécution de ce programme, le GPU retourne la couleur qui devra être affichée. Comme pour un processeur classique, le processeur graphique ne comprend que des instructions de bas niveau. Ainsi, pour faciliter le travail des développeurs, des langages de plus haut niveau (c’est-à-dire, plus intelligibles) à la syntaxe très proche des langages tels que le C ont été créés. Du côté d’OpenGL, le langage standardisé est le GLSL. Le fabricant de cartes graphiques NVidia a, quant à lui, créé le langage Cg. Enfin, de son côté, Microsoft a créé le HLSL (High Level Shader Language). C’est ce dernier langage qui est utilisé par l’API DirectX, et donc par XNA. Dans XNA, pour afficher quelque chose à l’écran vous devez passer obligatoirement par HLSL. Cependant, pour vous faciliter les choses (c’est le mot d’ordre de XNA après tout !) et vous éviter de devoir vous attaquer directement au HLSL, les développeurs ont créé les classes SpriteBatch et BasicEffect. En arrière-plan, ces classes travaillent directement avec des shaders. Vous pouvez d’ailleurs retrouver le code source de ces shaders à cette adresse : http://creators.xna.com/fr-FR/education/catalog/
Ajouter un fichier d’effet dans XNA
Les fichiers d’effets écrits en HLSL sont automatiquement gérés par le Content Manager de XNA. Exactement comme vous ajoutez n’importe quel type de fichier (une police de caractères par exemple), vous pouvez ajouter un fichier d’effet à XNA (figure 13-1). Vous avez ensuite toute latitude pour éditer le fichier .fx directement dans Visual Studio, avec un bloc-notes ou bien, si vous désirez des fonctionnalités plus avancées, notamment pour déboguer un effet, via FX Composer de NVidia (figure 13-2). Figure 13-1 Ajout d’un fichier .fx au projet FX Composer Téléchargez FX Composer à l’adresse suivante : http://developer.nvidia.com/object/fx_composer_home.html/ Puis, installez le programme comme une application classique.
Syntaxe du langage HLSL
Vous allez maintenant découvrir la syntaxe de base du langage HLSL. Celle-ci étant très proche des langages C/C++/C#, vous l’assimilerez très vite. Les variables HLSL Comme la plupart des langages de programmation, le HLSL permet de déclarer des variables de différents types. Vous pouvez ainsi utiliser des variables de type int, float, bool, etc., et même définir vos propres types grâce aux structures. Définir la matrice Pour définir les matrices, deux manières différentes s’offrent à vous en HLSL : • La première manière consiste à utiliser le mot-clé floatLxC, où L est le nombre de lignes et C, le nombre de colonnes. La ligne de code suivante sert donc à déclarer une matrice de 4 colonnes par 4 lignes. float4x4 myMatrix; • La seconde repose sur le mot-clé matrix auquel vous ajoutez le type des données et les dimensions de la matrice : matrix myMatrix; Accéder au contenu de la matrice Voici les deux manières d’accéder au contenu de la matrice : • La première consiste à considérer la matrice comme un tableau de tableaux : float element = myMatrix[0][0]; • La deuxième repose sur l’utilisation de la notation pointée « . », comme vous le faites en C# pour accéder aux membres d’une structure ou d’une classe : float element = myMatrix._m23; À la différence du C#, il n’existe pas de types Vector2, Vector3, etc. Vous utiliserez donc les types vector ou floatX, où X est le nombre de composantes utilisées. Ainsi, vous stockerez la position d’un vertex dans une variable de type vector ou float3. Accéder aux composantes d’un vecteur L’accès aux différentes composantes d’un vecteur se fait de deux manières : • La première consiste à utiliser les crochets [], comme vous le feriez pour un tableau en C#. float value = myVector[0]; • La deuxième consiste à utiliser la notation pointée « . ». Les composantes peuvent être identifiées par deux noms différents. La première série de noms est rgba et la seconde, xyzw. Par exemple, si vous avez un vecteur color, vous pouvez accéder à son niveau de vert des deux manières suivantes : float green = color.g; float green = color.y; Le swizzling sert à accéder à plusieurs composantes d’un vecteur en même temps. Dans les deux exemples suivants (qui utilisent les deux méthodes d’accès aux composantes), vous accédez aux composantes rouge et verte d’une couleur : float2 redGreen = { color[0],color[1]}; float2 redGreen = color.rg; =Labat FM.book Page 307 Vendredi, 19. juin 2009 4:01 16 Développement XNA pour la XBox et le PC 308 Les structures de contrôle Les structures de contrôle vous permettont d’effectuer des traitements avancés pour aboutir à des effets encore plus réussis. Vous retrouvez en HLSL une grande partie des structures de contrôle que vous connaissiez déjà en C# et qui s’utilisent de la même manière, à savoir : • If (si condition vérifiée, alors) ; • While (tant que condition vérifiée, faire) ; • Do (faire tant que condition vérifiée) ; • For (pour X de I a J, faire). Les fonctions fournies pas le langage Le langage HLSL fournit une très longue liste de fonctions faciles à utiliser dans les shaders. Le tableau 13-1 en cite quelques-unes. La liste complète se trouve dans la documentation de DirectX, à laquelle vous accéderez via la bibliothèque MSDN (voir annexe B). Sémantiques et structures pour formats d’entrée et de sortie Les sémantiques servent à lier les entrées et sorties d’une fonction. Par exemple, elles servent à lier la sortie de l’application XNA à l’entrée du vertex shader. Dans le processus de rastérisation, d’autres sémantiques lient la sortie du vertex shader avec l’entrée. Pour finir cette courte présentation, signalons également que le pixel shader reçoit et renvoie lui aussi des sémantiques. Vous déclarez vos propres formats d’entrée et de sortie en utilisant les structures. L’exemple ci-dessous définit le format d’entrée pour un vertex shader. Attention à l’erreur ! L’exemple de code suivant est invalide : vous ne pouvez pas mélanger les noms des deux groupes de noms de composantes. float2 redGreen = color.xg;