一种半常见的方法是制作我所谓的着色器组件,类似于我认为您在调用的模块。
这个想法类似于后处理图。您编写的着色器代码块既包含必要的输入,又包含生成的输出,然后包括实际在其上工作的代码。您有一个列表,该列表指示在任何情况下都应应用哪些着色器(此材质是否需要凹凸贴图组件,是否启用了延迟或前向组件等)。
现在,您可以获取此图形并从中生成着色器代码。这主要意味着将块的代码“粘贴”到适当的位置,并确保图形已按必要的顺序排列,然后适当地粘贴着色器输入/输出(在GLSL中,这意味着在其中定义“全局” ,输出和统一变量)。
这与ubershader方法不同。在Ubershader中,您可以将所有内容所需的所有代码放入一个单独的着色器中,可能使用#ifdef和统一等在编译或运行功能时打开和关闭功能。我个人不喜欢ubershader方法,但是使用了一些相当令人印象深刻的AAA引擎(尤其想到了Crytek)。
您可以通过多种方式处理着色器块。最先进的方法(如果打算支持GLSL,HLSL和控制台,则很有用)是为着色器语言编写解析器(可能尽可能接近HLSL / Cg或GLSL,以使开发人员获得最大的“可理解性” ),然后可以将其用于源到源的翻译。另一种方法是仅将着色器块包装在XML文件或类似文件中,例如
<shader name="example" type="pixel">
<input name="color" type="float4" source="vertex" />
<output name="color" type="float4" target="output" index="0" />
<glsl><![CDATA[
output.color = vec4(input.color.r, 0, 0, 1);
]]></glsl>
</shader>
请注意,使用这种方法,您可以为不同的API设置多个代码段,甚至可以对代码段进行版本控制(因此您可以拥有GLSL 1.20版本和GLSL 3.20版本)。您的图形甚至可以自动排除没有兼容代码段的着色器块,因此您可以在较旧的硬件上获得半优美的降级效果(例如,正常映射或仅在较旧的硬件上排除的,无需程序员支持就无法支持的着色器)做一堆显式检查)。
XMl示例然后可以生成类似的内容(如果这是无效的GLSL,则表示歉意,因为我已经使用了该API已有一段时间了):
layout (location=0) in vec4 input_color;
layout (location=0) out vec4 output_color;
struct Input {
vec4 color;
};
struct Output {
vec4 color;
}
void main() {
Input input;
input.color = input_color;
Output output;
// Source: example.shader
#line 5
output.color = vec4(input.color.r, 0, 0, 1);
output_color = output.color;
}
您可能会更聪明一些,并生成更“有效”的代码,但是老实说,不是完全废话的任何着色器编译器都将为您消除生成的代码中的冗余。也许较新的GLSL #line
现在也允许您将文件名放在命令中,但是我知道较旧的版本非常缺乏并且不支持该名称。
如果您有多个块,则将它们的输入(树中的祖先块不将其作为输出提供)与输出一样被串联到输入块中,并且代码被串联。要做一些额外的工作以确保阶段匹配(顶点与片段),并且顶点属性输入布局“正常工作”。这种方法的另一个好处是,您可以编写旧版GLSL不支持的显式统一和输入属性绑定索引,并在着色器生成/绑定库中处理这些索引。同样,您可以使用元数据来设置VBO和glVertexAttribPointer
调用,以确保兼容性并确保一切“正常”。
不幸的是,现在还没有像这样的良好的跨API库。Cg有点接近,但是它具有对AMD卡上OpenGL的废话支持,如果您使用除最基本的代码生成功能以外的任何功能,它的运行速度可能会非常慢。DirectX效果框架也可以工作,但是除HLSL之外,对任何其他语言的支持当然为零。GLSL有一些不完整的越野车类库,它们模仿DirectX库,但是鉴于上次我检查它们的状态,我只会编写自己的库。
ubershader方法仅意味着为某些功能定义“知名”预处理程序指令,然后针对具有不同配置的不同材料重新编译。例如,对于具有法线贴图的任何材质,您都可以定义USE_NORMAL_MAPPING=1
,然后在像素级ubershader中只需:
#if USE_NORMAL_MAPPING
vec4 normal;
// all your normal mapping code
#else
vec4 normal = normalize(in_normal);
#endif
此处的一个大问题是为预编译的HLSL处理此问题,您需要对所有使用中的组合进行预编译。即使使用GLSL,您也需要能够正确生成所有正在使用的预处理程序指令的键,以避免重新编译/缓存相同的着色器。使用制服可以降低复杂度,但是与预处理器制服不同的是,它不会减少指令数量,并且仍然会对性能产生一些小的影响。
需要明确的是,这两种方法(以及仅手动编写大量着色器变体)都在AAA空间中使用。使用最适合您的方法。