顶点缓冲区绑定索引和统一缓冲区绑定点?
Posted
技术标签:
【中文标题】顶点缓冲区绑定索引和统一缓冲区绑定点?【英文标题】:Vertex buffer binding index and uniform buffer binding points? 【发布时间】:2020-09-20 16:33:31 【问题描述】:只是了解统一缓冲区及其工作原理。在属性和缓冲区绑定方面,它们似乎与顶点缓冲区有相似之处。
由于我不想将缓冲区绑定点直接硬编码为glBindBufferBase
或glUniformBlockBinding
,并且对于未来的自动着色器组合,我尝试将顶点着色器缓冲区和统一缓冲区组合成一个缓冲区类的概念C++。
这是我现在的心智模型:
这是对两者的正确看法吗: 属性位置、顶点缓冲区和绑定索引 & 统一的位置、统一的缓冲区和绑定点 ?
【问题讨论】:
"我尝试将顶点着色器缓冲区和统一缓冲区合并到一个缓冲区类中" 你真的不应该那样做。缓冲区对象没有也不应该直接与其包含的数据类型相关联。 还有,位置为1
的uniform C
为什么指向绑定点2
?
@NicolBolas 好吧,我不确定是否要组合它们,它们对我来说看起来有点像(作为初学者),唯一的区别是调用 openGl 函数。我将 C++ 中的表示称为“缓冲区”的单个概念,我不打算在 OpenGL 中将顶点缓冲区和统一缓冲区合并为一个。您的第二条评论:统一 C 具有布局 C,与布局 C 关联的缓冲区对象具有绑定索引 2。
好的,我只记得统一缓冲区根本没有位置。它们有一个块索引和一个绑定索引,但没有“位置”。那么您的“位置”列实际上是什么意思?
@NicolBolas 是的,着色器中的块索引(类似于属性的位置索引)。它们都是整数,我可以在着色器中或通过代码手动设置它们。除了他们的名字“位置/区块索引”之外似乎没有什么区别?这就是我要问的,顶点缓冲区和统一缓冲区建立在相同的概念上(如帖子中的图像),只是它们的 OpenGL 调用有所不同
【参考方案1】:
您的类比的基本事实大致准确(假设我们使用的是顶点属性绑定)。 GLSL 中的顶点属性具有最终映射到缓冲区绑定索引的位置,缓冲区对象绑定到该索引。 GLSL 中的统一缓冲区有一个块索引,该索引最终与缓冲区绑定索引相关联,缓冲区对象绑定到该索引。
但是在这些事物的细节上存在太多差异,无法尝试形成任何类型的结构基础,以在形式化对象中以相同的方式处理这些构造。
考虑顶点属性索引和块索引之间的区别。是的,这些在某些方面是相似的。但是,您与他们互动的方式非常不同。块索引由 GLSL 编译器分配。属性索引由用户在代码中分配,通过着色器中的layout(location)
或在编译着色器之前通过glBindAttribLocation
。
简单地说,你查询块索引;您分配顶点属性位置。
现在,如果您想了解技术,GLSL 编译器将在编译着色器时为每个属性分配一个唯一位置,如果您没有以其他方式分配这些位置。因此,您可以查询位置。
但这给我们带来了您与他们互动方式的另一个不同之处。块索引没有任何意义;它们纯粹是着色器中特定统一块的数字标识符。块索引仅对特定着色器有用。
这不是属性位置的情况。属性位置和它们从中提取的缓冲区之间的关联包含在一个对象中与实际着色器分开:一个顶点数组对象。这意味着着色器指定的属性位置也必须与 VAO 中指定的属性格式相匹配才能正常工作。
所以属性位置的范围并不仅仅绑定到一个着色器。它必须同意它打算与之一起使用的任何 VAO。或者更重要的是,特定 VAO 的属性位置必须与该 VAO 将使用的任何着色器匹配。
在许多 GPU 中,更改顶点格式是一项相当昂贵的操作。所以保持相同的顶点格式(包括位置到绑定的关联)将是一件好事。这意味着拥有多个使用相同 VAO 的着色器是合理的。您可以更改绑定到渲染不同对象的缓冲区(这比格式更快的状态更改),但您不会在这些对象之间调用glBindVertexArray
。
要实现这一点,这些对象的所有着色器都必须为其属性使用相同的位置。但它们不一定具有相同的块索引,即使它们使用相同的统一块定义。
统一块索引值是任意的;位置不是。这就是为什么您可以分配位置但不能分配块索引的原因。
这也是为什么您可以在着色器中分配 UBO 绑定 索引(通过layout(binding=#)
),但您不能从着色器分配属性绑定索引。着色器不控制绑定索引; VAO 确实如此。
事实上,在着色器中为 UBO 分配绑定索引的能力可以从根本上消除块索引。您可以开发一组已知的具有明确含义的绑定索引。索引 0 可以用于每个场景数据(相机、透视矩阵等),索引 1 可以用于每个对象数据,索引 2 可以用于照明,索引 3 用于骨骼矩阵数组等。
但属性的类似启发式使用位置,而不是顶点缓冲区绑定索引。位置 0 可以用于位置,位置 1 用于法线,位置 2 用于颜色等。
所以从这个角度来看,从我们用来与它们交谈的 GLSL 代码的角度来看,属性位置可以被视为更类似于 UBO 绑定索引。尽管您没有将缓冲区绑定到属性位置,但还是会这样做。
还可以查看在绑定时如何与缓冲区交互的差异。 UBO 缓冲区绑定是明确范围的。您可以使用glBindBufferBase
绑定整个缓冲区,但这不是预期的用法。一般来说,拥有一堆小的缓冲区对象并不是一个好主意。如果您使用 UBO 来存储每个对象的数据,您可能只想映射一个缓冲区,一次传输所有对象数据,然后使用它,而不必一遍又一遍地绑定不同的缓冲区。
因此,UBO 绑定 API 的正常用法是将一小组缓冲区的适当子范围绑定到特定绑定点。
相比之下,顶点数组缓冲区绑定是未绑定的。您提供了起始偏移量,但范围没有上限。渲染调用可以从缓冲区存储中的任何字节获取(在偏移之后)。
这很重要,因为实例化和基础顶点渲染等功能可以让您将多个对象存储在同一个缓冲区中,而无需绑定新缓冲区来渲染不同的对象。顶点缓冲区绑定虽然不是很昂贵,但也不是世界上最便宜的事情,如果可以合理避免,您应该这样做。
这是另一个不同之处。 UBO 中存储的数据格式最终由着色器本身定义。您的 C++ 代码必须提供与 GLSL 着色器代码中的 UBO 块定义所定义的布局完全匹配的数据。
相比之下,存储在顶点缓冲区中的数据格式主要由 VAO 定义,而不是着色器。 vec4
类型的属性可以从 4 个浮点数的组、4 个转换为浮点数的规范化无符号字节、4 个以数字方式转换为浮点数的非规范化有符号短字节或许多其他替代方式获取其数据。
同样,虽然使用了类似的概念,但与它们的交互却非常不同。因此,试图构建一个系统,使这两种机制因代码结构而相似是不合适的。
【讨论】:
哇,非常感谢您的回答(以及您对 OpenGL 问题的所有其他解释)。对我来说,让 OGL 在幕后进行的所有交互都出现在我的脑海中是非常困难的。我完全忽略了 VAO 方面的事情。以上是关于顶点缓冲区绑定索引和统一缓冲区绑定点?的主要内容,如果未能解决你的问题,请参考以下文章