GLSL中的顶点着色器属性映射

38

我正在编写一个带有GLSL着色器的小型渲染引擎:

每个网格(或子网格)都有多个顶点流(例如位置、法线、纹理、切线等),它们被合并到一个大的VBO中,并带有一个MaterialID。

每种材质都有一组纹理和属性(例如高光颜色、漫反射颜色、颜色纹理、法线贴图等)

然后我有一个GLSL着色器,其中包含它的uniform变量和attribute变量。假设如下:

uniform vec3 DiffuseColor;
uniform sampler2D NormalMapTexture;
attribute vec3 Position;
attribute vec2 TexCoord;

我在尝试设计GLSL着色器定义属性和Uniform的流映射(语义),并将顶点流绑定到相应的属性。比如说对于网格,可以让其把位置流放在"Position"属性中,把纹理坐标放在"TexCoord"属性中,把材质漫反射颜色放在"DiffuseColor"属性中,把材质的第二个纹理放在"NormalMapTexture"属性中。

目前我正在使用硬编码属性名称(例如顶点位置总是"Position"),并检查每个Uniform和属性名称来了解着色器是如何使用它的。

我想找一种创建"顶点声明"的方法,其中包括Uniform和纹理。

因此,我想知道大型渲染引擎中人们是如何解决这个问题的。

编辑:

建议的方法概述:

1. 通过变量的名称确定属性/Uniform的语义 (我的当前做法) 为每个可能的属性使用预定义的名称。GLSL绑定程序将查询每个属性的名称,并根据变量名称链接顶点数组:

//global static variable

semantics (name,normalize,offset) = {"Position",false,0} {"Normal",true,1},{"TextureUV,false,2}

 ...when linking
for (int index=0;index<allAttribs;index++)
{
   glGetActiveAttrib(program,index,bufSize,length,size[index],type[index],name);      
   semantics[index]= GetSemanticsFromGlobalHardCodedList(name);
} 
... when binding vertex arrays for render
 for (int index=0;index<allAttribs;index++)
{
    glVertexAttribPointer(index,size[index],type[index],semantics[index]->normalized,bufferStride,semantics[index]->offset);

}  

2. 每个语义预定义的位置

GLSL绑定器将始终将顶点数组绑定到相同的位置。由着色器使用适当的名称进行匹配。(这似乎与方法1非常相似,但除非我误解了,否则这意味着绑定所有可用的顶点数据,即使着色器不使用它)。

.. when linking the program...
glBindAttribLocation(prog, 0, "mg_Position");
glBindAttribLocation(prog, 1, "mg_Color");
glBindAttribLocation(prog, 2, "mg_Normal");

3. Material、Engine全局变量、渲染器和网格可用属性的字典

维护当前材质、引擎全局变量、当前渲染器和场景节点发布的可用属性列表。

例如:

 Material has (uniformName,value) =  {"ambientColor", (1.0,1.0,1.0)}, {"diffuseColor",(0.2,0.2,0.2)}
 Mesh has (attributeName,offset) = {"Position",0,},{"Normals",1},{"BumpBlendUV",2}

然后在着色器中:

 uniform vec3 ambientColor,diffuseColo;
 attribute vec3 Position;

将顶点数据绑定到着色器时,GLSL绑定器将循环遍历属性并绑定到在字典中找到的一个(或没有找到?):

 for (int index=0;index<allAttribs;index++)
    {
       glGetActiveAttrib(program,index,bufSize,length,size[index],type[index],name);      
      semantics[index] = Mesh->GetAttributeSemantics(name);
}

同样适用于uniform变量,只查询活动材质和全局变量。


我相信你的三个观点在某种程度上是相同的,具体取决于你的配置需求 - 从“保持朴素简单”(2)到“数据驱动”(3)。说到顶点属性,我几年前实际上从(2)开始,认为我会根据需要转向(1)或(3)。我还没有改变的需要。我并不是说其他选项不好,这完全取决于你的需求。因为我正在删除我们引擎中过度工程化的代码,所以可能有偏见;) - rotoglup
我倾向于目前使用(3),因为它似乎在未来最容易扩展。我想从(2)简单开始;但我不理解(2)中的一点:如果你有一个带有4-5个属性流的VBO,如何根据着色器知道绑定哪些流?你只是绑定所有的流吗? - Radu094
3个回答

16

属性:

您的网格有许多数据流。对于每个流,您可以保留以下信息:(名称、类型、数据)。

链接时,您可以查询GLSL程序的活动属性,并为该程序形成属性字典。这里的每个元素仅为(名称、类型)。

当您使用指定的GLSL程序绘制网格时,您需要遍历程序的属性字典并绑定相应的网格流(或在不一致的情况下报告错误)。

统一变量:

让着色器参数字典成为(名称、类型、数据链接)集合。通常,您可以有以下字典:

  • 材料(漫反射、镜面反射、亮度等)-来自材料
  • 引擎(相机、模型、灯光、计时器等)-来自引擎单例(全局)
  • 渲染(与着色器创建者相关的自定义参数:SSAO半径、模糊度等)-由着色器创建类(render)专门提供

链接后,将向GLSL程序提供一组参数字典,以便使用以下元素格式填充其自己的字典:(位置、类型、数据链接)。这是通过查询活动统一变量列表并将(名称、类型)匹配与字典中的元素完成的。

结论: 该方法允许传递任何自定义顶点属性和着色器统一变量,而不需要在引擎中进行硬编码的名称/语义。基本上只有加载程序和渲染程序了解特定的语义:

  • 加载程序填写网格数据流声明和材料字典。
  • 渲染程序使用了一个了解名称的着色器,提供了额外的参数,并选择要绘制的正确网格。

相当不错的摘要,但你必须定义网格属性和着色器输入之间的关系。当然,硬编码有点生硬,但你必须在某个地方以任意方式定义这种关系(在加载器、导出器、着色器或建模软件中)。此外,通常情况下,对于多个网格/多个着色器,你不能承担太多语义上的问题。同质性有时比泛化更简单。 - rotoglup
@rotoglup。没有人强迫你使用“太多语义”,但如果你这样做-“通用性”方法比同质性更适合该使用模式。后者的问题是您将使用更多的顶点属性槽(因为您将每个槽预定义为语义,但不在每个网格中使用所有语义)。因此,在大型项目中,您将不得不使用一些技巧来适应GL提供的那16个插槽。同时,我描述的点对点方法没有这个问题。 - kvark
@Radu094:你可能以自己的方式理解了这个想法 :) 在原始想法中,字典是在代码中创建的(材料-在加载过程中,引擎-在全局初始化中,渲染-在渲染构造函数中)。另一方面,着色器不应该以特殊的方式进行解析。它们的活动统一资源标识符是通过标准GL方式查询的。 - kvark
1
@kvark。我理解你的提议,但我的观点是,在顶点属性方面,我无法看到这样一个灵活系统在应用程序中具体的使用案例。您是否曾经遇到过在同一应用程序中以不同方式命名位置的两个网格?或者从一个网格到另一个网格有非常不同的顶点属性?对我来说,很快就会成为一个制作噩梦,不知道哪个着色器适用于哪个网格。您在什么情况下使用这样的系统? - rotoglup
@kvark。好的,我希望我现在理解你的意思了。材料和引擎是发布任何可用数据的“发布者”,而着色器只是基于名称+类型的一种弱合同类型对其感兴趣的任何内容的被动“消费者”。这正确吗? - Radu094
显示剩余2条评论

8
根据我的经验,OpenGL没有定义属性或统一语义的概念。您可以通过使用您唯一可以控制的这些变量的参数——它们的位置来定义自己的映射方式将您的语义映射到OpenGL变量。
如果您没有受平台问题的限制,可以尝试使用“新”的GL_ARB_explicit_attrib_location(如果我没记错的话,在OpenGL 3.3中核心),它允许着色器明确表达哪个位置用于哪个属性。这样,您可以硬编码(或配置)要绑定在哪个属性位置上的数据,并在编译后查询着色器的位置。似乎这个功能还不成熟,可能会受到各种驱动程序中的错误影响。
另一种方法是使用glBindAttribLocation绑定属性的位置。为此,您必须知道要绑定的属性的名称和要分配给它们的位置。
要找出着色器中使用的名称,您可以:
- 查询活动属性的着色器 - 解析着色器源代码以自行查找
我不建议使用GLSL解析方式(尽管如果您处于足够简单的上下文中,它可能适合您的需求):解析器很容易被预处理器打败。假设您的着色器代码变得有些复杂,您可能希望开始使用#include、#define、#ifdef等。健壮的解析需要一个健壮的预处理器,这可能会变得非常麻烦。
无论如何,对于您的活动属性名称,您必须为它们分配位置(和/或语义),对于此,您需要根据您的用例自行完成。
在我们的引擎中,我们愉快地将预定义名称的位置硬编码为特定值,例如:
glBindAttribLocation(prog, 0, "mg_Position");
glBindAttribLocation(prog, 1, "mg_Color");
glBindAttribLocation(prog, 2, "mg_Normal");
...

之后,就由着色器编写者符合属性的预定义语义。

据我所知,这是最常见的做法,例如OGRE。 这不是什么高深的科学,但在实践中效果很好。

如果您想要一些控制,可以提供一个API来定义基于着色器的语义,甚至可以将此描述放入附加文件中,易于解析,与着色器源代码相邻。

对于uniforms,情况几乎相同,除了“新”的扩展允许您强制GLSL uniform块采用与应用程序兼容的内存布局。

我自己对所有这些都不满意,所以我很乐意得到一些相互矛盾的信息:)


ARB_explicit_attrib_location似乎是朝着正确方向迈出的一步!但我担心,将整个引擎/着色器依赖于一个可能或可能不被普遍支持的新扩展。我猜这是属性和统一变量的硬编码名称,除非出现其他技术。 - Radu094
@Radu094:我对explicit_attrib_location混杂着各种感觉,感觉只有在适当定义位置值的情况下才能使用它。而且GLSL的#include扩展非常笨拙...预定义的属性名称(硬编码或可配置)更容易配置。我觉得很难找到在GLSL中完成的“最先进”的渲染器的适当参考资料,以建立这些问题的知识库。 - rotoglup

3

您可能需要考虑实际解析GLSL本身。

uniform/attribute声明语法非常简单。您可以编写一个小型手动解析器,查找以uniformattribute开头的行,获取类型和名称,然后使用字符串公开一些C++ API。这将使您免于硬编码名称的麻烦。如果您不想亲自动手进行手动解析,那么一些像Spirit的库可以帮助您。
您可能不想完全解析GLSL,因此您需要确保在声明中不会做任何有可能改变实际含义的事情。其中一个复杂性是在GLSL中使用宏进行条件编译。


你好!即使我解析了GLSL,如果我没有使用硬编码的名称,那么我如何引入属性的语义呢?(例如,“attribute vec3 lPos2;”)除非我在属性后面写一些(硬编码的)注释(“//LIGHT_POS”),否则我找不到将语义数据附加到属性的方法。 - Radu094
为什么您想要将语义数据附加到属性上?从您的问题中很难看出您试图做什么的全貌。 - shoosh
因为我需要让图形引擎知道着色器想要绑定哪个属性的数据流。是顶点位置、第二组UV、反射率、温度,还是一些未来的花哨的每个顶点参数? - Radu094

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