1. Introduction

Puisque OpenSceneGraph est une surcouche d'OpenGL, la gestion de la lumière sous celui-ci se base essentiellement sur OpenGL et son attribut GL_LIGHT. La plupart des classes et des fonctions disponibles nous permettent en fait de créer et de modifier des lumières en influant sur les attributs liés à GL_LIGHT.

Dans un premier temps il est donc essentiel de comprendre comment fonctionne la lumière en OpenGL pour s'en servir avec OSG. Une fois ces notions acquises la création et la modification de lumières devient très aisée.

2. OpenGL et la lumière

OpenGL permet de gérer jusqu'à huit sources de lumières différentes en même temps. Ces huit lumières sont désignées par l'attribut GL_LIGHTi ou i est le numéro de la lumière.

Lorsqu'une lumière est allumée la couleur d'un vertex dépend de sa propre couleur mais aussi des quatre attributs de la lumière.

Ces attributs sont les suivants :

  • composante diffuse  : le rayon de lumière vient de la source et frappe la surface d'un objet. La couleur diffuse dépend de la position du sommet par rapport à la source de lumière. C'est en influant sur cet attribut qu'on règle la couleur de la lumière en elle-même ;
  • composante ambiante  : c'est la couleur de la lumière qui est renvoyée par l'environnement après avoir été renvoyée par les objets de la scène, cette lumière semble venir de toutes les directions.
    Image non disponible
    Schéma permettant de visualiser les composantes "ambiante" et "diffuse" de la lumière.
  • composante spéculaire  : contrôle l'effet spéculaire ;
    Image non disponible
  • lumière émise  : les objets eux-mêmes peuvent émettre leur lumière propre, qui augmentera leur intensité, mais n'influera pas sur les autres objets de la scène ;
  • type de lumière  : en plus de ces quatre attributs, la lumière peut être de deux types : ponctuelle ou directionnelle. Une lumière ponctuelle émet dans toutes les directions à partir de sa position, alors qu'une lumière directionnelle se propage dans une direction donnée comme si elle venait de l'infini en ayant tous ses rayons parallèles.
    Par exemple, la lumière du Soleil sur Terre est souvent considérée comme une lumière directionnelle, ses rayons semblent être parallèles et venir de l'infini.
  • Image non disponible
    Les deux types de sources de lumière

    atténuation de la lumière  : il possible de faire en sorte que la lumière s'atténue en fonction de la distance à la source. En effet dans la réalité plus un objet est loin d'une source lumineuse moins celle-ci a d'effet sur l'objet. On va donc pouvoir régler ce paramètre.


    OpenGL met à notre disposition trois types d'atténuations réglables à souhait :
    • l'atténuation constante ;
    • l'atténuation linéaire ;
    • l'atténuation quadratique.

    On verra plus en détail leur effet dans le programme d'exemple.
  • lumière ponctuelle orientée  : on peut noter qu'il est aussi possible de modifier une source de lumière ponctuelle pour la faire émettre seulement dans une direction donnée. On appelle cela un « spot ».
    Il suffit simplement de lui donner une direction, en OpenGL « GL_SPOT_DIRECTION » et un angle « GL_SPOT_CUTOFF ».
Image non disponible
Schéma permettant de visualiser les caractéristiques d'un « spot »

On a donc vu les principales caractéristiques d'une lumière en OpenGL. On va maintenant revenir à notre sujet principal et voir comment OpenSceneGraph nous permet de les contrôler.

3. Avec OpenSceneGraph

Comme on l'a déjà dit, une fois les principes OpenGL assimilés l'utilisation de lumières dans OSG devient très simple.

Pour créer une lumière il suffit d'avoir un nœud « Group » dans lequel la mettre. On crée ensuite un objet Light, dont on va pouvoir personnaliser tous les attributs venant d'OpenGL (diffus, ambiant, etc.). Enfin on associe cet objet Light à un objet LightSource qu'on va ajouter à notre nœud « Group ».

4. Classes utilisées

On utilisera ici, en plus des classes et fonctions liées à la lumière, certaines classes basiques d'OSG pour, par exemple, afficher des primitives géométriques.

4-A. 2.1. Classes non spécifiques à la gestion des lumières

osg::Group  : crée un container dans lequel on va mettre tous les objets de notre scène.

osgViewer::Viewer  : objet permettant la visualisation de la scène. Il est important de préciser que la classe Viewer active par défaut la gestion des lumières OpenGL et crée une première lumière GL_Light0. On peut donc soit garder GL_Light0 telle qu'elle est créée par OSG, soit la remplacer par une lumière de notre choix ou bien l'éteindre.

osg::Geode  : Geode pour « Geometry Node », la classe Geode est un container pour tout objet dont on voudrait effectuer le rendu. Via sa fonction addDrawable on peut lui ajouter la primitive géométrique de son choix sous la forme d'un objet ShapeDrawable. Ici on a simplement utilisé des sphères.

osg::ShapeDrawable  : cette classe permet d'effectuer un rendu simple et rapide d'un objet dérivant de la classe Shape (la classe osg::Sphere, par exemple). La documentation d'OSG nous dit qu'elle est surtout faite pour des tests ou pour un rendu rapide et qu'on devrait normalement lui préférer une méthode plus efficace.

osg::Sphere  : la classe Sphère dérive de la classe Shape, c'est une primitive 3d dont le rendu peut être facilement effectué avec les classes ci-dessus. On n'aura pas à spécifier ses normales.

osg ::Material  : permet de gérer les matériaux appliqués sur un objet.

osg::StateSet  : permet d'ajouter et de modifier les attributs d'une racine du Graphe de scène, par exemple ajouter des matériaux, allumer ou éteindre des lumières etc.

4-B. 2.2. Classes spécifiques à la gestion des lumières

osg::LightSource  : cette classe est un nœud du graph qui permet de définir une lumière dans la scène, elle doit être ajoutée au Group et prend en paramètre un objet osg::Light.

osg::Light  : c‘est le cœur de la gestion des lumières sous OSG. Elle permet de créer une lumière et d'en spécifier tous les attributs.

5. Mise en Pratique

Voici un exemple de l'utilisation de lumières dans un programme simple avec OSG. On va simplement créer des primitives géométriques sur lesquelles on va pouvoir observer l'effet d'une lumière créée avec la classe Light.

Voici notre programme principal. Notre scène est créée entièrement dans la fonction « CreateScene ».

 
Sélectionnez
int main() 
{ 
     // Construction du viewer pour visualiser la scène 
     osgViewer::Viewer *viewer = new osgViewer::Viewer; 

     // On remplit la couleur de fond avec du noir 
     viewer->getCamera()->setClearColor(osg::Vec4(0.0,0.0,0.0,0.0)); 

     // Création de la fenêtre a la position 32 32 et d'une taille 512*512 
     viewer->setUpViewInWindow( 32, 32, 512, 512 ); 

     // On ajoute les données avec la fonction CreateScene qui renvoie un Noeud (en fait ici le nœud racine) 
     viewer->setSceneData( CreateScene() ); 

     // Lance la boucle 
     return viewer->run(); 
}

Fonction CreateScene :

ici on va créer notre ou nos lumières et les objets de notre scène.

 
Sélectionnez
osg::Node* CreateScene() 
{ 
    osg::Group* pGroup = new osg::Group; 
    /****************** light source 1 *****************/ 
    // osg:Light nous permet de donner à notre lumière ses caractéristiques 
    osg::Light* pLight = new osg::Light; 
    pLight->setLightNum( 1 );// ici cette lumière sera GL_LIGHT1 
    pLight->setAmbient( osg::Vec4d(1.0, 1.0, 1.0, 0.0) ); 
    pLight->setDiffuse( osg::Vec4(1.0f, 1.0f, 1.0f, 0.0f) ); 
    pLight->setSpecular(osg::Vec4(1.0f,1.0f,1.0f,1.0f)); 
    pLight->setPosition(osg::Vec4(2.0f,0.0f,2.0f,1.0f)); 

On vient ici de créer la lumière numéro 1 (GL_LIGHT1). C'est une lumière blanche (couleur diffuse) et sa composante ambiante est également blanche. Elle est située à la position 2, 0 ,2.

On remarque ici que la position est un Vec4 et non un Vec3. Le 4e paramètre w sert en fait ici, comme en OpenGL, à dire si notre lumière est ponctuelle ou directionnelle.

Si w=0 notre lumière est directionnelle, sinon elle est ponctuelle. On a donc bien ici une lumière ponctuelle située en 2,0,2.

Si le denier paramètre avait été 0, les coordonnées auraient été celles du vecteur directionnel.

La fonction setSpecular permet quant à elle de fixer la couleur de l'effet de spéculaire.

Voyons maintenant les paramètres liés au Spot :

 
Sélectionnez
pLight->setDirection(osg::Vec3(-1.0f,0.0f,-1.0f)); // direction dans le cas d'un spot 
pLight->setSpotCutoff(45.0); // angle d'ouverture du spot 

Ces deux fonctions concernent le cas des lumières Spot. Ici on peut fixer la direction du Spot et son angle d'ouverture en degrés (voir schéma plus haut).

Nul besoin de définir que notre lumière est un spot pour qu'elle en devienne un, il suffit simplement de définir la Direction et le « SpotCutoff ».

Puis les paramètres liés à l'atténuation :

 
Sélectionnez
pLight->setConstantAttenuation(0.5); 
pLight->setLinearAttenuation(0.2); 
pLight->setQuadraticAttenuation(0.02); 

L'atténuation constante est une simple atténuation globale de la lumière. Par défaut elle est à 1. En la faisant varier, on fait varier l'intensité de la lumière, mais on ne la fera pas porter plus ou moins loin.

Les atténuations Linéaires et Quadratiques se font en fonction de la distance. Plus ces paramètres sont élevés, moins la lumière va porter loin.

On peut, par exemple, fixer une atténuation constante faible, mais une atténuation linéaire ou quadratique élevée. Cela aura pour effet d'avoir une forte lumière concentrée sur une petite zone. On peut donc jouer avec ces paramètres pour obtenir le résultat voulu.

Enfin la création de notre source de lumière à laquelle on attache la lumière que l'on vient de créer.

 
Sélectionnez
osg::LightSource* pLightSource = new osg::LightSource; 
pLightSource->setLight( pLight ); 
pGroup->addChild( pLightSource ); 

Voilà pour le paramétrage de notre lumière, on voit donc bien que cela est très lié à OpenGL.

Maintenant il suffit simplement de régler les attributs. Étant donné que le viewer donne une valeur par défaut à GL_LIGHT0, nous allons éteindre celle-ci. Si toutefois celle-ci vous convient, vous pouvez toujours travailler avec.

 
Sélectionnez
osg::StateSet* state = pGroup->getOrCreateStateSet(); 

state->setMode( GL_LIGHT0, osg::StateAttribute::OFF ); 
state->setMode( GL_LIGHT1, osg::StateAttribute::ON ); 

On éteint donc la lumière 0 et on allume notre propre lumière 1. Si nécessaire on peut aussi écrire par-dessus la lumière 0 pour s'en servir comme on veut, c'est une lumière comme une autre.

Il peut être intéressant d'appliquer un matériau à nos primitives, notamment pour mieux régler l'effet de spéculaire. On va donc associer un matériau à notre Group qui s'appliquera ainsi à toutes nos sphères.

 
Sélectionnez
osg::ref_ptr<osg::Material> mat = new osg::Material; 
mat->setDiffuse( osg::Material::FRONT, 
osg::Vec4( .0f, 1.0f, .0f, 1.f ) ); 
mat->setSpecular( osg::Material::FRONT, 
osg::Vec4( 1.f, 1.f, 1.f, 0.f ) ); 
mat->setShininess( osg::Material::FRONT, 96.f ); 
state->setAttribute( mat.get() ); 

On ne s'étendra pas trop sur les matériaux, ceux-ci n'étant pas le sujet principal. On peut régler sa couleur (diffuse), ici le matériau sera vert, la couleur de sa spéculaire, et la brillance « Shininess » de cette même spéculaire.

Enfin pour visualiser notre lumière on a besoin d'objets, on va donc créer quelques sphères dans ce même groupe :

 
Sélectionnez
osg::Geode* pGeode = new osg::Geode(); 

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::Sphere(osg::Vec3(5.0f,0.0f,0.0f),0.5f) ) ); 

pGeode->addDrawable( new osg::ShapeDrawable( new 
osg::Sphere(osg::Vec3(0.0f,00.0f,5.0f),0.5f) ) ); 

pGeode->addDrawable( new osg::ShapeDrawable( new 
osg::Sphere(osg::Vec3(0.0f,5.0f,0.0f),0.5f) ) ); 

pGroup->addChild(pGeode); 

On peut maintenant visualiser le résultat, tout d'abord sans régler de Spot ni d'atténuation. On a une seule lumière blanche ponctuelle en 2,0,2 et un matériau vert pour les sphères.

Image non disponible

La lumière semble bien se trouver à peu prés au milieu, sur le plan formé par les trois sphères du premier plan, donc en 2,0,2. On a un effet de spéculaire blanc.

Image non disponible

Ici la lumière est maintenant directionnelle, sa source se trouve à l'infini dans la direction du vecteur 2,0,2.

Donc, elle se propage en fait dans la direction inverse -2,0,-2 et frappe les sphères sur le dessus.

Image non disponible

Ici on passe en mode Spot avec une direction de 1,0,0 et une ouverture de 45 degrés, ainsi seule la sphère de droite est illuminée, les autres restent dans l'ombre.

Image non disponible

Ici on a mis une atténuation linaire de 0.2 et on a reculé un peu la sphère du haut, on voit que l'intensité de la lumière diminue avec la distance.

Avec l'atténuation constante, l'intensité de la lumière aurait diminué d'un même coefficient partout.

On peut ajouter des sphères pour mieux voir l'effet des spots.

Image non disponible

Ici pas de Spot, une lumière ponctuelle en 2,0,2, faisons-en un Spot :

Image non disponible

Spot de direction 1,0,1 avec un SpotCutoff de 45°.

Voila donc pour une mise en pratique simple, tout est une question de paramétrage, de couleurs, d'atténuation à régler au bon vouloir de l'utilisateur.

6. Aller plus loin

Les matériaux n'ont été utilisés ici qu'à titre d'exemple simple, il serait judicieux de les étudier plus en profondeur car ils seront toujours liés à la lumière d'une façon ou d'une autre. Comme pour la lumière, c'est une encapsulation dans différentes classes des outils d'OpenGL.

Un autre thème lié à la lumière est celui des ombres, qui ne sont pas créées automatiquement avec les lumières mais calculées à part avec d'autres classes d'OpenSceneGraph, qu'il peut être intéressant d'étudier elles aussi.

Il est aussi possible de refaire de sa propre manière le système d'éclairage et d'ombrage en utilisant des Shaders, qui sont manipulables facilement avec OSG. L'avantage peut être par exemple de pouvoir manipuler autant de sources de lumières que l'on souhaite, et ainsi ne plus être limité par leurs nombres réduits.

7. Remerciements

Merci jacques_jean pour les corrections.