【问题标题】:Concept behind OpenGL's 'Bind' functionsOpenGL 的“绑定”函数背后的概念
【发布时间】:2012-03-18 13:59:37
【问题描述】:

我正在向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 和缓冲区设置为 新缓冲区对象的名称将缓冲区对象名称绑定到目标。当缓冲区对象绑定到 目标,该目标的先前绑定会自动中断。

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

【问题讨论】:

  • 要记住的一件事...当有人说“绑定”时...一般来说。 b=5 你刚刚将5 绑定到bc="asdf" 你刚刚将"asdf" 绑定到c。您将“记忆”“绑定”到漂亮(或丑陋)的“名字”。
  • @n611x007,这正是我想要的。我一直在阅读文档并看到“绑定”这个词,但不知道它们是什么意思。

标签: opengl


【解决方案1】:

opengl 中的命令并不是孤立存在的。他们假设存在一个上下文。一种思考方式是,隐藏在背景中的 opengl 对象,函数是该对象上的方法。

所以当你调用一个函数时,它的作用当然取决于参数,但也取决于 opengl 的内部状态——上下文/对象。

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

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

【讨论】:

  • 谢谢,如果我正确理解您的答案,我可以将 GL_ARRAY_BUFFER 视为上下文的“数据成员”,我将 positionBufferObject 作为数组顶点位置的句柄分配给它。正确吗?
  • 可能stateful 是一些人可以在这里说的话
  • 听起来您对“绑定”在您的脑海中的含义有所了解,但无法准确地向其他人描述它
  • 我有一个问题,我们为什么不跳过binding,只提供句柄作为glBufferData 的额外参数? (换句话说,为什么 OpenGL API 不接受句柄作为参数?)
【解决方案2】:

OpenGL 技术可能非常不透明和令人困惑。我知道!多年来,我一直在编写基于 OpenGL 的 3D 引擎(断断续续)。就我而言,问题的一部分是,我编写引擎来隐藏底层 3D API (OpenGL),所以一旦我得到一些工作,我就再也看不到 OpenGL 代码了。

但这里有一种技术可以帮助我的大脑理解“OpenGL 方式”。我认为这种思考方式是正确的(但不是全部)。

想想硬件图形/GPU 卡。它们具有在硬件中实现的某些功能。例如,GPU 可能一次只能更新(写入)一个纹理。尽管如此,GPU 必须在 GPU 内部的 RAM 中包含许多纹理,因为 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 的所有原因(而且必须如此),我希望他们能发表自己的回复。我相信这是一个重要的问题和话题,其基本原理很少被描述,更不用说以我微弱的大脑可以理解的方式了。

【讨论】:

  • 确实,但是我要澄清一下这个陈述的一件事:1:生成纹理并将其标识符分配给无符号整数变量。虽然标识符取自名称池(在 OpenGL 3.0+ 中,该池对于每种类型的对象都是唯一的,在旧版本中不必如此),直到您第一次绑定对象时,它才真正不是实际的纹理.挑剔,我知道,但是 GL 充满了这样的怪癖:P 此外,glSetTexture (...) 类型的语法确实以GL_EXT_direct_state_access 的形式存在,但扩展似乎是永久固定在EXT 状态中的:(跨度>
  • glBindMultiTextureEXT (...) 实际上是这样的。所以OpenGL确实有一个解决愚蠢的绑定然后修改语义的方法,ARB并不急于正式解决这个问题。好消息是GL_EXT_direct_state_access 由所有主要供应商在大多数平台上实现。我们甚至可以使用 OpenGL 4.4 中新的无绑定纹理功能完全回避这个问题:GL_ARB_bindless_texture,您可以直接将句柄传递给 GLSL 着色器,而不是绑定纹理。
【解决方案3】:

来自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 指针。这不是绝对必要的,因为以后绑定到该目标的任何操作都会简单地取消绑定已经存在的内容。但除非您对渲染有非常严格的控制,否则通常最好取消绑定您绑定的对象。

【讨论】:

  • 是的,我已经读过了。我问这个问题的唯一原因是因为我不清楚。尤其是像“glBindBuffer 函数将新创建的缓冲区对象绑定到 GL_ARRAY_BUFFER 绑定目标”这样的句子。真正的意思。
  • ||"它们必须首先绑定到上下文"
  • :facepalm: 我知道。我不明白它在(上下文,目标,位置)等上下文中的含义。我不知道其他人,但是在启动OpenGL之后,不直观的行话是毁灭性的。
  • 态度只有-1。这有点粗鲁。
  • -1。如果教程中的内容对他来说很难理解,复制粘贴到这里而不做任何重新解释也无济于事。
【解决方案4】:

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

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

【讨论】:

  • 为什么 glgenBuffers() 需要分配更多内存?我已经存储数据的数组不能重复使用吗?
  • @manasij7479:glGenBuffers 不会分配内存,它会生成一个句柄,您可以通过该句柄识别缓冲区,然后使用 glBindBuffer 进行绑定
  • @manasij7479: ...此外,GL 缓冲区需要驻留在 GPU 端,因此在 CPU 端分配的数据“没有用”,只是值得传输的来源。
【解决方案5】:

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

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

【讨论】:

  • 如果您知道什么是状态机,这将非常有用。否则可能不会那么多。 “绑定”不是一个很有意义的词
  • API 基本上是一个美化的打印机。您只是在向机器加载一些数据,例如,如果您右键单击纹理并单击“打印”,您就是将该数据“绑定”到您的喷墨打印机,只需将其复制到打印机编程查看的任何内存芯片/位置来一个打印命令,在绑定后的某个时刻,您发送另一个命令,打印机或opengl获取数据通过一些预定义的过程运行它,无论是您所有的GLSL着色器都将渲染放在您的帧缓冲区上,还是您的激光打印机进行颜色分级和棕褐色效果或其他,在一张纸上。
  • 仅供参考,这个类比只有在打印机内置这些效果的情况下才有效,使用一些程序先用棕褐色滤镜制作新图像,然后将其发送出去,更像是“纹理烘焙”,但例如可以在打印机硬件本身上将原始图像转换为灰度的打印机与“像素着色器”非常相似,实际上从广义上讲,它就是一个。片段和顶点着色器不太适合类比,但它是相同的加载机器,开枪/信号,获得结果过程。
  • 你一直在说“绑定”,但从不解释什么是绑定。你知道吗?
猜你喜欢
  • 2012-02-24
  • 1970-01-01
  • 2012-09-25
  • 2012-04-17
  • 2014-05-29
  • 1970-01-01
  • 1970-01-01
  • 2019-03-01
  • 2011-10-22
相关资源
最近更新 更多