我在这里看到了关于这个主题的堆栈溢出的一些问题和答案。从这些答案中,我提出了将GLSL属性绑定到用户定义的语义的可能解决方案。我想得到一些意见和讨论,并检查它是否是一个有效的想法。
首先,让我们假设我们有一些用户定义的语义列表:
enum VertexElementSemantic
{
POSITION, NORMAL, AMBIENT, DIFFUSE, SPECULAR,
TEX_COORD0, TEX_COORD1, TEX_COORD2, TEX_COORD3,
INDICES
};
一种封装设置顶点属性指针所需数据的结构。
struct VertexElement
{
unsigned int m_source;
unsigned int m_offset;
unsigned int m_stride;
}
现在,一些RenderOperation类将包含VertexElementSemantics到VertexElements的映射。 VertexElement的规范化格式,大小和格式可由其语义决定。
为了设置这个指针,我们需要的最后一点信息是属性位置本身。这是我们想要将VertexElementSemantic绑定到特定位置的地方。
从the first answer to this question,我们了解到我们可以明确说明每个属性的所需位置,如下所示:
layout(location = 0) in vec3 position;
因此我们可以将我们的语义映射到这些硬编码位置,但是我们要求在每个着色器中对此位置进行硬编码。对这些位置的任何更改都需要我们浏览并编辑每个着色器。
但是,此值不必由着色器源提供。从the answer to this question开始,我们了解到我们可以将#defines外部添加到我们的着色器中,如下所示:
char *sources[2] = { "#define FOO\n", sourceFromFile };
glShaderSourceARB(shader, 2, sources, NULL);
使用这个,我们可以构建一个字符串,#string定义每个语义所需位置的变量。例如,我们可以构建一个字符串,最终将以下内容插入到每个着色器的开头:
#define POSITION_LOCATION 0
#define NORMAL_LOCATION 1
#define AMBIENT_LOCATION 2
...
回过头来明确说明我们的属性位置,我们现在应该可以这样说明:
layout(location = POSITION_LOCATION) in vec3 position;
layout(location = NORMAL_LOCATION) in vec3 normal;
layout(location = AMBIENT_LOCATION) in vec4 ambient;
此方法允许我们在代码中设置每个Semantic的所需属性位置。它还为着色器本身提供了一种语义绑定感觉。像这样的系统是否朝着正确的方向迈出了一步,以解决为属性位置提供意义的问题?
答案 0 :(得分:4)
让我们考虑一下这个想法的后果。
我们可以构建一个字符串,#string定义每个语义所需位置的变量。例如,我们可以构建一个字符串,最终将以下内容插入到每个着色器的开头:
嗯,这有两个方面很糟糕。首先,问题是#version
。如果您要使用 任何 除1.10之外的GLSL版本,则必须提供#version
声明。并且该声明必须在着色器中的第一件事,在评论和空白之外。
通过将这些#define
放入您的着色器源(无论是通过字符串连接,还是使用多个字符串),您必须接受某些后果。通常,每个单独的着色器文件都有自己的#version
声明,指定它使用的GLSL版本。但是如果你想使用除GLSL 1.10之外的其他东西,你就无法做到这一点。您必须在#version
之前生成#define
的C ++源代码。
这意味着您的着色器源现在与其编译的版本解耦。这是可行的,但这意味着您的着色器源现在不清楚,而不知道它是什么版本。您可以通过其他方式传达版本,例如使用文件名(例如,lit_transform_330.vert
将使用版本3.30)。但是你必须设计这样一个系统。
现在已经解决了版本问题,问到下一个问题:你正在做的是冗余。
您使用" semantic"等术语,这些术语对OpenGL没有任何意义。您似乎正在尝试将某种形式的名称分配给特定的顶点属性,以便您可以在着色器和C ++代码中看到该名称的使用,从而知道它的属性。
也就是说,你想在" name"之间定义一个映射。和"属性索引"。您希望它在一个地方定义,以便它自动传播到每个着色器并在整个C ++源代码中一致地使用。
我们已经在名称和属性索引之间建立了映射。它被称为"属性名称和属性索引之间的映射"。每个着色器必须为其属性提供名称。这是您在in vec4 position;
等定义中看到的字符串名称,该属性的名称为position
。这就是GLSL在使用变量时调用变量的原因。
如您所链接的答案中所述,您可以在链接程序之前将特定属性名称与C ++代码中的属性索引相关联。这是通过the glBindAttribLocation
function完成的。您可以设置任意数量的映射。链接程序时,将为该位置分配与指定位置匹配的属性。
您需要的只是一个"语义列表" (aka:属性索引)和字符串名称,您需要着色器用于这些属性。
你可能会说,"嗯,我希望着色器能够随心所欲地调用变量。"我的回答是......差异是什么?您建议的方案已要求用户遵守特定的命名约定。只是他们必须使用的名称不是变量的名称;它是在声明时与变量关联的某个标记的名称。
那究竟有什么区别?着色器的编写者必须遵守顶点属性变量名的集合命名方案?不是所有着色器中同一个概念的一致名称好东西吗?
唯一的区别是,如果他们错误地输入了#34;语义"在你的方案下,他们得到一个着色器编译错误(因为他们错误的"语义"名字赢得了与任何实际的#define
匹配)。然而,如果他们错误地输入属性的名称,如果他们在使用该属性时不会错误输入该名称,他们将只会遇到编译器错误。
有办法解决这个问题。它需要使用program introspection来遍历活动属性列表,并根据期望的属性名称进行检查。
你可以将它归结为一组非常简单的约定。使用你的"语义"定义:
enum VertexElementSemantic
{
POSITION, NORMAL, AMBIENT, DIFFUSE, SPECULAR,
TEX_COORD0, TEX_COORD1, TEX_COORD2, TEX_COORD3,
INDICES, NUM_SEMANTICS
};
//in the C++ file you use to link your shaders
const char *AttributeNames[] =
{
"position", "normal", "ambient", "diffuse", "specular",
"tex_coord0", "tex_coord1", "tex_coord2", "tex_coord3",
"indices",
}
static_assert(ARRAY_COUNT(AttributeNames) == NUM_SEMANTICS); //Where `ARRAY_COUNT` is a macro that computes the number of elements in a static array.
GLuint CreateProgram(GLuint vertexShader, GLuint fragmentShader)
{
GLuint prog = glCreateProgram();
//Attach shaders
for(int attrib = 0; attrib < NUM_SEMANTICS; ++attrib)
{
glBindAttribLocation(prog, attrib, AttributeNames[attrib]);
}
glLinkProgram(prog);
//Detach shaders
//Check for linking errors
//Verify that attribute locations are as expected.
//Left as an exercise for the reader.
return prog;
}
就个人而言,我只会使用一个数字。无论你使用什么,编写着色器的人都必须遵守一些约定。这意味着当他们去写一个占据位置的顶点着色器时,他们将不得不查找如何说&#34;这是一个位置&#34;。所以无论如何,他们都必须在某个地方的桌子上看 。
在这一点上,它归结为最可能的问题。最可能的问题可能是认为他们知道答案但实际上是错误的人(即:没有查找),以及错误输入答案的人。错误输入一个数字真的很难(虽然肯定会发生),而错误输入POSITION_LOCATION
会容易得多。前一个问题可能发生在一个或多或少相同的数字中。
所以在我看来,如果您的约定基于数字而不是单词,那么您更有可能减少常规错配问题。