将无符号整数输入属性传递给顶点着色器
Posted
技术标签:
【中文标题】将无符号整数输入属性传递给顶点着色器【英文标题】:Passing unsigned int input attribute to vertex shader 【发布时间】:2019-09-09 12:54:20 【问题描述】:在一个典型的顶点数组缓冲区构建中,我试图将一个无符号整数属性与其他经典属性(顶点、法线、纹理坐标...)一起传递。然而,这个属性的值最终出现了某种错误:我不确定该值是错误的还是该属性根本没有设置。
从一个简单的例子开始,假设我定义了以下 C++ 输入结构:
struct buffer_data_t
glm::vec3 vertex;
glm::vec3 normal;
glm::vec2 texCoords;
;
准备我的顶点数组如下所示:
// Assume this 'shader.attribute(..)' is working and returns the attribute's position
unsigned int shadInputs[] =
(unsigned int)shader.attribute("VS_Vertex"),
(unsigned int)shader.attribute("VS_Normal"),
(unsigned int)shader.attribute("VS_TexCoords"),
;
glGenBuffers(1, &glBuffer);
glBindBuffer(GL_ARRAY_BUFFER, glBuffer);
glBufferData(GL_ARRAY_BUFFER, vertice.size() * sizeof(buffer_data_t), &vertice[0], GL_STATIC_DRAW);
glGenVertexArrays(1, &glArray);
glBindVertexArray(glArray);
glBindBuffer(GL_ARRAY_BUFFER, glBuffer);
glVertexAttribPointer(shadInputs[0], 3, GL_FLOAT, GL_FALSE, sizeof(buffer_data_t), (void*)(sizeof(glm::vec3) * 0));
glVertexAttribPointer(shadInputs[1], 3, GL_FLOAT, GL_FALSE, sizeof(buffer_data_t), (void*)(sizeof(glm::vec3) * 1));
glVertexAttribPointer(shadInputs[2], 2, GL_FLOAT, GL_FALSE, sizeof(buffer_data_t), (void*)(sizeof(glm::vec3) * 2));
glEnableVertexAttribArray(shadInputs[0]);
glEnableVertexAttribArray(shadInputs[1]);
glEnableVertexAttribArray(shadInputs[2]);
glBindVertexArray(0);
顶点着色器输入会这样定义:
in vec3 VS_Vertex;
in vec3 VS_Normal;
in vec2 VS_TexCoords;
另外,为了我的例子,假设我的片段着色器中有一个纹理采样器,可以在其上使用这些输入 TexCoords:
uniform sampler2D ColourMap;
介绍我的问题
到目前为止一切顺利,通过上面的代码,我可以成功渲染纹理基元。现在我想根据要渲染的面部选择不同的颜色贴图。为此,我想引入一个 index 作为顶点属性的一部分。变化是:
C++ 数据结构:
struct buffer_data_t
glm::vec3 vertex;
glm::vec3 normal;
glm::vec2 texCoords;
unsigned int textureId; // <---
;
准备顶点数组:
unsigned int shadInputs[] =
(unsigned int)shader.attribute("VS_Vertex"),
(unsigned int)shader.attribute("VS_Normal"),
(unsigned int)shader.attribute("VS_TexCoords"),
(unsigned int)shader.attribute("VS_TextureId"),
;
// ...
glBindBuffer(GL_ARRAY_BUFFER, glBuffer);
glVertexAttribPointer(shadInputs[0], 3, GL_FLOAT, GL_FALSE, sizeof(buffer_data_t), (void*)(sizeof(glm::vec3) * 0));
glVertexAttribPointer(shadInputs[1], 3, GL_FLOAT, GL_FALSE, sizeof(buffer_data_t), (void*)(sizeof(glm::vec3) * 1));
glVertexAttribPointer(shadInputs[2], 2, GL_FLOAT, GL_FALSE, sizeof(buffer_data_t), (void*)(sizeof(glm::vec3) * 2));
glVertexAttribPointer(shadInputs[3], 1, GL_UNSIGNED_INT, GL_FALSE, sizeof(buffer_data_t), (void*)(sizeof(glm::vec3) * 2 + sizeof(glm::vec2))); // <---
glEnableVertexAttribArray(shadInputs[0]);
glEnableVertexAttribArray(shadInputs[1]);
glEnableVertexAttribArray(shadInputs[2]);
glEnableVertexAttribArray(shadInputs[3]);
然后顶点着色器定义新的输入,以及一个“平面”输出
in vec3 VS_Vertex;
in vec3 VS_Normal;
in vec2 VS_TexCoords;
in unsigned int VS_TextureId;
...
out flat unsigned int FS_TextureId;
片段着色器调整为输入平面,并且(再次为了示例)颜色图现在是我们可以从中选择的数组:
...
uniform sampler2D ColourMaps[2];
in flat unsigned int FS_TextureId;
...
texture2D(ColourMaps[FS_TextureId], ... );
这些更改不起作用,特别是因为顶点着色器输入属性“VS_TextureId”。我能够证明这一点(并找到解决方法),方法是不使用 unsigned int 类型,而是求助于 vec2(或 vec3,无论哪种方式都可以)。那就是:
VS:
in vec2 VS_TextureId;
out flat int FS_TextureId;
FS_TextureId = int(VS_TextureId.x);
FS:
in flat int FS_TextureId;
texture2D(ColourMaps[FS_TextureId], ... );
我的假设
我猜这是有问题的线路,虽然我不知道如何/为什么:
glVertexAttribPointer(shadInputs[3], 1, GL_UNSIGNED_INT, GL_FALSE, sizeof(buffer_data_t), (void*)(sizeof(glm::vec3) * 2 + sizeof(glm::vec2)));
注意:我检查了 'shader.attribute("VS_TextureId")' 的结果,它是正确的,这意味着顶点着色器中的属性已经定义好并找到了。
你能看出问题出在哪里吗?
【问题讨论】:
仅供参考:不透明类型数组(如采样器)的索引必须是动态统一值。而FS_TextureId
不是一(除非渲染命令中的每个三角形都获得相同的值)。
【参考方案1】:
如果你想用一个整数数据类型指定属性数组,那么你必须使用glVertexAttribIPointer
(关注函数名中间的I
),而不是比glVertexAttribPointer
.
见OpenGL 4.6 API Core Profile Specification; 10.2. CURRENT VERTEX ATTRIBUTE VALUES; page 348
VertexAttribI*
命令指定有符号或无符号定点值 分别存储为有符号或无符号整数。这样的值被称为纯整数。...
所有其他
VertexAttrib*
命令指定直接转换为内部浮点表示形式的值。
这意味着顶点属性的规范必须是:
//glVertexAttribPointer(shadInputs[3], 1, GL_UNSIGNED_INT, GL_FALSE,
// sizeof(buffer_data_t), (void*)(sizeof(glm::vec3) * 2 + sizeof(glm::vec2)));
glVertexAttribIPointer(shadInputs[3], 1, GL_UNSIGNED_INT,
sizeof(buffer_data_t), (void*)(sizeof(glm::vec3) * 2 + sizeof(glm::vec2)));
一般来说,你尝试的韭菜不是这样的。采样器数组的索引必须是“动态统一的”。这意味着所有片段的索引必须是“相同的”(例如常量或统一变量)。
见GLSL 4.60 Specification - 4.1.7. Opaque Types (page 33)
纹理组合采样器类型是不透明类型,其声明和行为与上述不透明类型相同。当在着色器中聚合成数组时,只能使用动态统一的积分表达式对它们进行索引,否则结果是不确定的。 [...]
我建议使用单个 TEXTURE_2D_ARRAY
纹理,而不是 TEXTURE_2D
纹理数组。见Texture。
在这种情况下,您可以使用 3 维浮点纹理坐标。
【讨论】:
天哪!谢谢,我知道它一定是小而愚蠢的东西!在将您的答案标记为已接受之前,我将尝试您的修复(尽管我确信它会证明可以解决问题)。再次感谢您! @Smoove 对不起,我没有把问题读到最后,我只是读到了顶点规范。阅读对您的问题的评论(@NicolBolas)。您尝试实现的目标并非如此。我建议使用TEXTURE_2D_ARRAY
实际上我对@NicolBolas 的评论感到困惑——我只是按照你的建议尝试使用 IPointer,现在一切正常(就像以前的解决方法一样)——这意味着我确实能够选择不同的基于该属性的纹理,在同一个渲染过程中(当然,给定的面,也就是顶点三元组具有相同的纹理索引)
@Smoove 我明白了。您使用哪个硬件和 OpenGL 版本?
@Smoove 硬件供应商经常超出规范,分别提供扩展,这些扩展还不是标准的一部分。规范在这一点上很清楚,它说如果FS_TextureId
是一个输入变量,那么ColourMaps[FS_TextureId]
是未定义的。但请注意,它并没有说它不允许工作。以上是关于将无符号整数输入属性传递给顶点着色器的主要内容,如果未能解决你的问题,请参考以下文章