Unterschiede und Beziehung zwischen glActiveTexture und glBindTexture

137

Soweit ich weiß, glActiveTexturesetzt die aktive "Textureinheit". Jede Textureinheit kann mehrere Texturziele haben (normalerweise GL_TEXTURE_1D, 2D, 3D oder CUBE_MAP).

Wenn ich das richtig verstehe, müssen Sie glActiveTexturezuerst die Textureinheit (initialisiert auf GL_TEXTURE0) aufrufen und dann (ein oder mehrere) "Texturziele" an diese Textureinheit binden?

Die Anzahl der verfügbaren Textureinheiten ist systemabhängig. Ich sehe Aufzählungen für bis zu 32 in meiner Bibliothek. Ich denke, dies bedeutet im Wesentlichen, dass ich das geringere Limit meiner GPU haben kann (was ich denke168) und 32 Texturen gleichzeitig im GPU-Speicher? Ich denke, es gibt ein zusätzliches Limit, bei dem ich den maximalen Speicher meiner GPU (angeblich 1 GB) nicht überschreite.

Verstehe ich die Beziehung zwischen Texturzielen und Textureinheiten richtig? Nehmen wir an, ich darf jeweils 16 Einheiten und 4 Ziele haben. Bedeutet das, dass Platz für 16 * 4 = 64 Ziele ist, oder funktioniert das nicht so?

Als nächstes möchten Sie normalerweise eine Textur laden. Sie können dies über tun glTexImage2D. Das erste Argument ist ein Texturziel. Wenn dies so funktioniertglBufferData , binden wir im Wesentlichen den "Handle" / "Texturnamen" an das Texturziel und laden dann die Texturdaten in dieses Ziel und ordnen sie somit indirekt diesem Handle zu.

Was ist mit glTexParameter? Müssen wir ein Texturziel binden und dann dasselbe Ziel erneut als erstes Argument auswählen? Oder muss das Texturziel nicht gebunden werden, solange wir die richtige aktive Textureinheit haben?

glGenerateMipmap funktioniert auch auf einem Ziel ... muss dieses Ziel noch an den Texturnamen gebunden sein, damit es erfolgreich ist?

Wenn wir dann unser Objekt mit einer Textur darauf zeichnen möchten, müssen wir dann beide eine aktive Textureinheit und dann ein Texturziel auswählen? Oder wählen wir eine Textureinheit und können dann Daten von einem der 4 Ziele abrufen, die dieser Einheit zugeordnet sind? Dies ist der Teil, der mich wirklich verwirrt.

mpen
quelle

Antworten:

259

Alles über OpenGL-Objekte

Das Standardmodell für OpenGL-Objekte lautet wie folgt.

Objekte haben Zustand. Betrachten Sie sie als struct. Möglicherweise haben Sie ein Objekt wie folgt definiert:

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

Das Objekt enthält bestimmte Werte und den Status . OpenGL-Objekte haben auch Status.

Status ändern

Wenn Sie in C / C ++ eine Instanz vom Typ haben Object, ändern Sie ihren Status wie folgt: obj.count = 5;Sie verweisen direkt auf eine Instanz des Objekts, erhalten den bestimmten Status, den Sie ändern möchten, und verschieben einen Wert in diesen.

In OpenGL tun Sie dies nicht .

Um den Status eines OpenGL-Objekts zu ändern, müssen Sie es aus früheren Gründen, die besser ungeklärt bleiben, zunächst an den Kontext binden . Dies geschieht mit einigen von glBind*Anrufen.

Das C / C ++ - Äquivalent dazu lautet wie folgt:

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

Texturen sind interessant; Sie stellen einen Sonderfall der Bindung dar. Viele glBind*Aufrufe haben einen "Ziel" -Parameter. Dies stellt verschiedene Stellen im OpenGL-Kontext dar, an denen Objekte dieses Typs gebunden werden können. Sie können beispielsweise ein Framebuffer-Objekt zum Lesen ( GL_READ_FRAMEBUFFER) oder zum Schreiben ( GL_DRAW_FRAMEBUFFER) binden . Dies wirkt sich darauf aus, wie OpenGL den Puffer verwendet. Dies ist, was der locobige Parameter darstellt.

Texturen sind etwas Besonderes, da sie beim ersten Binden an ein Ziel besondere Informationen erhalten. Wenn Sie eine Textur zum ersten Mal als binden, legen GL_TEXTURE_2DSie tatsächlich einen speziellen Status in der Textur fest. Sie sagen, dass diese Textur eine 2D-Textur ist. Und es wird immer eine 2D-Textur sein; Dieser Zustand kann niemals geändert werden . Wenn Sie eine Textur haben, die zuerst als gebunden wurde GL_TEXTURE_2D, müssen Sie sie immer als binden GL_TEXTURE_2D. Der Versuch, es so zu binden GL_TEXTURE_1D, führt zu einem Fehler (zur Laufzeit).

Sobald das Objekt gebunden ist, kann sein Status geändert werden. Dies erfolgt über generische Funktionen, die für dieses Objekt spezifisch sind. Auch sie nehmen einen Ort ein, der angibt, welches Objekt geändert werden soll.

In C / C ++ sieht dies folgendermaßen aus:

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;
  }
}

Beachten Sie, wie diese Funktion den aktuell gebundenen locWert festlegt .

Für Texturobjekte sind die Hauptfunktionen zum Ändern des Texturzustands glTexParameter. Die einzigen anderen Funktionen , dass Veränderungen Textur Zustand sind die glTexImageFunktionen und deren Variationen ( glCompressedTexImage, glCopyTexImagedie jüngste glTexStorage). Die verschiedenen SubImageVersionen ändern den Inhalt der Textur, ändern jedoch technisch nicht ihren Zustand . Die ImageFunktionen weisen Texturspeicher zu und legen das Format der Textur fest. Die SubImageFunktionen kopieren nur Pixel. Dies wird nicht als Zustand der Textur angesehen.

Lassen Sie mich wiederholen: Dies sind die einzigen Funktionen, die den Texturstatus ändern. glTexEnvändert den Umgebungszustand; Es wirkt sich nicht auf etwas aus, das in Texturobjekten gespeichert ist.

Aktive Textur

Die Situation für Texturen ist komplexer, wiederum aus alten Gründen, die am besten nicht bekannt gegeben werden. Hier kommt ins glActiveTextureSpiel.

Für Texturen gibt es nicht nur Ziele ( GL_TEXTURE_1D, GL_TEXTURE_CUBE_MAPusw.). Es gibt auch Textur - Einheiten . In Bezug auf unser C / C ++ - Beispiel haben wir Folgendes:

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;
}

Beachten Sie, dass wir jetzt nicht nur eine 2D-Liste von Objects haben, sondern auch das Konzept eines aktuellen Objekts. Wir haben eine Funktion zum Festlegen des aktuellen Objekts, wir haben das Konzept einer maximalen Anzahl aktueller Objekte und alle unsere Objektmanipulationsfunktionen werden angepasst, um aus dem aktuellen Objekt auszuwählen.

Wenn Sie das aktuell aktive Objekt ändern, ändern Sie den gesamten Satz von Zielpositionen. Sie können also etwas binden, das in das aktuelle Objekt 0 geht, zum aktuellen Objekt 4 wechseln und ein völlig anderes Objekt ändern.

Diese Analogie mit Texturobjekten ist perfekt ... fast.

Siehe, glActiveTexturenimmt keine ganze Zahl; es braucht einen Enumerator . Was theoretisch bedeutet, dass es alles von GL_TEXTURE0bis nehmen kann GL_TEXTURE31. Aber eines müssen Sie verstehen:

DAS IST FALSCH!

Die tatsächliche Reichweite, die erreicht werden glActiveTexturekann, wird von bestimmt GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS. Dies ist die maximale Anzahl gleichzeitiger Multitexturen, die eine Implementierung zulässt. Diese sind jeweils in verschiedene Gruppierungen für verschiedene Shader-Stufen unterteilt. Auf Hardware der GL 3.x-Klasse erhalten Sie beispielsweise 16 Vertex-Shader-Texturen, 16 Fragment-Shader-Texturen und 16 Geometrie-Shader-Texturen. Daher GL_MAX_COMBINED_TEXTURE_IMAGE_UNITSwird 48 sein.

Es gibt jedoch keine 48 Enumeratoren. Deshalb glActiveTexturebraucht man nicht wirklich Enumeratoren. Die richtige Art zu telefonieren glActiveTextureist wie folgt:

glActiveTexture(GL_TEXTURE0 + i);

wo iist eine Zahl zwischen 0 und GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS.

Rendern

Was hat das alles mit dem Rendern zu tun?

Wenn Sie Shader verwenden, stellen Sie Ihre Sampler-Uniformen auf eine Texturbildeinheit ein ( glUniform1i(samplerLoc, i)wo isich die Bildeinheit befindet). Das ist die Nummer, mit der Sie verwendet haben glActiveTexture. Der Sampler wählt das Ziel basierend auf dem Sampler-Typ aus. Also wird ein sampler2DWille aus dem GL_TEXTURE_2DZiel auswählen . Dies ist ein Grund, warum Sampler unterschiedliche Typen haben.

Das klingt verdächtig, als könnten Sie zwei GLSL-Sampler mit unterschiedlichen Typen verwenden, die dieselbe Texturbildeinheit verwenden. Aber du kannst nicht; OpenGL verbietet dies und gibt beim Rendern einen Fehler aus.

Nicol Bolas
quelle
12
Beeindruckend! Noch eine wundervolle Antwort - danke Nicol! Ich mag besonders diesen Absatz über eine 2D-Textur, die immer eine 2D-Textur ist. Ich baue jetzt eine Hülle um einige dieser Dinge und war mir nicht sicher, ob ich das offen lassen sollte, um mich zu ändern. Und der Teil über GL_TEXTURE0 + i- Ich wollte die Aufzählungswerte überprüfen, um festzustellen, ob dies gültig ist oder nicht. Und der letzte Absatz - wusste nicht, ob das legal ist oder nicht. Ausgezeichnet! Ich setze ein Lesezeichen für alle Ihre Antworten, damit ich wieder darauf verweisen kann.
Mpen
6
@Nicol Bolas: Das ist wirklich gut erklärt. Sie sollten einen Teil davon in das Texturkapitel in Ihrem Online-OpenGl-Buch kopieren. Ich denke, das ist so viel klarer und würde das Kapitel gut ergänzen.
WesDec
3
@Nicol Bolas Ich fange gerade an, OpenGL zu lernen und diese Antwort hat mir sehr geholfen. Danke dir!
Inline
2
Hey nico, ich möchte nur auf deinen kleinen Tippfehler hinweisen: Es ist GL_DRAW_FRAMEBUFFER, nicht GL_WRITE_FRAMEBUFFER
Defd
3
@Nicol: Wow, die beste Definition, die ich bisher hatte, war aus Ihren Arcsynthesis-Tutorials. Jetzt haben Sie sogar diese brillante Quelle übertroffen. Vielen Dank
Baggers
20

Ich werde es versuchen ! All dies ist nicht so kompliziert, nur eine Frage der Begriffe, hoffe ich werde mich klar machen.


Sie können ungefähr so ​​viele Texturobjekte erstellen, wie in Ihrem System Speicher verfügbar ist. Diese Objekte enthalten die tatsächlichen Daten (Texel) Ihrer Texturen sowie die von glTexParameter bereitgestellten Parameter (siehe FAQ ).

Wenn erstellt wird, müssen Sie eine zuweisen Texture Ziel einem Textur - Objekt, das den Typ der Textur darstellt ( GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE, ...).

Diese beiden Elemente, Texturobjekt und Texturziel, repräsentieren die Texturdaten. Wir werden später darauf zurückkommen.

Textureinheiten

Jetzt bietet OpenGL eine Reihe von Textureinheiten , die gleichzeitig beim Zeichnen verwendet werden können. Die Größe des Arrays hängt vom OpenGL-System ab, Ihr Array hat 8.

Sie können ein Texturobjekt an eine Textureinheit binden , um die angegebene Textur beim Zeichnen zu verwenden.

In einer einfachen Welt würden Sie zum Zeichnen mit einer bestimmten Textur ein Texturobjekt an die Textureinheit binden und Folgendes tun (Pseudocode):

glTextureUnit[0] = textureObject

Da der GL eine Zustandsmaschine ist, funktioniert er leider nicht auf diese Weise. Angenommen, wir textureObjecthaben Daten für das GL_TEXTURE_2DTexturziel, drücken wir die vorherige Zuordnung wie folgt aus:

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

Beachten Sie, dass dies GL_TEXTURE_2Dwirklich von der Art der Textur abhängt, die Sie binden möchten.

Texturobjekte

Im Pseudocode würden Sie zum Festlegen von Texturdaten oder Texturparametern beispielsweise Folgendes tun:

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

OpenGL kann Texturobjekte nicht direkt manipulieren, um ihren Inhalt zu aktualisieren / einzustellen oder ihre Parameter zu ändern. Sie müssen sie zuerst an die aktive Textureinheit binden (je nachdem, um welche es sich handelt). Der entsprechende Code lautet:

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)

Shader

Shader haben Zugriff auf alle Textureinheiten, die aktive Textur ist ihnen egal.

Sampler-Uniformen sind intWerte, die den Index der für den Sampler zu verwendenden Textureinheit darstellen (und nicht das zu verwendende Texturobjekt).

Sie müssen also Ihre Texturobjekte an die Einheiten binden, die Sie verwenden möchten.

Der Typ des Samplers stimmt mit dem Texturziel überein, das in der Textureinheit verwendet wird: Sampler2Dfür GL_TEXTURE_2Dusw.

Rotoglup
quelle
Eine Sache verstehe ich nicht. Nehmen wir an, ich habe eine Textur und diese wird in vielen Shadern auf verschiedenen Textureinheiten verwendet. Nehmen wir an, ich möchte die Texturfilterung zur Laufzeit ändern. Welche Textureinheit soll ich verwenden? Kann ich den Texturstatus auf Einheit 0 ändern und diese Textur dann auf einer anderen Einheit verwenden?
Majakthecoder
@majakthecoder In meiner Antwort halte ich die Filterung als eine Eigenschaft des Textur - Objekt - das bedeutet , dass Sie es nicht speziell in einer Textureinheit ändern kann. Abhängig von der Version von OpenGL, auf die Sie abzielen, können Sie möglicherweise Objekte abtasten, um dieses Problem zu lösen ( opengl.org/wiki/Sampler_Object ). Andernfalls müssen Sie möglicherweise das Texturobjekt duplizieren, um mehrere Filter gleichzeitig ausführen zu können.
Rotoglup
12

Stellen Sie sich die GPU wie eine Lackieranlage vor.

Es gibt eine Reihe von Tanks, die Farbstoffe an einige Lackiermaschinen liefern. In der Lackiermaschine wird der Farbstoff dann auf das Objekt aufgetragen. Diese Tanks sind die Textureinheiten

Diese Tanks können mit verschiedenen Farbstoffen ausgestattet werden. Jede Art von Farbstoff erfordert eine andere Art von Lösungsmittel. Das "Lösungsmittel" ist das Texturziel . Der Einfachheit halber ist jeder Tank an eine Lösungsmittelversorgung angeschlossen, und in jedem Tank kann jeweils nur eine Art von Lösungsmittel verwendet werden. So gibt es ein Ventil / Schalter TEXTURE_CUBE_MAP, TEXTURE_3D, TEXTURE_2D, TEXTURE_1D. Sie können alle Farbstofftypen gleichzeitig in den Tank füllen. Da jedoch nur eine Art von Lösungsmittel eindringt, wird nur die Art der Farbstoffanpassung "verdünnt". Sie können also jede Art von Textur binden lassen, aber die Bindung mit dem "wichtigsten" Lösungsmittel wird tatsächlich in den Tank gelangen und sich mit der Art des Farbstoffs mischen, zu dem sie gehört.

Und dann ist da noch der Farbstoff selbst, der aus einem Lager stammt und durch "Binden" in den Tank gefüllt wird. Das ist deine Textur.

Datenwolf
quelle
2
Eine seltsame Analogie ... Ich bin mir nicht sicher, ob sie wirklich etwas aufklärt. Besonders der Teil über das "Verdünnen" und "wichtigste Lösungsmittel". Sie sagen, wenn ich sowohl eine 2D-Textur als auch eine 3D-Textur binde, kann ich nur eine davon verwenden, oder was? Welches wäre das wichtigste?
Mpen
2
@ Mark: Nun, ich habe versucht, in Bezug auf einen Maler zu sprechen, der mit wörtlichen Farbstoffen arbeitet (sagen wir auf Öl- und Wasserbasis). Ja, wenn Sie mehrere Texturziele binden und aktivieren, hat dies Vorrang: CUBE_MAP> 3D> TEXTURE_ARRAY> 2D> 1D.
Datenwolf
1
Ordentlich! Ich wusste nichts über den Vorrang. Das macht jetzt mehr Sinn, da ich weiß, dass nur ein Texturziel pro Textureinheit verwendet werden kann.
Mpen
1
@ legends2k: Nun, jetzt wird es interessant. Sprechen wir über den Kern oder das Kompatibilitätsprofil? Nehmen wir ideale oder fehlerhafte Fahrer an? Theoretisch wählt der Typ der Uniform aus, welches Ziel der Textureinheit ausgewählt werden soll. In der Praxis geschieht dies im Kernprofil. Erwarten Sie im Kompatibilitätsprofil, dass einige fehlerhafte Treiber Ihnen die rein weiße Standardtextur anzeigen, wenn das vorhergehende Ziel der Textureinheit nicht mit dem Typ des Samplers übereinstimmt.
Datenwolf
1
@ legends2k: Überlegen Sie auch, was passieren würde, wenn eine 2D- und eine 3D-Textur an dieselbe Einheit gebunden wären und Sie eine 2D- und eine 3D-Sampler-Uniform hätten, die Sie an dieselbe Einheit binden? Sie können damit alle Arten von seltsamen Treiberfehlern auslösen. In der Praxis sorgt das Denken im alten Prioritätsmodell mit festen Funktionen dafür, dass Ihr Verstand gesund bleibt und Ihr Programm funktioniert, da sich die meisten Fahrer auf vorhersehbare Weise so verhalten.
Datenwolf
2

Wenn Sie in Ihrem Shader nach 2 Texturen suchen müssen:

uniform sampler2D tex1;  
uniform sampler2D tex2;  

Für tex1 und tex2 müssen die Quellen wie folgt angegeben werden:

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");

in der Render-Schleife:

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

Mit einer gl_bindtexture ist so etwas nicht möglich. Andererseits ist eine mögliche Verwendung einer Bindung in der Rendering-Schleife der Fall, wenn Sie eine Textur mit einem Inhalt im Stream (Video, Webcam) füttern:

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
quelle