Couleurs et lumières pour OpenGL sous Windows
Les bases de la couleur et des sources de lumière dans la nature
Ce chapitre peut
être difficile à appréhender. D'où les rappels (minimales)
suivants:
- La couleur blanche est composée de toutes les couleurs (tout le spectre de la lumière).
- La couleur noire, c'est l'absence de couleur. Le noir n'est donc pas une couleur !
- Les sources primaires sont les corps qui produisent de la lumière (le soleil par exemple).
- Les objets diffusants reçoivent de la lumière et la renvoient
partiellement ou entièrement dans toutes les directions (la lune).
Les objets absorbent donc toute ou partie de la lumière reçue. La lumière non absorbée est donc diffusée et donne "la couleur" de l'objet.
D'autres explications viendront ensuite dans les différentes étapes de ce chapitre...
La couleur ambiante sous OpenGL
Vous avez déjà vu que pour gérer la couleur, il fallait tout d'abord l'activer par un glEnable(GL_COLOR_MATERIAL); (Voir chapitre sur la 2D)
Pour modifier la couleur d'un vertex, vous utilisiez la fonction glColor() entre glBegin() et glEnd().
Ceci permettant d'affecter une couleur à un vertex...
Nous allons mettre un terme sur cette couleur, il s'agit de la couleur ambiante.
Il faut en fait considérer pour le moment que vous avez une source de lumière qui par défaut est blanche et dont les constantes RVB sont au maximum de leur valeur.
Cette lumière est elle-même une lumière ambiante, ce qui signifie:
- Il n'est plus possible de déterminer la source des rayons lumineux car ces rayons rebondissent d'un peu de partout, Ce qui implique que toutes les faces de votre objet sont éclairées de la même manière.
- Comme la lumière est blanche, les objets, suivant leur capacité d'absorption du spectre de la lumière, peuvent "refléter" toutes sortes de couleurs du spectre.
La couleur dans la nature
Mais dans la nature, ce n'est pas tout à fait comme cela que cela se passe:
Il y a une source lumineuse plus ou moins lointaine (par exemple le soleil). Les objets apparaissent alors avec des couleurs plus ou moins assombries ou éclairées suivant qu'ils sont exposés directement ou non à la source de lumière. Il s'agit en fait de la couleur diffuse.
Sous OpenGL, il faudra modifier le type de la couleur de l'objet en type "diffuse" pour pouvoir l'utiliser avec ce type de lumière. Ainsi, lorsque vous indiquerez la couleur de l'objet, vous allez en fait indiquer la couleur qui ne sera pas absorbée par votre objet. Résultat, si la source de lumière émet un spectre de lumière ne comprenant pas la couleur diffuse de votre objet, l'objet paraîtra noir. Ce qui sous-entend que vous allez certainement pouvoir indiquer à votre source de lumière la couleur à émettre (Heureusement).
Dans la nature, la lumière non absorbée va rebondir sur les objets. Ce qui veut dire qu'un autre objet proche devrait subir aussi un éclairage indirect. Sous OpenGL, ce cas n'est pas traité, la lumière rebondi sur un objet et c'est tout, cela s'arrête là. C'est que cela consomme du temps CPU tout cela !
De même, un objet devant une source de lumière est un obstacle à la lumière, cela se traduit normalement par l'apparition d'une ombre. Cependant, OpenGL ne gère pas les ombres. Il faudra donc le faire soit même !
Modifier le comportement d'un objet et indiquer type de couleur qui sera utiliser par défaut pour les modifications de couleurs
Pour le moment, vous avez affecter une couleur ambiante à vos objets (c'est l'option par défaut). Mais aussi sur la diffuse, mais là, vous ne le saviez pas !
Pour modifier les composantes d'une couleur, vous utiliserez toujours la fonction glColor().
Par contre, pour indiquer le type de couleur (Ambiante, diffuse et même spéculaire) que vous voulez modifier, vous utiliserez la fonction
glColorMaterial(face, mode);
où face permet d'indiquer la face affectée. face peut prendre les valeurs suivantes: GL_FRONT, GL_BACK, et GL_FRONT_AND_BACK,
mode, le type de couleur:GL_EMISSION, GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, et GL_AMBIENT_AND_DIFFUSE,
Par défaut sous OpenGL, les valeurs sont positionnées sur GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
C'est pour cette raison que vous n'aviez pas à utiliser cette fonction jusqu'ici...
Cette fonction n'est pas à utiliser entre glBegin et glEnd. Sinon, elle retournera GL_INVALID_OPERATION...
Enfin, elle retournera aussi GL_INVALID_ENUM si la valeur de face ou de mode est incorrecte.
Pour rappel: Lorsque l'on indique une couleur à glColor() (autre que ambiante), on indique en fait que l'objet n'absorbe pas une certaine sorte de couleur. Evidemment, si une source de lumière n'est pas composée de cette couleur, l'objet paraîtra noir !
Les sources de lumières
Nous allons gérer les sources de lumières. Il sera possible d'indiquer le type, la position, les couleurs...
Activer/désactiver l'éclairage:
Par défaut, les sources de lumière ne sont pas utilisables. Pour utiliser ces sources de lumière, il fauudra les activer en utilisant glEnable(GL_LIGHTING);
Evidemment, pour ne plus les utiliser, vous ferez glDisable(GL_LIGHTING);
Ensuite, il faut savoir qu'OpenGL propose au minimum 8 sources de lumière. Si vous voulez connaître le nombre maximum de source de lumière, vous utiliserez la constante GL_MAX_LIGHT.
Type de lumière et position de la source:
Il existe deux types de lumières, la lumière ponctuelle et la lumière directionnelle.
La lumière directionnelle:
Pour comprendre, imaginez une étoile très lointaine. Celle-ci est tellement lointaine que si vous pouviez prendre ses rayons lumineux qui vous parviennent, ces rayons sembleraient tous être parallèles entre eux.
La lumière ponctuelle:
Cette fois-ci, tous les rayons lumineux semblent former un disque en 2D, une sphère en 3D, dont le centre est la source de lumière. La lumière partira donc dans tous les sens. C'est le cas d'une ampoule par exemple.
Une source de
lumière n'a pas de volume, c'est un simple point sous OpenGL.
Concernant
la position des sources de lumière, elle est faite à partir de
la matrice ModelView. Ce qui implique que toutes transformations
de cette matrice va transformer les positions de vos sources de lumière
de la même manière que pour vos primitives.
Toujours à
propos de la position d'une source, dans le cas d'une lumière ponctuelle,
la position va indiquer la position de votre éclairage. Sinon, dans le
cas d'une lumière directionnelle, la source de lumière est à
l'infinie, et la position indique en fait un vecteur donnant ainsi le sens de
la lumière.
Pour affecter le type de lumière ainsi que sa position, vous utiliserez la fonction glLight()
Et plus exactement:
void glLightfv(
GLenum light,
GLenum pname,
const GLfloat *params
);
ou
void glLightiv(
GLenum light,
GLenum pname,
const GLint *params
);
En entrée:
ligth étant la lumière à modifier (de GL_LIGHT0 à GL_LIGHTn où n est un nombre variant de 0 à GL_MAX_LIGHT-1)
pname qui prendra ici le nom de GL_POSITION
et enfin params qui est un pointeur sur un tableau de 4 éléments contenant les positions x, y et z de votre source de lumière et w qui indiquera le type d'éclairage. w vaut 0 pour une lumière directionnelle, toutes autres valeurs pour la ponctuelle. Ce tableau devra être de type flottant si vous utilisez glLightfv(), et de type entier si vous utilisez glLightiv() (cf. fonctions polyformes).
En sortie:
GL_INVALID_ENUM si ligth est une lumière inexistante.
GL_INVALID_OPERATION si vous utilisez la fonction entre glBegin et glEnd.
A savoir, par défaut, OpenGL utilise les valeurs suivantes : (0,0,1,0)
Paramétrer la couleur à émettre suivant le type de couleur (ambiant, diffuse ou spéculaire) à utiliser:
Pour paramétrer les lumières, nous utiliserons encore la fonction glLigth() qui se décline en plusieurs sous-fonctions suivant les paramètres à modifier et leur typage.
Les sources de lumières peuvent avoir une couleur sous la forme rouge-vert-bleu-alpha (RGBA). Les sources de lumières peuvent aussi traiter les couleurs ambiante, diffuse et spéculaire.
En ce qui concerne les couleurs, nous utiliserons la fonction
void glLightfv(
GLenum light,
GLenum pname,
const GLfloat *params
);
ou
void glLightiv(
GLenum light,
GLenum pname,
const GLint *params
);
Avec en entrée:
ligth étant la lumière à modifier (de GL_LIGHT0 à GL_LIGHTn où n est un nombre variant de 0 à GL_MAX_LIGHT-1)
pname qui prendra ici le nom du type de couleur à modifier: GL_AMBIENT, GL_DIFFUSE ou GL_SPECULAR
et enfin params qui est un pointeur sur un tableau de 4 éléments contenant les composantes RGBA de notre couleur. Ce tableau devra être de type flottant si vous utilisez glLightfv(), et de type entier si vous utilisez glLightiv()
En sortie:
GL_INVALID_ENUM si ligth est une lumière inexistante.
GL_INVALID_OPERATION si vous utilisez la fonction entre glBegin et glEnd.
Allumez, éteindre les lumières:
Vous avez au minimum huit sources de lumière possible, mais il faut pouvoir les allumer (et aussi les éteindre tant qu'on y est !):
Pour allumer une source, vous utiliserez glEnable(GL_LIGHTn); où n est un nombre variant de 0 à GL_MAX_LIGHT-1.
Pour les éteindre : glDisable(GL_LIGHTn); où n est toujours un nombre variant de 0 à GL_MAX_LIGHT-1.
Premier exemple
Bin, oui, je dis que c'est le premier, car j'ai bien dis que ce chapitre était difficile...
Dans cet exemple, nous faisons tourner un cube sur lui-même. Il y a un
éclairage devant ce cube qui lui ne bouge pas.
Notre caméra est un peu décalée afin de voir les effets sur l'objet.
Le rendu est très moche ! On a l'impression d'utiliser un éclairage directionnel et que celui tourne ! De plus, le haut, est éclairé de la même manière que les faces présentes devant la source de lumière...
Beurk...
Les normales
Toujours dans la nature, les rayons lumineux rebondissent sur les objets suivant un angle: l'angle d'incidence. (Revoyez vos cours de physique (ou plus précisément d'optique) si nécessaire).
Sous OpenGL, pour déterminer comment la lumière va rebondir, vous utiliserez un vecteur normal (vecteur perpendiculaire à la surface encore appelé normale).
Il faut définir les normales à toutes vos surfaces. Et plus exactement aux vertice de vos surfaces.
Pour l'instant, aucune normale n'étant définie, OpenGL utilise celle par défaut: le vecteur est initialement (0,0,1). Et ceci pour toutes les faces du cube de l'exemple précédent, voilà pourquoi la lumière semble directionnelle.
Dans un bête cube, les normales seront faciles à déterminer. Cependant, dans le cas de plan incliné, cela peut vite devenir difficile, je vous l'accorde.
La fonction à utiliser sera glNormal() (qui est polyforme). Il suffit simplement d'initialiser les valeurs de la normale via cette fonction avant de positionner un vertex. Le vertex aura asinsi automatiquement le nouveau vecteur normal qui lui sera associé.
Les différentes formes de la fonction:
void glNormal3b(
GLbyte nx,
GLbyte ny,
GLbyte nz
);
void glNormal3d(
GLdouble nx,
GLdouble ny,
GLdouble nz
);
void glNormal3f(
GLfloat nx,
GLfloat ny,
GLfloat nz
);
void glNormal3i(
GLint nx,
GLint ny,
GLint nz
);
void glNormal3s(
GLshort nx,
GLshort ny,
GLshort nz
);
Ou encore, en passant par des tableaux:
void glNormal3bv(
const GLbyte *v
);
void glNormal3dv(
const GLdouble *v
);
void glNormal3fv(
const GLfloat *v
);
void glNormal3iv(
const GLint *v
);
void glNormal3sv(
const GLshort *v
);
Où v est un tableau contenant x, y et z suivant le type d'appelle.
Un vecteur
normal doit toujours être unitaire si vous n'utilisez pas l'option glEnable
(GL_NORMALIZE). Si vous l'utilisez, vous pouvez passer n'importe quelles valeurs,
OpenGL le rendra automatiquement unitaire. Cependant, cela peut-être gourmand
en temps. Par défaut, GL_NORMALIZE est désactivé.
Deuxième exemple
Mais ce n'est pas encore fini...
Le code ne change pas beaucoup, toujours le même cube et même source
de lumière. Il n'y a que les rajouts de normales.
Le résultat est mieux, mais c'est toujours décevant !
Nous avons indiqué une normale à chacun de nos vertice et non sur une face, on pourrait alors s'attendre à un dégradé de la lumière sur nos faces qui soit un peu mieux que ce rendu !
L'atténuation de la lumière
En fait, il faut considérer que l'objet évolue dans un milieu dépourvu de toutes poussières ou humidité, la lumière n'est donc pas gênée par des obstacles. Sur terre, un tel milieu n'existe pas, pollution et autres poussières... font que votre lumière apeu de chance d'être perceptible à partir d'une certaine distance. OpenGL propose donc d'atténuer la lumière sur la distance parcourue par un rayon lumineux.
Soit d, une distance entre un point et la source de lumière.
La quantité de lumière qui arrive sur ce point sera : qte de lumière* 1 / (Kq*d² + Kl*d + Kc)
Si on remplace qte par les composantes de la couleur, vous obtenez alors la quantité de chaque composante de la lumière qui arrive sur ce point.
Kc se nomme sous OpenGL GL_CONSTANT_ATTENUATION
Kl se nomme GL_LINEAR_ATTENUATION
et enfin Kq GL_QUADRATIC_ATTENUATION
Par défaut, Hc vaut 1, Kl et Kq valent 0. Ce qui se traduit par aucune atténuation de la lumière d'où l'effet que nous avons sur notre cube d'exemple...
Les valeurs de
Kc, Kl et Kq sont toujours supérieures ou égales à 0.
Pour modifier ces valeurs, vous utiliserez la fonction ... glLight() encore une fois !
void glLightf(
GLenum light,
GLenum pname,
GLfloat param
);
ou
void glLighti(
GLenum light,
GLenum pname,
GLint param
);
Avec light étant toujours la source de lumière...
pname pouvant être une des trois valeurs GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION ou GL_QUADRATIC_ATTENUATION
param étant la valeur à passer.
En sortie:
GL_INVALID_ENUM si ligth est une lumière inexistante.
GL_INVALID_OPERATION si on utilise la fonction entre glBegin et glEnd.
GL_INVALID_VALUE si la valeur a utiliser pour l'atténuation est négative.
L'atténuation
ne marche pas pour une lumière directionnelle, il ne faut en effet pas
oublier que l'on considère que cette lumière est placée
à l'infini, par conséquent, l'atténuation n'a pas lieu
d'être...
Troisième exemple
On rajoute l'atténuation, oh que c'est beau !
Modèle d'éclairage
Il s'agit ici d'indiquer à OpenGL que les calculs doivent se faire ou non de la même manière sur l'envers et l'endroit d'une même face. Enfin, que l'oeil est à l'infini ou dans la scène. Le premier cas étant plus rapide, mais moins réaliste que le second.
void glLightModelfv(
GLenum pname,
const GLfloat *params
);
void glLightModeliv(
GLenum pname,
const GLint *params
);
avec pname à GL_LIGHT_MODEL_LOCAL_VIEWER ou GL_LIGHT_MODEL_TWO_SIDE
params à 0 pour oeil à l'infini, les autres valeurs pour oeil dans la scène.
GL_INVALID_ENUM si pname est incorrecte.
GL_INVALID_OPERATION si on utilise la fonction entre glBegin et glEnd.
(PAr défaut, GL_LIGHT_MODEL_LOCAL_VIEWER et 0)
Une lumière supplémentaire
Je vous disais au début que les objets avaient une couleur ambiante, et qu'il y avait donc une source de lumière blanche...
Hé bien, il est possible de paramètrer cette lampe perdue...
Lorsque vous regardez cette image, vous voyez que la partie supérieure du cube qui n'est pas éclairée n'est pourtant pas noire !
Il existe en fait une lampe "ambiante" qui est allumée par défaut avec une RGBA à 0.2, 0.2, 0.2, 1.0, expliquant que la partie supérieure soit faiblement mais quand même visible.
Pour modifier le paramétrage de cette lampe, il suffira d'utiliser glLightModel, mais en passant la valeur GL_LIGHT_MODEL_AMBIENT et un tableau de 4 entiers ou flottants contenant les composantes RGBA (RVBA en français)
Evidemment, si vous positionnez les valeurs des composantes RVB de cette source au maximum, cela reviendra au même que de ne pas gérer les lumières !
Contrairement à ce que l'on peut lire sur internet, la couleur ambiante peut servir. Mais par contre, il est préférable d'indiquer au niveau d'un objet les mêmes composantes RVB lors de la définition des couleurs diffuse et ambiante. En effet, si un objet n'absorbe pas une certaine forme de lumière, cela devrait toujours être la même et ceci quel que soit la source de lumière. Sinon, effectivement, la gestion de la couleur deviendra difficile.
Grâce à cette lampe, vous pourrez alors assez facilement traiter le cas d'un lever et coucher de soleil, il suffira de faire varier l'intensité et les couleurs de cette source de lumière !
Annexes
OpenGL pour Linux, Windows, Android...