如何设计一个简单的 GLSL 包装器以供着色器使用

Posted

技术标签:

【中文标题】如何设计一个简单的 GLSL 包装器以供着色器使用【英文标题】:How to design a simple GLSL wrapper for shader use 【发布时间】:2011-05-29 05:58:07 【问题描述】:

更新:因为我马上需要一些东西,所以我创建了一个简单的着色器包装器来完成我需要的事情。你可以在这里找到它:ShaderManager on GitHub。请注意,它是为 Objective-C / iOS 设计的,所以可能不是对每个人都有用。如果您对设计改进有任何建议,请告诉我!

原来的问题:

我是使用 GLSL 着色器的新手。我对 GLSL 语言和 OpenGL 接口足够熟悉,但是我在设计一个简单的 API 来使用着色器时遇到了麻烦。

OpenGL 与着色器交互的 C 接口似乎很麻烦。我似乎在网上找不到任何涵盖此类东西的 API 设计的教程。

我的问题是:有没有人有好的、简单的 API 设计或模式来包装 OpenGL 着色器程序 API?

举个简单的例子。假设我有一个仅模拟固定功能的顶点着色器和两个片段着色器 - 一个用于绘制平滑矩形,一个用于绘制平滑圆形。我有以下文件:

Shader.vsh : Simple vertex shader, with the following inputs/outputs:
    -- Uniforms: mat4 Model, mat4 View, mat4 Projection
    -- Attributes: vec4 Vertex, vec2 TexCoord, vec4 Color
    -- Varying: vec4 vColor, vec2 vTexCoord

Square.fsh : Fragment shader for drawing squares based on tex coord / color
Circle.fsh : Fragment shader for drawing circles based on tex coord / color

基本链接

现在使用这些的标准方法是什么?我是否将上述着色器链接到两个 OpenGL 着色器程序?那就是:

Shader.vsh + Square.fsh = SquareProgram
Shader.vsh + Circle.fsh = CircleProgram

或者我应该创建一个大程序,让片段着色器检查一些条件统一变量并调用着色器函数来生成它们的结果。例如:

Shader.vsh + Square.fsh + Circle.fsh + Main.fsh = ShaderProgram
//Main.fsh here would simply check whether to call out to square or circle

我大概需要调用两个单独的程序

glUseProgram(CircleProgram); or glUseProgram(SquareProgram);

在我要绘制的每种类型的元素之前。然后我需要在使用之前设置每个程序的制服(模型/视图/投影)和属性。这看起来太笨拙了。

使用单个 ShaderProgram 选项,我仍然需要在片段着色器中设置某种布尔开关(圆形或方形),在绘制每个像素之前将对其进行检查。这似乎也很复杂。

作为旁注,我是否可以将两个片段着色器(每个都有一个 main() 函数)链接到一个着色器程序中? OpenGL 怎么知道调用哪一个?

设置变量

电话:

glUniform*
glVertexAttribPointer

用于在当前程序上设置制服和属性指针位置。

不同的类和结构可能需要从代码的不同位置访问和设置当前着色器(或更改当前着色器)上的变量。我想不出一个很好的方法来将着色器代码与想要使用它的代码分离。

也就是说,我要绘制的每个形状都需要设置顶点和纹理坐标属性——需要 OpenGL 生成的那些属性的句柄。

相机需要在顶点着色器中将其投影矩阵设置为统一,而管理模型矩阵堆栈的类需要在顶点着色器中设置自己的统一。

在绘制场景的中途更改着色器意味着所有这些类都需要重新设置它们的制服和属性。

大多数人是如何围绕这个设计的?

通过句柄或名称访问的着色器的全局字典,其参数带有 getter 和 setter?

带有每个都有参数的着色器对象的 OO 设计?

我查看了以下包装器:

Jon's Teapot: GLSL Shader Manager - 在 C++ 类中包装着色器。它似乎只不过是一个在 C API 上强制执行 OO 原则的包装器,从而产生了一个大致相同的 C++ API。

我追求任何可以简化着色器程序使用的设计,并不关心使用的特定范式(OO、程序等)

【问题讨论】:

一个可能的起点:***.com/questions/2795044/…。它确实尝试处理制服、属性等,但至少简化了编译和链接。 谢谢 Jerry,这是一个很好的起点,并回答了我关于在多个着色器程序上使用单个着色器程序的问题。 【参考方案1】:

我看到这是用 ios 标记的,所以如果你偏爱 Objective-C,我会好好看看 Jeff LaMarche 的 GLProgram 包装类,他描述了 here 并有可用的源代码 here。我在自己的应用程序中使用它来简化一些着色器程序设置,并使代码更简洁。

例如,您可以使用如下代码设置着色器及其属性和制服:

sphereDepthProgram = [[GLProgram alloc] initWithVertexShaderFilename:@"SphereDepth" fragmentShaderFilename:@"SphereDepth"];
[sphereDepthProgram addAttribute:@"position"];
[sphereDepthProgram addAttribute:@"inputImpostorSpaceCoordinate"];
if (![sphereDepthProgram link])

    NSLog(@"Depth shader link failed");
    NSString *progLog = [sphereDepthProgram programLog];
    NSLog(@"Program Log: %@", progLog); 
    NSString *fragLog = [sphereDepthProgram fragmentShaderLog];
    NSLog(@"Frag Log: %@", fragLog);
    NSString *vertLog = [sphereDepthProgram vertexShaderLog];
    NSLog(@"Vert Log: %@", vertLog);
    [sphereDepthProgram release];
    sphereDepthProgram = nil;


sphereDepthPositionAttribute = [sphereDepthProgram attributeIndex:@"position"];
sphereDepthImpostorSpaceAttribute = [sphereDepthProgram attributeIndex:@"inputImpostorSpaceCoordinate"];
sphereDepthModelViewMatrix = [sphereDepthProgram uniformIndex:@"modelViewProjMatrix"];
sphereDepthRadius = [sphereDepthProgram uniformIndex:@"sphereRadius"];

当您需要使用着色器程序时,您可以执行以下操作:

[sphereDepthProgram use];

这并没有解决您在上面提到的分支与单个着色器的问题,但 Jeff 的实现确实提供了一些 OpenGL ES 样板着色器设置代码的良好封装。

【讨论】:

感谢您向我指出这一点 - 看来我已经在上面的课程中重新发明了***。如果我在几个小时前才找到这个!【参考方案2】:

基本链接:

这里没有标准方法。至少有两种通用方法:

    Monolithic - 一个着色器涵盖多种情况,使用统一的布尔开关。这些分支不会影响性能,因为条件结果对于任何片段组(实际上,对于所有片段)都是恒定的。

    多对象程序合成 - 主着色器声明一组入口点(如“get_diffuse”、“get_specular”等),这些入口点在附加的单独着色器对象中实现。这意味着每个对象都有单独的着色器,但任何类型的缓存都有帮助。

设置变量:制服

我将仅描述我开发的方法。

每个着色器程序都有一个统一字典列表。它用于在程序(重新)链接时填充统一源列表。当程序被激活时,它会遍历统一列表,从它们的源中获取值并将它们上传到 GL。结果,数据与用户着色器程序没有直接联系,任何管理它的人都不关心使用它的程序。

例如,其中一个字典可以是核心字典,其中包含模型、视图转换、相机投影等。

设置变量:属性

首先,着色器程序是一个属性消费者,所以它必须从网格(或任何其他数据存储)中提取这些属性并以它需要的方式将它们上传到 GL。它还应该确保提供的属性类型与请求的类型相匹配。

当使用整体着色器方法时,当禁用的分支方式需要一个未提供的顶点属性时,可能会出现不愉快的情况。我建议使用另一个属性的数据来​​提供缺失的数据,因为在这种情况下我们不关心实际值。

附: 你可以在这里找到这些想法的实际实现:http://code.google.com/p/kri/

【讨论】:

谢谢,这非常有帮助。我没有意识到整体着色器中的常量分支会被优化掉 - 当你解释它时它是有道理的。 glUniform* 系列函数的性能开销很大吗? @simeon。不,在我的实验中,为每个着色器激活的每个变量调用 glUniform 与缓存变体相比没有任何成本。

以上是关于如何设计一个简单的 GLSL 包装器以供着色器使用的主要内容,如果未能解决你的问题,请参考以下文章

可以将 GLSL 着色器应用于 SDL_Textures 吗?

GLSL 问题:一个程序中有多个着色器

GLSL片段着色器 - 绘制简单的粗曲线

无法获得简单的 2D 着色器以在 C++ openGL 中绘制红色三角形(无编译器错误)

使用着色器进行计算

如何使用 Nsight 调试(GLSL)着色器?