关于 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;
}
纹理很有趣;它们代表了绑定的特殊情况。许多调用都有一个"target"参数。这表示 OpenGL 上下文中可以绑定该类型对象的不同位置。例如,可以绑定帧缓冲器对象以读取 () 或写入 ()。这会影响 OpenGL 使用缓冲区的方式。这就是上面的参数所代表的内容。glBind*
GL_READ_FRAMEBUFFER
GL_DRAW_FRAMEBUFFER
loc
纹理是特殊的,因为当您第一次将它们绑定到目标时,它们会获得特殊信息。首次将纹理绑定为 时,实际上是在纹理中设置特殊状态。你是说这个纹理是一个2D纹理。它将永远是2D纹理;此状态永远无法更改。如果你有一个纹理,它首先被绑定为 ,你必须始终将其绑定为 ;尝试绑定它将导致错误(在运行时)。GL_TEXTURE_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的用武之地。
对于纹理,不仅有目标 (, 等)。还有 纹理单元。就我们的C/C++示例而言,我们有这样的:GL_TEXTURE_1D
GL_TEXTURE_CUBE_MAP
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;
}
请注意,现在,我们不仅有一个s的2D列表,而且我们还有一个当前对象的概念。我们有一个设置当前对象的函数,我们有一个最大数量的当前对象的概念,并且我们所有的对象操作函数都经过调整以从当前对象中进行选择。Object
更改当前活动对象时,将更改整个目标位置集。因此,您可以绑定进入当前对象 0 的内容,切换到当前对象 4,然后修改一个完全不同的对象。
这种与纹理对象的类比是完美的...几乎。
看,不取整数;它需要一个枚举器。从理论上讲,这意味着它可以从 到 .但有一件事你必须明白:glActiveTexture
GL_TEXTURE0
GL_TEXTURE31
这是错误的!
可以采用的实际范围由 控制。这是实现允许的最大并发多纹理数。它们分别被划分为不同的分组,用于不同的着色器阶段。例如,在 GL 3.x 类硬件上,您将获得 16 个顶点着色器纹理、16 个片段着色器纹理和 16 个几何着色器纹理。因此,将是48。glActiveTexture
GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
但是没有48个枚举器。这就是为什么不真正采用枚举器的原因。正确的调用方式如下:glActiveTexture
glActiveTexture
glActiveTexture(GL_TEXTURE0 + i);
其中 是介于 0 和 之间的数字。i
GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
渲染
那么,所有这些与渲染有什么关系呢?
使用着色器时,将采样器制服设置为纹理图像单元(图像单元位于何处)。这表示您与 一起使用的数字。采样器将根据采样器类型选择目标。因此,将从目标中挑选。这是采样器具有不同类型的原因之一。glUniform1i(samplerLoc, i)
i
glActiveTexture
sampler2D
GL_TEXTURE_2D
现在,这听起来很可疑,就像您可以拥有两个GLSL采样器一样,它们具有使用相同纹理图像单元的不同类型。但你不能;OpenGL禁止这样做,并且在您尝试渲染时会给您一个错误。
您可以创建大约与系统中可用内存一样多的纹理对象。这些对象保存纹理的实际数据(纹素)以及glTexParameter提供的参数(请参阅常见问题解答)。
创建时,必须将一个纹理目标分配给一个纹理对象,该对象表示纹理的类型 (, , , ...)。GL_TEXTURE_2D
GL_TEXTURE_3D
GL_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
因此,您必须将纹理对象绑定到要使用的单位。
采样器的类型将与纹理单元中使用的纹理目标进行匹配:for ,依此类推...Sampler2D
GL_TEXTURE_2D
想象一下,GPU就像一些油漆加工厂。
有许多罐子,它们将染料输送到一些涂装机。在涂漆机中,然后将染料应用于物体。这些坦克是纹理单位
这些罐可以配备不同种类的染料。每种染料都需要某种其他类型的溶剂。"溶剂"是质地目标。为方便起见,每个罐子都连接到一些溶剂供应,但每个罐中一次只能使用一种溶剂。所以有一个阀门/开关, , , .您可以同时将所有染料类型填充到罐中,但由于只有一种溶剂进入,因此只会"稀释"匹配的染料。因此,您可以绑定每种质地,但是与"最重要的"溶剂的结合实际上会进入罐中并与它所属的染料混合。TEXTURE_CUBE_MAP
TEXTURE_3D
TEXTURE_2D
TEXTURE_1D
然后是染料本身,它来自仓库,并通过"绑定"将其填充到罐中。这就是你的质地。
何时使用 glActiveTexture?
在 OpenGL 编程指南一书中,示例 9-7 使用两个纹理对象,它在调用 glBindTexture 之前不调用 glActiveTexture,而在示例 9-10 中,它在 glBindTexture 之前调用 glActiveTexture。有什么区别?
glActiveTexture 和 glBindTexture 有什么区别?似乎它们都可以激活纹理。
想象一下,OpenGL 内部看起来像这样:
struct TextureUnit
{
GLuint targetTexture1D;
GLuint targetTexture2D;
GLuint targetTexture3D;
GLuint targetTextureCube;
...
};
TextureUnit textureUnits[GL_MAX_TEXTURE_IMAGE_UNITS]
GLuint currentTextureUnit = 0;
glActiveTexture的意思是:
void glActiveTexture(GLenum textureUnit)
{
currentTextureUnit = GL_TEXTURE0 - textureUnit;
}
glBindTexture就是这样做的:
void glBindTexture(GLenum textureTarget, GLuint textureObject)
{
TextureUnit *texUnit = &textureUnits[currentTextureUnit];
switch(textureTarget)
{
case GL_TEXTURE_1D: texUnit->targetTexture1D = textureObject; break;
case GL_TEXTURE_2D: texUnit->targetTexture2D = textureObject; break;
case GL_TEXTURE_3D: texUnit->targetTexture3D = textureObject; break;
case GL_TEXTURE_CUBEMAP: texUnit->targetTextureCube = textureObject; break;
}
}
显然会有错误测试,但这就是想法。修改纹理的所有函数都使用 glActiveTexture 设置的"currentTextureUnit"。它们采用的目标参数告诉要使用的纹理单元中的绑定纹理位置。
因此,如果您有:
glActiveTexture(GL_TEXTURE0 + 5);
glBindTexture(GL_TEXTURE_2D, object);
glTexImage2D(GL_TEXTURE_2D, ...);
要上传到的纹理是存储在 textureUnits[5]->targetTexture2D 中的纹理。绑定的每个纹理都有一个纹理目标和一个纹理单元。这将指定其在上下文中的唯一位置。
我没有那本特定的书,但人们经常将纹理绑定到上下文中,只是为了上传一些数据或修改它。此时,将其绑定到哪个纹理单元并不重要,因此无需设置当前纹理单元。glTexImage2D不关心当前活动纹理是0,1,40还是其他什么。
但是,纹理单元对对象的渲染具有特殊意义。绑定纹理以便与纹理一起渲染时,需要将它们绑定到特定的纹理单元。因此,您需要在执行绑定之前设置当前纹理单元。
示例9-10不完整;也许这就是为什么你感到困惑。以这种方式思考:如果需要同时使用多个纹理,则必须应用 glActiveTexture() 来设置每个纹理单元(请参阅 Alfonse 之前的帖子)。如果一次只需要一个纹理,则只使用一个单位(通常为 0)。
请注意,纹理单元的数量是有限的。在移动设备上,通常只有两个。在台式机系统上,它应至少为 8。
你已经得到了一些很好的答案。我只想补充一点。glActiveTexture 选择活动纹理单元,glBindTexture 将纹理绑定到活动纹理单元。所以你看到他们携手合作。
GL 不是使用幕后的"活动纹理单元"选择器来帮助定义 BindTexture 的功能,而是可以在一个独立的 GL 调用中将参数提供给两个 API。事实上,这就是在EXT_direct_state_access 12函数 glBindMultiTextureEXT:
void BindMultiTextureEXT(enum texunit, enum target, uint texture);
这完全符合我们刚才讨论的内容。你告诉它你想要绑定哪个"纹理",它的纹理类型是什么(目标),以及你想要绑定它的"纹理单元"。一切都在那里,没有任何幕后选择器。
此扩展为许多其他纹理调用定义了类似的 API,此外还有许多其他调用。
如果不调用 glActiveTexture,则它使用默认值,即 0(GL_TEXTURE0)。
如果需要将纹理绑定到另一个 tex 单元,则需要 glActiveTexture。
尽管有这个名字,glActiveTexture不会激活纹理单元。我真的不知道为什么他们不只是在GL 1.2.1中创建glBindMultiTexture函数。
当时有一种"让我们不要修改旧的GL函数,而只是引入另一个"的态度。现在必须忍受它。
GLES2标准至少需要8个纹理单元,但不太确定GLES1需要什么。顺便说一句,使用PowerVR SGX的N900仅支持:8个纹理单元。按照今天的标准,N900中的SGX在图腾柱上相当低,并且较新的设备通常比N900中的SGX更快(也足够频繁地使用SGX,但更高的时钟和更高的"型号")。
红皮书在固定功能方面非常出色,但几乎没有提供关于可编程管道中正在发生的事情的线索。另一方面,Wiki过于简单化了。
您有六个不同的常量,用于定义可以使用多少个采样器对象:
- GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS,
- GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS,
- GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS,
- GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS,
- GL_MAX_TEXTURE_IMAGE_UNITS和
- GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS。
前5个中的每一个都定义了特定可编程流水线级中纹理图像单元的最大数量。最后一个定义了可在所有阶段中同时使用的纹理图像单元的总数。从理论上讲,MAX_COMBINED可以是所有其他值的总和,但在实践中,由于纹理单元可能是共享的,因此该数字可能会更小。
纹理参数存储在纹理对象中,而不是纹理单元中。因此,当您调用后跟 时,调用会修改您刚刚绑定到该纹理单元的纹理的状态。如果随后将不同的纹理绑定到该纹理单元并调用 ,它将修改第二个纹理的状态;第一个纹理将保留其未绑定时的状态。glBindTexture
glTexParameter
glTexParameter
glTexParameter
纹理单元只不过是对实际纹理的引用集合。其状态完全由绑定到每个目标的纹理以及(自 3.3 起)绑定到纹理单元的任何采样器(采样器对象允许将采样状态与纹理分开存储)组成。使用 OpenGL 4.5 或扩展,可以创建和修改纹理,而无需将它们绑定到纹理单元(通过 、 、 )。使用扩展(不在任何核心版本中),可以使用纹理,而无需将它们绑定到纹理单元。ARB_direct_state_access
glTextureStorage*
glTextureSubImage*
glTextureParameter*
ARB_bindless_texture