Różnice i relacje między glActiveTexture i glBindTexture

137

Z tego, co wiem, glActiveTextureustawia aktywną „jednostkę tekstury”. Każda jednostka tekstur może mieć wiele celów tekstur (zwykle GL_TEXTURE_1D, 2D, 3D lub CUBE_MAP).

Jeśli dobrze rozumiem, musisz najpierw wywołać, glActiveTextureaby ustawić jednostkę tekstury (zainicjowaną GL_TEXTURE0), a następnie powiązać (jeden lub więcej) "celów tekstury" z tą jednostką tekstury?

Liczba dostępnych jednostek tekstur zależy od systemu. W mojej bibliotece widzę wyliczenia do 32 lat. Wydaje mi się, że zasadniczo oznacza to, że mogę mieć mniejszy limit mojego GPU (który moim zdaniem jest168) i 32 tekstury w pamięci GPU w dowolnym momencie? Wydaje mi się, że istnieje dodatkowy limit polegający na tym, że nie przekraczam maksymalnej pamięci mojego GPU (podobno 1 GB).

Czy poprawnie rozumiem relacje między obiektami docelowymi tekstur a jednostkami tekstur? Powiedzmy, że mogę mieć 16 jednostek i 4 cele w każdym, czy to oznacza, że ​​jest miejsce na 16 * 4 = 64 cele, czy nie działa to w ten sposób?

Następnie zazwyczaj chcesz załadować teksturę. Możesz to zrobić przez glTexImage2D. Pierwszym argumentem jest cel tekstury. Jeśli to działaglBufferData , to zasadniczo wiążemy "uchwyt" / "nazwę tekstury" z celem tekstury, a następnie ładujemy dane tekstury do tego celu, a tym samym pośrednio kojarzymy je z tym uchwytem.

O co chodzi glTexParameter? Musimy powiązać cel tekstury, a następnie wybrać ten sam cel ponownie jako pierwszy argument? A może cel tekstury nie musi być związany, jeśli mamy odpowiednią aktywną jednostkę tekstury?

glGenerateMipmap działa również na celu ... ten cel musi być nadal powiązany z nazwą tekstury, aby odniósł sukces?

Następnie, gdy chcemy narysować nasz obiekt z teksturą, czy musimy wybrać zarówno aktywną jednostkę tekstury, jak i cel tekstury? A może wybieramy jednostkę teksturującą, a następnie możemy pobrać dane z dowolnego z 4 celów powiązanych z tą jednostką? To jest ta część, która naprawdę mnie dezorientuje.

mpen
źródło

Odpowiedzi:

259

Wszystko o obiektach OpenGL

Standardowy model obiektów OpenGL jest następujący.

Obiekty mają stan. Pomyśl o nich jak o struct. Więc możesz mieć obiekt zdefiniowany w ten sposób:

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

Obiekt ma zapisane w sobie określone wartości i ma stan . Obiekty OpenGL również mają stan.

Zmiana stanu

W C / C ++, jeśli masz wystąpienie typu Object, możesz zmienić jego stan w następujący sposób: obj.count = 5;Możesz bezpośrednio odwołać się do wystąpienia obiektu, pobrać konkretny stan, który chcesz zmienić, i wrzucić do niego wartość.

W OpenGL tego nie robisz.

Ze starszych powodów lepiej pozostawić je niewyjaśnione, aby zmienić stan obiektu OpenGL, należy najpierw powiązać go z kontekstem. Odbywa się to z niektórymi z glBind*połączeń.

Odpowiednik C / C ++ do tego jest następujący:

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

Tekstury są interesujące; stanowią szczególny przypadek wiążący. Wiele glBind*wywołań ma parametr „target”. Reprezentuje różne lokalizacje w kontekście OpenGL, w których można wiązać obiekty tego typu. Na przykład, możesz powiązać obiekt bufora ramki do odczytu ( GL_READ_FRAMEBUFFER) lub do zapisu ( GL_DRAW_FRAMEBUFFER). Ma to wpływ na sposób, w jaki OpenGL używa bufora. To właśnie locreprezentuje powyższy parametr.

Tekstury są wyjątkowe, ponieważ kiedy po raz pierwszy związujesz je z celem, otrzymują specjalne informacje. Kiedy po raz pierwszy wiążesz teksturę jako teksturę GL_TEXTURE_2D, w rzeczywistości ustawiasz specjalny stan tekstury. Mówisz, że ta tekstura jest teksturą 2D. I zawsze będzie to tekstura 2D; tego stanu nigdy nie można zmienić . Jeśli masz teksturę, która została najpierw oprawiona jako a GL_TEXTURE_2D, musisz zawsze związać ją jako GL_TEXTURE_2D; próba powiązania go jako GL_TEXTURE_1Dspowoduje błąd (w czasie wykonywania).

Po związaniu obiektu można zmienić jego stan. Odbywa się to za pomocą ogólnych funkcji specyficznych dla tego obiektu. One również przyjmują lokalizację, która reprezentuje obiekt do zmodyfikowania.

W C / C ++ wygląda to tak:

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

Zwróć uwagę, jak ta funkcja ustawia wszystko, co znajduje się w aktualnie powiązanej locwartości.

W przypadku obiektów tekstur głównymi funkcjami zmiany stanu tekstury są glTexParameter. Tylko inne funkcje, które zmieniają stan tekstury są glTexImagefunkcje i ich odmiany ( glCompressedTexImage, glCopyTexImage, ostatnie glTexStorage). Różne SubImagewersje zmieniają zawartość tekstury, ale technicznie nie zmieniają jej stanu . Te Imagefunkcje przeznaczyć przechowywania tekstur i ustawić format fakturze za; te SubImagefunkcje po prostu skopiować piksele wokół. Nie jest to uważane za stan tekstury.

Pozwólcie, że powtórzę: to jedyne funkcje, które modyfikują stan tekstury. glTexEnvmodyfikuje stan środowiska; nie wpływa na nic przechowywanego w obiektach tekstur.

Aktywna tekstura

Sytuacja dotycząca tekstur jest bardziej złożona, ponownie ze względu na dziedzictwo lepiej nie ujawniać. Tutaj glActiveTexturepojawia się.

Tekstury, nie są tylko cele ( GL_TEXTURE_1D, GL_TEXTURE_CUBE_MAPitp). Istnieją również jednostki tekstur . Jeśli chodzi o nasz przykład C / C ++, mamy to:

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

Zauważ, że teraz mamy nie tylko listę dwuwymiarowych Objects, ale mamy również koncepcję bieżącego obiektu. Mamy funkcję ustawiania bieżącego obiektu, koncepcję maksymalnej liczby bieżących obiektów, a wszystkie nasze funkcje manipulacji obiektami są dostosowane do wybierania z bieżącego obiektu.

Zmiana aktualnie aktywnego obiektu powoduje zmianę całego zestawu docelowych lokalizacji. Możesz więc powiązać coś, co trafia do bieżącego obiektu 0, przełączyć się na bieżący obiekt 4 i będzie modyfikować zupełnie inny obiekt.

Ta analogia z obiektami tekstur jest idealna ... prawie.

Widzisz, glActiveTexturenie przyjmuje liczby całkowitej; wymaga modułu wyliczającego . Co w teorii oznacza, że ​​może to potrwać od GL_TEXTURE0do GL_TEXTURE31. Ale jest jedna rzecz, którą musisz zrozumieć:

TO NIEPRAWDA!

Rzeczywisty zakres, jaki glActiveTexturemoże zająć, jest regulowany przez GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS. To jest maksymalna liczba jednoczesnych multitekstur, na jaką pozwala implementacja. Są one podzielone na różne grupy dla różnych etapów modułu cieniującego. Na przykład na sprzęcie klasy GL 3.x otrzymasz 16 tekstur Vertex Shader, 16 tekstur Fragment Shader i 16 Geometry Shader. Dlatego GL_MAX_COMBINED_TEXTURE_IMAGE_UNITSbędzie 48.

Ale nie ma 48 rachmistrzów. Dlatego glActiveTexturetak naprawdę nie biorą rachunków. Prawidłowy sposób połączenia glActiveTexturejest następujący:

glActiveTexture(GL_TEXTURE0 + i);

gdzie ijest liczbą od 0 do GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS.

Wykonanie

Więc co to wszystko ma wspólnego z renderowaniem?

Korzystając z shaderów, ustawiasz mundury próbnika na jednostkę obrazu tekstury ( glUniform1i(samplerLoc, i)gdzie ijest jednostka obrazu). To oznacza liczbę, z którą korzystałeś glActiveTexture. Próbnik wybierze cel na podstawie typu próbnika. Więc sampler2Dwybierze z GL_TEXTURE_2Dcelu. Jest to jeden z powodów, dla których próbniki mają różne typy.

Teraz brzmi to podejrzanie, jakbyś mógł mieć dwa samplery GLSL, z różnymi typami, które używają tej samej jednostki obrazu tekstury. Ale nie możesz; OpenGL zabrania tego i wyświetli błąd podczas próby renderowania.

Nicol Bolas
źródło
12
Łał! Jeszcze jedna wspaniała odpowiedź - dzięki Nicol! Szczególnie podoba mi się ten akapit o teksturze 2D, która zawsze jest teksturą 2D. Tworzę teraz opakowanie wokół niektórych z tych rzeczy i nie byłem pewien, czy powinienem zostawić to otwarte na zmiany. A część dotycząca GL_TEXTURE0 + i- miałem zamiar sprawdzić wartości wyliczenia, aby sprawdzić, czy są prawidłowe, czy nie. I ostatni akapit - nie wiedziałem, czy to legalne, czy nie. Doskonały! Zapisuję wszystkie Twoje odpowiedzi jako zakładki, więc mogę do nich ponownie zajrzeć.
mpen
6
@Nicol Bolas: To jest naprawdę dobrze wyjaśnione. Powinieneś skopiować część tego do rozdziału o teksturach w swojej książce online opengl. Myślę, że jest to o wiele jaśniejsze i dobrze uzupełniłoby ten rozdział.
WesDec
3
@Nicol Bolas Dopiero zaczynam uczyć się OpenGL i ta odpowiedź bardzo mi pomogła. Dziękuję Ci!
inline
2
Hej nico, po prostu chcę zwrócić uwagę na swoją małą literówkę: to GL_DRAW_FRAMEBUFFER, a nie GL_WRITE_FRAMEBUFFER
Defd
3
@Nicol: Wow, najlepsza definicja, jaką miałem do tej pory, pochodziła z twoich tutoriali o arcsyntezie, teraz prześcignęłaś nawet to genialne źródło. Thankyou
Baggers
20

Dam temu szansę ! To wszystko nie jest takie skomplikowane, tylko kwestia terminów, mam nadzieję, że wyjaśnię.


Możesz utworzyć mniej więcej tyle obiektów tekstury, ile jest dostępnej pamięci w twoim systemie. Obiekty te przechowują rzeczywiste dane (tekstury) twoich tekstur, wraz z parametrami dostarczonymi przez glTexParameter (patrz FAQ ).

Kiedy powstaje, trzeba przypisać jedną Texture docelowy do jednego obiektu tekstury, która reprezentuje typ tekstury ( GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE, ...).

Te dwa elementy, obiekt tekstury i obiekt docelowy tekstury, reprezentują dane tekstury. Wrócimy do nich później.

Jednostki tekstury

Teraz OpenGL zapewnia szereg jednostek tekstur , których można używać jednocześnie podczas rysowania. Rozmiar tablicy zależy od systemu OpenGL, twoja ma 8.

Możesz powiązać obiekt tekstury z jednostką tekstury, aby używać danej tekstury podczas rysowania.

W prostym i łatwym świecie, aby narysować daną teksturę, związałbyś obiekt tekstury z jednostką tekstury i zrobiłbyś (pseudokod):

glTextureUnit[0] = textureObject

Ponieważ GL jest maszyną stanową, niestety nie działa w ten sposób. Zakładając, że mamy textureObjectdane dla GL_TEXTURE_2Dcelu tekstury, poprzednie przypisanie wyrażamy jako:

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

Zauważ, że to GL_TEXTURE_2Dnaprawdę zależy od rodzaju tekstury, którą chcesz związać.

Obiekty tekstury

W pseudokodzie, aby ustawić dane tekstury lub parametry tekstury, zrobiłbyś na przykład:

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

OpenGL nie może bezpośrednio manipulować obiektami tekstur, aby zaktualizować / ustawić ich zawartość lub zmienić ich parametry, musisz najpierw powiązać je z aktywną jednostką tekstury (cokolwiek to jest). Odpowiedni kod to:

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)

Shadery

Shadery mają dostęp do wszystkich tekstur, nie dbają o aktywną teksturę.

Mundury próbnika to intwartości reprezentujące indeks jednostki tekstury, która ma być używana przez próbnik (a nie obiekt tekstury, który ma być użyty).

Musisz więc powiązać obiekty tekstur z jednostkami, których chcesz użyć.

Typ próbnika dopasuje się do docelowej tekstury używanej w jednostce tekstury: Sampler2Dfor GL_TEXTURE_2Ditd.

rotoglup
źródło
Jednej rzeczy nie rozumiem. Załóżmy, że mam jakąś teksturę i jest ona używana w wielu modułach cieniujących na różnych jednostkach tekstur. Załóżmy, że chcę zmienić filtrowanie tekstur w czasie wykonywania. Jakiej jednostki teksturowej powinienem użyć? Czy mogę zmienić stan tekstury w jednostce 0, a następnie użyć tej tekstury na innej jednostce?
majakthecoder
@majakthecoder W mojej odpowiedzi uważam filtrowanie za właściwość obiektu tekstury - co oznacza, że ​​nie można go zmienić konkretnie w jednostce tekstury. W zależności od rodzaju OpenGL, na który celujesz , możesz spróbować samplować obiekty, aby rozwiązać ten problem ( opengl.org/wiki/Sampler_Object ), w przeciwnym razie może być konieczne zduplikowanie obiektu tekstury, aby mieć wiele jednoczesnych filtrów.
rotoglup
12

Wyobraź sobie GPU jak jakąś fabrykę farb.

Istnieje wiele zbiorników, które dostarczają barwnik do niektórych maszyn malarskich. W maszynie malarskiej na przedmiot nanoszony jest następnie barwnik. Te czołgi to jednostki tekstury

Zbiorniki te można wyposażyć w różnego rodzaju barwniki. Każdy rodzaj barwnika wymaga innego rodzaju rozpuszczalnika. „Rozpuszczalnik” to docelowa tekstura . Dla wygody każdy zbiornik jest podłączony do pewnego źródła rozpuszczalnika, ale w każdym zbiorniku można stosować tylko jeden rodzaj rozpuszczalnika. Więc nie jest to zawór / wyłącznik TEXTURE_CUBE_MAP, TEXTURE_3D, TEXTURE_2D, TEXTURE_1D. Możesz napełniać zbiornik jednocześnie wszystkimi typami barwników, ale ponieważ do zbiornika trafia tylko jeden rodzaj rozpuszczalnika, „rozcieńczy” on tylko ten rodzaj barwnika, który pasuje. Można więc związać każdą teksturę, ale wiązanie „najważniejszym” rozpuszczalnikiem w rzeczywistości trafia do zbiornika i miesza się z rodzajem barwnika, do którego należy.

A potem jest sam barwnik, który pochodzi z magazynu i jest napełniany do zbiornika poprzez „wiązanie”. To twoja tekstura.

datenwolf
źródło
2
Coś w rodzaju dziwnej analogii ... Nie jestem pewien, czy to naprawdę coś wyjaśnia. Zwłaszcza część o „rozcieńczalniku” i „najważniejszym rozpuszczalniku”. Mówisz, że jeśli połączę zarówno teksturę 2d, jak i teksturę 3D, mogę użyć tylko jednej z nich, czy co? Który z nich zostałby uznany za najważniejszy?
mpen
2
@Mark: Cóż, próbowałem mówić w kategoriach malarza, który pracuje z dosłownym barwnikiem (powiedzmy na bazie oleju i wody). Tak czy inaczej, tak, jeśli powiążesz i włączysz wiele celów tekstur, ma pierwszeństwo: CUBE_MAP> 3D> TEXTURE_ARRAY> 2D> 1D.
datenwolf
1
Schludny! Nie wiedziałem o pierwszeństwie. Ma to więcej sensu teraz, gdy wiem, że tylko jeden cel tekstury może być użyty na jednostkę tekstury.
mpen
1
@ legends2k: Cóż, teraz robi się ciekawie. Czy mówimy o rdzeniu lub profilu zgodności. Czy zakładamy kierowców idealnych, czy buggy. Teoretycznie rodzaj munduru decyduje o wyborze celu jednostki tekstury. W praktyce dzieje się to w profilu podstawowym. W profilu zgodności spodziewaj się, że niektóre błędne sterowniki będą prezentować całą białą domyślną teksturę, jeśli poprzedni cel jednostki teksturującej nie pasuje do typu próbnika.
datenwolf
1
@ legends2k: Zastanów się również, co by się stało, gdyby były tekstury 2D i 3D powiązane z tą samą jednostką, a ty miałbyś mundur próbnika 2D i 3D, który łączysz z tą samą jednostką? Możesz w ten sposób wywołać różnego rodzaju dziwne błędy sterowników. W praktyce myślenie w starym modelu ustalonego priorytetu funkcji utrzymuje umysł przy zdrowych zmysłach, a program działa, ponieważ w ten sposób większość kierowców zachowuje się w przewidywalny sposób.
datenwolf,
2

Jeśli w swoim module cieniującym potrzebujesz wyszukiwania z 2 tekstur:

uniform sampler2D tex1;  
uniform sampler2D tex2;  

dla tex1 i tex2 należy wskazać ich źródła w następujący sposób:

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

w pętli renderowania:

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

Z gl_bindtexture nie można tego zrobić. Z drugiej strony możliwe użycie wiązania w pętli renderowania ma miejsce w przypadku, gdy podajesz teksturę z zawartością w strumieniu (wideo, kamera internetowa):

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
źródło