更新:因为我现在需要一些东西,所以我创建了一个简单的着色器包装器来完成我需要的东西。你可以在这里找到它: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
或者我是否创建了一个大程序,片段着色器检查一些条件统一变量并调出着色器函数来生成结果。 E.g:
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大致相同。
我正在采用任何简化Shader程序使用的设计,并不关心使用的特定范例(OO,程序等)
答案 0 :(得分:8)
我看到这是用iOS标记的,所以如果你偏爱Objective-C,我会好好看看Jeff LaMarche的GLProgram包装类,他描述了here并且有源here。 3}}。我在自己的应用程序中使用它来简化一些着色器程序设置,并使代码更清晰。
例如,您可以使用以下代码设置着色器及其属性和制服:
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样板着色器设置代码的很好的封装。
答案 1 :(得分:5)
基本链接:
这里没有标准的方法。至少有两种一般方法:
单片 - 一个着色器覆盖许多情况,使用统一的布尔开关。这些分支不会损害性能,因为条件结果对于任何片段组都是恒定的(实际上,对于所有片段)。
多对象程序合成 - 主着色器声明一组入口点(如'get_diffuse','get_specular'等),这些入口点在附加的单独着色器对象中实现。这意味着每个对象都有单独的着色器,但任何类型的缓存都有帮助。
设置变量:制服
我将描述我开发的方法。
每个着色器程序都有一个统一字典列表。它用于在程序(重新)链接时填充统一源列表。程序激活后,它会通过统一列表,从其来源获取值并将它们上传到GL。在结果中,数据不直接与用户着色器程序相关联,无论管理它的是什么,都不关心使用它的程序。
其中一个词典可以是,例如,核心词典,包含模型,视图变换,相机投影和其他可能的东西。
设置变量:属性
首先,着色器程序是一个属性使用者,因此必须从网格(或任何其他数据存储)中提取这些属性,并以所需的方式将它们上传到GL。它还应确保提供的属性类型与请求的类型匹配。
使用单片着色器方法时,如果禁用的分支方式需要未提供的顶点属性,则可能会出现令人不快的情况。我建议使用另一个属性的数据来提供缺失的数据,因为在这种情况下我们不关心实际值。
P.S。 您可以在此处找到这些想法的实际实现:http://code.google.com/p/kri/