Diferenças e relação entre glActiveTexture e glBindTexture

137

Pelo que entendi, glActiveTexturedefine a "unidade de textura" ativa. Cada unidade de textura pode ter vários destinos de textura (geralmente GL_TEXTURE_1D, 2D, 3D ou CUBE_MAP).

Se bem entendi, você precisa chamar glActiveTexturepara definir a unidade de textura primeiro (inicializada em GL_TEXTURE0) e depois vincular (um ou mais) "destinos de textura" a essa unidade de textura?

O número de unidades de textura disponíveis depende do sistema. Vejo enumerações para até 32 na minha biblioteca. Eu acho que isso significa essencialmente que eu posso ter o menor limite da minha GPU (que eu acho que é168) e 32 texturas na memória da GPU a qualquer momento? Acho que há um limite adicional de que não excedo a memória máxima da minha GPU (supostamente 1 GB).

Estou entendendo o relacionamento entre destinos de textura e unidades de textura corretamente? Digamos que me permitam 16 unidades e 4 alvos cada, isso significa que há espaço para 16 * 4 = 64 alvos ou não funciona assim?

Em seguida, você normalmente deseja carregar uma textura. Você pode fazer isso via glTexImage2D. O primeiro argumento é um destino de textura. Se isso funcionarglBufferData , vincularemos essencialmente o "identificador" / "nome da textura" ao destino da textura e, em seguida, carregaremos os dados da textura nesse destino e, assim, associá-lo indiretamente a esse identificador.

Que tal glTexParameter? Temos que vincular um destino de textura e escolher o mesmo destino novamente como o primeiro argumento? Ou o destino da textura não precisa ser vinculado desde que tenhamos a unidade de textura ativa correta?

glGenerateMipmap opera em um alvo também ... esse alvo ainda deve estar vinculado ao nome da textura para que seja bem-sucedido?

Então, quando queremos chamar nosso objeto com uma textura sobre ele, nós temos que ambos escolher uma unidade de textura ativa, e depois um alvo textura? Ou escolhemos uma unidade de textura e podemos coletar dados de qualquer um dos quatro destinos associados a essa unidade? Esta é a parte que realmente está me confundindo.

mpen
fonte

Respostas:

259

Tudo sobre objetos OpenGL

O modelo padrão para objetos OpenGL é o seguinte.

Objetos têm estado. Pense neles como um struct. Portanto, você pode ter um objeto definido assim:

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

O objeto possui certos valores armazenados e possui estado . Objetos OpenGL também têm estado.

Mudando de estado

No C / C ++, se você tiver uma instância do tipo Object, altere seu estado da seguinte maneira: obj.count = 5;Você faria referência direta a uma instância do objeto, obteria a parte específica do estado que deseja alterar e colocaria um valor nela.

No OpenGL, você não faz isso.

Por motivos legados, é melhor deixar inexplicável, para alterar o estado de um objeto OpenGL, você deve primeiro vinculá- lo ao contexto. Isso é feito com alguns de glBind*chamada.

O equivalente em C / C ++ a isso é o seguinte:

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

Texturas são interessantes; eles representam um caso especial de ligação. Muitas glBind*chamadas têm um parâmetro "target". Isso representa diferentes locais no contexto do OpenGL em que objetos desse tipo podem ser vinculados. Por exemplo, você pode vincular um objeto buffer de moldura para leitura ( GL_READ_FRAMEBUFFER) ou para gravação ( GL_DRAW_FRAMEBUFFER). Isso afeta como o OpenGL usa o buffer. É isso que o locparâmetro acima representa.

Texturas são especiais porque quando você primeiro ligá-los a um alvo, que recebem informações especiais. Quando você liga uma textura pela primeira vez GL_TEXTURE_2D, você está realmente definindo um estado especial na textura. Você está dizendo que essa textura é uma textura 2D. E sempre será uma textura 2D; esse estado não pode ser alterado nunca . Se você tem uma textura que foi primeiro vinculada como a GL_TEXTURE_2D, sempre deve vinculá-la como a GL_TEXTURE_2D; tentar vinculá-lo, pois GL_TEXTURE_1Dcausará um erro (durante o tempo de execução).

Depois que o objeto é vinculado, seu estado pode ser alterado. Isso é feito através de funções genéricas específicas para esse objeto. Eles também usam um local que representa qual objeto modificar.

Em C / C ++, isso se parece com:

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

Observe como esta função define o que quer que esteja no locvalor atualmente vinculado .

Para objetos de textura, as principais funções de alteração do estado da textura são glTexParameter. As únicas outras funções que alteram o estado da textura são as glTexImagefunções e suas variações ( glCompressedTexImage, glCopyTexImagerecentes glTexStorage). As várias SubImageversões alteram o conteúdo da textura, mas tecnicamente não alteram seu estado . As Imagefunções alocam armazenamento de textura e definem o formato da textura; as SubImagefunções apenas copiam pixels ao redor. Esse não é considerado o estado da textura.

Permita-me repetir: estas são as únicas funções que modificam o estado da textura. glTexEnvmodifica o estado do ambiente; isso não afeta nada armazenado em objetos de textura.

Textura ativa

A situação das texturas é mais complexa, mais uma vez por motivos legados, é melhor deixar de lado. É aqui que glActiveTextureentra.

Para texturas, não são alvos apenas ( GL_TEXTURE_1D, GL_TEXTURE_CUBE_MAP, etc.). Existem também unidades de textura . Em termos de nosso exemplo de C / C ++, o que temos é o seguinte:

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

Observe que agora não temos apenas uma lista 2D de Objects, mas também temos o conceito de um objeto atual. Temos uma função para definir o objeto atual, temos o conceito de um número máximo de objetos atuais e todas as nossas funções de manipulação de objetos são ajustadas para selecionar o objeto atual.

Quando você altera o objeto ativo no momento, altera todo o conjunto de locais de destino. Portanto, você pode vincular algo que entra no objeto atual 0, alterna para o objeto atual 4 e modifica um objeto completamente diferente.

Essa analogia com objetos de textura é perfeita ... quase.

Veja, glActiveTexturenão leva um número inteiro; é preciso um enumerador . O que, em teoria, significa que pode levar qualquer coisa de GL_TEXTURE0para GL_TEXTURE31. Mas há uma coisa que você deve entender:

ISTO É FALSO!

O intervalo real que glActiveTexturepode ser adotado é controlado por GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS. Esse é o número máximo de multi-estruturas simultâneas que uma implementação permite. Cada um deles é dividido em diferentes agrupamentos para diferentes estágios do shader. Por exemplo, no hardware da classe GL 3.x, você obtém 16 texturas de shader de vértice, 16 de shader de fragmento e 16 de shaver de geometria. Portanto, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITSserá 48.

Mas não há 48 enumeradores. É por isso glActiveTextureque realmente não recebe enumeradores. A maneira correta de ligar glActiveTextureé a seguinte:

glActiveTexture(GL_TEXTURE0 + i);

onde ié um número entre 0 e GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS.

Renderização

Então, o que tudo isso tem a ver com renderização?

Ao usar shaders, você define os uniformes do amostrador para uma unidade de imagem de textura ( glUniform1i(samplerLoc, i), onde ié a unidade de imagem). Isso representa o número com o qual você usou glActiveTexture. O amostrador selecionará o alvo com base no tipo de amostrador. Então, um sampler2Dvai escolher a partir do GL_TEXTURE_2Dalvo. Essa é uma das razões pelas quais os amostradores têm tipos diferentes.

Agora, parece suspeito que você pode ter dois amostradores GLSL, com tipos diferentes que usam a mesma unidade de imagem de textura. Mas você não pode; O OpenGL proíbe isso e gera um erro quando você tenta renderizar.

Nicol Bolas
fonte
12
Uau! Mais uma resposta maravilhosa - obrigado Nicol! Eu gosto especialmente desse parágrafo sobre uma textura 2D sempre sendo uma textura 2D. Estou criando um invólucro em torno de algumas dessas coisas agora, e não tinha certeza se deveria deixar isso aberto à mudança. E a parte sobre GL_TEXTURE0 + i- eu pretendia inspecionar os valores da enumeração para ver se isso era válido ou não. E o último parágrafo - não sabia se isso era legal ou não. Excelente! Estou marcando todas as suas respostas para que eu possa consultá-las novamente.
MPEN
6
@ Nicol Bolas: Isso está realmente bem explicado. Você deve copiar um pouco disso para o capítulo sobre texturas em seu livro opengl online. Eu acho que isso é muito mais claro e complementaria bem o capítulo.
WesDec
3
@ Nicol Bolas Estou apenas começando a aprender o OpenGL e esta resposta me ajudou muito. Obrigado!
inline
2
Ei, nico, só quero apontar o seu pequeno erro de digitação: GL_DRAW_FRAMEBUFFER não GL_WRITE_FRAMEBUFFER #
01/01/13
3
@ Nicol: Uau, a melhor definição que eu tive disso antes agora foi nos seus tutoriais de arcsíntese, agora você superou até essa fonte brilhante. Obrigado
Baggers
20

Vou dar uma chance ! Tudo isso não é tão complicado, apenas uma questão de termos, espero que eu me esclareça.


Você pode criar aproximadamente tantos Objetos de Textura quanto houver memória disponível em seu sistema. Esses objetos mantêm os dados reais (texels) de suas texturas, juntamente com os parâmetros fornecidos pelo glTexParameter (consulte as Perguntas frequentes ).

Ao ser criado, você tem que atribuir uma textura alvo a uma textura objeto, que representa o tipo de textura ( GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE, ...).

Esses dois itens, objeto de textura e destino de textura, representam os dados da textura. Voltaremos a eles mais tarde.

Unidades de textura

Agora, o OpenGL fornece uma variedade de unidades de textura , que podem ser usadas simultaneamente ao desenhar. O tamanho da matriz depende do sistema OpenGL, o seu possui 8.

Você pode vincular um objeto de textura a uma unidade de textura para usar a textura fornecida durante o desenho.

Em um mundo simples e fácil, para desenhar com uma determinada textura, você vincularia um objeto de textura à unidade de textura e faria (pseudocódigo):

glTextureUnit[0] = textureObject

Como GL é uma máquina de estado, infelizmente, não funciona dessa maneira. Supondo que temos textureObjectdados para o GL_TEXTURE_2Ddestino da textura, expressaremos a atribuição anterior como:

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

Observe que GL_TEXTURE_2Drealmente depende do tipo de textura que você deseja vincular.

Objetos de textura

No pseudo-código, para definir dados ou parâmetros de textura, você faria por exemplo:

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

O OpenGL não pode manipular diretamente objetos de textura, para atualizar / definir seu conteúdo ou alterar seus parâmetros, você deve primeiro vinculá-los à unidade de textura ativa (qualquer que seja). O código equivalente se torna:

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

Os shaders têm acesso a todas as unidades de textura, eles não se importam com a textura ativa.

Uniformes do amostrador são intvalores que representam o índice da unidade de textura a ser usada no amostrador (e não o objeto de textura a ser usado).

Então você deve vincular seus objetos de textura às unidades que deseja usar.

O tipo do amostrador fará a correspondência com o destino da textura usado na unidade de textura: Sampler2Dfor GL_TEXTURE_2De assim por diante ...

rotoglup
fonte
Uma coisa que eu não entendo. Vamos supor que tenho alguma textura e é usada em muitos shaders em diferentes unidades de textura. Vamos supor que eu queira alterar a filtragem de textura no tempo de execução. Que unidade de textura devo usar? Posso alterar o estado da textura na Unidade 0 e depois usá-la em outra unidade?
majakthecoder
@majakthecoder Na minha resposta, considero a filtragem como uma propriedade do objeto de textura - o que significa que você não pode alterá-la especificamente em uma unidade de textura. Dependendo do tipo de OpenGL que você está alvejando, pode ser possível amostrar objetos para resolver esse problema ( opengl.org/wiki/Sampler_Object ); caso contrário, pode ser necessário duplicar o objeto de textura para ter várias filtragem simultâneas.
rotoglup
12

Imagine a GPU como uma planta de processamento de tinta.

Existem vários tanques, que fornecem corante a alguma máquina de pintura. Na máquina de pintura, o corante é então aplicado ao objeto. Esses tanques são as unidades de textura

Esses tanques podem ser equipados com diferentes tipos de corante. Cada tipo de corante requer algum outro tipo de solvente. O "solvente" é o alvo da textura . Por conveniência, cada tanque está conectado a algum suprimento de solvente, mas apenas um tipo de solvente pode ser usado por vez em cada tanque. Portanto, há uma válvula / switch TEXTURE_CUBE_MAP, TEXTURE_3D, TEXTURE_2D, TEXTURE_1D. Você pode encher todos os tipos de corante no tanque ao mesmo tempo, mas como apenas um tipo de solvente entra, ele "dilui" apenas o tipo de correspondência de corante. Assim, você pode vincular cada tipo de textura, mas a ligação com o solvente "mais importante" entra no tanque e se mistura com o tipo de corante a que pertence.

E depois há o próprio corante, que vem de um armazém e é enchido no tanque "ligando-o". Essa é a sua textura.

datenwolf
fonte
2
Uma analogia estranha ... Não sei se isso realmente esclarece tudo. Especialmente a parte sobre o "diluidor" e o "solvente mais importante". Você está dizendo que se eu vincular uma textura 2D e uma textura 3D, posso usar apenas uma delas, ou o quê? Qual seria considerado mais importante?
MPEN
2
@ Mark: Bem, eu estava tentando falar em termos de um pintor que trabalha com corante literal (digamos, à base de óleo e à base de água). De qualquer forma, sim, se você vincular e ativar vários destinos de textura, haverá precedência: CUBE_MAP> 3D> TEXTURE_ARRAY> 2D> 1D.
datenwolf
1
Arrumado! Eu não sabia sobre a precedência. Faz mais sentido agora que eu sei que apenas um destino de textura pode ser usado por unidade de textura.
MPEN
1
@ legends2k: Bem, agora está ficando interessante. Estamos falando sobre o núcleo ou o perfil de compatibilidade. Assumimos drivers ideais ou com erros. Em teoria, o tipo de uniforme seleciona qual alvo da unidade de textura selecionar. Na prática, isso acontece no perfil principal. No perfil de compatibilidade, espere que alguns drivers com erros apresentem a textura padrão toda branca, se o destino anterior da unidade de textura não corresponder ao tipo do amostrador.
datenwolf
1
@ legends2k: Além disso, pense no que aconteceria se houvesse uma textura 2D e 3D ligada à mesma unidade e você tivesse um uniforme de amostrador 2D e 3D, vinculado à mesma unidade? Você pode acionar todos os tipos de erros estranhos de driver com isso. Na prática, pensar no antigo modelo de precedência de função fixa mantém sua mente sã e seu programa funcionando, porque é assim que a maioria dos drivers se comporta de maneira previsível.
datenwolf
2

Se no seu shader você precisar procurar em 2 texturas:

uniform sampler2D tex1;  
uniform sampler2D tex2;  

é necessário indicar para tex1 e tex2 suas fontes da seguinte forma:

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

no loop de renderização:

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

Com uma gl_bindtexture, não é possível fazer isso. Por outro lado, um possível uso de um bind no loop de renderização é o caso em que você alimenta uma textura com um conteúdo em fluxo (vídeo, 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
fonte