Différences et relation entre glActiveTexture et glBindTexture

137

D'après ce que je comprends, glActiveTexturedéfinit "l'unité de texture" active. Chaque unité de texture peut avoir plusieurs cibles de texture (généralement GL_TEXTURE_1D, 2D, 3D ou CUBE_MAP).

Si je comprends bien, vous devez d'abord appeler glActiveTexturepour définir l'unité de texture (initialisée à GL_TEXTURE0), puis vous liez (une ou plusieurs) "cibles de texture" à cette unité de texture?

Le nombre d'unités de texture disponibles dépend du système. Je vois des énumérations pour jusqu'à 32 dans ma bibliothèque. Je suppose que cela signifie essentiellement que je peux avoir la plus petite de la limite de mon GPU (ce que je pense est168) et 32 ​​textures dans la mémoire du GPU à la fois? Je suppose qu'il y a une limite supplémentaire pour ne pas dépasser la mémoire maximale de mon GPU (supposément 1 Go).

Est-ce que je comprends correctement la relation entre les cibles de texture et les unités de texture? Disons que j'ai droit à 16 unités et 4 cibles chacune, cela signifie-t-il qu'il y a de la place pour 16 * 4 = 64 cibles, ou cela ne fonctionne-t-il pas comme ça?

Ensuite, vous souhaitez généralement charger une texture. Vous pouvez le faire via glTexImage2D. Le premier argument est une cible de texture. Si cela fonctionne commeglBufferData , alors nous lions essentiellement le "handle" / "nom de la texture" à la cible de texture, puis chargeons les données de texture dans cette cible, et donc l'associons indirectement à ce handle.

Et quoi glTexParameter? Nous devons lier une cible de texture, puis choisir à nouveau cette même cible comme premier argument? Ou la cible de texture n'a-t-elle pas besoin d'être liée tant que nous avons la bonne unité de texture active?

glGenerateMipmap fonctionne sur une cible aussi ... cette cible doit encore être liée au nom de la texture pour qu'elle réussisse?

Ensuite, lorsque nous voulons dessiner notre objet avec une texture dessus, devons-nous choisir à la fois une unité de texture active, puis une cible de texture? Ou choisissons-nous une unité de texture, puis nous pouvons récupérer les données de l'une des 4 cibles associées à cette unité? C'est la partie qui me déroute vraiment.

mpen
la source

Réponses:

259

Tout sur les objets OpenGL

Le modèle standard pour les objets OpenGL est le suivant.

Les objets ont un état. Considérez-les comme un struct. Ainsi, vous pourriez avoir un objet défini comme ceci:

struct Object
{
    int count;
    float opacity;
    char *name;
};

L'objet a certaines valeurs stockées et il a un état . Les objets OpenGL ont également un état.

Changement d'état

En C / C ++, si vous avez une instance de type Object, vous changeriez son état comme suit: obj.count = 5;vous référeneriez directement une instance de l'objet, obtiendrez l'élément d'état particulier que vous souhaitez modifier et y inséreriez une valeur.

Dans OpenGL, vous ne faites pas cela.

Pour des raisons d'héritage qu'il vaut mieux laisser inexpliquées, pour changer l'état d'un objet OpenGL, vous devez d'abord le lier au contexte. Ceci est fait avec certains de l' glBind*appel.

L'équivalent C / C ++ de ceci est le suivant:

Object *g_objs[MAX_LOCATIONS] = {NULL};    
void BindObject(int loc, Object *obj)
{
  g_objs[loc] = obj;
}

Les textures sont intéressantes; ils représentent un cas particulier de liaison. De nombreux glBind*appels ont un paramètre "cible". Cela représente différents emplacements dans le contexte OpenGL où les objets de ce type peuvent être liés. Par exemple, vous pouvez lier un objet framebuffer pour la lecture ( GL_READ_FRAMEBUFFER) ou pour l'écriture ( GL_DRAW_FRAMEBUFFER). Cela affecte la façon dont OpenGL utilise le tampon. C'est ce que représente le locparamètre ci-dessus.

Les textures sont spéciales car lorsque vous les liez pour la première fois à une cible, elles obtiennent des informations spéciales. Lorsque vous liez une texture pour la première fois en tant que GL_TEXTURE_2D, vous définissez en fait un état spécial dans la texture. Vous dites que cette texture est une texture 2D. Et ce sera toujours une texture 2D; cet état ne peut pas être changé jamais . Si vous avez une texture qui a d'abord été liée en tant que a GL_TEXTURE_2D, vous devez toujours la lier en tant que GL_TEXTURE_2D; tenter de le lier comme GL_TEXTURE_1Dcela entraînera une erreur (lors de l'exécution).

Une fois l'objet lié, son état peut être modifié. Cela se fait via des fonctions génériques spécifiques à cet objet. Eux aussi prennent un emplacement qui représente l'objet à modifier.

En C / C ++, cela ressemble à:

void ObjectParameteri(int loc, ObjectParameters eParam, int value)
{
  if(g_objs[loc] == NULL)
    return;

  switch(eParam)
  {
    case OBJECT_COUNT:
      g_objs[loc]->count = value;
      break;
    case OBJECT_OPACITY:
      g_objs[loc]->opacity = (float)value;
      break;
    default:
      //INVALID_ENUM error
      break;
  }
}

Notez comment cette fonction définit tout ce qui se trouve être dans la locvaleur actuellement liée .

Pour les objets de texture, les principales fonctions de changement d'état de texture sont glTexParameter. Les seules autres fonctions que l' état de texture de changement sont les glTexImagefonctions et leurs variations ( glCompressedTexImage, glCopyTexImage, la récente glTexStorage). Les différentes SubImageversions changent le contenu de la texture, mais elles ne changent pas techniquement son état . Les Imagefonctions allouent le stockage de texture et définissent le format de la texture; les SubImagefonctions copient simplement les pixels autour. Cela n'est pas considéré comme l'état de la texture.

Permettez-moi de répéter: ce sont les seules fonctions qui modifient l'état de la texture. glTexEnvmodifie l'état de l'environnement; cela n'affecte rien de stocké dans les objets de texture.

Texture active

La situation des textures est plus complexe, encore une fois pour des raisons d'héritage qu'il vaut mieux ne pas divulguer. C'est là glActiveTexturequ'intervient.

Pour les textures, il n'y a pas des cibles (seulement GL_TEXTURE_1D, GL_TEXTURE_CUBE_MAP, etc.). Il existe également des unités de texture . En termes de notre exemple C / C ++, voici ce que nous avons:

Object *g_objs[MAX_OBJECTS][MAX_LOCATIONS] = {NULL};
int g_currObject = 0;

void BindObject(int loc, Object *obj)
{
  g_objs[g_currObject][loc] = obj;
}

void ActiveObject(int currObject)
{
  g_currObject = currObject;
}

Notez que maintenant, nous avons non seulement une liste 2D de Objects, mais nous avons également le concept d'un objet courant. Nous avons une fonction pour définir l'objet courant, nous avons le concept d'un nombre maximum d'objets courants, et toutes nos fonctions de manipulation d'objets sont ajustées pour sélectionner à partir de l'objet courant.

Lorsque vous modifiez l'objet actuellement actif, vous modifiez l'ensemble des emplacements cibles. Ainsi, vous pouvez lier quelque chose qui va dans l'objet actuel 0, passer à l'objet actuel 4 et modifier un objet complètement différent.

Cette analogie avec les objets de texture est parfaite ... presque.

Voir, glActiveTexturene prend pas un entier; il faut un enquêteur . Ce qui signifie en théorie qu'il peut prendre n'importe quoi de GL_TEXTURE0à GL_TEXTURE31. Mais il y a une chose que vous devez comprendre:

C'EST FAUX!

La plage réelle que glActiveTexturepeut prendre est régie par GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS. C'est le nombre maximum de multitextures simultanées qu'une implémentation permet. Ceux-ci sont chacun divisés en différents groupes pour différentes étapes de shader. Par exemple, sur le matériel de classe GL 3.x, vous obtenez 16 textures de shader de sommet, 16 textures de shader de fragment et 16 textures de shader de géométrie. Par conséquent, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITSsera 48.

Mais il n'y a pas 48 recenseurs. C'est pourquoi glActiveTexturene prend pas vraiment les recenseurs. La manière correcte d'appeler glActiveTextureest la suivante:

glActiveTexture(GL_TEXTURE0 + i);

iest un nombre entre 0 et GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS.

Le rendu

Alors qu'est-ce que tout cela a à voir avec le rendu?

Lorsque vous utilisez des shaders, vous définissez les uniformes de votre échantillonneur sur une unité d'image de texture ( glUniform1i(samplerLoc, i), où iest l'unité d'image). Cela représente le nombre que vous avez utilisé glActiveTexture. L'échantillonneur choisira la cible en fonction du type d'échantillonneur. Donc, un sampler2Dchoisira de la GL_TEXTURE_2Dcible. C'est l'une des raisons pour lesquelles les échantillonneurs ont différents types.

Maintenant, cela semble suspect que vous pouvez avoir deux échantillonneurs GLSL, avec différents types qui utilisent la même unité d'image de texture. Mais vous ne pouvez pas; OpenGL interdit cela et vous donnera une erreur lorsque vous tenterez de rendre.

Nicol Bolas
la source
12
Hou la la! Encore une réponse merveilleuse - merci Nicol! J'aime particulièrement ce paragraphe sur une texture 2D étant toujours une texture 2D. Je suis en train de créer une enveloppe autour de certaines de ces choses maintenant, et je ne savais pas si je devais laisser cela ouvert au changement. Et la partie sur GL_TEXTURE0 + i- je voulais inspecter les valeurs d'énumération pour voir si cela était valide ou non. Et le dernier paragraphe - je ne savais pas si c'était légal ou non. Excellent! Je mets en favori toutes vos réponses afin que je puisse les consulter à nouveau.
mpen
6
@Nicol Bolas: C'est vraiment bien expliqué. Vous devriez en copier une partie dans le chapitre sur les textures de votre livre opengl en ligne. Je pense que c'est tellement plus clair et complimenterait bien le chapitre.
WesDec
3
@Nicol Bolas Je commence tout juste à apprendre OpenGL et cette réponse m'a beaucoup aidé. Je vous remercie!
ligne
2
Hey nico, je veux juste souligner votre petite faute de frappe: c'est GL_DRAW_FRAMEBUFFER pas GL_WRITE_FRAMEBUFFER
Defd le
3
@Nicol: Wow, la meilleure définition que j'en avais avant était de vos tutoriels d'arcsynthèse, maintenant vous avez surpassé même cette source brillante. Thankyou
Baggers
20

Je vais essayer ! Tout cela n'est pas si compliqué, juste une question de termes, j'espère que je vais être clair.


Vous pouvez créer à peu près autant d' objets de texture que de mémoire disponible dans votre système. Ces objets contiennent les données réelles (texels) de vos textures, ainsi que les paramètres, fournis par glTexParameter (voir FAQ ).

Au moment de la création, vous devez attribuer une texture cible à un objet de texture, ce qui représente le type de la texture ( GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE, ...).

Ces deux éléments, objet de texture et cible de texture, représentent les données de texture. Nous y reviendrons plus tard.

Unités de texture

Maintenant, OpenGL fournit un tableau d' unités de texture , qui peuvent être utilisées simultanément pendant le dessin. La taille du tableau dépend du système OpenGL, la vôtre en a 8.

Vous pouvez lier un objet de texture à une unité de texture pour utiliser la texture donnée lors du dessin.

Dans un monde simple et facile, pour dessiner avec une texture donnée, vous lieriez un objet de texture à l'unité de texture, et vous feriez (pseudocode):

glTextureUnit[0] = textureObject

Comme GL est une machine à états, hélas, cela ne fonctionne pas de cette façon. En supposant que notre textureObjectpossède des données pour la GL_TEXTURE_2Dcible de texture, nous exprimerons l'affectation précédente comme suit:

glActiveTexture(GL_TEXTURE0);                   // select slot 0 of the texture units array
glBindTexture(GL_TEXTURE_2D, textureObject);    // do the binding

Notez que GL_TEXTURE_2Dcela dépend vraiment du type de texture que vous souhaitez lier.

Objets de texture

Dans le pseudo code, pour définir des données de texture ou des paramètres de texture, vous feriez par exemple:

setTexData(textureObject, ...)
setTexParameter(textureObject, TEXTURE_MIN_FILTER, LINEAR)

OpenGL ne peut pas manipuler directement les objets de texture, mettre à jour / définir leur contenu, ou modifier leurs paramètres, vous devez d'abord les lier à l'unité de texture active (quelle qu'elle soit). Le code équivalent devient:

glBindTexture(GL_TEXTURE_2D, textureObject)       // this 'installs' textureObject in texture unit
glTexImage2D(GL_TEXTURE_2D, ...)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)

Shaders

Les shaders ont accès à toutes les unités de texture, ils ne se soucient pas de la texture active.

Les uniformes de l'échantillonneur sont des intvaleurs représentant l'indice de l'unité de texture à utiliser pour l'échantillonneur (et non l'objet de texture à utiliser).

Vous devez donc lier vos objets de texture aux unités que vous souhaitez utiliser.

Le type de l'échantillonneur fera la correspondance avec la cible de texture qui est utilisée dans l'unité de texture: Sampler2Dpour GL_TEXTURE_2D, et ainsi de suite ...

rotoglup
la source
Une chose que je ne comprends pas. Supposons que j'ai une certaine texture et qu'elle est utilisée dans de nombreux shaders sur différentes unités de texture. Supposons que je veuille changer le filtrage de texture au moment de l'exécution. Quelle unité de texture dois-je utiliser? Puis-je changer l'état de la texture sur l'unité 0 et ensuite utiliser cette texture sur une unité différente?
majakthecoder
@majakthecoder Dans ma réponse, je considère le filtrage comme une propriété de l' objet texture - ce qui signifie que vous ne pouvez pas le modifier spécifiquement dans une unité de texture. Selon la saveur d'OpenGL que vous ciblez, vous pourrez peut-être échantillonner des objets pour résoudre ce problème ( opengl.org/wiki/Sampler_Object ), sinon, vous devrez peut-être dupliquer l'objet de texture, pour avoir plusieurs filtrages simultanés.
rotoglup
12

Imaginez le GPU comme une usine de traitement de peinture.

Il existe un certain nombre de réservoirs qui fournissent du colorant à une machine à peindre. Dans la machine à peindre, le colorant est ensuite appliqué sur l'objet. Ces chars sont les unités de texture

Ces réservoirs peuvent être équipés de différents types de colorants. Chaque type de colorant nécessite un autre type de solvant. Le "solvant" est la cible de la texture . Pour plus de commodité, chaque réservoir est connecté à une alimentation en solvant, mais un seul type de solvant peut être utilisé à la fois dans chaque réservoir. Donc , il y a une vanne / commutateur TEXTURE_CUBE_MAP, TEXTURE_3D, TEXTURE_2D, TEXTURE_1D. Vous pouvez remplir tous les types de colorants dans le réservoir en même temps, mais comme un seul type de solvant entre, il "diluera" uniquement le type de colorant correspondant. Vous pouvez donc avoir chaque type de texture lié, mais la liaison avec le solvant "le plus important" ira en fait dans le réservoir et se mélangera avec le type de colorant auquel elle appartient.

Et puis il y a le colorant lui-même, qui vient d'un entrepôt et est rempli dans le réservoir en le "liant". C'est ta texture.

datenwolf
la source
2
Une sorte d'analogie étrange ... Je ne suis pas sûr que cela clarifie vraiment quoi que ce soit. Surtout la partie sur la "dilution" et le "solvant le plus important". Vous dites que si je lie à la fois une texture 2D et une texture 3D, je ne peux utiliser qu'une seule d'entre elles, ou quoi? Lequel serait considéré comme le plus important?
mpen
2
@Mark: Eh bien, j'essayais de parler en termes d'un peintre qui travaille avec un colorant littéral (disons à base d'huile et à base d'eau). Quoi qu'il en soit, oui si vous liez et activez plusieurs cibles de texture, il y a la priorité: CUBE_MAP> 3D> TEXTURE_ARRAY> 2D> 1D.
datenwolf
1
Soigné! Je ne connaissais pas la préséance. Cela a plus de sens maintenant que je sais qu'une seule cible de texture peut être utilisée par unité de texture.
mpen
1
@ legends2k: Eh bien, maintenant ça devient intéressant. Parlons-nous du noyau ou du profil de compatibilité. Supposons-nous des pilotes idéaux ou bogués. En théorie, le type d'uniforme sélectionne la cible de l'unité de texture à sélectionner. En pratique, cela se produit dans le profil de base. Dans le profil de compatibilité, attendez-vous à ce que certains pilotes bogués vous présentent la texture par défaut entièrement blanche si la cible précédente de l'unité de texture ne correspond pas au type de l'échantillonneur.
datenwolf
1
@ legends2k: Pensez également à ce qui se passerait s'il y avait une texture 2D et une texture 3D liées à la même unité et que vous aviez un uniforme d'échantillonneur 2D et 3D, que vous liez à la même unité? Vous pouvez déclencher toutes sortes de bogues de pilotes étranges avec cela. En pratique, penser à l'ancien modèle de priorité des fonctions fixes garde votre esprit sain d'esprit et votre programme fonctionne, car c'est ainsi que la plupart des pilotes se comporteront de manière prévisible.
datenwolf
2

Si dans votre shader vous avez besoin d'une recherche à partir de 2 textures:

uniform sampler2D tex1;  
uniform sampler2D tex2;  

il faut indiquer pour tex1 et tex2 leurs sources comme suit:

tex1 = gl.createTexture();  
gl.activeTexture(gl.TEXTURE3);  
gl.bindTexture(gl.TEXTURE_2D, tex1); 
gl.texParameteri(gl.TEXTURE_2D, ...);  
....


tex2 = gl.createTexture();  
gl.activeTexture(gl.TEXTURE7);  
gl.bindTexture(gl.TEXTURE_2D, tex2); 
gl.texParameteri(gl.TEXTURE_2D, ...);  
....  
var tex1Loc  = gl.getUniformLocation(your_shader,"tex1");  
var tex2Loc  = gl.getUniformLocation(your_shader,"tex2");

dans la boucle de rendu:

gl.uniform1i(tex1Loc, 3);  
gl.uniform1i(tex2Loc, 7);  
// but you can dynamically change these values

Avec une gl_bindtexture, pas possible de faire une telle chose. Par contre une utilisation possible d'un bind dans la boucle de rendu, est le cas où vous alimentez une texture avec un contenu en flux (vidéo, webcam):

gl.bindTexture(gl.TEXTURE_2D, tex1);  
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);  
// in the render loop
Philippe Oceangermanique
la source