glActiveTexture和glBindTexture之间的区别和关系


137

根据我的收集,glActiveTexture设置活动的“纹理单位”。每个纹理单元可以具有多个纹理目标(通常为GL_TEXTURE_1D,2D,3D或CUBE_MAP)。

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

可用的纹理单位数量取决于系统。我在库中最多看到32个枚举。我想这实质上意味着我可以减少GPU的限制(我认为这是168)是否可以同时在GPU内存中添加32个纹理?我猜还有一个额外的限制,那就是我不超过GPU的最大内存(据说是1 GB)。

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

接下来,您通常要加载纹理。您可以通过进行此操作glTexImage2D。其第一个参数是纹理目标。如果这类似于glBufferData,则我们实质上将“句柄” /“纹理名称”绑定到纹理目标,然后将纹理数据加载到该目标中,从而将其与该句柄间接关联。

glTexParameter呢 我们必须绑定一个纹理目标,然后再次选择相同的目标作为第一个参数?还是只要我们具有正确的活动纹理单位,就不需要绑定纹理目标?

glGenerateMipmap 也可以在目标上操作...该目标是否仍必须绑定到纹理名称才能成功?

然后,当我们想在其上绘制带有纹理的对象时,是否必须同时选择一个活动的纹理单位和一个纹理目标?还是选择纹理单元,然后可以从与该单元关联的4个目标中的任意一个获取数据?这部分确实让我感到困惑。

Answers:


259

所有关于OpenGL对象

OpenGL对象的标准模型如下。

对象具有状态。认为他们是一个struct。因此,您可能有一个这样定义的对象:

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

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

改变状态

在C / C ++中,如果您具有type的实例,则可以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上下文中可以绑定该类型对象的不同位置。例如,您可以绑定帧缓冲区对象以进行读取(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功能及其变体(glCompressedTexImage,,glCopyTexImage最近的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;
}

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

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

这种与纹理对象的类比非常完美……几乎。

看,glActiveTexture不取整数;它需要一个枚举器。从理论上讲,这意味着可以采取任何GL_TEXTURE0措施GL_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);

i0和之间的数字GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS

渲染图

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

使用着色器时,可以将采样器均匀设置为纹理图像单位(glUniform1i(samplerLoc, i),其中i为图像单位)。代表您使用的数字glActiveTexture。采样器将根据采样器类型选择目标。因此,a sampler2D将从GL_TEXTURE_2D目标中选择。这就是采样器具有不同类型的原因之一。

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


12
哇!另一个很棒的答案-谢谢尼科尔!我特别喜欢关于2D纹理始终是2D纹理的那段。我现在正在围绕这些事情构建一个包装器,但是我不确定是否应该让它保持变化。关于GL_TEXTURE0 + i-我的意思是检查枚举值以查看其是否有效。最后一段-不知道那是否合法。优秀的!我正在为您的所有答案添加书签,以便我再次参考它们。
mpen 2012年

6
@Nicol Bolas:这真的很好解释。您应该将其中一些内容复制到在线opengl书中的“纹理”一章中。我认为这非常清楚,可以很好地补充本章内容。
WesDec

3
@Nicol Bolas我才刚刚开始学习OpenGL,这个答案对我有很大帮助。谢谢!
内联

2
嘿nico,只想指出您的小错字:GL_DRAW_FRAMEBUFFER而不是GL_WRITE_FRAMEBUFFER
Defd 2013年

3
@Nicol:哇,我以前对这个最好的定义来自您的arcsynthesis教程,现在您甚至已经超越了这个出色的资源。谢谢
Baggers

20

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


您可以创建与系统中可用内存大致相同数量的纹理对象。这些对象保存了glTexParameter提供的纹理的实际数据(纹理)以及参数(请参阅FAQ)。

在创建时,你必须指定一个纹理目标到一个纹理对象,它代表的纹理类型(GL_TEXTURE_2DGL_TEXTURE_3DGL_TEXTURE_CUBE,...)。

纹理对象纹理目标这两个项目代表纹理数据。我们待会儿再回来。

纹理单位

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

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

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

glTextureUnit[0] = textureObject

因为GL是状态机,所以它不能这样工作。假设我们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实际上取决于您要绑定的纹理的类型。

纹理对象

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

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

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

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代表采样器要使用的纹理单位(而不是要使用的纹理对象)的索引的值。

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

采样器的类型将与纹理单位中使用的纹理目标进行匹配:Sampler2Dfor GL_TEXTURE_2D,等等。


我不明白的一件事。假设我有一些纹理,并且在不同纹理单元上的许多着色器中使用了它。假设我想在运行时更改纹理过滤。我应该使用什么纹理单位?我可以更改单元0上的纹理状态,然后在其他单元上使用该纹理吗?
majakthecoder

@majakthecoder在我的回答中,我将过滤视为纹理对象的一个属性-这意味着您不能专门在纹理单位中对其进行更改。根据您要定位的OpenGL的风格,您可以对对象采样以解决此问题(opengl.org/wiki/Sampler_Object),否则,您可能必须复制纹理对象,才能同时进行多个过滤。
rotoglup

12

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

有许多缸,可以将染料输送到某些喷漆机上。然后在喷漆机中将染料施加到物体上。这些坦克是纹理单位

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

然后是染料本身,它来自仓库,通过“捆绑”将其填充到槽中。那就是你的质感。


2
有点奇怪的比喻...我不确定它是否真的可以清除所有内容。特别是有关“稀释”和“最重要的溶剂”的部分。您是说如果我同时绑定2d纹理和3d纹理,则只能使用其中之一,还是什么?哪一个最重要?
mpen 2012年

2
@Mark:好吧,我想说的是一位用字面染料工作的画家(比如说油基和水基)。无论如何,是的,如果您绑定并启用了多个纹理目标,则优先级为:CUBE_MAP> 3D> TEXTURE_ARRAY> 2D> 1D。
datenwolf 2012年

1
整齐!我不知道优先顺序。现在更有意义,因为我知道每个纹理单元只能使用一个纹理目标。
mpen 2012年

1
@ legends2k:好,现在变得有趣了。我们在谈论核心还是兼容性概要文件。我们是否假设理想或有故障的驱动程序。理论上,制服的类型选择要选择纹理单元的目标。实际上,这发生在核心配置文件中。在兼容性配置文件中,如果纹理单元的先前目标与采样器的类型不匹配,则可能会有一些错误的驱动程序为您呈现全白色的默认纹理。
datenwolf 2012年

1
@ legends2k:另外,请考虑一下,如果将2D和3D纹理绑定到同一单元,并且您将2D和3D采样器统一绑定到同一单元,将会发生什么情况?您可以由此触发各种奇怪的驱动程序错误。在实践中,使用旧的固定功能优先级模型进行思考可以使您头脑清醒,并使程序正常运行,因为这是大多数驱动程序将以可预测的方式进行行为的方式。
datenwolf 2012年

2

如果在着色器中,则需要从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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.