Laboratoire 09

Introduction

Dans les laboratoires précédents, nous avons appris que, pour ajouter des détails à notre objet, nous pouvons utiliser les couleurs associées à chaque sommet. Pour pouvoir créer des objets détaillés, cela signifie que nous avons beaucoup de sommets. Nous pouvons donc spécifier une large gamme de couleurs et utiliser beaucoup de mémoire. Les textures sont utilisées pour résoudre ce problème.

Pour les besoins de cet atelier, une texture est une image 2D (il existe également des textures 1D et 3D ) utilisée pour ajouter des détails à l'objet. Pensez à la texture comme à un morceau de papier (avec un dessin) qui est plié sur l'objet 3D. Comme nous pouvons ajouter tous les détails souhaités dans une seule image, nous pouvons donner l’illusion que l’objet est très détaillé sans ajouter de sommets supplémentaires.

La différence entre un objet texturé et le même objet non texturé est remarquablement grande en termes de précision de la représentation de cet objet:

Placage des textures

Pour mapper (envelopper) une texture sur un triangle, il faut spécifier la partie de la texture à laquelle correspond chaque sommet. Par conséquent, chaque sommet doit avoir un ensemble de coordonnées de texture (c.-à-d. 2D glm::vec2) qui spécifie la partie de la texture où il se produit. L'interpolation entre les sommets est effectuée dans le fragment shader .

Les coordonnées de la texture sont dans la plage $[0, 1]$ pour les axes x et y (dans le cas de 2D). Les coordonnées de la texture partent du point $(0, 0)$ pour le coin inférieur gauche de l'image jusqu'au point $(1, 1)$ qui est dans le coin supérieur droit.

Un point sur l'image et qui se trouve dans l'espace $[0,1] \times [0,1]$ cela s'appelle texel , le nom venant de l' élément texture . Comme vous pouvez le voir dans l'image ci-dessous, en fonction des coordonnées de chaque sommet du triangle, la partie de la texture mappée sur le triangle peut être différente:

Creation d'une texture

Pour construire une texture dans OpenGL, nous avons d’abord besoin des pixels de l’image qui sera utilisée comme texture. Les pixels doivent être générés de manière fonctionnelle ou chargés à partir d'une image. Cette étape est indépendante d'OpenGL. En laboratoire, pour lire des textures, la classe peut être utilisée Texture2D:

Texture2D::Load2D(const char* fileName, GLenum wrapping_mode)

où cela wrapping_mode peut être:

  • GL_REPEAT: la texture se répète sur toute la surface de l'objet
  • GL_MIRRORED_REPEAT: la texture se répète mais sera visible dans le miroir pour des répétitions impaires
  • GL_CLAMP_TO_EDGE: les coordonnées seront comprises entre 0 et 1
  • GL_CLAMP_TO_BORDER: similaire à l'attacher au bord, sauf que ce qui est au-delà du bord de l'image n'est plus texturé.

Pour définir manuellement le mode d'habillage de la texture, vous pouvez utiliser:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapping_mode); // modul de wrapping pe orizontala
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapping_mode); // modul de wrapping pe verticala

Après avoir reçu les pixels de l'image chargée, nous pouvons générer un objet de texture OpenGL à l'aide de la commande:

unsigned int gl_texture_object;
 
glGenTextures(1, &gl_texture_object);

Comme avec tous les autres processus dans OpenGL, nous ne travaillons pas directement avec la texture, mais nous devons l'associer à un point de liaison. De plus, les points de reliure pour les textures dépendent à leur tour des unités de texturation . Le concept d'une unité de texture est très similaire aux tuyaux auxquels nous envoyons des attributs. Nous définissons l'unité de texturation à l'aide de la commande (une seule unité de texturation peut être activée):

glActiveTexture(GL_TEXTURE0 + nr_unitatii_de_texturare_dorite);

Et pour lier l'objet de texture généré précédemment à l'unité de texture active, nous utilisons le point de liaison GL_TEXTURE_2D:

glBindTexture(GL_TEXTURE_2D, gl_texture_object);

Pour charger les données réelles en texture, nous utilisons la commande:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
  • Le premier argument spécifie le type de texture. En d'autres termes GL_TEXTURE_2D, cela signifie que cette fonction associera l'objet de texture (précédemment passé bind) à une texture 2D (donc, si nous en avons un GL_TEXTURE_1D ou un GL_TEXTURE_3D, ils ne seront pas affectés).
  • Le deuxième argument spécifie le niveau de mipmap pour lequel nous voulons créer l'image. Nous allons expliquer ce qu'est un mipmap sur le laboratoire. Pour l'instant, on peut laisser cette valeur 0.
  • Le troisième argument spécifie le format dans lequel nous voulons que l'image soit stockée. Dans notre cas, c'est RVB.
  • Les 4ème et 5ème arguments définissent la taille de l'image.
  • Le prochain argument doit toujours être 0 (héritage)
  • Les arguments 7 et 8 spécifient le format et le type de données de l'image source.
  • Le dernier argument est le vecteur de données de l'image.

Par conséquent, glTextImage2Dchargez une image définie par les données réelles , c’est-à-dire un tableau de unsigned chars, sur l’objet texturé lié au point de reliure de GL_TEXTURE_2D l’ unité de texturation active au moment courant, niveau 0 (nous prendrons cette constante comme telle pour le moment), avec le format interne GL_RGBavec la largeur largeur et la hauteur hauteur, à partir du format GL_RGB. Les données lues sont de type GL_UNSIGNED_BYTE(c'est-à-dire unsigned char) et sont lues à partir de l'adresse donnée.

Utilisation de la texture

Pour utiliser une texture dans le shader, vous devez suivre ce processus:

glActiveTexture(GL_TEXTURE0);
 
glBindTexture(GL_TEXTURE_2D, texture1->GetTextureID());
 
glUniform1i(glGetUniformLocation(shader->program, "texture_1"), 0);
 
glActiveTexture(GL_TEXTURE1);
 
glBindTexture(GL_TEXTURE_2D, texture2->GetTextureID());
 
glUniform1i(glGetUniformLocation(shader->program, "texture_2"), 1);

L'unité de texture est utile lorsque vous souhaitez affecter la texture à une variable uniforme dans le shader. Le but de ce mécanisme est de nous permettre d’utiliser plus d’une texture dans notre shader. En utilisant les unités de texturation, nous pouvons nous lier à plusieurs textures, à condition de les définir comme actives.

OpenGl dispose d'un minimum de 16 unités de texturation pouvant être activées à l'aide GL_TEXTURE0de GL_TEXTURE15. Il n'est pas nécessaire de spécifier manuellement le numéro, car l'unité de texturation avec le numéro Xpeut être activée à l'aide de GL_TEXTURE0 + X.

Le code suivant est un exemple de shader pouvant utiliser la liaison précédente, où il texcoordreprésente les coordonnées de texture reçues en tant qu'attributs dans le vertex shader, puis transmises au rasterizer pour interpolation:

#version 330
 
uniform sampler2D texture_1;
 
in vec2 texcoord;
 
layout(location = 0) out vec4 out_color;
 
void main()
{
	vec4 color = texture2D(texture_1, texcoord);         
	out_color = color;
}

La multitexturation est utile lorsque nous représentons un objet topologique complexe, par exemple des feuilles, avec une texture dont la topologie est beaucoup moins complexe que la complexité (par exemple un quad). Si nous utilisons une telle réduction de complexité, nous devons disposer d’une méthode permettant d’éliminer, au niveau des fragments, les fragments inutiles (mis en évidence dans l’image suivante).

Pour cela, nous pouvons utiliser une texture d'opacité (alpha) qui nous indique quels sont les fragments réels et visibles de l'objet. La combinaison de texture d'opacité et de texture de couleur est suffisante pour définir ce bambou:

Pour omettre les fragments de dessin qui ne sont pas visibles, utilisez la directive shader discard.

if (alpha == 0) {
	discard;
}

Filtrare

Les coordonnées par lesquelles les sommets de l'objet sont mappés sur la texture ne dépendent pas de la résolution de l'image, mais correspondent à des valeurs comprises floatdans l'intervalle $[0, 1]$, , et OpenGL doit déterminer quel pixel de texture mapper pour les coordonnées données. Pour résoudre ce problème, le filtrage est utilisé, qui est une méthode d'échantillonnage et de reconstruction d'un signal.

La reconstruction est le processus par lequel, en utilisant ces pixels, nous pouvons obtenir des valeurs pour n’importe laquelle des positions dans la texture (c’est-à-dire, pas nécessairement exactement aux coordonnées au milieu du pixel, où la réalité dans l’espace post-projection a été échantillonnée).

Pour faciliter ce processus, OpenGL dispose d’un certain nombre de filtres qui peuvent être utilisés pour obtenir le mappage souhaité. Les plus couramment utilisés sont: GL_NEARESTet GL_LINEAR.

GL_NEAREST(également appelé filtrage par le voisin le plus proche ) est le filtrage par défaut pour OpenGL. Lors de l'utilisation de ce filtre, OpenGL sélectionne le pixel dont le centre est le plus proche des coordonnées de la texture. Ci-dessous, vous pouvez voir 4 pixels où la croix représente exactement les coordonnées de la texture. Le texte en haut à gauche a le centre le plus proche de la coordonnée de la texture et est choisi comme suit:

GL_LINEAR (connu sous le nom de filtrage bilinéaire) prend la valeur interpolée à partir des textures voisines de la coordonnée de texture, permettant ainsi une meilleure approximation de la couleur. Plus la distance entre la coordonnée de texture et le centre du texel est petite, plus la contribution couleur de ce texel est importante. Ci-dessous, nous pouvons voir comment le pixel est retourné

Différents filtres peuvent être utilisés dans le cas où nous voulons agrandir l’image et lorsque nous voulons la réduire ( upscaling et downscaling ). Le filtre est spécifié en utilisant la méthode glTextPameter:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

Mipmaps

Imaginez si nous avons une salle pleine d'objets qui utilisent la même texture mais sont dans des positions différentes. Ceux qui sont plus éloignés apparaîtront plus petits que ceux qui sont plus proches, mais tous auront la même texture haute résolution.

Parce que les objets distants n'utiliseront probablement que quelques fragments de l'image de base, OpenGL aura du mal à obtenir la couleur de la texture haute résolution car il doit choisir la couleur pouvant représenter une très grande partie de la texture. La compression d'une grande partie de la texture en un seul fragment peut générer des artefacts visibles pour les petits objets, outre le gaspillage de mémoire, en utilisant une texture plus grande pour les petits objets.

Pour résoudre ce problème, un concept appelé mipmap est utilisé . Il s'agit en fait d'une collection de copies de la même image, chaque copie étant deux fois plus petite que la copie précédente. L'idée derrière le concept de mipmap est assez simple: après un certain seuil de distance, OpenGL utilisera une texture de mipmap plus petite pour cet objet. Étant donné que l'objet se trouve à distance, le fait que la résolution soit inférieure ne sera pas observé par l'utilisateur. Une texture mipmap ressemble à ceci:

La création manuelle de textures mipmaps est assez fastidieuse. OpenGL peut donc effectuer tout ce processus automatiquement, à l'aide de la fonction glGenerateMipmap(GL_TEXTURE_2D); créée après la création de la texture.

Lorsque vous regardez un objet sous un certain angle, OpenGL peut basculer entre différents niveaux de textures mipmap, ce qui peut entraîner des artefacts, comme indiqué dans l'image ci-dessous:

Tout comme le filtrage normal, il est possible d’utiliser un filtrage entre différents niveaux de mipmaps NEARESTet LINEARlors du basculement entre les niveaux. Pour spécifier ce type de filtre, nous remplaçons le filtrage précédent par les 4 options suivantes:

  • GL_NEAREST_MIPMAP_NEAREST : utilise la texture mipmap la plus proche et utilise l'interpolation du voisin le plus proche pour choisir la couleur.
  • GL_LINEAR_MIPMAP_NEAREST : utilise la texture mipmap la plus proche et utilise une interpolation linéaire pour obtenir la couleur.
  • GL_NEAREST_MIPMAP_LINEAR : interpole linéairement entre les deux textures de mipmap les plus proches et utilise l'interpolation du voisin le plus proche pour obtenir la couleur
  • GL_LINEAR_MIPMAP_LINEAR : Linéaire interpole entre les deux textures de mipmap les plus proches et utilise une interpolation linéaire pour obtenir des couleurs.

Exemple d'utilisation:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
 
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

Une erreur courante consiste à utiliser le filtre mipmap pour la taille de l'image. Ce filtre n'aura aucun effet car les textures de mipmap sont principalement utilisées lorsque les objets deviennent plus petits . L'augmentation de la texture n'utilise pas mipmap et si je donne un tel filtre, nous obtiendrons une erreur du type GL_INVALID_ENUM.

Pour plus d'informations et de détails sur le filtrage, consultez la page glTexParameter

Exercices

  1. Completez la fonction RenderSimpleMesh afin d’envoyer la texture correctement à Shader
  2. Completez les coordonnées de texture pour le carré
  3. Completez le shader pour qu'il utilise les coordonnées de la texture
  4. Completez le shader de manière avec l'alpha discard
    • En laboratoire, une image différente ne sera pas utilisée pour la suppression alpha mais testera la composante alpha dans la texture.
    • Ex: Discard si la valeur alpha est inférieure à 0.5f
  5. Créer et charger une texture aléatoire sur le GPU
    • compléter la fonction Laborator9::CreateRandomTexture
    • ! généré le mipmaps : glGenerateMipmap(GL_TEXTURE_2D);
    • la texture sera utilisée dans le rendu sur le cube de gauche
  6. Modifiez les filtres GL_TEXTURE_MIN_FILTER et GL_TEXTURE_MAG_FILTER et notez les différences d'affichage
    • GL_TEXTURE_MAG_FILTER vous n'avez que 2 valeurs possibles
      • GL_LINEAR
      • GL_NEAREST
    • GL_TEXTURE_MIN_FILTER vous n'avez que 6 valeurs possibles
      • GL_NEAREST
      • GL_LINEAR
      • GL_NEAREST_MIPMAP_NEAREST
      • GL_LINEAR_MIPMAP_NEAREST
      • GL_NEAREST_MIPMAP_LINEAR
      • GL_LINEAR_MIPMAP_LINEAR
  7. Rendre un quad en utilisant 2 textures. Utilisez la fonction mix() dans le fragment shader pour obtenir une combinaison des deux textures.
vec3 color = mix(color1, color2, 0.5f); // le dernier paramètre représente le facteur d'interpolation entre les 2 couleurs, avec une valeur comprise entre 0 et 1

Bonus:

  1. Envoyez le temps d'application au fragment shader Engine::GetElapsedTime() et utilisez-le pour parcourir la texture du globe terrestre (sur la coordonnée OY) (uniquement pour cet objet, une variable uniforme est donc nécessaire pour tester l'objet rendu).
  2. Faites pivoter dans la direction de la pièce (uniquement sur OY) la texture d’herbe quadruplée de sorte qu’elle soit toujours orientée vers la caméra.
egc/laboratoare/fr/09.txt · Last modified: 2019/12/05 08:09 by alexandru.gradinaru
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0