38

我正在使用 GLSL 着色器编写一个小型渲染引擎:

每个网格(嗯,子网格)都有许多顶点流(例如位置、法线、纹理、切线等)到一个大 VBO 和一个 MaterialID。

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

然后我有一个 GLSL 着色器,带有它的制服和属性。比方说:

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

我有点卡在尝试为 GLSL 着色器设计一种方法来定义属性和制服的流映射(语义),然后将顶点流绑定到适当的属性。

对网格说的话:“将您的位置流放在属性“Position”中,将您的tex坐标放在“TexCoord”中。还将材质的漫反射颜色放在“DiffuseColor”中,将材质的第二个纹理放在“NormalMapTexture”中

目前我正在使用硬编码的属性名称(即顶点位置始终是“位置”等)并检查每个统一和属性名称以了解着色器使用它的目的。

我想我正在寻找某种创建“顶点声明”的方法,但也包括制服和纹理。

所以我只是想知道人们如何在大型渲染引擎中做到这一点。

编辑:

回顾建议的方法:

1. 属性/统一语义由变量的名称给出 (我现在正在做的)为每个可能的属性使用预定义的名称。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 binder 将始终将顶点数组绑定到相同的位置。由着色器使用适当的名称进行匹配。(这似乎与方法 1 非常相似,但除非我误解了,这意味着绑定所有可用的顶点数据,即使着色器不使用它)

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

3. 材质、引擎全局、渲染器和网格的可用属性字典

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

例如:

 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);
}

和制服一样,只查询活动材质和全局。

4

3 回答 3

16

属性:

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

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

当你用指定的 GLSL 程序绘制网格时,你会通过程序属性字典并绑定相应的网格流(或在不一致的情况下报告错误)。

制服:

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

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

链接后,GLSL 程序将获得一组参数字典,以便使用以下元素格式填充它自己的字典:(位置、类型、数据链接)。该填充是通过查询活动制服列表并与字典中的匹配(名称,类型)对来完成的。

结论: 此方法允许传递任何自定义顶点属性和着色器制服,而引擎中没有硬编码的名称/语义。基本上只有加载器和渲染器知道特定的语义:

  • Loader 填写网格数据流声明和材料字典。
  • 渲染使用知道名称的着色器,提供附加参数并选择要绘制的正确网格。
于 2011-02-07T21:58:18.850 回答
8

根据我的经验,OpenGL 没有定义属性或统一语义的概念。

您所能做的就是定义自己的方式将语义映射到 OpenGL 变量,使用您可以控制的关于这些变量的唯一参数:它们的位置

如果您不受平台问题的限制,您可以尝试使用“新的” GL_ARB_explicit_attrib_location(如果我没记错的话,它是 OpenGL 3.3 中的核心),它允许着色器明确表示哪个位置用于哪个属性。这样,您可以硬编码(或配置)要在哪个属性位置绑定哪些数据,并在编译后查询着色器的位置。看来这个功能还不成熟,可能会受到各种驱动的bug的影响。

另一种方法是使用glBindAttribLocation绑定属性的位置。为此,您必须知道要绑定的属性的名称以及要分配它们的位置。

要找出着色器中使用的名称,您可以:

  • 查询着色器的活动属性
  • 解析着色器源代码以自己找到它们

我不建议使用 GLSL 解析方式(尽管如果您在足够简单的上下文中它可能适合您的需要):解析器很容易被预处理器击败。假设您的着色器代码变得有些复杂,您可能想要开始使用#includes、#defines、#ifdef 等。强大的解析假设您有一个强大的预处理器,这可能会变得相当繁重。

无论如何,使用您的活动属性名称,您必须为它们分配位置(和/或语义),为此,您将独自处理您的用例。

在我们的引擎中,我们很乐意将预定义名称的位置硬编码为特定值,例如:

glBindAttribLocation(prog, 0, "mg_Position");
glBindAttribLocation(prog, 1, "mg_Color");
glBindAttribLocation(prog, 2, "mg_Normal");
...

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

AFAIK 这是最常见的做事方式,例如OGRE使用它。这不是火箭科学,但在实践中效果很好。

如果你想添加一些控制,你可以提供一个 API 来在着色器的基础上定义语义,甚至可能在一个附加的文件中包含这个描述,易于解析,靠近着色器源代码。

我不会进入情况几乎相同的制服,除了“较新”的扩展允许您将 GLSL 统一块强制为与您的应用程序兼容的内存布局。

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

于 2011-02-07T20:00:32.033 回答
3

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

统一/属性声明语法非常简单。您可以提出一个小型手动解析器,它查找以uniformor开头的行attribute,获取类型和名称,然后使用字符串公开一些 C++ API。这将为您省去硬编码名称的麻烦。如果您不想通过手动解析弄脏您的手,那么使用Spirit 之类的工具可以解决问题。
您可能不想完全解析 GLSL,因此您需要确保在可能改变实际含义的减速中不要做任何有趣的事情。想到的一种复杂情况是在 GLSL 中使用宏进行条件编译。

于 2011-02-07T09:05:11.740 回答