4

我一直在阅读这篇OpenGL4.1 新功能评论。我并不真正理解 GL_ARB_separate_program_objects 用法背后的想法,至少基于帖子作者的说法:

它允许独立使用着色器阶段而无需更改其他着色器阶段。我认为它有两个主要原因:Direct3D、Cg 甚至旧的 OpenGL ARB 程序都可以做到,但更重要的是,它带来了一些软件设计灵活性,允许以较低粒度查看图形管道。例如,我最大的敌人 VAO 是一个容器对象,它链接缓冲区数据、顶点布局数据和 GLSL 程序输入数据。如果没有专门的软件设计,这意味着当我更改对象的材质(新的片段着色器)时,我需要不同的 VAO……幸运的是,可以保持相同的 VAO,并且只通过定义如何更改程序的约定来更改程序。在 C++ 程序和 GLSL 程序之间进行通信。即使仍然存在一些缺点,它也能很好地工作。

现在,这一行:

例如,我最大的敌人 VAO,是一个容器对象,它链接缓冲区数据、顶点布局数据和 GLSL 程序输入数据。没有专门的软件设计,这意味着当我更改对象的材质时(新的片段着色器) , 我需要不同的 VAO...

让我想知道。在我的 OpenGL 程序中,我使用 VAO 对象,我可以在不同的着色器程序之间切换,而无需对 VAO 本身进行任何更改。那么,我误解了整个想法吗?也许他的意思是我们可以在不重新链接的情况下为同一个程序切换着色器?

4

2 回答 2

12

我将这个答案分成多个部分。

ARB_separate_shader_objects 的目的是什么

此功能的目的是能够轻松地在顶点/片段/几何/镶嵌着色器之间进行混合和匹配。

目前,您必须将所有着色器阶段链接到一个整体程序中。所以我可以使用相同的顶点着色器代码和两个不同的片段着色器。但这会导致两个不同的程序

每个程序都有自己的一套制服和其他状态。这意味着如果我想更改顶点着色器中的一些统一数据,我必须在两个程序中都进行更改。我必须glGetUniformLocation在每个上使用(因为它们可能有不同的位置)。然后我必须单独设置每个值。

这是一个很大的痛苦,而且是非常不必要的。使用单独的着色器,您不必这样做。您有一个仅包含顶点着色器的程序,以及两个包含两个片段着色器的程序。更改顶点着色器制服不需要两次glGetUniformLocation调用。事实上,缓存数据更容易,因为只有一个顶点着色器。

此外,它还处理着色器组合的组合爆炸。

假设您有一个执行简单刚性转换的顶点着色器:它需要一个模型到相机矩阵和一个相机到剪辑矩阵。也可能是法线矩阵。你有一个片段着色器,它将从一些纹理中采样,基于法线进行一些光照计算,并返回一个颜色。

现在假设您添加了另一个片段着色器,它采用额外的光照和材质参数。它没有来自顶点着色器的任何新输入(没有新的纹理坐标或任何东西),只有新的制服。也许它用于投影照明,顶点着色器不参与其中。任何。

现在假设我们添加了一个执行顶点加权蒙皮的新顶点着色器。它提供与旧的顶点着色器相同的输出,但它有一堆用于蒙皮的制服和输入权重。

这给了我们 2 个顶点着色器和 2 个片段着色器。一共4个程序组合。

当我们添加 2 个以上兼容的片段着色器时会发生什么?我们得到 8 种组合。如果我们有 3 个顶点着色器和 10 个片段着色器,我们总共有30个程序组合。

使用单独的着色器,3 个顶点着色器和 10 个片段着色器需要 30 个程序管道对象,但只有13 个程序对象。这比非独立案例少 50% 以上的程序对象。

为什么引用的文字是错误的

现在,这条线 [...] 让我想知道。

它应该让你想知道;它在几个方面是错误的。例如:

VAO 是一个容器对象,它链接缓冲区数据、顶点布局数据和 GLSL 程序输入数据。

不,不是的。它将提供顶点数据的缓冲区对象与该数据的顶点格式联系起来。它指定了哪些顶点属性索引。但这与“GLSL 程序输入数据”的耦合程度如何完全取决于您。

如果没有专门的软件设计,这意味着当我改变一个对象的材质(一个新的片段着色器)时,我需要不同的 VAO...

除非这条线将“专门的软件设计”等同于“合理的编程实践”,否则这纯粹是胡说八道。

这就是我的意思。当他们设置顶点数据时,您会在网上看到这样的示例代码:

glBindBuffer(GL_ARRAY_BUFFER, buffer_object);
glEnableVertexAttribArray(glGetAttribLocation(prog, "position"));
glVertexAttribPointer(glGetAttribLocation(prog, "position"), ...);

对此有一个技术术语:糟糕的代码。这样做的唯一原因是如果 指定的着色器prog不在您的直接控制之下。如果是这样的话......你怎么知道它prog有一个名为“位置”的属性

着色器的合理编程实践是使用约定。这就是您知道prog有一个名为“位置”的属性的方式。但是如果你知道每个程序都会有一个名为“位置”的属性,为什么不更进一步呢?当需要链接程序时,请执行以下操作:

GLuint prog = glCreateProgram();
glAttachShader(prog, ...); //Repeat as needed.
glBindAttribLocation(prog, 0, "position");

毕竟,你知道这个程序必须有一个名为“位置”的属性;您将假设稍后获得它的位置。所以去掉中间人,告诉 OpenGL使用什么位置。

这样,您不必使用glGetAttribLocation; 当您的意思是“位置”时,只需使用 0。

即使prog没有名为“位置”的属性,这仍然会成功链接。如果您绑定不存在的属性位置,OpenGL 不介意。因此,您可以对您创建的每个程序应用一系列glBindAttribLocation调用,而不会出现问题。实际上,您可以对属性名称有多种约定,只要您坚持一组或另一组,就可以了。

更好的是,将其粘贴在着色器中,并且根本不用理会glBindAttribLocation解决方案:

#version 330
layout(location = 0) in vec4 position;

简而言之:始终为您的属性位置使用约定。如果您glGetAttribLocation在程序中看到,请考虑代码异味。这样,您可以将任何 VAO 用于任何程序,因为 VAO 只是违反约定编写​​的。

我不明白约定如何等同于“专用软件设计”,但是,嘿,我也没有写那行。

于 2013-04-04T12:06:24.683 回答
5

我可以在不同的着色器程序之间切换

是的,但是您必须完全替换整个程序。单独的着色器对象允许您仅替换一个阶段(例如,仅顶点着色器)。

例如,如果您有 N 个顶点着色器和 M 个顶点着色器,则使用常规链接您将拥有 N * M 个程序对象(以涵盖所有可能的组合)。使用单独的着色器对象,它们彼此分离,因此您只需要保留 N + M 个着色器对象。这是在复杂场景中的显着改进。

于 2013-04-04T11:34:19.173 回答