Textures

Objectif de scène 3D obtenue en fin de séance
Code d'exemple de textures
Considérez désormais le code présent dans le répertoire scenes_inf443/04b_textures/a_texture_uv/
Compilez et exécuter le projet correspondant à ce répertoire. Vous devriez obtenir une scène affichant un quadrangle muni d’une texture.
L'application d'une texture nécessite deux étapes:
- 1. Le choix des coordonnées de textures, appelées également coordonnées \((u,v)\), ou encore \((s,t)\)
-
- Ici les coordonnées sont stockées dans le buffer quadrangle_mesh.uv.
- 2. Le choix d'une image qui va être plaquée sur la surface. Cette image est généralement chargée depuis un fichier, et qui doit être stockée sur le GPU.
-
- Le chargement de l'image ainsi que l'envoie sur la mémoire du GPU est réalisé par la fonction [opengl_texture_image_structure].load_and_initialize_texture_2d_on_gpu(filename [, parameter_u, parameter_v]).
- (filename est le chemin vers un fichier d'image (.jpg ou .png)).
- La structure "mesh_drawable" est prévue pour stocker une texture dans l'attribut [mesh_drawable].texture.
Remarques:
-
- Le shader utilisé par défaut lors de l'affichage d'un maillage utilise dans tous les cas une texture, même si vous ne l'avez pas spécifié explicitement. Par défaut, les coordonnées de textures associées aux sommets sont à (0,0), et une image de texture blanche est utilisée.
- - Dans le shader, la couleur (R,B,G) affectée à chaque fragment de la surface est obtenue en multipliant les trois composantes suivantes:
-
- - la couleur de l'objet (défini en tant qu'uniform: [mesh_drawable].material.color = ...)
- - la couleur associé au sommet ([mesh].color[k] = ...),
- - la couleur du pixel correspondant aux coordonnées (u,v) dans l'image de texture.
Coordonnées de textures
Pour appliquer une texture sur une surface, il est indispensable de définir pour chaque sommets des coordonnées de textures (les coordonnées (u,v)).
Ces coordonnées définissent quelle partie de l'image doivent être appliquées aux sommets de la surface - et par interpolation sur les fragments composant l'intérieur des triangles. Ces coordonnées sont attachées aux sommets en tant que tels (et non pas aux positions 3D (x,y,z)) - Ainsi si vous modifiez les positions de la surface, l'image va s'appliquer sur les mêmes sommets.
Si les triangles de la surface s'étirent (/se compressent), l'image mappée dessus apparaitra également étirée (/compressée).

Exemple d'un triangle formé par les points \(p_0, p_1, p_2\) dont les coordonnées de textures sont respectivement données par \(uv_0, uv_1, uv_2\).
Modifiez désormais les coordonnées de textures et tentez d'expliquer le résultat obtenu dans les configurations suivantes
quadrangle_mesh.uv = { {0,0}, {0.5f,0}, {0.5f,0.5f}, {0,0.5f} }; quadrangle_mesh.uv = { {0.25,0.25}, {0.5,0.25}, {0.5,0.5}, {0.25,0.5} }; quadrangle_mesh.uv = { {0,0}, {2,0}, {2,2}, {0,2} }; quadrangle_mesh.uv = { {0,0}, {1,0}, {1,3}, {0,3} }; quadrangle_mesh.uv = { {-1,-1}, {2,-1}, {2,2}, {-1,2} };
- - Notez que l'espace des coordonnées \(uv\) sont normalisés entre 0 et 1. Les coins d'une image de texture correspondent donc aux coordonnées \(uv\) (0,0), (0,1), (1,1), et (1,0).
- - Utiliser des coordonnées (u,v) inférieures à 0, ou supérieures à 1, revient à dépasser les bords de l'image d'origine. Dans l'exemple de code, le dépassement apparait alors en noir.
- - La plupart des images utilisent une convention d'orientation différente (axe vertical descendant), ce qui peut amener à visualiser une image inversé. Pour éviter cela, la structure mesh_drawable possède l'attribut booléen [mesh_drawable].material.texture_settings.inverse_v utilisé pour inverser la coordonnées v (on considère alors la coordonnée 1-v). La valeur de inverse_v est à True par défaut, mais peut être modifiée à n'importe quel moment du programme.
Pour rappel les variables écrites en majuscules sous la forme GL_XXX correspondent à des valeurs prédéfinies dans le standard OpenGL
- - Notez qu'il est possible d'utiliser également les valeurs GL_MIRRORED_REPEAT (répétitions en miroir), et GL_CLAMP_TO_EDGE (copie de la couleur le long de l'arête).
- - Notez également qu'il y a deux paramètres: l'un pour le comportement suivant la coordonnées de texture \(u\), et l'autre suivant \(v\).

Pour d'avantage d'information, vous pouvez regarder le code de la fonction opengl_load_texture_image(image_raw const& im, GLint wrap_s, GLint wrap_t), où vous pourrez y trouver les appels brutes aux fonctions d'OpenGL permettant de paramétrer le comportement des textures. Ces comportements étant paramétrés en particulier par la fonction glTexParameteri.
Coordonnées sur des surfaces
Le fichier models_textures.cpp propose un code de création de tore, cylindre, et disque. Le tore et le cylindre sont des surfaces paramétriques construites de manière similaire au terrain de l'introduction, mais cette fois sous une forme \(f(u,v)=(x(u,v),y(u,v),z(u,v))\). Notez que ces \((u,v)\) sont les coordonnées paramétriques de la surface - ce ne sont pas forcément les coordonnées de textures elles-même.
Pour l'instant ces surfaces n'ont pas de coordonnées de textures spécifiées explicitement.> Complétez la ligne tore.uv[kv+N*ku] = {?,?}, où les "?" sont des coordonnées à compléter. Vous pouvez tester une solution naive liée à la paramétrisation de la géométrie tore.uv[kv+N*ku] = {u,v}; et tore.uv[kv+N*ku] = {v,u}; puis adapter ces coordonnées pour reproduire l'image suivante.
- Notez qu'il faudra initialiser une structure de type mesh_drawable dans votre scene avec l'appel à la fonction torus_with_texture et adapter l'image de texture qui est associée (des images sont disponibles dans le répertoire assets/).

Similairement, complétez les coordonnées de la fonction de création du cylindre pour obtenir l'apparence texturé suivante

Par la suite, vous pouvez compléter l'extrémité du cylindre par un disque, et adapter celui-ci de manière à donner l'impression finale d'une buche de bois coupée.
- - Pensez aux coordonnées de sommets que vous devez affecter aux sommets de votre disque.
- - N'oubliez pas que dans l'espace de l'image, les coordonnées de textures représentants les 4 coins sont entre 0 et 1.


Application au terrain
Reprenez l'application du terrain et modifiez celle-ci afin d'y plaquer une texture. Pour l'ajout des coordonnées de texture, il est nécessaire de modifier la fonction create_terrain en-
Initialisant le buffer correspondant aux coordonnées de texture
terrain.uv.resize(N*N);
-
Affectant les coordonnées de texture dans la boucle de remplissage des coordonnées
terrain.uv[kv+N*ku] = ...

- Rem. En "bonus", si vous avez bien compris le principe, vous pouvez tous à fait appliquer des textures également sur vos arbres. Dans ce cas, il faudra par contre différencier le tronc du feuillage dans deux mesh_drawable différents, chacun étant associé à une image de texture différente.
Bruit de Perlin
Considérez désormais le code présent dans le répertoire scenes_inf443/04b_textures/b_perlin
Ce code applique une déformation de hauteur sur une grille régulière (initialement plane) sous la forme d'un bruit de Perlin. Celui-ci est paramétrable à l'aide des sliders de l'interface.
On rappel que le bruit de Perlin est formé à partir d'une fonction pseudo-aléatoire \(b(p)\) qui a tout point \(p\) renvoit une valeur scalaire déterministe. \(b\) est une fonction lisse à valeur entre \([0,1]\) et non périodique.
Le bruit de Perlin \(P\) est obtenu en sommant plusieurs instances de \(b\) avec des fréquences croissantes, et d'amplitude décroissantes.
\(\displaystyle P(p) = h\;\sum_{k=0} ^ {octaves} \alpha ^ k\,b(\omega ^ k\,p)\)- \(\alpha\): persistance
- \(\omega\): gain en fréquence
- octaves: nombres d'itérations
- \(h\): amplitude
Les différents paramètres de ce bruit peuvent être manipulés interactivement à l'aide des sliders de l'interface. Retrouvez le rôle de chacun.
- - Notez que la couleur associée à chaque sommet dépend de sa hauteur.
- - Le bruit de Perlin est évalué par une bibliothèque externe utilisant lui même l'approche du simplex noise. Notez que la bibliothèque permet de générer des bruits en dimension 1, 2, et 3 (volume).
> Inspirez vous de cette approche pour ajouter un bruit de Perlin à votre terrain déjà existant.
- - Essayez de préserver l'apparence globale de votre fonction initiale (forme à basse résolution contrôlée explicitement par des fonctions gaussiennes) sur laquelle vous ajoutez le bruit procédural de faible amplitude pour donner l'impression visuelle de détails naturels à plus hautes fréquences.
- - Le principe est d'appeler la fonction noise_perlin et d'utiliser celle-ci dans la fonction d'évaluation de la hauteur de votre terrain. Il est inutile de générer un autre maillage, ou d'utiliser la fonction update de l'exemple qui est utilisé pour appliquer une déformation interactive.
- - Votre terrain doit avoir une résolution suffisament élevée pour échantillonner correctement et voir les détails du bruit.
- - Vous n'avez pas besoin d'ajouter une interface (avec les sliders).
- - L'implémentation proposée du "simplex noise" doit être utilisée uniquement pour des coordonnées positives.
-
- \(\rightarrow\) Dans le cas de votre terrain, vous pouvez renormaliser les coordonnées \((x,y)\) vers des paramètres \((u,v)\in [0,1]\) ou \([0,a]\) avec \(a>0\).

Billboards et objets transparents
Les billboards (ou imposteurs), consistent à utiliser des images d'objets sur fond transparent en tant que texture pour représenter des objets d'apparence complexe sur une géométrie simple (typiquement des quadrangles).
Considérez finalement le code présent dans le répertoire scenes_inf443/04b_textures/c_billboards


Texture de brins d'herbes (fond transparent), et fenêtre dont la partie rouge est semi-transparente.
Ces textures sont appliquées de la manière suivante:
- - Tout d'abord l'ensemble des objets non transparents sont affichés de manière classique.
- - Ensuite, le mode "blending" de couleur est activé avant de débuter l'affichage des objets semi-transparents.
glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- - L'herbre est ré-orientée à chaque frame de manière à faire au mieux face à la caméra tout en restant orienté suivant la verticale.
- - L'écriture dans le buffer de profondeur est désactivé, et le quadrangle le plus éloigné de la caméra est affiché avant le plus proche.
-
- - Le buffer de profondeur ne peut en effet pas être utilisé pour afficher les pixels entre objets comportant de la semi-transparence car il empêcherait d'afficher le contenu situé derrière l'objet transparent.
- - Lors de l'utilisation d'un mélange de couleur, il est donc nécessaire de trier les objets transparents afin d'afficher les objets les plus éloignées d'abord, puis de plus en plus proche de la caméra afin d'accumuler les couleurs dans le bon ordre.
Observez la mise en place de cette approche dans la fonction display_semiTransparent().
Application dans le cas de votre terrain
> Utilisez les billboards pour ajouter des détails à votre scène, par exemple en ajoutant des fleurs et de la végétation sur le terrain.- Pour simplifier le code, vous pouvez vous passer de l'étape de tris des billboards en fonction de leur profondeur vis-à-vis de la caméra. Quelque artefacts visuels pourront exister, mais restent peu visibles.
Exemple possible de terrains avec billboards (les quadrangles ne sont pas triés).