Diferencias y relación entre glActiveTexture y glBindTexture

137

Por lo que deduzco, glActiveTextureestablece la "unidad de textura" activa. Cada unidad de textura puede tener múltiples objetivos de textura (generalmente GL_TEXTURE_1D, 2D, 3D o CUBE_MAP).

Si entiendo correctamente, ¿tiene que llamar glActiveTexturepara establecer primero la unidad de textura (inicializada en GL_TEXTURE0) y luego vincular (uno o más) "objetivos de textura" a esa unidad de textura?

El número de unidades de textura disponibles depende del sistema. Veo enumeraciones de hasta 32 en mi biblioteca. Supongo que esto esencialmente significa que puedo tener el menor límite de mi GPU (que creo que esdieciséis8) y 32 texturas en la memoria de la GPU al mismo tiempo? Supongo que hay un límite adicional que no excedo la memoria máxima de mi GPU (supuestamente 1 GB).

¿Estoy entendiendo la relación entre los objetivos de textura y las unidades de textura correctamente? Digamos que tengo 16 unidades y 4 objetivos cada uno, ¿eso significa que hay espacio para 16 * 4 = 64 objetivos, o no funciona así?

A continuación, normalmente desea cargar una textura. Puedes hacerlo a través de glTexImage2D. El primer argumento del cual es un objetivo de textura. Si esto funciona asíglBufferData , esencialmente vinculamos el "identificador" / "nombre de textura" al objetivo de textura, y luego cargamos los datos de textura en ese objetivo, y así lo asociamos indirectamente con ese identificador.

¿Qué hay de glTexParameter? ¿Tenemos que vincular un objetivo de textura y luego elegir ese mismo objetivo nuevamente como primer argumento? ¿O no es necesario vincular el objetivo de textura siempre que tengamos la unidad de textura activa correcta?

glGenerateMipmap también opera en un objetivo ... ¿ese objetivo aún debe estar vinculado al nombre de la textura para que tenga éxito?

Luego, cuando queremos llamar nuestro objeto con una textura en él, tenemos que tanto elegir una unidad de textura activo, y luego un objetivo textura? ¿O elegimos una unidad de textura, y luego podemos tomar datos de cualquiera de los 4 objetivos asociados con esa unidad? Esta es la parte que realmente me confunde.

mpen
fuente

Respuestas:

259

Todo sobre los objetos OpenGL

El modelo estándar para objetos OpenGL es el siguiente.

Los objetos tienen estado. Piense en ellos como a struct. Entonces, podría tener un objeto definido de esta manera:

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

El objeto tiene ciertos valores almacenados y tiene estado . Los objetos OpenGL también tienen estado.

Estado cambiante

En C / C ++, si tiene una instancia de tipo Object, cambiaría su estado de la siguiente manera: obj.count = 5;haría referencia directa a una instancia del objeto, obtendría la pieza de estado en particular que desea cambiar y colocará un valor en ella.

En OpenGL, no haces esto.

Por razones heredadas, es mejor dejarlo sin explicar, para cambiar el estado de un objeto OpenGL, primero debe vincularlo al contexto. Esto se hace con algunos de glBind*llamada.

El C / C ++ equivalente a esto es el siguiente:

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

Las texturas son interesantes; representan un caso especial de encuadernación. Muchas glBind*llamadas tienen un parámetro "objetivo". Esto representa diferentes ubicaciones en el contexto de OpenGL donde se pueden vincular objetos de ese tipo. Por ejemplo, puede vincular un objeto framebuffer para lectura ( GL_READ_FRAMEBUFFER) o para escritura ( GL_DRAW_FRAMEBUFFER). Esto afecta la forma en que OpenGL usa el búfer. Esto es lo locque representa el parámetro anterior.

Las texturas son especiales porque cuando las vincula por primera vez a un objetivo, obtienen información especial. Cuando vincula una textura por primera vez GL_TEXTURE_2D, en realidad está configurando un estado especial en la textura. Estás diciendo que esta textura es una textura 2D. Y siempre será una textura 2D; Este estado no se puede cambiar nunca . Si tiene una textura que primero se unió como a GL_TEXTURE_2D, siempre debe unirla como a GL_TEXTURE_2D; Si intenta vincularlo, se GL_TEXTURE_1Dproducirá un error (durante el tiempo de ejecución).

Una vez que el objeto está vinculado, su estado se puede cambiar. Esto se realiza mediante funciones genéricas específicas de ese objeto. También toman una ubicación que representa qué objeto modificar.

En C / C ++, esto se ve así:

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 cómo esta función establece lo que esté en el locvalor actualmente vinculado .

Para objetos de textura, las principales funciones de cambio de estado de textura son glTexParameter. Las únicas otras funciones que cambian de estado textura son las glTexImagefunciones y sus variaciones ( glCompressedTexImage, glCopyTexImage, el reciente glTexStorage). Las diversas SubImageversiones cambian el contenido de la textura, pero técnicamente no cambian su estado . Las Imagefunciones asignan almacenamiento de textura y establecen el formato de la textura; las SubImagefunciones solo copian píxeles. Eso no se considera el estado de la textura.

Permítanme repetir: estas son las únicas funciones que modifican el estado de la textura. glTexEnvmodifica el estado del entorno; no afecta a nada almacenado en objetos de textura.

Textura activa

La situación de las texturas es más compleja, de nuevo por razones heredadas que es mejor no revelar. Aquí es donde glActiveTextureentra.

Para texturas, no sólo no son objetivos ( GL_TEXTURE_1D, GL_TEXTURE_CUBE_MAP, etc). También hay unidades de textura . En términos de nuestro ejemplo C / C ++, lo que tenemos es esto:

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

Tenga en cuenta que ahora, no solo tenemos una lista 2D de Objects, sino que también tenemos el concepto de un objeto actual. Tenemos una función para establecer el objeto actual, tenemos el concepto de un número máximo de objetos actuales, y todas nuestras funciones de manipulación de objetos se ajustan para seleccionar del objeto actual.

Cuando cambia el objeto actualmente activo, cambia todo el conjunto de ubicaciones de destino. Por lo tanto, puede vincular algo que va al objeto actual 0, cambiar al objeto actual 4 y modificará un objeto completamente diferente.

Esta analogía con los objetos de textura es perfecta ... casi.

Mira, glActiveTextureno toma un número entero; Se necesita un enumerador . Lo que en teoría significa que puede llevar cualquier cosa desde GL_TEXTURE0hasta GL_TEXTURE31. Pero hay una cosa que debes entender:

¡ESTO ES FALSO!

El rango real que glActiveTexturepuede tomar se rige por GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS. Esa es la cantidad máxima de texturas múltiples simultáneas que permite una implementación. Estos se dividen en diferentes grupos para diferentes etapas de sombreado. Por ejemplo, en el hardware de clase GL 3.x, obtiene 16 texturas de sombreador de vértices, 16 texturas de sombreador de fragmentos y 16 texturas de sombreador de geometría. Por lo tanto, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITSserá 48.

Pero no hay 48 enumeradores. Es por eso glActiveTextureque realmente no toma enumeradores. La forma correcta de llamar glActiveTexturees la siguiente:

glActiveTexture(GL_TEXTURE0 + i);

donde ies un número entre 0 y GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS.

Representación

Entonces, ¿qué tiene que ver todo esto con el renderizado?

Al usar sombreadores, configura los uniformes de muestra en una unidad de imagen de textura ( glUniform1i(samplerLoc, i)donde iestá la unidad de imagen). Eso representa el número que usaste glActiveTexture. La muestra elegirá el objetivo en función del tipo de muestra. Entonces a sampler2Delegirá del GL_TEXTURE_2Dobjetivo. Esta es una razón por la cual los muestreadores tienen diferentes tipos.

Ahora esto suena sospechosamente como que puede tener dos muestreadores GLSL, con diferentes tipos que usan la misma unidad de imagen de textura. Pero no puedes; OpenGL prohíbe esto y le dará un error cuando intente renderizar.

Nicol Bolas
fuente
12
¡Guauu! Otra respuesta maravillosa: ¡gracias, Nicol! Me gusta especialmente ese párrafo sobre una textura 2D que siempre es una textura 2D. Estoy construyendo una envoltura alrededor de algunas de estas cosas ahora, y no estaba seguro de si debería dejar eso abierto para cambiar. Y la parte sobre GL_TEXTURE0 + i: quería inspeccionar los valores de enumeración para ver si eso era válido o no. Y el último párrafo: no sabía si eso era legal o no. ¡Excelente! Estoy marcando todas sus respuestas para poder consultarlas nuevamente.
mpen
66
@Nicol Bolas: Esto está muy bien explicado. Debe copiar algo de esto al capítulo de textura en su libro de opengl en línea. Creo que esto es mucho más claro y complementaría bien el capítulo.
WesDec
3
@Nicol Bolas Estoy empezando a aprender OpenGL y esta respuesta me ha ayudado mucho. ¡Gracias!
línea
2
Hola Nico, solo quiero señalar tu pequeño error tipográfico: es GL_DRAW_FRAMEBUFFER no GL_WRITE_FRAMEBUFFER
Defd
3
@Nicol: Wow, la mejor definición que tenía de esto antes era de tus tutoriales de arcosíntesis, ahora has superado incluso a esa fuente brillante. Gracias
Baggers
20

Lo probaré ! Todo esto no es tan complicado, solo es una cuestión de términos, espero que me quede claro.


Puede crear aproximadamente tantos objetos de textura como haya memoria disponible en su sistema. Estos objetos contienen los datos reales (texels) de sus texturas, junto con los parámetros, proporcionados por glTexParameter (consulte las preguntas frecuentes ).

Al crearse, tiene que asignar una textura de destino a un objeto textura, lo que representa el tipo de la textura ( GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE, ...).

Estos dos elementos, el objeto de textura y el objetivo de textura representan los datos de textura. Volveremos a ellos más tarde.

Unidades de textura

Ahora, OpenGL proporciona una variedad de unidades de textura que se pueden usar simultáneamente mientras se dibuja. El tamaño de la matriz depende del sistema OpenGL, el suyo tiene 8.

Puede vincular un objeto de textura a una unidad de textura para usar la textura dada mientras dibuja.

En un mundo simple y fácil, para dibujar con una textura dada, unirías un objeto de textura a la unidad de textura y harías (pseudocódigo):

glTextureUnit[0] = textureObject

Como GL es una máquina de estados, lamentablemente no funciona de esta manera. Suponiendo que nuestro textureObjecttiene datos para el GL_TEXTURE_2Dobjetivo de textura, expresaremos la asignación anterior como:

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

Tenga en cuenta que GL_TEXTURE_2Drealmente depende del tipo de textura que desea unir.

Objetos de textura

En pseudocódigo, para establecer datos de textura o parámetros de textura, haría, por ejemplo:

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

OpenGL no puede manipular directamente los objetos de textura, actualizar / configurar su contenido o cambiar sus parámetros, primero debe vincularlos a la unidad de textura activa (lo que sea). El código equivalente se convierte en:

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)

Sombreadores

Los sombreadores tienen acceso a todas las unidades de textura, no les importa la textura activa.

Los uniformes de intmuestra son valores que representan el índice de la unidad de textura a usar para la muestra (y no el objeto de textura a usar).

Por lo tanto, debe vincular sus objetos de textura a las unidades que desea usar.

El tipo de muestra hará la coincidencia con el objetivo de textura que se utiliza en la unidad de textura: Sampler2Dpara GL_TEXTURE_2D, etc.

rotoglup
fuente
Una cosa que no entiendo. Supongamos que tengo algo de textura y se usa en muchos sombreadores en diferentes unidades de textura. Supongamos que quiero cambiar el filtrado de textura en tiempo de ejecución. ¿Qué unidad de textura debo usar? ¿Puedo cambiar el estado de la textura en la Unidad 0 y luego usar esa textura en una unidad diferente?
majakthecoder
@majakthecoder En mi respuesta, considero el filtrado como una propiedad del objeto de textura , lo que significa que no puede cambiarlo específicamente en una unidad de textura. Dependiendo del sabor de OpenGL al que esté apuntando, es posible que pueda muestrear objetos para resolver este problema ( opengl.org/wiki/Sampler_Object ), de lo contrario, es posible que deba duplicar el objeto de textura para tener múltiples filtros simultáneos.
rotoglup
12

Imagine la GPU como una planta de procesamiento de pintura.

Hay varios tanques, que entregan tinte a alguna máquina de pintura. En la máquina de pintura, el tinte se aplica al objeto. Esos tanques son las unidades de textura.

Esos tanques pueden equiparse con diferentes tipos de tinte. Cada tipo de tinte requiere algún otro tipo de solvente. El "solvente" es el objetivo de textura . Por conveniencia, cada tanque está conectado a un suministro de solvente, y solo se puede usar un tipo de solvente a la vez en cada tanque. Así que hay una válvula / conmutador TEXTURE_CUBE_MAP, TEXTURE_3D, TEXTURE_2D, TEXTURE_1D. Puede llenar todos los tipos de tinte en el tanque al mismo tiempo, pero como solo entra un tipo de solvente, "diluirá" solo el tipo de tinte correspondiente. Por lo tanto, puede unir cada tipo de textura, pero la unión con el solvente "más importante" en realidad irá al tanque y se mezclará con el tipo de tinte al que pertenece.

Y luego está el tinte en sí, que proviene de un almacén y se llena en el tanque al "atarlo". Esa es tu textura.

datenwolf
fuente
2
Una especie de analogía extraña ... No estoy seguro de que realmente aclare algo. Especialmente la parte sobre el "diluyente" y el "disolvente más importante". ¿Estás diciendo que si ato una textura 2d y una textura 3d, solo puedo usar una de ellas, o qué? ¿Cuál se consideraría más importante?
mpen
2
@ Mark: Bueno, estaba tratando de hablar en términos de un pintor que trabaja con tinte literal (por ejemplo, a base de aceite y agua). De todos modos, sí, si vincula y habilita múltiples objetivos de textura, tiene prioridad: CUBE_MAP> 3D> TEXTURE_ARRAY> 2D> 1D.
datenwolf
1
¡Ordenado! No sabía sobre la precedencia. Tiene más sentido ahora que sé que solo se puede usar un objetivo de textura por unidad de textura.
mpen
1
@ legends2k: Bueno, ahora se está poniendo interesante. ¿Estamos hablando del núcleo o del perfil de compatibilidad? ¿Asumimos conductores ideales o con errores? En teoría, el tipo de uniforme selecciona qué objetivo de la unidad de textura seleccionar. En la práctica esto sucede en el perfil central. En el perfil de compatibilidad, espere que algunos controladores con errores le presenten la textura predeterminada completamente blanca si el objetivo anterior de la unidad de textura no coincide con el tipo de muestra.
datenwolf
1
@ legends2k: Además, piense en lo que sucedería si hubiera una textura 2D y 3D unida a la misma unidad y usted tuviera un uniforme de muestra 2D y 3D, que se une a la misma unidad. Puede activar todo tipo de errores de controladores extraños con esto. En la práctica, pensar en el viejo modelo de precedencia de funciones fijas mantiene su mente cuerda y su programa funcionando, porque así es como la mayoría de los conductores se comportarán de manera predecible.
datenwolf
2

Si en tu sombreador necesitas buscar en 2 texturas:

uniform sampler2D tex1;  
uniform sampler2D tex2;  

Es necesario indicar para tex1 y tex2 sus fuentes de la siguiente manera:

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

en el bucle de renderizado:

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

Con una gl_bindtexture, no es posible hacer tal cosa. Por otro lado, un posible uso de un enlace en el bucle de renderizado es el caso en el que se alimenta una textura con un contenido en secuencia (video, cámara web):

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
fuente