I-3. Tutoriel 3 : Shaders et système d'effets▲
I-3-a. Résumé▲
Dans le tutoriel précédent, nous avons configuré un vertex buffer et envoyé un triangle au GPU. Maintenant, nous allons en fait passer par étapes au travers du pipeline graphique et regarder comment chaque niveau fonctionne. Le concept de shaders et du système d'effets sera expliqué.
Notez que ce tutoriel partage le même code source que le précédent, mais correspondera à différentes sections.
I-3-b. Source▲
(SDK root)\Samples\C++\Direct3D10\Tutorials\Tutorial03
I-3-c. Le pipeline graphique▲
Dans le tutoriel précédent, nous avons configuré le vertex buffer, et ensuite, nous avons associé une composition de sommets avec un objet technique. Maintenant, nous allons expliquer l'objet technique et les shaders qui le composent. Pour comprendre pleinement chacun des shaders, nous allons faire un pas en arrière et regarder l'intégralité du pipeline graphique.
Dans le tutoriel 2, quand nous avons appelé Apply depuis la technique, nous avons en fait limité notre shader à un niveau du pipeline. Ensuite, quand nous avons appelé Draw, nous avons commencé à traiter les données des sommets passées dans le pipeline graphique. Les sections suivantes décrivent en détail ce qu'il se produit après la commande Draw.
I-3-d. Les shaders▲
Dans Direct3D 10, les shaders résident dans différents niveaux du pipeline graphique. Ce sont de courts programmes qui, exécutés par le GPU, prennent certaines données d'entrées, traitent ces données, et ensuite, produisent le résultat vers le niveau suivant du pipeline. Direct3D 10 supporte 3 types de shaders : le vertex shader, le geometry shader, et le pixel shader. Un vertex shader prend un sommet comme entrée. Il est utilisé une seule fois pour tous les sommets passés au GPU via des vertex buffers. Un geometry shader prend une primitive comme entrée, et est utilisé une seule fois pour toutes les primitives passées au GPU. Une primitive est un point, une ligne, ou un triangle. Un pixel shader prend un pixel (ou parfois appelé un fragment) comme entrée, et est utilisé une seule fois pour chaque pixel d'une primitive que nous souhaitons afficher. Ensemble, les vertex, geometry, et pixel shaders se situent où l'essentiel de l'action se produit. Lors de l'affichage avec Direct3D 10, le GPU doit avoir un vertex shader et un pixel shader valide. Le geometry shader est une fonctionnalité avancée dans Direct3D 10 et est optionnel, donc nous ne discuterons pas des geometry shaders dans ce tutoriel.
I-3-e. Les vertex shaders▲
Les vertex shaders sont de courts programmes qui sont executés par le GPU sur des sommets. Pensez aux vertex shaders comme des fonctions C qui prennent chaque sommet comme entrée, traitent l'entrée, et ensuite produisent le sommet modifié. Après que l'application ait passé les données des sommets au GPU sous la forme d'un vertex buffer, le GPU itère au travers les sommets du vertex buffer, et exécute le vertex shader actif une fois pour chaque sommet, en passant les données des sommets au vertex shader en tant que paramètres d'entrées.
Tant qu'un vertex shader peut être utilisé pour effectuer plusieurs tâches, le travail le plus important d'un vertex shader est la transformation. La transformation est le procédé de conversion des vecteurs d'un système de coordonnée vers un autre. Par exemple, un triangle dans une scène 3D peut avoir ses sommets aux positions (0, 0, 0) (1, 0, 0) (0, 1, 0). Quand le triangle est dessiné sur un buffer de texture 2D, le GPU doit connaître les coordonnées 2D des points du buffer où les sommets doivent être dessinés. C'est la transformation qui nous aide à accomplir celà. La transformation sera traitée en détails dans le tutoriel suivant. Pour ce tutoriel, nous utiliserons un vertex shader simple, qui ne fait rien excepté passer les données d'entrée en tant que sortie.
Dans les tutoriels Direct3D 10, nous écrirons nos shaders en High-Level Shading Language (HLSL), et les applications utiliseront ces shaders avec le système d'effets. Rappelez vous que nos données sommets ont une position 3D, et le vertex shader ne traitera pas du tout l'entrée. Le vertex shader résultant ressemblera à ceci :
float4 VS( float4 Pos : POSITION ) : SV_POSITION
{
return
Pos;
}
Ce vertex shader ressemble fortement à une fonction C. HLSL utilise une syntaxe ressemblant au C pour rendre l'apprentissage plus simple pour les programmeurs C/C++. Nous pouvons voir que ce vertex shader, nommé VS, prend un paramètre de type float4 et retourne une valeur float4. Dans HLSL, un float4 est un vecteur à 4 composants, où chaque composant est un nombre à virgule flottante. Les deux points définissent les sémantiques du paramètre ainsi que la valeur retournée. Comme mentionné plus haut, les sémantiques dans HLSL décrivent la nature des données. Dans notre shader précédent, nous choisissons POSITION comme sémantique du paramètre d'entrée Pos car ce paramètre contiendra la position du sommet. Les sémantiques de la valeur retournée, SV_POSITION, sont des sémantiques prédéfines avec des significations spéciales. Ces sémantiques disent au pipeline graphique que les données associées aux sémantiques définissent la position dans l'espace cadré. Cette position est requise par le GPU afin de dessiner les pixels à l'écran. (On discutera de l'espace cadré dans le tutoriel suivant.) Dans notre shader, on rend la donnée de position d'input et d'output de la même manière dans le pipeline.
I-3-f. Les pixel shaders▲
Les moniteurs d'ordinateurs modernes sont habituellement des affichages matriciels, ce qui signifie que l'écran est en fait une grille à deux dimensions de petits points appelés pixels. Chaque pixel contient une couleur indépendante des autres pixels. Quand nous affichons un triangle à l'écran, nous n'affichont pas réellement un triangle en tant qu'une seule entité. Mais plutôt, nous allumons un groupe de pixels qui sont couverts par la zone du triangle. L'image 2 montre une illustration de ceci.
A Gauche : ce que nous souhaitons dessiner. A Droite : ce qui se trouve réellement sur l'écran.
Le procédé de conversion d'un triangle défini par 3 sommets vers un ensemble de pixels couverts par le triangle est appelé "rasterisation". Le GPU détermine d'abord quels pixels sont couverts par le triangle qui est dessiné. Ensuite, il invoque le pixel shader actif pour chacun de ces pixels. Le but premier d'un pixel shader est de calculer la couleur que chaque pixel doit avoir. Le shader prend certaines entrées du pixel qui va être colorié, calcule la couleur du pixel, ensuite retourne cette couleur vers le pipeline. Les entrées qu'il prend proviennent du geometry shader actif, ou, si un geometry shader n'est pas présent, comme dans ce tutoriel, l'input provident directement du vertex shader.
Le vertex shader que nous avons créé précédemment produit un float4 avec les sémantiques SV_POSITION. Cela sera l'entrée de notre pixel shader. Comme les pixel shaders produisent des valeurs de couleur, la sortie de notre pixel shader sera un float4. On donne à la sortie les sémantiques SV_TARGET pour signifier la sortie vers le format de l'affichage ciblé. Le pixel shader ressemble à ceci :
float4 PS( float4 Pos : SV_POSITION ) : SV_Target
{
return
float4( 1.0
f, 1.0
f, 0.0
f, 1.0
f ); // Jaune, avec Alpha = 1
}
I-3-g. Système d'effets▲
Notre fichier d'effet se compose de deux shaders, un vertex shader et un pixel shader, et la définition de la technique. La définition de la technique définira les shaders correspondants pour chaque section. En plus, il y a aussi la semantique pour compiler le shader. Notez que le geometry shader est laissé à NULL car il n'est pas nécessaire et sera traité plus tard.
// Définition de la technique
technique10 Render
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VS() ) );
SetGeometryShader( NULL
);
SetPixelShader( CompileShader( ps_4_0, PS() ) );
}
}
I-3-h. Créer l'effet et la technique d'effet▲
Dans le code de l'application, nous devrons créer un objet effet. Cet objet effet représente notre fichier d'effet, et est créé en appelant D3D10CreateEffectFromFile(). Une fois que nous avons créé l'objet d'effet, nous pouvons appeler la méthode ID3D10Effect::GetTechniqueByName(), en passant "Render" en tant que nom, pour obtenir l'objet technique qui sera utilisé pour faire l'affichage actuel. Le code est démontré ci-dessous :
// Crée l'effet
if
( FAILED( D3DX10CreateEffectFromFile( L"Tutorial03.fx"
, NULL
, NULL
, D3D10_SHADER_ENABLE_STRICTNESS, 0
, g_pd3dDevice, NULL
, NULL
, &
g_pEffect, NULL
) ) )
return
FALSE;
// Obtient la technique
g_pTechnique =
g_pEffect->
GetTechniqueByName( "Render"
);
I-3-i. Rassembler tout cela▲
Après avoir parcouru le pipeline graphique, nous pouvons commencer à comprendre le procédé d'affichage du triangle que nous avons créé au début du tutoriel 2. La création des applications Direct3D requiert deux étapes distinctes. La première serait de créer la source des données dans les données de sommets, comme nous avons fait dans le tutoriel 2; le second niveau sera de créer les shaders qui transformeront ces données pour l'affichage, qui a été montré dans ce tutoriel.