从我收集的内容中,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个目标中的任何一个中获取数据?这是让我感到困惑的部分。
答案 0 :(得分:234)
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
函数及其变体(glCompressedTexImage
,glCopyTexImage
,最近的glTexStorage
)。各种SubImage
版本会更改纹理的内容,但它们在技术上不会更改其状态。 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
的2D列表,而且我们还拥有当前对象的概念。我们有一个设置当前对象的函数,我们有最大数量的当前对象的概念,并且我们调整了所有对象操作函数以从当前对象中进行选择。
更改当前活动对象时,可以更改整个目标位置集。所以你可以绑定进入当前对象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
之间的数字。
那么所有这些与渲染有什么关系?
使用着色器时,将采样器制服设置为纹理图像单元(glUniform1i(samplerLoc, i)
,其中i
是图像单位)。这表示您与glActiveTexture
一起使用的数字。采样器将根据采样器类型选择目标。因此,sampler2D
会从GL_TEXTURE_2D
目标中挑选。这是采样器具有不同类型的一个原因。
现在这听起来很可疑,你可以拥有两个使用相同纹理图像单元的不同类型的GLSL采样器。但你不能; OpenGL禁止这样做,并在您尝试渲染时给出错误。
答案 1 :(得分:17)
我会试一试!所有这一切都不是那么复杂,只是一个条款问题,希望我能说清楚。
您可以创建大致与系统中可用内存一样多的纹理对象。这些对象包含纹理的实际数据(纹素)以及 glTexParameter 提供的参数(参见FAQ)。
创建时,您必须将一个纹理目标分配给一个纹理对象,该对象表示纹理的类型(GL_TEXTURE_2D
,GL_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_MAP
,TEXTURE_3D
,TEXTURE_2D
,TEXTURE_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