游戏引擎设计– Ubershader-着色器管理设计


18

我想实现一个带有延迟着色的灵活的Ubershader系统。我目前的想法是从模块中创建着色器,以处理某些功能,例如FlatTexture,BumpTexture,位移映射等。也有一些模块可以解码颜色,进行色调映射等。这具有我可以如果GPU不支持某些类型的模块,请更换它们,以便我可以适应当前的GPU功能。我不确定这种设计是否良好。我担心我现在可能会做出错误的设计选择,以后再为此付出代价。

我的问题是,在哪里可以找到有关如何有效实施着色器管理系统的资源,示例和文章?有谁知道大型游戏引擎是如何做到的?


3
不够长的时间才能得出真正的答案:如果您从小做起,让它根据您的需求有机地增长,而不是尝试先构建MegaCity-Shader之一,可以使用这种方法做得很好。首先,您可以减轻最大的烦恼,即过多地进行前期设计,如果无法解决,则稍后再付款;其次,您避免进行多余的工作,这些工作永远不会被使用。
Patrick Hughes

不幸的是,我们不再接受“资源请求”问题。
Gnemlock

Answers:


23

一种半常见的方法是制作我所谓的着色器组件,类似于我认为您在调用的模块。

这个想法类似于后处理图。您编写的着色器代码块既包含必要的输入,又包含生成的输出,然后包括实际在其上工作的代码。您有一个列表,该列表指示在任何情况下都应应用哪些着色器(此材质是否需要凹凸贴图组件,是否启用了延迟或前向组件等)。

现在,您可以获取此图形并从中生成着色器代码。这主要意味着将块的代码“粘贴”到适当的位置,并确保图形已按必要的顺序排列,然后适当地粘贴着色器输入/输出(在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空间中使用。使用最适合您的方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.