Aller au menu - Aller au contenu

Icône Introduction aux shaders

Avatar
Mise à jour : 23/03/2011
Difficulté : Facile Facile Creative Commons BY-SA
1 621 visites depuis 7 jours, dont 24 sur ce chapitre classé 80/786
Voici un chapitre qui ne manquera pas de ravir les amateurs d'effets visuels que nous sommes, puisqu'il va y être question des shaders. Ces petits programmes qui ont radicalement changés le monde de la 3D.

Nous allons nous attarder sur la manière de les intégrer à un programme utilisant Irrlicht. Ce qui va nous permettre par la suite d'obtenir des rendus beaucoup plus attrayants. :)
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Théorie générale

Pour commencer, attardons nous un moment sur la notion même de shader. Si vous êtes un peu familier du développement 3D, vous avez du rencontrer cet mot à de nombreuses reprises. Mais savez vous vraiment ce qu'il désigne ? Notre amie Wikipédia dit à ce sujet :

Citation : Wikipédia
Un shader est un programme utilisé en image de synthèse pour paramétrer une partie du processus de rendu réalisé par une carte graphique ou un moteur de rendu logiciel.


En effet vous n'êtes pas sans savoir depuis quelques chapitres que le processus de rendu d'Irrlicht est une chose hautement paramétrable. Et encore nous n'avons vu qu'une partie de ce qu'il est possible de faire. Mais pour être tout à fait honnête, les shaders qui nous intéressent ne vont pas directement agir sur le moteur mais sur la carte graphique.

Les cartes graphiques possèdent ce qu'on appelle un pipeline de rendu (FFP), dont vous pouvez d'ailleurs voir un schéma sur cette FAQ de developpez.com. Comme les cases rouges le montrent clairement, les shaders vont servir à remplacer certaines étapes du FFP. Ce qui veut dire qu'un shader est exécuté par le GPU et non par le CPU. Et donc que malheureusement les cartes graphiques les plus anciennes ne gèrent pas les shaders.

Sachant que nous allons écrire nous même ces fameux shaders, cela signifie que nous allons avoir un contrôle très élevé sur le processus de rendu, ce qui permet notamment de mettre en place tout un tas d'effets très chouettes. Voici d'ailleurs quelques screenshots tirés de la galerie du site officiel montrant des programmes utilisant des shaders :

Image utilisateurImage utilisateurImage utilisateur




Il existe différents langages permettant de les programmer, dont les 2 plus connus sont sans doute le GLSL (pour OpenGL) et le HLSL (pour Direct3D). Vous l'aurez compris, les shaders sont différents selon l'API 3D utilisée. En revanche leur intégration dans Irrlicht est la même quel que soit le langage.

Comme OpenGL est utilisé dans les exemples depuis le début de ce tutoriel, les shaders présentés ici seront écrits en GLSL. Et d'ailleurs, bien que ce soit le principal sujet de ce chapitre, celui-ci ne vous apprendra pas à créer les shaders proprement dit. Cela dépasse largement l'humble cadre de ce tutoriel. Ce que nous allons voir en revanche est comment les interfacer avec Irrlicht (ce qui est beaucoup moins évident qu'il n'y parait).

Si vous avez besoin d'une mise au point sur le langage GLSL, je vous propose de consulter ce tutoriel d'Yno : Les shaders en GLSL , ou encore ce site fournissant de bons exemples pour démarrer : Lighthouse 3D.


Du point de vue d'Irrlicht, un shader est un type de material qui n'est pas présent de base dans le moteur. Donc tout le gros du travail va être de créer et d'ajouter ce nouveau material. Une fois que ce sera fait, son utilisation ne différera pas de celle des autres. Nous distinguons donc 2 étapes :
  • Création de la classe contenant le shader
  • Enregistrement de cette classe auprès du moteur

Création de la classe contenant le shader

C'est la classe irr::video::IShaderConstantSetCallBack que nous allons dériver afin d'implémenter un shader. Elle permet en fait de faire passer des informations à celui-ci lors de chaque frame. C'est indispensable sachant que le shader ne peut pas accéder directement aux variables du reste du programme. Voila à quoi ressemble la déclaration :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
namespace irr
{
namespace video
{


class CShaderPerso : public irr::video::IShaderConstantSetCallBack
{

public:

    virtual void OnSetConstants(irr::video::IMaterialRendererServices* 
        services, irr::s32 userData);

};


} // end of video namespace
} // end of irr namespace


Vous ne manquerez pas de noter qu'elle ne possède qu'une seule fonction, celle qui va envoyer les informations au shader. Celles-ci dépendent d'ailleurs entièrement du shader. Vous pouvez lui passer n'importe quoi sous forme de variable. Nous verrons un code minimal juste après. Je passe également sur IMaterialRendererServices qui est gérée en interne et n'est donc pas indispensable à connaitre pour le moment. Nous reviendrons néanmoins sur la question lors d'un prochain chapitre.


Maintenant voyons l'implémentation de OnSetConstants :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void irr::video::CShaderPerso::OnSetConstants(irr::video::IMaterialRendererServices* services,
    irr::s32 userData)
{
    irr::video::IVideoDriver* driver = services->getVideoDriver();

    core::matrix4 ModelViewProjectionMatrix;
    ModelViewProjectionMatrix = driver->getTransform(video::ETS_PROJECTION);
    ModelViewProjectionMatrix *= driver->getTransform(video::ETS_VIEW);
    ModelViewProjectionMatrix *= driver->getTransform(video::ETS_WORLD);

    services->setVertexShaderConstant("ModelViewProjectionMatrix", ModelViewProjectionMatrix.pointer(), 16);
}


Pour notre premier shader, visons petit en ne cherchant uniquement qu'à recréer un pipeline de rendu basique (en fait moins puisqu'on ne gère même pas les textures). Pour ce faire on a uniquement besoin de la bonne matrice, qui est créée ligne 6 et qui va nous servir à positionner les sommets comme il se doit. Si ce n'est pas très clair pour vous, un bon petit tuto sur OpenGL vous aidera surement.

C'est la ligne 11 qui nous intéresse le plus puisque c'est la fonction setVertexShaderConstant qui va véritablement "passer" les variables (en l'occurrence la matrice) au shader. Le premier paramètre correspond au nom de la variable qui va recevoir les données à l'intérieur du shader. Cette variable n'est pas automatiquement créée, il faut donc la déclarer à l'intérieur du shader avec le même nom qu'ici (où l'inverse, chacun ses gouts). Le deuxième paramètre est un pointeur vers un tableau de floats. Ce qui signifie qu'il faut éventuellement, selon le type de variable, faire un cast pour que cela corresponde. Dans le cas d'une matrice c'est inutile puisque les valeurs sont stockées de base dans un tableau de floats. Et enfin le dernier paramètre sert uniquement à indiquer la taille du tableau.


Tant que nous y sommes, abordons le code du (des) shader(s) proprement dit. Ils vont se situer à l'extérieur d'Irrlicht dans deux fichiers indépendants. L'un portant l'extension .vert pour le vertex shader et l'autre l'extension .frag pour le fragment shader. Ces fichiers seront indiqués lors de l'enregistrement auprès du moteur. Et chose promise, chose due, voilà un parfait shader qui ne fait rien de particulier :

Code : Autre
1
2
3
4
5
6
7
//opengl.vert
uniform mat4 mWorldViewProj;

void main(void)
{
    gl_Position = mWorldViewProj * gl_Vertex;
}

Code : Autre
1
2
3
4
5
//opengl.frag
void main (void)
{
    gl_FragColor = gl_Color;
}


Enregistrement auprès du moteur

Prochaine étape : faire savoir à Irrlicht qu'on veut utiliser un nouveau type de material. Pour cela on a besoin de passer par la classe IGPUProgrammingServices qui sert comme son nom l'indique d'interface permettant d'accéder au GPU (je rappelle que les shaders tournent sur le GPU et non le CPU). Sans plus attendre voici le code, que nous allons commenter après :

Code : C++
1
2
3
4
5
6
7
8
irr::video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
irr::video::CShaderPerso* shaderPerso = new irr::video::CShaderPerso();

irr::s32 newMaterial = gpu->addHighLevelShaderMaterialFromFiles(
    "opengl.vert", "main", irr::video::EVST_VS_1_1,
    "opengl.frag", "main", irr::video::EPST_PS_1_1,
    shaderPerso, irr::video::EMT_SOLID);
shaderPerso->drop();


Lors de la première ligne nous récupérons donc un accès au GPU. Ligne 2, on crée une instance de la classe communiquant avec notre shader. C'est ligne 4 que les choses se corsent avec la fonction qui va retourner le s32 servant d'identificateur au nouveau type de material. Les paramètres 1 et 4 concernent les noms des fichiers contenant le code du (des) shader(s) proprement dit (que nous allons voir juste après). Le 7eme paramètre est évident, et le dernier désigne le type de material déjà existant sur lequel va se baser celui que nous créons. La principale différence se trouve au niveau du rendu bien sûr. En mettant irr::video::EMT_TRANSPARENT_ADD_COLOR par exemple, on obtiendra un material translucide alors qu'avec irr::video::EMT_SOLID il sera opaque.

Les autres paramètres sont un peu trop complexes pour être abordés dans ce chapitre d'introduction, mais soyez sûrs que nous y reviendrons lors d'un prochain qui ira beaucoup plus au fond des choses. Et enfin l'utilisation de ce nouveau material ne devrait pas poser de problème particulier :

Code : C++
1
2
3
4
irr::scene::IMeshSceneNode *node =
    sceneManager->addMeshSceneNode(sceneManager->getMesh("nom_du_fichier"));

node->setMaterialType((irr::video::E_MATERIAL_TYPE)newMaterial);


La seule subtilité consiste à faire un cast étant donné que setMaterialType attend une variable de type irr::video::E_MATERIAL_TYPE et non un s32.



En cas de problème



Il est possible, bien que peu probable si vous suivez ce tutoriel, que votre carte graphique ne supporte pas ou partiellement les shaders. Pour vérifier que c'est le cas, il existe une fonction dans le driver qui permet de s'en assurer : queryFeature.

Code : C++
1
2
3
4
if (!driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1))
{
    device->getLogger()->log("Erreur : pixel shaders non supportés !");
}


La condition de ce test est de vérifier que les pixels shaders soient pris en charge. Si queryFeature renvoie false, alors on log dans la console le message en ligne 3. Pour vérifier les vertex shaders, il suffit de remplacer EVDF_PIXEL_SHADER_1_1 par EVDF_VERTEX_SHADER_1_1.
Comme l'indique son titre, ce chapitre n'est réellement qu'une introduction aux shaders sous Irrlicht. Il existe beaucoup d'autres moyen de s'en servir que celui que nous avons vu. Néanmoins je pense que c'est la plus pratique. Vous pouvez par exemple modifier les fichiers contenant les shaders sans avoir à recompiler l'application, ce qui est très agréable quand on fait des tests dessus.

Bref, vous devriez avoir de quoi vous occuper pour le moment, en attendant un prochain chapitre plus détaillé sur le sujet. ;)
Chapitre précédent Sommaire Chapitre suivant

Partager

Il n'y a pas encore de commentaire pour ce tuto.