I. Les couleurs▲
Dans les premiers chapitres, nous avons rapidement montré comment travailler avec les couleurs dans OpenGL, mais nous n’avons fait qu’effleurer le sujet. Dans ce chapitre nous allons approfondir nos connaissances et concevoir une scène de base pour les tutoriels sur l’éclairage.
Dans le monde réel, les couleurs sont innombrables : chaque objet ayant sa propre couleur. Dans le monde numérique, il nous faut représenter cette infinité de couleurs avec un nombre limité de valeurs numériques, on ne pourra donc pas représenter toutes les nuances possibles. On peut cependant représenter un très grand nombre de teintes ce qui sera largement suffisant. Les couleurs sont recomposées en utilisant le rouge, le vert et le bleu, ce qu’on nomme le modèle RVB (RGB en anglais). En utilisant différentes combinaisons de ces trois couleurs fondamentales, on pourra recomposer toutes les couleurs voulues (ou presque). Par exemple pour obtenir la couleur d’un corail, on définira un vecteur couleur avec ces composantes :
glm::
vec3 coral(1.0
f, 0.5
f, 0.31
f);
Les couleurs que nous voyons ne sont pas les couleurs réelles des objets mais sont celles qui émanent de l’objet : les couleurs qui ne sont pas absorbées par l’objet sont réfléchies par l’objet et ce sont celles que nous percevons. Par exemple, la lumière du soleil est perçue comme une lumière blanche, qui est en réalité la combinaison de beaucoup de couleurs différentes (comme schématisé dans la figure suivante). Si l’on éclaire un objet bleu avec une lumière blanche, les couleurs autres que le bleu sont absorbées. La couleur bleue est au contraire réfléchie par l’objet et c’est elle que notre œil perçoit, l’objet nous apparaît donc bleu. La figure suivante montre ce phénomène sur un objet de couleur corail, qui réfléchit certaines couleurs avec des intensités différentes :
On voit que la lumière solaire est une composition de toutes les couleurs visibles et que l’objet absorbe une grande partie d’entre elles. Seules les couleurs représentant la couleur de l’objet sont réfléchies et ce sont celles-ci que l’on perçoit.
Ces règles de réflexion des couleurs sont reproduites dans le monde graphique. Lorsque l’on crée une source de lumière avec OpenGL, on lui donne une couleur. Comme dans l’exemple précédent, on peut lui donner une couleur blanche. Si l’on multiplie alors la couleur de la source lumineuse par la couleur d’un objet, la couleur résultante de l’objet est celle qui serait réfléchie par cet objet (et donc la couleur perçue par l’œil). Reprenons notre objet de couleur corail, et voyons comment nous calculerions sa couleur perçue dans le monde graphique. Nous retrouvons cette couleur perçue en effectuant une multiplication des composantes de chacune de ces deux couleurs :
glm::
vec3 lightColor(1.0
f, 1.0
f, 1.0
f);
glm::
vec3 toyColor(1.0
f, 0.5
f, 0.31
f);
glm::
vec3 result =
lightColor *
toyColor; // = (1.0f, 0.5f, 0.31f);
On voit que l’objet absorbe une grande partie des couleurs de la source blanche, mais reflète une part de rouge, de vert et de bleu, en fonction de sa propre couleur. On peut donc définir la couleur d’un objet comme la quantité de chaque composante reflétée à partir d’une source de lumière blanche. Mais que se passerait-il avec une source de lumière verte ?
glm::
vec3 lightColor(0.0
f, 1.0
f, 0.0
f);
glm::
vec3 toyColor(1.0
f, 0.5
f, 0.31
f);
glm::
vec3 result =
lightColor *
toyColor; // = (0.0f, 0.5f, 0.0f);
Comme on peut le voir, les composantes bleue et rouge sont nulles. La composante verte est absorbée pour moitié, mais l’autre moitié est reflétée par l’objet. La couleur perçue serait donc un vert assez foncé. La source ne comprenant que du vert, seule la composante verte peut être reflétée, le rouge et le bleu sont inexistants. L’objet corail apparaîtrait donc d’un vert très foncé. Voyons un autre exemple avec une source de couleur vert olive :
glm::
vec3 lightColor(0.33
f, 0.42
f, 0.18
f);
glm::
vec3 toyColor(1.0
f, 0.5
f, 0.31
f);
glm::
vec3 result =
lightColor *
toyColor; // = (0.33f, 0.21f, 0.06f);
On constate que l’on obtient des couleurs inattendues pour un objet suivant la source de lumière qui l’éclaire. Ce n’est pas difficile d’être créatif avec les couleurs.
Essayons maintenant de créer une scène où nous pourrons tester ces différents effets.
I-A. Une scène éclairée▲
Dans les prochains chapitres, nous allons créer d’intéressants effets visuels en simulant un éclairage du monde réel par l’utilisation des couleurs. Puisque nous allons utiliser des sources lumineuses, nous souhaitons les afficher en tant qu’objets visuels dans la scène et ajouter au moins un objet pour simuler ces éclairages.
La première chose qu’il nous faut est un objet pour y projeter la lumière, nous utiliserons ce pauvre conteneur des tutoriels précédents. Il nous faut aussi un objet lumineux pour montrer d’où vient la lumière de la scène. Pour simplifier, nous représenterons aussi la source de lumière comme un cube (nous avons déjà les sommets pour un cube).
Les opérations d’initialisation d’un VBO, de définition des attributs de sommets vous sont maintenant familières, on ne s’attardera pas sur ce point. En cas de problème, revoyez les précédents tutoriels, y compris les exercices, avant de continuer.
Nous avons besoin d’un vertex shader pour afficher le conteneur. Les positions des sommets du conteneur restent les mêmes (nous n’utiliserons pas les coordonnées de textures pour cette fois), rien de nouveau donc dans le code. Nous utilisons une version allégée du vertex shader des chapitres précédents :
#
version
330
core
layout
(
location =
0
) in
vec3
aPos;
uniform
mat4
model;
uniform
mat4
view;
uniform
mat4
projection;
void
main
(
)
{
gl_Position
=
projection *
view *
model *
vec4
(
aPos, 1
.0
);
}
Assurez-vous de mettre à jour vos données de sommets et les pointeurs d’attributs de sommets pour coller à ce nouveau vertex shader (vous pouvez conserver les textures si vous le souhaitez, nous ne les utiliserons pas. Aussi cela peut être une bonne idée de repartir avec un nouveau projet).
Puisque nous voulons créer une lampe cube, nous allons créer un nouveau VAO pour cette lampe. Nous pourrions aussi utiliser le même VAO et simplement effectuer une transformation de la matrice de modèle, mais dans les prochains chapitres nous modifierons souvent les données de sommets et les pointeurs d’attributs de sommets de l’objet conteneur et nous ne voulons pas que l’objet lampe en soit affecté, nous créons donc un nouveau VAO :
unsigned
int
lightVAO;
glGenVertexArrays(1
, &
lightVAO);
glBindVertexArray(lightVAO);
// nous avons juste besoin de lier le VBO, les données du container sont déjà prêtes.
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// définit les attributs de sommets (seulement la position)
glVertexAttribPointer(0
, 3
, GL_FLOAT, GL_FALSE, 3
*
sizeof
(float
), (void
*
)0
);
glEnableVertexAttribArray(0
);
Le code doit vous paraître assez direct. Après avoir créé ces deux objets, le conteneur et la lampe, il nous faut coder le fragment shader :
#
version
330
core
out
vec4
FragColor;
uniform
vec3
objectColor;
uniform
vec3
lightColor;
void
main
(
)
{
FragColor =
vec4
(
lightColor *
objectColor, 1
.0
);
}
Le fragment shader utilise une couleur objet et une couleur de lumière, définies avec des variables uniformes. Ensuite, on multiplie la couleur de la source avec la couleur de l’objet comme expliqué au début de ce chapitre. Le shader est assez simple à comprendre. Définissons la couleur de l’objet corail et la source blanche :
// ne pas oublier de lier le shader program avant d’affecter la variable uniforme
lightingShader.use();
lightingShader.setVec3("objectColor"
, 1.0
f, 0.5
f, 0.31
f);
lightingShader.setVec3("lightColor"
, 1.0
f, 1.0
f, 1.0
f);
À noter que si nous modifions le vertex shader ou le fragment shader, la lampe sera aussi modifiée ; au contraire, nous ne voulons pas que la couleur de la source lumineuse soit affectée par les calculs d’éclairage, mais plutôt qu’elle soit isolée du reste. Nous voulons maintenir un éclairage constant, et fonctionner réellement comme une source de lumière.
Dans ce but, nous allons créer un second ensemble de shaders que nous utiliserons pour dessiner la lampe, indépendants des shaders de l’éclairage. Ce vertex shader est le même que le vertex shader actuel, on se contentera de recopier le code source pour ce nouveau shader. Le fragment shader de la lampe assurera que la couleur de la lampe reste constante et de couleur blanche :
#
version
330
core
out
vec4
FragColor;
void
main
(
)
{
FragColor =
vec4
(
1
.0
); // définit les quatre composantes du vecteur à 1.0
}
Pour dessiner des objets, nous utiliserons le shader d’éclairage que nous venons de définir, alors que pour afficher la lampe, nous utiliserons le shader de la lampe. Au cours des prochains tutoriels, nous ferons évoluer les shaders d’éclairage pour parvenir à des résultats plus réalistes.
Le but principal de la lampe cube est de modéliser l’endroit d’où provient la lumière. Nous définirons la position de la source lumineuse dans la scène, mais ce n’est qu’une position sans réalité visuelle. Pour montrer la lampe réelle, nous dessinerons la lampe cube au même endroit que la source de lumière. Cela est réalisé en affichant l’objet lampe avec le shader de la lampe, cette lampe restera toujours blanche, quelles que soient les conditions d’éclairage de la scène.
Déclarons une variable globale vec3 qui représente l’emplacement de la source lumineuse dans l’espace monde :
glm::
vec3 lightPos(1.2
f, 1.0
f, 2.0
f);
Ensuite, nous déplacerons le cube de la lampe à la position de la source lumineuse avant de l’afficher, et nous la réduirons de façon à la rendre plus discrète :
model =
glm::
mat4();
model =
glm::
translate(model, lightPos);
model =
glm::
scale(model, glm::
vec3(0.2
f));
Le code pour afficher la lampe ressemblera à cela :
lampShader.use();
// configurer les variables uniformes pour les matrices de modèle, vue et projection
...
// dessin de la lampe
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0
, 36
);
En replaçant ces lignes de code au bon endroit, nous aurons une application OpenGL proprement configurée pour nos tests d’éclairages. Si tout se passe bien, on obtient ce résultat :
Pas encore très spectaculaire, mais je vous promets des résultats plus intéressants par la suite.
Si vous rencontrez quelques difficultés pour assembler tous ces bouts de code, le code source est ici, inspectez l’ensemble attentivement.
Après ces quelques éléments sur les couleurs dans une scène simple, le chapitre suivant commencera à produire des effets plus magiques.
I-B. Remerciements▲
Ce tutoriel est une traduction réalisée par Jean-Michel Fray dont l’original a été écrit par Joey de Vries et qui est disponible sur le site Learn OpenGL.