OpenGL的“绑定”功能背后的概念

时间:2012-03-18 13:59:38

标签: opengl

我正在从this tutorial学习OpenGL。 我的问题是关于一般的规范,而不是关于特定的功能或主题。 查看以下代码时:

glGenBuffers(1, &positionBufferObject);
glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

我对在设置缓冲区数据之前和之后调用绑定函数的效用感到困惑。 由于我对OpenGL和计算机图形学一般缺乏经验,这对我来说似乎是多余的。

手册页说:

  

glBindBuffer允许您创建或使用命名缓冲区对象。调用glBindBuffer并将目标设置为          GL_ARRAY_BUFFER,GL_ELEMENT_ARRAY_BUFFER,GL_PIXEL_PACK_BUFFER或GL_PIXEL_UNPACK_BUFFER和缓冲区设置为          新缓冲区对象的名称将缓冲区对象名称绑定到目标。当缓冲区对象绑定到a时          target,该目标的先前绑定会自动中断。

将某些东西“绑定”到“目标”的概念/效用究竟是什么?

5 个答案:

答案 0 :(得分:29)

opengl中的命令不是孤立存在的。他们假设存在上下文。想到这一点的一种方法是在后台隐藏一个opengl对象,并且函数是该对象上的方法。

所以当你调用一个函数时,它的作用取决于参数,当然,还取决于opengl的内部状态 - 在context / object上。

这与bind非常清楚,它表示“将其设置为当前X”。然后函数修改“当前X”(例如,X可能是缓冲区)。 [update:]正如你所说,正在设置的东西(对象中的属性,或“数据成员”)是第一个绑定的参数。所以GL_ARRAY_BUFFER命名你正在设置的特定事物。

并回答问题的第二部分 - 将其设置为0只是清除值,这样您就不会在其他地方意外地进行计划外更改。

答案 1 :(得分:25)

OpenGL技术令人难以置信的不透明和令人困惑。我知道!多年来我一直在使用基于OpenGL的3D引擎(关闭和开启)。在我的情况下,部分问题是,我编写引擎来隐藏底层的3D API(OpenGL),所以一旦我开始工作,我再也看不到OpenGL代码了。

但这是一种帮助我的大脑理解“OpenGL方式”的技巧。我认为这种思考方式是正确的(但不是整个故事)。

考虑一下硬件图形/ GPU卡。它们具有在硬件中实现的某些功能。例如,GPU可能仅能够一次更新(写入)一个纹理。尽管如此,GPU必须在GPU内部包含许多纹理,因为CPU内存和GPU内存之间的传输速度非常慢。

所以OpenGL API的作用是创建“活动纹理”的概念。然后,当我们调用OpenGL API函数将图像复制到纹理时,我们必须这样做:

1:  generate a texture and assign its identifier to an unsigned integer variable.
2:  bind the texture to the GL_TEXTURE bind point (or some such bind point).
3:  specify the size and format of the texture bound to GL_TEXTURE target.
4:  copy some image we want on the texture to the GL_TEXTURE target.

如果我们想在另一个纹理上绘制图像,我们必须重复相同的过程。

当我们最终准备好在显示器上渲染某些东西时,我们需要我们的代码来制作我们创建的一个或多个纹理并复制图像,以便我们的片段着色器可以访问它们。

事实证明,片段着色器可以通过访问多个“纹理单元”(每个纹理单元一个纹理)一次访问多个纹理。因此,我们的代码必须将我们想要的纹理绑定到我们的片段着色器期望它们绑定到的纹理单元。

所以我们必须做这样的事情:

glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_2D, mytexture0);

glActiveTexture (GL_TEXTURE1);
glBindTexture (GL_TEXTURE_2D, mytexture1);

glActiveTexture (GL_TEXTURE2);
glBindTexture (GL_TEXTURE_2D, mytexture2);

glActiveTexture (GL_TEXTURE3);
glBindTexture (GL_TEXTURE_2D, mytexture3);

现在,我必须说我喜欢OpenGL有很多原因,但这种方法让我感到疯狂。那是因为我多年来写的所有软件都是这样的:

error = glSetTexture (GL_TEXTURE0, GL_TEXTURE_2D, mytexture0);
error = glSetTexture (GL_TEXTURE1, GL_TEXTURE_2D, mytexture1);
error = glSetTexture (GL_TEXTURE2, GL_TEXTURE_2D, mytexture2);
error = glSetTexture (GL_TEXTURE3, GL_TEXTURE_2D, mytexture3);

巴莫。无需一遍又一遍地设置所有这些状态。只需指定将纹理附加到哪个纹理单元,加上纹理类型以指示如何访问纹理,以及我想要附加到纹理单元的纹理的ID。

我也不需要将纹理绑定为活动纹理来复制图像,我只需要提供我想要复制的纹理的ID。为什么需要绑定?

嗯,有一种方法迫使OpenGL以疯狂的方式构建。因为硬件做了一些事情,而软件驱动程序做了其他事情,并且因为做什么是变量(取决于GPU卡),他们需要一些方法来控制复杂性。他们的解决方案基本上只对每种实体/对象只有一个绑定点,并要求我们在调用操作它们的函数之前将我们的实体绑定到那些绑定点。作为第二个目的,绑定实体使GPU可用,以及我们在GPU中执行的各种着色器。


至少我是如何将“OpenGL方式”直接放在脑海中的。坦率地说,如果有人真的,真的,真的理解OpenGL的所有原因(并且必须)按照它的方式构建,我希望他们发布自己的回复。我相信这是一个重要的问题和主题,而且根本没有描述的理由很少,更不用说我的小脑子能理解的方式了。

答案 2 :(得分:15)

来自Introduction: What is OpenGL?

部分
  

像结构一样的复杂聚合永远不会直接暴露在OpenGL中。任何此类构造都隐藏在API后面。这样可以更轻松地将OpenGL API公开给非C语言而无需复杂的转换层。

     

在C ++中,如果你想要一个包含整数,浮点数和字符串的对象,你可以创建它并像这样访问它:

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

//Create the storage for the object.
Object newObject;

//Put data into the object.
newObject.count = 5;
newObject.opacity = 0.4f;
newObject.name = "Some String";
  

在OpenGL中,您将使用看起来更像这样的API:

//Create the storage for the object
GLuint objectName;
glGenObject(1, &objectName);

//Put data into the object.
glBindObject(GL_MODIFY, objectName);
glObjectParameteri(GL_MODIFY, GL_OBJECT_COUNT, 5);
glObjectParameterf(GL_MODIFY, GL_OBJECT_OPACITY, 0.4f);
glObjectParameters(GL_MODIFY, GL_OBJECT_NAME, "Some String");
  

当然,这些都不是实际的OpenGL命令。这只是这种对象的界面看起来像的一个例子。

     

OpenGL拥有所有OpenGL对象的存储空间。因此,用户只能通过引用访问对象。几乎所有OpenGL对象都由无符号整数(GLuint)引用。对象由glGen *形式的函数创建,其中*是对象的类型。第一个参数是要创建的对象数,第二个参数是接收新创建的对象名的GLuint *数组。

     

要修改大多数对象,必须先将它们绑定到上下文。许多对象可以绑定到上下文中的不同位置;这允许以不同方式使用相同的对象。这些不同的位置称为目标;所有对象都有一个有效目标列表,有些只有一个。在上面的示例中,虚构目标“GL_MODIFY”是objectName绑定的位置。

这是大多数OpenGL对象的工作方式,缓冲区对象是“大多数OpenGL对象”。

如果这还不够好,本教程将再次在Chapter 1: Following the Data中介绍它:

void InitializeVertexBuffer()
{
    glGenBuffers(1, &positionBufferObject);

    glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}
  

第一行创建缓冲区对象,将句柄存储在全局变量positionBufferObject中。虽然该对象现在存在,但它还没有任何内存。那是因为我们没有用这个对象分配任何东西。

     

glBindBuffer函数将新创建的缓冲区对象绑定到GL_ARRAY_BUFFER绑定目标。正如在介绍中所提到的,OpenGL中的对象通常必须绑定到上下文才能使它们做任何事情,缓冲对象也不例外。

     

glBufferData函数执行两个操作。它为当前绑定到GL_ARRAY_BUFFER的缓冲区分配内存,这是我们刚创建和绑定的缓冲区。我们已经有了一些顶点数据;问题是它在我们的记忆中而不是OpenGL的记忆中。 sizeof(vertexPositions)使用C ++编译器来确定vertexPositions数组的字节大小。然后,我们将此大小传递给glBufferData,作为为此缓冲区对象分配的内存大小。因此,我们分配足够的GPU内存来存储我们的顶点数据。

     

glBufferData执行的另一个操作是将数据从我们的内存数组复制到缓冲区对象中。第三个参数控制它。如果此值不为NULL,则在这种情况下,glBufferData会将指针引用的数据复制到缓冲区对象中。在此函数调用之后,缓冲区对象将精确存储vertexPositions存储的内容。

     

第四个参数是我们将在未来的教程中看到的内容。

     

第二个绑定缓冲区调用只是清理。通过将缓冲区对象0绑定到GL_ARRAY_BUFFER,我们使先前绑定到该目标的缓冲区对象从中解除绑定。在这种情况下,零工作很像NULL指针。这不是绝对必要的,因为任何后来绑定到这个目标将简单地解除已经存在的东西。但除非你对渲染有非常严格的控制,否则解绑你绑定的对象通常是一个好主意。

答案 3 :(得分:5)

将缓冲区绑定到目标就像设置全局变量一样。然后,后续函数调用对该全局数据进行操作。在OpenGL的情况下,所有“全局变量”一起形成 GL上下文。事实上,所有GL函数都从该上下文中读取或以某种方式对其进行修改。

glGenBuffers()调用有点像malloc(),分配缓冲区;我们设置了一个全局指向它glBindBuffer();我们调用一个在全局(glBufferData())上运行的函数,然后我们将全局设置为NULL,这样它就不会再使用glBindBuffer()无意中在该缓冲区上运行。

答案 4 :(得分:2)

OpenGL就是所谓的“状态机”,为此,OpenGL有几个“绑定目标”,每个目标只能绑定一个东西。绑定其他内容会替换当前绑定,从而更改其状态。因此,通过绑定缓冲区,您可以(重新)定义机器的状态。

作为一个状态机,无论你绑定什么信息都会影响机器的下一个输出,在OpenGL中它是下一个绘制调用。完成后,您可以绑定新的顶点数据,绑定新的像素数据,绑定新的目标等,然后启动另一个绘制调用。如果你想在屏幕上创造运动的幻觉,当你满意的时候你已经绘制了整个场景(一个3d引擎概念,而不是OpenGL概念),你就可以翻转帧缓冲。