OpenGL : les lumières

1. On démarre

"Que la lumière soit."
Moi ;-)

Nous allons maintenant discuter des sources de lumière. C'est un sujet assez étendu et un peu plus compliqué que ce qu'on a vu précédement. Et comme il y a beaucoup à dire, nous n'allons voir dans une première partie que les généralités et les sources omnidirectionnelles (les 'omni ' pour les fans 3DS). Nous verrons les spots dans le deuxième chapitre. Je vous préviens tout de suite : il y a beaucoup plus de bla-bla que dans les autres, parce qu'il y a beaucoup à expliquer, même si certains connaissent déjà ces notions.

Comme d'habitude, reprenons notre programme de base avec la fonction Draw() réduite au minimum :

void Draw()
{

 

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

SwapBuffers(DC)

// glutSwapBuffers(); pour glut
glutPostRedisplay(); // Uniquement pour GLUT
}

 

Le meilleur objet pour illustrer l'exemple des lumières est la sphère. Nous aurions pu dessiner une sphère face par face comme pour le cube du tutorial sur les couleurs, mais bon on n'est pas maso non plus. Alors on va utiliser une procédure bien pratique de glut.h : glutSolidSphere(). Elle prend en arguments le rayon de la sphère, le nombre de parallèles, et le nombre de méridiens. Nous allons illuminer cette sphère par une source de lumière ponctuelle située au même endroit que nous. Mais pour que la sphère soit éclairée, il faut d'abord faire quelques initialisations :

void InitGL()
{

 

  glEnable(GL_DEPTH_TEST); // Active le test de profondeur
  glEnable(GL_LIGHTING); // Active l'éclairage
  glEnable(GL_LIGHT0); // Allume la lumière n°1
}

 

Le code parle de lui-même : glEnable(GL_DEPTH_TEST) active le test de profondeur, c'est-à-dire que si un point d'une face se situe derrière une autre face, il n'est pas dessiné. glEnable(GL_LIGHTING)active la gestion de lumières, et glEnable(GL_LIGHT0)allume la lumière n°1. Vous aurez bien sûr compris qu'il suffit de faire glEnable(GL_LIGHT1)pour allumer la lumière n°2 etc. Vous disposez d'au moins 8 sources de lumières, le nombre maximum (définit par la constant GL_MAX_LIGHT) étant limité par votre système. Maintenant que nous avons allumé la lumière, nous pouvons donc dessiner notre sphère :

void Draw()
{

 

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glTranslated(0,0,-5);  
glutSolidSphere(1,30,30);  

SwapBuffers(DC)

// glutSwapBuffers(); pour glut
glutPostRedisplay(); // Uniquement pour GLUT
}

 

 

2. Facteur de brillance et couleur spéculaire

Et voilà ce que ça donne. Bon OK en 256 couleurs c'est pas super mais on voit quand même bien que c'est une sphère, alors que sans lumière ça aurait fait juste un disque tout blanc et tout pas beau. Maintenant plongons-nous encore plus dans les lumières : nous avons vu dans le tutorial précédent que chaque objet était constitué d'un matériau, avec des propriétés comme la couleurs ambiente, diffuse etc... Ces paramètres définissent comment est réfléchie la lumière sur les sommets pour chaque composante (0 : composante totalement absorbée par l'objet ; 1 : composante totalement réfléchie par l'objet). Mais un matériau dispose aussi d'une autre propriété : la brillance. Ce paramètre définit la taille de la tache spéculaire. Une valeur de 0 définit une tache spéculaire la plus grande possible, et une valeur de 128 définit une tache toute petite. La valeur par défaut est de 0, donc normalement la partie illuminée devrait être de la couleur spéculaire, sans dégradé. Or ici c'est bien la couleurs diffuse que l'on voit, et non la spéculaire. Pourquoi ? Et bien tout simplement parce que la couleur spéculaire par défaut du matériau est nulle. Nous allons donc la changer. Pour cela, ce n'est pas dur, nous l'avons vu dans le tutorial précédent, il suffit d'appeler glColor() en ayant préalablement choisi GL_SPECULAR avec glColorMaterial(). Mais il y a un autre moyen de la modifier : c'est glMaterial(). En fait, glColor() est une alternative à glMaterial() pour modifier les couleurs d'un matériau. Si GL_COLOR_MATERIAL est désactivé (par défaut), on utilise glMaterial() pour changer les couleurs. S'il est activé, alors on utilisera glColor(), et glMaterial() n'aura plus d'effet sur les couleurs (par contre il en aura sur les autres paramètres des matériaux). glMaterial() a l'avantage de pouvoir être appelé entre glBegin() et glEnd() (glColor() aussi, mais pas glColorMaterial() ), mais il est quand même plus lent que glColor(), c'est pourquoi il est conseillé d'utiliser glColor() plutôt que glMaterial() pour modifier les couleurs d'un matériau.
Bon, bref, si on met la couleur spéculaire de notre sphère à (1,1,1) comme elle le devrait, on obtiendrait l'image que vous voyez à droite. On a plutôt l'impression d'être revenu au point de départ : l'objet est tout blanc, on ne distingue rien etc etc... Mais je vous l'ai dit : c'est parce que l'indice de brillance (shininess) est à 0, ce qui fait que la tache spéculaire est tellement grande qu'elle a bouffé la partie diffuse. Il suffit donc de modifier ce paramètre grâce à glMaterial() pour tout de suite avoir un aspect beaucoup plus réaliste. Si on règle ce paramètre à 100 (sa valeur peut être comprise entre 0 et 128), on obtient quelque chose dans le genre de ce que vous pouvez voir à votre gauche. C'est quand même autrement plus joli. Mais pour ma part, je ne suis pas entièrement satisfait. En effet, la source de lumière est au même endroit que la caméra, ce qui fait que toutes les partie visibles de l'objet sont éclairées. Ceci serait plus beau si la source de lumière provenait d'un point dans l'espace. Et c'est là que nous allons parler des lumières directionnelles (directionnal lights) et ponctuelles (positionnal lights).
 

3. Lumière directionnelle et lumière ponctuelle

Ici encore les adeptes de 3DS ou autres POV n'auront aucun mal à saisir ces notions, puisque ce sont les bases de tout modeleur 3D qui se respecte. Allons-y : une source de lumière ponctuelle est une source dont les rayons partent tous du centre de la source. Une source de lumière directionnelle est une source dont les rayons ont la même direction en tout point de l'espace, comme une source ponctuelle qui serait située tellement loin de la scène que les rayons pourraient être considérés comme parallèles. L'exemple parfait de lumière directionnelle dans notre monde, qui est d'ailleurs cité dans n'importe quel bouquin expliquant ce genre de trucs, est bien sûr le soleil : tous ses rayons vont dans la même direction, que l'on soit à Paris où à Québec, même si des milliers de km séparent ces 2 villes. Voilà un petit shéma histoire de vous éclaircir les idées, plutôt que de vous embrouiller avec mes explications douteuses :
 

4. Position de la source

Voilà, là je pense que vous comprenez ce que je veux dire. Nous allons donc modifier notre source de lumière pour qu'elle rayonne de biais par rapport à la sphère. Mais au fait, quels sont les paramètres par défaut de GL_LIGHT0 ? Et bien elle est directionnelle et rayonne le long de l'axe z, dans le sens des valeur négatives. Donc si on se place par exemple en (5,5,5) en regardant le point (0,0,0) avec gluLookAt(), on devrait voir une sphère éclairée de biais. Or, si vous faites le test, vous verrez exatcement la même image que lorsque vous étiez en (0,0,-5). Pourquoi donc ? Et bien en fait la position des lumières est déterminée de la même façon que pour les primitives : avec la matrice modelview. Lorsque vous définissez la postition de la lumière, elle est multipliée par la matrice modelview courante. Or la position par défaut de la lumière est définie à l'initialisation et plus du tout après. Donc s'il y a une modification de matrice après la définition de la position de la lumière, cette dernière n'est pas affectée, tout comme une primitive.
Il faut donc redéfinir la position de la lumière après les modifications de matrice, comme pour tout objet, si vous voulez avoir une lumière stationnaire. Ca a l'air bizarre et compliqué, mais en fait c'est plus pratique : vous pouvez effectuer des transformations aux sources de lumière comme vous voulez comme si elles étaient des objets normaux, et en plus si vous voulez une source de lumière relative au point de vue (comme par exemple une lampe de casque de mineur), il suffit de la définir avant toute modification de matrice. Voilà trois exemples-type pour illustrer ces longues explications :

Lumière fixe, point de vue mobile Point de vue fixe, lumière mobile Point de vue mobile, lumière relative au point de vue

Vous aurez deviné que le point jaune représente la lumière et les lignes rouges et jaunes les axes X et Z. Voyons donc maintenant le code corespondant à chacun des exemples. Je ne rajoute pas les fonctions de dessin des lignes et du point, histoire de ne conserver que l'essentiel.

 

4.1 Premier exemple : lumière fixe

Il suffit d'effectuer en premier lieu les transformations, et ensuite de placer la lumière et les objets :

double a=0;
int LightPos[4] = {0,0,3,1};
int MatSpec [4] = {1,1,1,1};
 
void Draw()
{

glMaterialiv(GL_FRONT_AND_BACK,GL_SPECULAR,MatSpec);
glMateriali(GL_FRONT_AND_BACK,GL_SHININESS,100);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0,6,6,0,0,0,0,1,0);
glRotated(a,0,1,0);
glLightiv(GL_LIGHT0,GL_POSITION,LightPos);
glutSolidSphere(1,50,50);
a=+1;

SwapBuffers(DC)

// glutSwapBuffers(); pour glut
glutPostRedisplay(); // Uniquement pour GLUT
}

On modifie d'abord les paramètres du matériau courant : couleur spéculaire blanche et braillance 100 (remarquez que comme le matériau est toujours le même, on aurait pu mettre ces lignes dans InitGL() ).
Vous voyez ensuite que l'on modifie les paramètres d'une source de lumière par glLight{if}[v](). Il suffit de lui transmettre le nom de la lumière, le nom du paramètre que l'on veut changer, et sa nouvelle valeur. Ici encore, le postfixe 'i' signifie que l'on passe des entiers, 'f' que l'on passe des float, et 'v' que le troisième paramètre est un pointeur sur les nouvelles valeurs.
Voyons maintenant de plus près ce que l'on transmet comme position : il ne s'agit pas comme on pourrait s'y attendre de trois valeurs x,y et z, mais de 4 valeurs. Les trois premières sont effectivement les coordonnées cartésiennes, mais que vient foutre ici le 4e élément ? Ben en fait c'est grâce à lui que vous allez déterminer si une source est ponctuelle ou directionnelle (c'est vrai, quoi : je vous bassine avec ça depuis tout-à-l'heure, il fallait bien qu'il se trouve quelque part). Si w (c'est son nom) est à 0 comme dans l'exemple, alors la lumière est ponctuelle. Sinon elle est directionnelle et son sens part du point position transmis vers le point (0,0,0) (donc évitez de créer une lumière directionnelle avec comme position le point (0,0,0) ). Essayez de changer la lumière de l'exemple en lumière directionnelle, pour voir.

 

4.2 Deuxième exemple : lumière mobile

Là non plus c'est pas compliqué : il suffit de ne faire tourner que la lumière et non la sphère, donc d'écrire :

double a=0;
int LightPos[4] = {0,0,3,1};
int MatSpec [4] = {1,1,1,1};
void Draw()
{

glMaterialiv(GL_FRONT_AND_BACK,GL_SPECULAR,MatSpec);
glMateriali(GL_FRONT_AND_BACK,GL_SHININESS,100);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0,6,6,0,0,0,0,1,0);
glRotated(a,0,1,0);
glLightiv(GL_LIGHT0,GL_POSITION,LightPos);
glRotated(-a,0,1,0);
glutSolidSphere(1,50,50);
a=+1;

SwapBuffers(DC)

// glutSwapBuffers(); pour glut
glutPostRedisplay(); // Uniquement pour GLUT
}

Et voilà. Au lieu de faire glLoadIdentity() puis gluLookAt() avant de dessiner la sphère, j'ai mis glRotated(-a,0,1,0), cequi annule la rotation, donc revient au même. Si vous testez cet exemple, vous ne verrez en fait aucune différence par rapport au précédent, car vous n'avez pas de repère fixe. Le résultat sera le même : on a l'impression que la lumière tourne autour de la sphère, car celle-ci est tellement parfaite qu'on ne distingue pas si elle tourne ou non. Par contre, si vous lui attribuez 5 méridiens et 5 parallèles au lieu de 50, là vous verrez une différence.

 

4.3 Troisième exemple : lumière relative au point de vue

Comme je l'ai également expliqué, il suffit de définir la position de la lumière avant toute transformation pour que la position soit toujours la même par rapport à la caméra. Donc notre fonction Draw() sera comme ceci :

double a=0;
int LightPos[4] = {0,0,0,1};
int MatSpec [4] = {1,1,1,1};
 
void Draw()
{

glMaterialiv(GL_FRONT_AND_BACK,GL_SPECULAR,MatSpec);
glMateriali(GL_FRONT_AND_BACK,GL_SHININESS,100);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glLightiv(GL_LIGHT0,GL_POSITION,LightPos);
gluLookAt(0,6,6,0,0,0,0,1,0);
glRotated(a,0,1,0);
glutSolidSphere(1,50,50);
a=+1;

SwapBuffers(DC)

// glutSwapBuffers(); pour glut
glutPostRedisplay(); // Uniquement pour GLUT
}

Ici la lumière est ponctuelle et située exactement au même endroit que l'oeil (remarquez que comme elle ne bouge jamais et ne se préoccupe pas des transformations, on aurait pu mettre glLightiv() dans InitGL() ). Si vous conservez 50 méridiens et 50 parallèles, vous aurez du mal à voir la sphère tourner. Ici encore, essayez 5 méridiens et 5 parallèles, vous y verrez plus clair.

 

5. Atténuation

Bon, vous en avez déjà pas mal vu jusqu'ici, mais il reste encore quelques points à voir. Et notemment l'atténuation. En effet, il est rare de voir une lumière ponctuelle pouvant éclairer à une distance infinie. Si vous êtes capable de voir la Grande Ourse de chez vous, je doute que votre lampe de chevet puisse éclairer aussi loin que cette étoile. OpenGL vous permet d'ajouter des facteurs d'atténuation à vos sources de lumière. Comme une lumière directionnelle est considérée comme une lumière étant à une distance infinie, l'atténuation n'est pas valable pour ce genre de sources.
Vous disposez de 3 constantes d'atténuation pour chaque lumière : GL_CONSTANT_ATTENUATION (appelons-la Kc), GL_LINEAR_ATTENUATION (Kl) et GL_QUADRATIC_ATTENUATION (Kq). Si d est la distance d'un point à la source de lumière, la quantité de chaque composante de la lumière qui arrive à ce point est multipliée par : 1 / (Kc + Kl*d + Kq*d²). Pour changer les constantes d'atténuation il suffit d'appeler glLight(). Par exemple,
glLightf(GL_LIGHT0,GL_QUADRATIC_ATTENUATION,.01f);
Mettra à 0.01 la constante d'atténuation Kq de la lumière GL_LIGHT0.
Par défaut, on a Kc=1, Kl=0 et Kq=0, donc la lumière n'est pas atténuée. Si par exemple vous choisissez pour Kc, Kl et Kq des valeurs respective de 0.1, 0 et 0.01, vous pourrez obtenir quelque chose de ce genre :

Chaque sphère a un rayon de 0.4 et l'espace entre 2 centres est de 1. Notez qu'il n'est pas possible d'atténuer une seuke composante (rouge, bleue ou verte) et que toutes les couleurs de la lumière (ambiente, diffuse et spéculaire) sont atténuées.

 

6. Couleur

Tiens, justement, qu'est-ce que c'est que ça, "les couleurs de la lumière" ? On connaissait déjà les couleurs des matériaux, mais pas ça. Et bien, comme vous auriez pu vous en douter, chaque lumière a ses propres couleurs. En effet, un girophare est rouge ou bleu, une lampe à incandescence émet une lumière plutôt jaune, le soleil couchant lance ses ultimes rayons rouge-orangés sur la verte colline... Bref, y'a pas que les objets qui ont des couleurs : les lumières aussi ont droit à la différence. Et comme les objets, elles possèdent chacune une couleur ambiente, une diffuse et une spéculaire. Prenons l'exemple d'un plafonnier dans une pièce : les rayons rebondissent sur les murs et les objet recoivent une lumière dont la provenance est impossible à déterminer tant la lumière a été dispersée : c'est la composante ambiente de la lumière. La lumière diffuse est ce qu'on pourrait appeler la couleur réelle de la source : c'est la couleur des rayons qui viennent directement frapper l'objet. La couleur spéculaire est la couleur de la tache spéculaire que produira la source. Pour un effet réaliste, elle doit être identique à la couleur diffuse.
Ces composantes ne suppriment bien sûr pas les couleurs des matériaux : lors du calcul de la couleur des sommets, les couleurs de la lumière et du matériau sont mélangées. Par exemple, une lumière cyan (0,1,1) éclairant une sphère jaune (1,1,0) donnera une sphère verte (0,1,0). Pour modifier la couleur d'une source de lumière, il faut bien sûr appeler glLight(). Par exemple, pour mettre la lumière diffuse de la lumière n°1 à (.5,.5,1), il suffit de faire
float LightDif[4] = {.5f,.5f,1.f,1.f};
glLightfv(GL_LIGHT0,GL_DIFFUSE,LightDif);
Pour ce qui est de la 4e composante, il s'agit de la composante alpha. Ne vous en préoccupez pas, elle ne sert à rien pour l'instant (vous pouvez la mettre à 0 si ça vous amuse, ça ne changera rien).

 

Conclusion

Et ben voilà, je crois qu'on a fait le tour des sources ponctuelles (et directionnelles, qui en sont des dérivées). On a tout de même vu dans ce tutorial comment allumer une lumière, changer sa couleur, sa position, ses facteurs d'atténuation, et si elle est ponctuelle ou directionnelle. Entrainez-vous bien avec les exemples que je vous ai donné (surtout au niveau de la position). On continuera la prochaine fois avec les spots.

Antoche
 


← Les couleurs↑ Tutoriaux OpenGL ↑Les spots →