使用两个着色器,而不是一个带IF语句的着色器


9

我一直在努力将相对较大的opengl ES 1.1源移植到ES 2.0。

在OpenGL ES 2.0(这意味着所有内容都使用着色器)中,我想绘制3次茶壶。

  1. 第一个具有统一的颜色(例如旧的glColor4f)。

  2. 第二个,具有每个顶点的颜色(茶壶也具有顶点颜色的数组)

  3. 第三个,具有每个顶点的纹理

  4. 也许是第四个,同时具有每个顶点的纹理和颜色。然后可能是第五名,也有法线。

据我所知,实现有两种选择。第一个方法是制作支持以上所有内容的着色器,并设置要更改其行为的均匀性(例如,使用奇异颜色均匀性或每个顶点颜色均匀性)。

第二种选择是为每种情况创建一个不同的着色器。使用一些自定义的着色器预处理,并不是那么复杂,但是要担心的是在图形对象之间切换着色器的性能成本。我读过它并不小。

我的意思是,执行此操作的最佳方法是同时进行构建和度量,但最好听取任何意见。

Answers:


10

分支的性能成本也可以不小。在您的情况下,绘制的所有顶点和片段都将沿着着色器采用相同的路径,因此在现代台式机硬件上,它并没有它可能的糟糕,但是您使用的是ES2,这意味着您没有在使用现代的桌面硬件。

分支的最坏情况将是这样的:

  • 分支的两边都进行评估。
  • 着色器编译器将生成“混合”或“步骤”指令,并将其插入您的代码中以决定使用哪一侧。

所有这些额外的指令将针对您绘制的每个顶点或片段运行。可能有数以百万计的额外指令需要权衡着色器更改的成本。

苹果公司的《iOS OpenGL ES编程指南》(可以用作目标硬件的代表)对分支的说法是这样的:

避免分支

不建议在着色器中使用分支,因为它们会降低在3D图形处理器上并行执行操作的能力。如果您的着色器必须使用分支,请遵循以下建议:

  • 最佳性能:在编译着色器时在已知的常数上分支。
  • 可接受:在统一变量上分支。
  • 可能很慢:分支到着色器内部计算的值。

与其创建带有许多旋钮和杠杆的大型着色器,不如创建专用于特定渲染任务的较小着色器。在减少着色器中的分支数量与增加创建的着色器数量之间需要权衡。测试不同的选项,然后选择最快的解决方案。

即使您满意这里的“可接受”插槽,您仍然需要考虑在4或5种情况之间进行选择,这将增加着色器中的指令数。您应该了解目标硬件上的指令数量限制,并确保不要超出这些限制,请再次从上面的Apple链接中引用:

超出这些限制时,不需要OpenGL ES实施即可实施软件后备;相反,着色器根本无法编译或链接。

并不是说分支不是满足您需求的最佳解决方案。您正确地确定了应该概述这两种方法的事实,因此这是最终建议。但是请务必注意,随着着色器变得越来越复杂,基于分支的解决方案所带来的开销可能远远高于对着色器的一些更改。


3

绑定着色器的成本可能不小,但是除非您要渲染数千个项目而不批处理使用相同着色器的所有对象,否则绑定着色器不会成为您的瓶颈。

尽管我不确定这是否适用于移动设备,但是如果条件介于常数和统一之间,GPU的分支运行速度不会太慢。两者都是有效的,都已在过去使用过,将来会继续使用,请选择您认为适合的情况中的任何一种。

此外,还有其他一些方法可以实现此目的:“ Uber着色器”和OpenGL着色器程序链接方式的一些技巧。

从本质上讲,“ Uber着色器”是减去分支的首选,但是您将拥有多个着色器。而不是使用的if语句,你可以使用预处理器- ,#define#ifdef#else#endif和编译不同的版本,包括正确的#define是给你所需要的。

vec4 color;
#ifdef PER_VERTEX_COLOR
color = in_color;
#else
color = obj_color;
#endif

您也可以将着色器分解为单独的功能。有一个着色器定义所有功能的原型并调用它们,链接一堆包含适当实现的额外着色器。我已使用此技巧进行阴影映射,以轻松交换如何对所有对象进行过滤,而无需修改所有着色器。

//ins, outs, uniforms

float getShadowCoefficient();

void main()
{
    //shading stuff goes here

    gl_FragColor = color * getShadowCoefficient();
}

然后,我可以拥有多个其他的着色器文件getShadowCoefficient(),这些文件定义了,必要的制服以及其他内容。例如,shadow_none.glsl包含:

float getShadowCoefficient()
{
    return 1;
}

并且shadow_simple.glsl包含(从实现CSM的着色器中简化):

in vec4 eye_position;

uniform sampler2DShadow shad_tex;
uniform mat4 shad_mat;

float getShadowCoefficient()
{
    vec4 shad_coord = shad_mat * eye_position;
    return texture(shad_tex, shad_coord).x;
}

您可以通过链接不同的shadow_*着色器来简单地选择是否要着色。该解决方案可能会产生更多开销,但是与其他方式相比,我想认为GLSL编译器足以优化任何额外开销。我没有对此进行任何测试,但这是我喜欢的方式。

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.