做的opengl游戏倾向于使用多个着色器?

对于任何给定的对象,你想渲染,可能有一大堆事情需要考虑渲染(材料,纹理,照明,混合等)。 但是,也可能有一些非常简单的物体,没有纹理或材质,也不需要任何混合,忽略灯光。

那么,人们如何来解决呢? 你写了一个GLSL着色器,它有各种各样的布尔值和检查,需要设置顶点通过,或者你为每个普通类别的对象编写单独的着色器,并作为你的渲染之间切换?

为了回答您的具体问题,并重点关注两个最新的OpenGL标题:

  • Doom 3 BFG版的框架可以使用〜12个不同的GLSL程序。
  • 一个愤怒的框架可以使用〜38个不同的GLSL程序。

(使用GL拦截捕获)。

所以答案是肯定的 – OpenGL游戏(或者至less这些)会使用多个着色器。

作为一般情况下的答案,更多的情况是“取决于”。 使用多个着色器或使用分支的select通常可能是更昂贵的选项 – 更改着色器或者处理每个顶点或片段必须采取分支。

在过去,这是相当清晰的 – 第一个可用的着色语言根本不支持分支,所以你没有select – 你使用了多个着色器。

当分支支持开始时,事情就变得有趣了。 首先,您可能需要支持较旧的硬件,在这种情况下,您同样没有select – 没有分支。 其次,着色器分支的原始forms相当原始, 基本上这两个分支将被执行,内部硬件寄存器将根据哪一个通过更新(即分支最有可能只是使用mix / lerp / step / etc指令来模拟 – 如果编译的话,你仍然可以在HLSL代码中看到这一点禁用分支并查看它的反汇编)。 所以再次最好的select是不分支。

现代硬件当然更加灵活,你可以使用两种forms的分支。 基于着色器均匀(GLSL)或常量(HLSL)的分支应该非常便宜 – 不是免费的,但是硬件可以知道当前状态集的着色器的所有执行将采用相同的分支,并相应地进行优化。

基于运行时确定的某个值进行分支(或通过顶点attribinput)更加有趣。 现代硬件将在顶点或片段的“组”(我在这里避免厂商特定的术语)中执行着色器,并且如果给定“组”的着色器的所有执行采用相同的分支,则它也可以拉动一些优化。

还有一个事实,即GLSL通过使当前的统一值成为程序状态的一部分而使事情变得复杂一些。 HLSL没有这个function,这不是硬件工作的方式,但是这是为GLSL做出的决定,所以这意味着着色器的变化也可能包括存储硬件寄存器的现有内容,然后加载新的值作为新程序状态的一部分。 当然,使用UBO也会避免这种情况,但是如果您使用独立制服,那么您需要注意,更改着色器可能涉及的不仅仅是交换一些小的可执行映像。

当然,这并不是说拥有一个具有大量分支的大着色器是首选。 分支仍然有自己的成本,甚至在现代硬件上,它也可能比着色器更改花费更多的开销。 你还需要考虑你的顶点着色器input与着色器本身是紧密相连的,除非你使用一个公共的顶点格式和布局。 在具有几何graphics和镶嵌着色器阶段的现代硬件上,您可能需要处理这些(可选)着色器阶段在当前程序中可能会或可能不会被激活的情况。 特别要注意几何着色器,因为它可能是很慢的 – 即使只是一个直通着色器 – 所以你不会希望它活跃,除非你正在做一些明确要求的东西(比如写入一个非零的viewport索引) 。

因此,所有这些冗长的序言对于这样的问题来说都是一个相当迂回的结论:每个人的用例都不一样,所以轮廓,基准,find哪个是最快的,决定哪个性能比灵活性/代码清洁度您将会或不会接受的折衷,并将您的代码基于这些真实世界的指标。

一般来说,我相信你要做的就是尽量减less改变OpenGL状态的次数。 这意味着要尽可能地避免调用glUniform,glUseProgram等。 要做到这一点,你需要将所有的对象分组,以便那些需要相同OpenGL状态的对象一起渲染(即一次渲染没有纹理和材质的对象)。

通常这样做的方式是,每个将被渲染的对象都将使用特定的“材质”,而不是OpenGL意义上的,而是在“需要什么样的状态设置来渲染”这个意义上。 这些包括纹理,着色器和渲染所需的其他属性。 你想要做的就是将这些“材料”中的许多“连在一起”,以避免改变参数,或者至less改变很less。