IV. Textures de lumière (lighting maps)▲
Dans le chapitre précédent, nous avons montré l’intérêt de disposer pour un objet d’un matériau unique, caractérisant la façon dont l’objet réagit à la lumière. Cela permet de donner à chaque objet une apparence qui lui est propre, et le distingue des autres objets dans une scène éclairée, mais cela n’offre pas encore assez de souplesse quant à l’aspect visuel d’un objet.
Précédemment, nous avons défini un matériau pour un objet entier, mais en réalité, les objets ne sont pas constitués d’un seul matériau, mais de plusieurs. Prenez par exemple une voiture : la carrosserie est de nature brillante, les vitres reflètent l’extérieur de la voiture, les pneus sont ternes, les jantes sont très brillantes (si on les nettoie, bien sûr). Les couleurs ambiante et diffuse de la voiture ne sont pas non plus les mêmes pour toute la voiture. On voit bien qu’un objet doit avoir des propriétés de matériau différentes pour chacune de ses parties.
Les données de matériau du chapitre précédent ne sont donc suffisantes que pour les objets les plus simplistes et nous devons donc améliorer notre structure de données en introduisant des textures diffuse et spéculaire. Cela nous permettra de paramétrer avec plus de précision la composante spéculaire et la composante diffuse (et indirectement la composante ambiante puisqu’elle est presque toujours la même que la composante diffuse).
IV-A. Textures de lumière diffuse▲
Nous cherchons un moyen de définir la couleur diffuse d’un objet pour chaque fragment individuellement. Un moyen de retrouver la couleur en fonction de la position d’un fragment d’un objet.
Cela devrait vous paraître assez familier et pour être honnête, nous avons utilisé un tel système depuis quelque temps déjà. Cela ressemble à une texture, ce que nous avons présenté en détail dans l’un de nos précédents tutoriels et c’est bien cela : une texture. Nous utilisons un nom différent pour un même principe : utiliser une image plaquée sur l’objet, que l’on peut indexer pour définir la couleur de chaque fragment. Habituellement, on appelle cela une texture de lumière diffuse (diffuse map), car une telle texture représente toutes les couleurs diffuses de l’objet.
Pour montrer ces textures de lumière diffuse, nous allons utiliser l’image suivante d’un conteneur en bois muni d’un bord en acier :
L’utilisation d’une texture de lumière diffuse dans un shader utilise les mêmes principes que ceux expliqués dans le tutoriel consacré aux textures. Cependant, nous mémoriserons la texture comme un sampler2D à l’intérieur de la structure Material. Nous remplaçons le vecteur vec3 défini pour la couleur diffuse par notre texture de lumière diffuse.
Rappelez-vous que sampler2D est un type opaque, ce qui signifie qu’on ne peut pas instancier un tel type de données, mais seulement définir les variables de ces types comme uniformes. Si l’on instanciait cette structure autrement que par une variable uniforme (comme un paramètre de fonction), GLSL donnerait des erreurs étranges : la même chose s’applique à tous les types opaques.
Nous allons aussi supprimer le vecteur de composante ambiante, car sa couleur est presque dans tous les cas la même que celle de la composante diffuse et il n’est pas utile de la mémoriser en plus.
struct Material {
sampler2D
diffuse;
vec3
specular;
float
shininess;
}
;
…
in
vec2
TexCoords;
Si vous êtes très rigoureux et souhaitez conserver la composante ambiante du matériau avec une valeur différente, il suffit de conserver ce vecteur, mais la composante ambiante sera la même pour tout l’objet. Pour obtenir une composante ambiante différente pour chaque fragment, il vous faudra utiliser une texture de lumière ambiante.
Nous aurons besoin de coordonnées de textures dans le fragment shader, nous déclarons donc une nouvelle variable d’entrée. Il suffit ensuite d’échantillonner la texture pour retrouver la couleur diffuse pour chaque fragment :
vec3
diffuse =
light.diffuse *
diff *
vec3
(
texture
(
material.diffuse, TexCoords));
Et n’oublions pas de définir la composante ambiante du matériau égale à sa composante diffuse :
vec3
ambient =
light.ambient *
vec3
(
texture
(
material.diffuse, TexCoords));
C’est suffisant pour utiliser une texture de lumière diffuse. Rien de bien nouveau, mais l’effet visuel en est très nettement amélioré. Pour que cela fonctionne, il nous faut mettre à jour les données de sommets avec les coordonnées de texture, les transférer comme attributs de sommets au fragment shader, charger la texture et lier l’unité de texture correspondante.
Les nouvelles données de sommets sont disponibles ici. Ces données incluent les positions, les normales et les coordonnées de texture pour chaque sommet du cube. Mettons à jour le vertex shader pour prendre les coordonnées de texture comme attributs de sommets et passons-les au fragment shader :
#
version
330
core
layout
(
location =
0
) in
vec3
aPos;
layout
(
location =
1
) in
vec3
aNormal;
layout
(
location =
2
) in
vec2
aTexCoords;
…
out
vec2
TexCoords;
void
main
(
)
{
…
TexCoords =
aTexCoords;
}
Assurez-vous de mettre à jour les pointeurs d’attributs de sommets des VAOs pour tenir compte de ces nouvelles données et chargez l’image du conteneur comme texture. Avant d’afficher le conteneur, assignons l’unité de texture à l’échantillonneur uniforme material.diffuse et lions la texture du conteneur à cette unité :
lightingShader.setInt("material.diffuse"
, 0
);
…
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);
L’utilisation de la texture de lumière diffuse améliore très nettement l’affichage et le conteneur devient réellement brillant. Le vôtre devrait ressembler à cela :
Vous trouverez le code complet de l’application ici.
IV-B. Textures de lumière spéculaire▲
Vous avez probablement remarqué que la composante spéculaire paraît un peu forte, car notre conteneur est composé de bois, et ne devrait renvoyer que peu de lumière. Nous pourrions rétablir cela en donnant à la composante spéculaire du matériau une valeur nulle, mais dans ce cas, les bords métalliques ne refléteraient plus rien, alors que l’acier est un matériau assez brillant. À nouveau, nous voulons contrôler quelles parties de l’objet doivent renvoyer de la lumière avec une intensité paramétrable.
Ce problème ressemble vraiment à celui traité précédemment pour la lumière diffuse.
On peut aussi définir une texture pour la composante spéculaire. Nous devons donc générer une texture noire et blanche (ou en couleur si vous le souhaitez) qui définit l’intensité de la propriété spéculaire du matériau pour chaque partie de l’objet. Un exemple de texture spéculaire est montré sur la figure suivante :
L’intensité de la composante spéculaire est déterminée par la brillance de chaque pixel de l’image. Chaque pixel de l’image de la texture spéculaire peut être vu comme un vecteur de couleur, où le noir est le vecteur vec3(0.0), le gris un vecteur vec3(0.5) par exemple. Dans le fragment shader, on échantillonnera la valeur de la couleur et on la multipliera par l’intensité de la composante spéculaire de la source de lumière. Plus un pixel sera blanc, plus le résultat de la multiplication sera grand et plus la composante spéculaire sera intense.
Le conteneur est surtout composé de bois, ce matériau ne reflète que très peu la lumière et donc la partie correspondante de la texture est noire, la partie en bois ne donnera aucune composante spéculaire. Le bord du conteneur est en acier et aura des intensités de lumière spéculaire variables, l’acier étant très brillant, et les défauts, beaucoup moins.
En réalité, le bois donne aussi des reflets, mais peu intenses, et pour des raisons pédagogiques nous supposerons que le bois ne donne aucune composante de lumière spéculaire.
En utilisant des outils comme Photoshop ou Gimp, il est assez facile de transformer une texture diffuse en image spéculaire, en effaçant certaines parties, puis en convertissant l’image en nuances de gris et en jouant sur le contraste.
IV-C. Échantillonnage de texture spéculaire▲
Une texture de lumière spéculaire est juste comme toute autre texture et le code est similaire à celui utilisé pour les textures de lumière diffuse. Chargez l’image et générez un objet texture. Puisque nous utilisons une seconde texture dans le même fragment shader, il faut une seconde unité de texture pour la texture de lumière spéculaire (voir le chapitre sur les textures), et ensuite il nous faut lier l’unité correcte avant d’effectuer le rendu :
lightingShader.setInt("material.specular"
, 1
);
…
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMap);
Mettons à jour les propriétés du matériau du fragment shader pour prendre un sampler2D comme composante spéculaire à la place d’un vec3 :
struct Material {
sampler2D
diffuse;
sampler2D
specular;
float
shininess;
}
;
Et enfin, nous échantillonnons la texture spéculaire pour déterminer l’intensité spéculaire du fragment :
vec3
ambient =
light.ambient *
vec3
(
texture
(
material.diffuse, TexCoords));
vec3
diffuse =
light.diffuse *
diff *
vec3
(
texture
(
material.diffuse, TexCoords));
vec3
specular =
light.specular *
spec *
vec3
(
texture
(
material.specular, TexCoords));
FragColor =
vec4
(
ambient +
diffuse +
specular, 1
.0
);
En utilisant une texture de lumière spéculaire, on peut définir très précisément quelles parties d’un objet sont brillantes et avec quelle intensité. Cela nous donne un niveau de contrôle supplémentaire.
Si vous le souhaitez, vous pouvez aussi utiliser les couleurs réelles de l’image pour la texture spéculaire, pour contrôler l’intensité, mais aussi la couleur de la lumière spéculaire. Mais, en réalité, la couleur des reflets est essentiellement déterminée par celle de la source de lumière et vous n’aurez donc pas des effets très réalistes (et c’est pourquoi on s’en tient à l’intensité et que l’on utilise des images en nuances de gris).
En lançant l’application, on voit bien que le matériau du conteneur ressemble beaucoup à celui d’un vrai conteneur en bois avec des bords métalliques :
Le code de l’application se trouve ici.
L’utilisation de textures pour la lumière diffuse et la lumière spéculaire permet d’ajouter de nombreux détails à des objets assez simples. On peut même augmenter cette précision en utilisant d’autres textures de matériau comme les textures de normales, ou encore des textures pour la réflexion, mais nous réservons cela pour des chapitres ultérieurs.
IV-D. Exercices▲
- Jouez avec les composantes de la source de lumière et voyez comment cela modifie l’aspect de la scène.
- Inversez les couleurs de la texture spéculaire dans le fragment shader de façon à ce que le bois devienne brillant et les bords ternes (les aspérités des bords deviennent alors brillantes) : solution.
- Créez une texture spéculaire à partir de la texture diffuse qui utilise les couleurs de l’image et voir que le résultat n’est pas très réaliste. Vous pouvez utiliser cette texture spéculaire colorée si vous ne voulez pas en générer une vous-même : résultat.
- Ajoutez ce qu’on appelle une texture d’émission qui contient des valeurs d’émission de lumière pour chaque fragment. Ces valeurs sont des couleurs qu’un objet peut émettre comme une source de lumière. De cette façon, un objet peut rayonner indépendamment des autres éclairages. Ces textures sont utilisées dans les jeux (comme les yeux d’un robot, une bande de lumière sur un conteneur). Ajouter la texture suivante (de creativesam) comme texture d’émission sur le conteneur et voyez si les lettres émettent de la lumière : solution ; résultat.
IV-E. 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.