glVertexAttribPointer澄清

95

我想确认一下我的理解是否正确(我本来想在SO Chat上问的,但那里已经死了!):

我们有一个Vertex Array,通过绑定它使其“当前”
然后我们有一个Buffer,将其绑定到Target
然后我们通过glBufferData填充该Target
这实际上是填充绑定到该Target的任何内容,即我们的Buffer
然后我们调用glVertexAttribPointer描述数据的布局方式——数据是绑定到GL_ARRAY_BUFFER的任何内容
并将此描述符保存到我们原始的Vertex Array中

(1) 我的理解正确吗?
文档对所有内容的相关性有些缺乏解释。

(2) 是否有某种默认的Vertex Array?因为我忘记/省略了glGenVertexArraysglBindVertexArray,但我的程序没有问题。


编辑:我错过了一个步骤... glEnableVertexAttribArray

(3) Vertex Attrib是否在调用glVertexAttribPointer时与Vertex Array绑定,然后我们可以随时通过glEnableVertexAttribArray启用/禁用该属性,而不管当前绑定的是哪个Vertex Array?

或者 (3b) 在调用glEnableVertexAttribArray时,Vertex Attrib是否与Vertex Array绑定,因此我们可以通过在不同的时间点调用glEnableVertexAttribArray来将相同的Vertex Attrib添加到多个Vertex Array中?

2个回答

218

一些术语有点不正确:

  • Vertex Array只是包含顶点数据的数组(通常为float[]),不需要绑定任何东西,与Vertex Array Object或VAO不要混淆,稍后会讨论它
  • Buffer Object通常称为Vertex Buffer Object用于存储顶点,简称VBO,这就是你所说的Buffer
  • 没有什么被保存到顶点数组中,glVertexAttribPointer的工作方式与glVertexPointerglTexCoordPointer完全相同,只是您可以提供一个数字来指定自己的属性,将此值传递为index。所有的glVertexAttribPointer调用都会排队等待下一次调用glDrawArraysglDrawElements。如果您绑定了VAO,则VAO将存储所有属性的设置。

主要问题在于混淆了顶点属性和VAO。顶点属性仅是定义绘制所需的新方式,例如顶点、纹理坐标、法线等。VAO存储状态。首先我将解释使用顶点属性的绘图方式,然后解释如何通过VAO减少方法调用次数:

  1. 必须在着色器中使用属性之前启用它。例如,如果您希望将顶点发送到着色器,最有可能的是将其作为第一个属性,0发送。因此,在渲染之前,您需要使用glEnableVertexAttribArray(0);启用它。
  2. 现在,已启用属性,需要定义要使用的数据。为此,需要绑定您的VBO - glBindBuffer(GL_ARRAY_BUFFER, myBuffer);
  3. 现在我们可以定义属性了-glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);。按参数顺序:0是您要定义的属性,3是每个顶点的大小,GL_FLOAT是类型,GL_FALSE表示不规范化每个顶点,最后两个零表示没有跨度或偏移量。
  4. 使用它绘制一些东西-glDrawArrays(GL_TRIANGLES, 0, 6);
  5. 下一个要绘制的内容可能不会使用属性0(实际上可能会,但这是一个例子),所以可以禁用它-glDisableVertexAttribArray(0);

将其包装在glUseProgram()调用中,您就拥有了一个正确使用着色器的渲染系统。但是,假设您有5个不同的属性:顶点、纹理坐标、法线、颜色和光照贴图坐标。首先,您将为每个属性进行单独的glVertexAttribPointer调用,并且必须预先启用所有属性。假设您按我列出的属性0-4进行定义。您可以使用以下方式启用所有属性:

for (int i = 0; i < 5; i++)
    glEnableVertexAttribArray(i);
然后,您需要为每个属性绑定不同的VBO(除非您将它们全部存储在一个VBO中并使用偏移/步幅),然后您需要进行5个不同的glVertexAttribPointer调用,从顶点到光照贴图坐标分别调用glVertexAttribPointer(0,...);glVertexAttribPointer(4,...);
希望这个系统本身是有意义的。现在我将转向VAO,解释如何使用它们来减少进行此类渲染时的方法调用次数。请注意,使用VAO并非必需。
顶点数组对象或VAO用于存储所有glVertexAttribPointer调用和被针对的VBO的状态,当每个glVertexAttribPointer调用被执行时。
通过调用glGenVertexArrays生成一个VAO。要在VAO中存储所有所需内容,请使用glBindVertexArray绑定,然后进行完整的绘制调用。所有绘制绑定调用都会被拦截并存储在VAO中。您可以使用glBindVertexArray(0);取消绑定VAO。
现在,当您想要绘制对象时,您无需重新调用所有VBO绑定或glVertexAttribPointer调用,您只需要使用glBindVertexArray绑定VAO,然后调用glDrawArraysglDrawElements,您将绘制与进行所有这些方法调用相同的内容。之后可能还需要取消绑定VAO。
一旦解除绑定VAO,所有状态都将恢复为绑定VAO之前的状态。我不确定在绑定VAO时所做的任何更改是否会保存,但可以通过测试程序轻松找出。我想您可以将glBindVertexArray(0);视为绑定到“默认”VAO......

10
“顶点缓冲对象”(Vertex Buffer Object,简称 VBO)有时被称为“缓冲对象”(Buffer Object),这是因为它的确就是一个缓冲对象,和你用于统一块、像素传输、变换反馈或其他任何用途的任何其他缓冲对象没有什么不同。OpenGL 规范从未将任何东西称为“顶点缓冲对象”;即使是原始扩展规范,也从未称其为“顶点缓冲对象”。 - Nicol Bolas
3
很好的回答。感谢您抽出时间写下这篇文章!不过我有几个跟进问题:(1)您说“在渲染之前,在定义属性之前,您需要使用glEnableVertexAttribArray(0)启用它”——您确定需要在调用glVertexAttribPointer之前启用它吗?在我的测试中,顺序似乎并不重要。(2) 如果我理解正确,顶点属性是全局的,只有它们的启用/禁用状态保存到当前绑定的VAO中? - mpen
1
(1) 我认为顺序并不重要,只要在glDrawArraysglDrawElements之前启用它即可。我会更新帖子以反映这一点。(2) 是的,但存储的不仅是启用/禁用状态,还包括与这些调用相关的所有内容 - 在GL_ARRAY_BUFFER上绑定的内容、类型、步幅和偏移量。基本上,它存储了足够的信息,可以将所有顶点属性更改回使用VAO设置它们的方式。 - Robert Rouhani
@RobertRouhani:如果是这样,你的意思是我可以绑定一个VAO,设置一些顶点属性,然后绑定另一个VAO并重用相同的属性索引而不会丢失任何信息? - mpen
2
是的,VAO旨在让您用绑定VAO来替换大部分绘制方法。每个实体都可以有一个单独的VAO,它仍然可以正常工作。但是,您仍然需要更新uniform并绑定自己的纹理。而且,您必须使用相同的属性索引,因为您必须将着色器的属性与属性索引绑定,无论是通过着色器中的layout(location = x)还是在编译着色器时使用glBindAttributeLocation示例 - Robert Rouhani
8
请注意,在现代OpenGL中,不再有默认的顶点数组对象,您需要自己创建一个,否则您的应用程序将无法在向前兼容的上下文中工作。 - Overv

2
API的术语和调用顺序确实相当令人困惑。更令人困惑的是如何将各个方面 - 缓冲区、通用顶点属性和着色器属性变量关联起来。请参见OpenGL-Terminology,其中有一个相当不错的解释。
此外,链接OpenGL-VBO,shader,VAO展示了一个带有必要API调用的简单示例。对于那些从即时模式转换到可编程管线的人来说特别有用。
希望这有所帮助。
编辑:正如您可以从下面的评论中看到的那样,人们可能会做出假设并得出结论。现实情况是,对于初学者来说确实很困惑。

请参阅OpenGL术语表以获得相当不错的解释。在不到一分钟的时间里,我已经发现了一个错误信息:“这些被替换为具有标识符(称为索引)的通用顶点属性,该标识符与处理属性的着色器变量(用于坐标、颜色等)相关联。”它们不叫“indices”,而是“locations”。这是非常重要的区别,因为顶点属性也有“indices”,这与locations是非常不同的。那是一个非常糟糕的网站。 - Nicol Bolas
2
这是一个公正的评论,但并不完全准确。如果您查看OpenGL API以定义通用属性glVertexAttribPointervoid glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer),标识符被称为“index”。在程序上下文中,相同的标识符在API glGetAttribLocation中被称为“location”。 - ap-osd
有人知道如果缓冲区和着色器变量都是浮点数,规范化是如何定义的吗?它只是什么也不做吗? - Desperado17

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接