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 | |