Les Shaders avec OpenSceneGraph

Alys Beauvarlet, Thierry Bonnefoy, Bertrand Rix, Guillaume Saby

Démonstration de l'utlisation des shaders dans OpenSceneGraph.

Commentez

- Titre : Les Shaders avec OpenSceneGraph

- Auteur : Alys Beauvarlet, Thierry Bonnefoy, Bertrand Rix, Guillaume Saby

- Parution : 25 juin 2012

- Mise à jour : 27 janvier 2013

- Public visé :

- Licence : Copyright ® 2013 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- 1. Introduction

- 1-A. Qu'est-ce qu'un shader ?

- 2. Classes utilisées

- 3. Mise en pratique

- 3-A. Comment charger un shader et l'activer ?

- 3-B. Comment passer un ou plusieurs paramètres à un shader ?

- 3-C. Le passage de texture

- 3-D. Le programme de test

- 4. Pour aller plus loin

- 5. Remerciements

1 - Introduction

Ce tutoriel a pour but d'explorer le mécanisme d'utilisation d'un shader avec OpenSceneGraph.

Celui-ci possède une interface simple sous la forme de sa classe osg::shader.

1-A - Qu'est-ce qu'un shader ?

Ceci n'étant pas le but du tutoriel, mais pouvant quand même être utile, nous allons donner une rapide définition de ce qu'est un shader.

Un shader est tout d'abord un programme, écrit dans un certain langage. Ici nous utiliserons le GLSL pour « OpenGL Shading Language ».

Les shaders servent en fait à programmer le pipeline de rendu de la carte graphique pour lui faire faire ce que l'on veut, allégeant ainsi la charge de travail du processeur.

Il existe plusieurs types de shaders :

- les vertex shaders, qui permettent de modifier les attributs des différents sommets d'une scène ;

- les pixels shaders, aussi appelés fragment shaders avec lesquels on va pouvoir modifier le rendu de chaque pixel qui s'affiche à l'écran.

Osg nous permet de charger tous les types de shaders très simplement, et de les activer/ désactiver comme on le souhaite.

Ces différents shaders sont ensuite incorporés dans ce qu'on appelle un « program », l'exécutable qui va permettre de faire tourner ces shaders.

2 - Classes utilisées

On va utiliser ici un certain nombre de classes basiques n'ayant pas de rapport direct avec les shaders, on en donnera juste la liste. Pour leurs descriptions, voir les autres tutoriels comme celui sur la lumière qui en utilise un grand nombre en commun.

osg::Group , osgViewer::Viewer , osg::Geode, osg::ShapeDrawable, osg::Sphere (et autres primitives).

Ainsi que plusieurs classes particulières pour les shaders :

- osg::shader ;

- osg::Program ;

- osg ::Uniform.

3 - Mise en pratique

Osg peut charger des shaders contenus dans des fichiers, mais aussi dans une simple chaîne de caractères. Étant donné la simplicité des shaders utilisés pour ce tutoriel, nous allons simplement les stocker dans une chaîne de caractères, en tant que variable globale du programme.

Nous allons donc utiliser les deux types de shaders principaux : un vertex shader et un fragment shader.

Le vertex shader appliquera un effet d'aplatissement des formes 3D du programme, en annulant une des composantes de chaque sommet afin que la forme 3D soit aplatie sur un plan.

Le fragment shader changera simplement la couleur de ces formes par un couleur qu'on lui passera via une variable de type Uniform.

Voici nos deux shaders stockés dans deux chaînes de caractères :

static const char* vertSource =
"uniform bool plat;"
"void main(void)"
"{"
"
vec4 v = vec4(gl_Vertex);"
"
if(plat)"
"
v.y = 0.0;"
"
gl_Position = gl_ModelViewProjectionMatrix*v;"
"}";

static const char* fragSource =
"uniform vec4 color;"
"void main()"
"{"
"
gl_FragColor = color;"
"}";

3-A - Comment charger un shader et l'activer ?

Commençons d'abord par le code minimal de la fonction principale :

création d'un viewer et installation de la fenêtre, la variable pGeode est un nœud d'objet 3d que nous allons construire et auquel nous allons attacher un shader.

// Construction du viewer
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;

// Création d'une fenêtre 512x512 à 32 32
viewer->setUpViewInWindow( 32, 32, 512, 512 );

/*
Code lié aux shaders et aux objets de la scène
*/

viewer->setSceneData(pGeode);
// exécution de la boucle principale
return viewer->run();

Pour charger un shader on construit un objet de la classe osg ::shader. Le constructeur de cette classe prend en paramètre le type de shader (vertex ou fragment).

Après avoir construit l'objet on appelle la fonction setShaderSource sur celui-ci avec comme paramètre la chaîne de caractères de notre shader.

osg::ref_ptr<osg::Shader> f = new osg::Shader( osg::Shader::FRAGMENT );
f->setShaderSource( fragSource );

osg::ref_ptr<osg::Shader> v = new osg::Shader( osg::Shader::VERTEX );
v->setShaderSource( vertSource );

Ici nos shaders sont chargés et construits mais pas encore utilisables. Pour cela il faut les insérer dans un « program ».

On peut aussi les charger à partir d'un fichier externe au « program ». On utilise alors la fonction loadShaderSourceFromFile de la classe osg ::shader :

v->loadShaderSourceFromFile("monShader.vert");

On crée donc un objet de la classe osg ::progam auquel on peut donner un nom (utile pour le debug) et ajouter des shaders. Nous allons donc lui ajouter les deux shaders que nous venons de construire.

osg::ref_ptr<osg::Program> rPrg = new osg::Program;
rPrg->setName( "myShader" );
rPrg->addShader( f.get() );
rPrg->addShader( v.get() );

Nos shaders sont maintenant utilisables, il ne nous reste donc plus qu'à les activer. Mais il faut d'abord avoir quelque chose sur quoi les activer. Nous allons donc créer un ensemble d'objets 3D sur lesquels nos shaders vont agir :

/* Construction de la scène */
osg::Geode* pGeode = new osg::Geode();

// On crée quelques formes simples
pGeode->addDrawable( new osg::ShapeDrawable(
new osg::Sphere(osg::Vec3(0.0f,0.0f,0.0f),0.5f) ) );

pGeode->addDrawable( new osg::ShapeDrawable(
new osg::Box(osg::Vec3(2.0f,0.0f,0.0f),2.0f) ) );

pGeode->addDrawable( new osg::ShapeDrawable(
new osg::Cone(osg::Vec3(4.0f,0.0f,0.0f),0.5f,3.0f) ) );

pGeode->addDrawable( new osg::ShapeDrawable(
new osg::Cylinder(osg::Vec3(6.0f,0.0f,0.5f),0.5f,3.0f) ) );

pGeode->addDrawable( new osg::ShapeDrawable(
new osg::Capsule(osg::Vec3(8.0f,0.0f,1.0f),0.5f,3.0f) ) );

Nous voila donc avec une sphère, un cube, un cône, un cylindre et une capsule que l'on va ajouter à notre scène et sur lesquels ont va pouvoir activer nos shaders.

Pour activer ceux-ci, il faut passer par le membre StateSet, donc nous avons déjà présenté les fonctions dans plusieurs tutoriels précédents.

// on active le shader
pGeode->getOrCreateStateSet()->setAttributeAndModes( rPrg.get(), osg::StateAttribute::ON );

Il est nécessaire d'activer le « program » au moins une fois, sinon celui-ci n'est pas pris en compte. Dans notre programme de test, sa désactivation via osg ::StateAttribute ::OFF semble n'avoir aucun effet.

Ici nos shaders n'auront donc d'effet que sur les objets 3D contenus dans pGeode et uniquement dans ceux-là.

3-B - Comment passer un ou plusieurs paramètres à un shader ?

Comme on peut le voir dans le code des shaders ceux-ci possèdent des variables de type « uniform ». Ce sont elles qui permettent de faire communiquer le programme principal et le shader.

Les valeurs de ces variables se modifient grâce encore une fois à la classe StateSet et à sa fonction addUniform, qui prend en paramètre un objet de type osg ::Uniform. À la construction de cet objet Uniform on doit préciser le nom de la variable uniform correspondant dans le shader (variable à laquelle la valeur va être attribuée) ainsi que la variable qui va être envoyée. Cette variable peut être d'un grand nombre de type (voir la documentation Osg qui contient toutes les surcharges de constructeurs).

On peut ainsi passer des types simples : int, bool, float, des textures (sampler) mais aussi des types plus complexes d'Osg comme des vec2, vec3 ou bien des objets de type Matrix.

Geode->getOrCreateStateSet()->addUniform( new osg::Uniform("color",osg::Vec4(1.0,1.0,0.0,1.0)));

Donc, le code ci-dessus dit : « Donne la valeur 1.0, 1.0, 0.0, 1.0 à la variable Uniform nommée color du shader attaché à pGeode ».

Il doit bien évidement n'y avoir qu'une seule valeur Uniform de ce nom dans tous les shaders attachés à pGeode. Le type de la variable passée doit être du même type dans le shader. Ici on passe un osg::Vec4 la variable Uniform du shader est aussi un vec4, Osg fait la conversion tout seul.

On peut ensuite récupérer cette variable ou bien la modifier grâce aux fonctions de la classe osg ::Uniform.

3-C - Le passage de texture

Le passage de texture à un shader sous Osg utilise lui aussi la classe osg::Uniform avec en plus les possibilités de chargement de Textures d'Osg.

On crée d'abord un objet de type osg::Texture que l'on paramètre comme on le veut (image à charger, filtre, ect.). On attache cette texture à un StateSet en lui donnant un identifiant comme si on voulait l'utiliser normalement. Puis on crée un objet Uniform auquel on passe le nom du sampler dans le shader et l'identifiant de la texture et le reste fonctionne comme précédemment.

3-D - Le programme de test

Le programme de test construit pour ce tutoriel propose le chargement d'un vertex shader et d'un pixel shader et le passage de paramètres uniform à ceux-ci.

On a en plus défini une gestion des évènements clavier pour pouvoir interagir en temps réel avec nos shaders. Pour plus de détails sur les évènements clavier, voir le tutoriel qui leur est dédié. Cette gestion d'évènements permet via les touches fléchées, de changer la couleur des objets 3D affichés, mais aussi de les rendre plats avec la touche Alt gauche. Toutes ces transformations sont bien sûr faites avec les shaders.

On peut changer la couleur des primitives grâce aux touches fléchées

Le code source est disponible avec le tutoriel pour plus de détails bien qu'une grande partie du code ait été décrite précédemment.

Mais aussi changer la géométrie pour rendre les objets plats

4 - Pour aller plus loin

Pour plus d'informations sur les classes utilisées ici, voir la documentation d'OSG :

- osg::shader ;

- osg:program ;

- osg::Uniform.

5 - Remerciements

Merci jacques_jean pour les corrections.