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上下文中可以绑定该类型对象的不同位置。例如,您可以将帧缓冲对象绑定为读取(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:
break;
}
}
请注意这个函数如何设置当前绑定的loc
值。
对于纹理对象,主要更改纹理状态的函数是glTexParameter
。唯一改变纹理状态的其他函数是glTexImage
函数及其变体(glCompressedTexImage
,glCopyTexImage
,最近的glTexStorage
)。各种SubImage
版本会更改纹理的内容,但它们并不从技术上更改其 state 。Image
函数分配纹理存储并设置纹理的格式; SubImage
函数仅复制像素。这不被认为是纹理的状态。
请允许我重申:这些是修改纹理状态的唯一函数。 glTexEnv
修改环境状态; 它不会影响存储在纹理对象中的任何内容。
活动纹理
对于纹理而言,情况更加复杂,这是由于历史原因最好不予公开。这就是glActiveTexture
发挥作用的地方。
对于纹理,不仅有目标(GL_TEXTURE_1D
,GL_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
的二维列表,还有一个当前对象的概念。我们有一个设置当前对象的函数,有一个最大当前对象数量的概念,并且我们所有的对象操作函数都被调整为从当前对象中选择。
当您更改当前活动对象时,您会更改整个目标位置集。因此,您可以将某些内容绑定到当前对象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);
其中 i
是介于 0 和 GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
之间的数字。
渲染
那么所有这些与渲染有什么关系呢?
使用着色器时,您将采样器uniform设置为纹理图像单元(glUniform1i(samplerLoc, i)
,其中i
是图像单元)。这代表您与glActiveTexture
一起使用的数字。采样器将根据采样器类型选择目标。因此,sampler2D
将从GL_TEXTURE_2D
目标中选择。这就是采样器具有不同类型的原因之一。
现在这听起来很像您可以有两个GLSL采样器,具有不同的类型,并且使用相同的纹理图像单元。但实际上你不能这样做;OpenGL禁止这样做,并会在尝试渲染时给出错误提示。
GL_TEXTURE0 + i
的部分-我想检查枚举值以确定是否有效。最后一段-不知道是否合法。太棒了!我会将您所有的回答收藏起来,以便今后参考。 - mpen