glActiveTexture和glBindTexture之间的差异和关系

时间:2012-01-15 01:36:51

标签: opengl textures

从我收集的内容中,glActiveTexture设置了活动的“纹理单元”。每个纹理单元可以有多个纹理目标(通常是GL_TEXTURE_1D,2D,3D或CUBE_MAP)。

如果我理解正确,你必须先调用glActiveTexture来设置纹理单元(初始化为GL_TEXTURE0),然后将(一个或多个)“纹理目标”绑定到该纹理单元?

可用的纹理单元数取决于系统。我在我的图书馆看到最多32个枚举的枚举。我想这实际上意味着我可以拥有GPU的限制(我认为是 16 8)中的较小者和GPU内存中的32个纹理在任何时候?我想还有一个额外的限制,即我不超过GPU的最大内存(假设为1 GB)。

我是否正确理解纹理目标和纹理单元之间的关系?假设我允许每个16个单位和4个目标,这是否意味着有16 * 4 = 64个目标的空间,或者它不能像那样工作?

接下来,您通常要加载纹理。您可以通过glTexImage2D执行此操作。第一个参数是纹理目标。如果这个works like glBufferData,那么我们基本上将“handle”/“texture name”绑定到纹理目标,然后将纹理数据加载到该目标中,从而间接地将它与该句柄相关联。

glTexParameter怎么样?我们必须绑定一个纹理目标,然后再次选择同一个目标作为第一个参数?或者,只要我们有正确的活动纹理单元,纹理目标是否需要绑定?

glGenerateMipmap也在目标上运行......目标必须仍然绑定到纹理名称才能成功?

然后,当我们想要在其上绘制纹理时,我们是否必须两者选择一个活动纹理单元,然后选择一个纹理目标?或者我们选择一个纹理单元,然后我们可以从与该单元相关的4个目标中的任何一个中获取数据?这是让我感到困惑的部分。

4 个答案:

答案 0 :(得分:234)

所有关于OpenGL对象

OpenGL对象的标准模型如下。

对象有状态。将它们视为struct。所以你可能有一个像这样定义的对象:

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

该对象具有存储在其中的某些值,并且具有状态。 OpenGL对象也有状态。

改变状态

在C / C ++中,如果你有一个Object类型的实例,你可以改变它的状态如下:obj.count = 5;你可以直接引用一个对象的实例,得到特定的状态你想要改变,并将价值推入其中。

在OpenGL中,你没有这样做。

由于遗留原因,最好不明原因,要更改OpenGL对象的状态,必须首先绑定到上下文。这是通过glBind*调用中的一些来完成的。

与此相当的C / C ++如下:

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

纹理很有趣;它们代表了绑定的特殊情况。许多glBind*来电都有"目标"参数。这表示OpenGL上下文中可以绑定该类型的对象的不同位置。例如,您可以绑定framebuffer对象以进行读取(GL_READ_FRAMEBUFFER)或写入(GL_DRAW_FRAMEBUFFER)。这会影响OpenGL使用缓冲区的方式。这就是上面loc参数所代表的内容。

纹理是特殊的,因为当首先将它们绑定到目标时,它们会获得特殊信息。首次将纹理绑定为GL_TEXTURE_2D时,实际上是在纹理中设置特殊状态。你是说这个纹理是2D纹理。并且总是是2D纹理;此状态无法更改永远。如果您的纹理最初被绑定为GL_TEXTURE_2D,则必须始终将其绑定为GL_TEXTURE_2D;尝试将其绑定为GL_TEXTURE_1D将导致错误(运行时)。

绑定对象后,可以更改其状态。这是通过特定于该对象的通用函数完成的。它们也占据了一个代表要修改的对象的位置。

在C / C ++中,这看起来像:

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

注意此函数如何设置当前绑定的loc值中发生的任何事情。

对于纹理对象,主要纹理状态更改函数为glTexParameter。改变纹理状态的唯一其他函数是glTexImage函数及其变体(glCompressedTexImageglCopyTexImage,最近的glTexStorage)。各种SubImage版本会更改纹理的内容,但它们在技术上不会更改其状态Image函数分配纹理存储并设置纹理的格式; SubImage函数只是复制像素。这不被认为是纹理的状态。

请允许我重复一遍:这些是修改纹理状态的唯一函数。 glTexEnv修改环境状态;它不会影响存储在纹理对象中的任何东西。

活动纹理

纹理的情况更复杂,再次出于传统原因,最好不要公开。这是glActiveTexture的用武之地。

对于纹理,不仅仅是目标(GL_TEXTURE_1DGL_TEXTURE_CUBE_MAP等)。还有纹理单位。就我们的C / C ++示例而言,我们拥有的是:

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

请注意,现在,我们不仅拥有Object的2D列表,而且我们还拥有当前对象的概念。我们有一个设置当前对象的函数,我们有最大数量的当前对象的概念,并且我们调整了所有对象操作函数以从当前对象中进行选择。

更改当前活动对象时,可以更改整个目标位置集。所以你可以绑定进入当前对象0的东西,切换到当前对象4,并修改一个完全不同的对象。

与纹理对象的这种类比非常完美......差不多。

请参阅glActiveTexture不带整数;它需要一个枚举器。理论上这意味着它可以从GL_TEXTURE0GL_TEXTURE31采取任何行动。但是你必须明白一件事:

这是假的!

glActiveTexture可以采取的实际范围由GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS决定。这是实现允许的最大同时多重纹理数。对于不同的着色器阶段,每个分为不同的分组。例如,在GL 3.x类硬件上,您将获得16个顶点着色器纹理,16个片段着色器纹理和16个几何着色器纹理。因此,GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS将为48。

但是没有48名普查员。这就是为什么glActiveTexture并不真正采用调查员的原因。 正确方式调用glActiveTexture如下:

glActiveTexture(GL_TEXTURE0 + i);

其中i是介于0和GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS之间的数字。

渲染

那么所有这些与渲染有什么关系?

使用着色器时,将采样器制服设置为纹理图像单元(glUniform1i(samplerLoc, i),其中i是图像单位)。这表示您与glActiveTexture一起使用的数字。采样器将根据采样器类型选择目标。因此,sampler2D会从GL_TEXTURE_2D目标中挑选。这是采样器具有不同类型的一个原因。

现在这听起来很可疑,你可以拥有两个使用相同纹理图像单元的不同类型的GLSL采样器。但你不能; OpenGL禁止这样做,并在您尝试渲染时给出错误。

答案 1 :(得分:17)

我会试一试!所有这一切都不是那么复杂,只是一个条款问题,希望我能说清楚。


您可以创建大致与系统中可用内存一样多的纹理对象。这些对象包含纹理的实际数据(纹素)以及 glTexParameter 提供的参数(参见FAQ)。

创建时,您必须将一个纹理目标分配给一个纹理对象,该对象表示纹理的类型(GL_TEXTURE_2DGL_TEXTURE_3D,{{1} },...)。

这两个项目纹理对象纹理目标表示纹理数据。我们稍后会再回来。

纹理单位

现在,OpenGL提供了一个纹理单元的数组,可以在绘制时同时使用。数组的大小取决于OpenGL系统,你的系统有8个。

您可以纹理对象绑定到纹理单元,以便在绘制时使用给定的纹理。

在一个简单易用的世界中,要使用给定的纹理进行绘制,您需要将纹理对象绑定到纹理单元,然后执行(伪代码):

GL_TEXTURE_CUBE

由于GL是状态机,唉,它不会这样工作。假设我们的glTextureUnit[0] = textureObject 包含textureObject纹理目标的数据,我们会将之前的分配表达为:

GL_TEXTURE_2D

请注意,glActiveTexture(GL_TEXTURE0); // select slot 0 of the texture units array glBindTexture(GL_TEXTURE_2D, textureObject); // do the binding 实际上取决于您要绑定的纹理的类型。

纹理对象

在伪代码中,要设置纹理数据或纹理参数,例如:

GL_TEXTURE_2D

OpenGL无法直接操作纹理对象,更新/设置其内容或更改其参数,您必须先将它们绑定到活动纹理单元(无论哪个)。等效代码变为:

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

<强>着色

着色器可以访问所有纹理单元,它们不关心活动纹理。

采样器制服是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) 值,表示用于采样器的纹理单元的索引(要使用的纹理对象)。

因此,您必须将纹理对象绑定到要使用的单位。

采样器的类型将与纹理单元中使用的纹理目标匹配:int Sampler2D,等等......

答案 2 :(得分:12)

想象GPU就像一些油漆加工厂。

有许多罐子可以将染料送到一些喷漆机上。然后在涂漆机中将染料施加到物体上。那些坦克是纹理单位

这些水箱可以配备不同种类的染料。每种染料都需要其他种类的溶剂。 “溶剂”是纹理目标。为方便起见,每个罐连接到一些溶剂供应,但每个罐中一次只能使用一种溶剂。所以有一个阀门/开关TEXTURE_CUBE_MAPTEXTURE_3DTEXTURE_2DTEXTURE_1D。您可以同时将所有染料类型填充到罐中,但由于只有一种溶剂进入,它将仅“稀释”染料匹配的种类。因此,您可以将各种纹理绑定,但与“最重要”溶剂的结合实际上会进入罐中并与其所属的染料混合。

然后是染料本身,它来自仓库,通过“绑定”它填充到罐中。那是你的质地。

答案 3 :(得分:1)

如果在着色器中需要从2种纹理中查找:

uniform sampler2D tex1;  
uniform sampler2D tex2;  

有必要为tex1和tex2指明其来源,如下所示:

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

在渲染循环中:

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

使用gl_bindtexture,不可能做这种事情。另一方面,当您在流(视频,网络摄像头)中馈送带有内容的纹理时,可能会在渲染循环中使用绑定:

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