3

我正在尝试使用 GLSL(在 iOS 中),并且我编写了一个简单的着色器,它采用颜色值和两个圆(centerradiusedgeSmoothing)的参数。它是在整个屏幕上使用单个四边形绘制的,着色器使用gl_FragCoord并确定每个点是在圆圈内还是在圆圈外 - 它计算圆圈内的 alpha 为 1.0,平滑地着色到外部 0.0 radius + edgeSmoothing,然后应用镜像 -将样式钳位到 alpha(三角波以获得奇偶填充规则效果)并设置gl_FragColor = mix(vec4(0.0), color, alpha);.

这很好用,但我想要 5 种不同颜色的 10 个圆圈,所以我调用glUniform所有着色器制服并glDrawElements分别绘制四边形五次(使用不同的颜色和圆圈参数),并且我的混合模式是相加的,因此不同的颜色相加很好地给出我想要的图案,完美!

请记住,这是一个实验,所以我试图了解 GL 和 GLSL 而不是画圆圈。

现在我认为只绘制一次四边形并将所有 10 个圆的参数传递到统一数组(centers[10]radii[10]等)中,在 GLSL 中循环它们并将它们在着色器中产生的颜色相加会更有效. 所以我编写了这个着色器并重构了我的代码以一次传递所有的圆形参数。我得到了正确的结果(输出看起来完全一样),但我的帧速率从 15fps 下降到大约 3fps - 它慢了五倍!

着色器代码现在有循环,但使用相同的数学计算每对圆的 alpha 值。为什么这么慢?当然,我所做的工作比填充整个屏幕五次而 GL 进行五次加法混合(即读取像素值、混合和写回)要少吗?现在我只是计算累积的颜色并填充整个屏幕一次?

谁能解释为什么我认为的优化会产生相反的效果?

更新:将此代码粘贴到ShaderToy以查看我在说什么。

#ifdef GL_ES
precision highp float;
#endif

uniform float time;

void main(void)
{
    float r, d2, a0, a1, a2;
    vec2 pos, mid, offset;
    vec4 bg, fg;

    bg = vec4(.20, .20, .40, 1.0);
    fg = vec4(.90, .50, .10, 1.0);
    mid = vec2(256.0, 192.0);

    // Circle 0
    pos = gl_FragCoord.xy - mid;
    d2 = dot(pos, pos);
    r = 160.0;
    a0 = smoothstep(r * r, (r + 1.0) * (r + 1.0), d2);

    // Circle 1
    offset = vec2(110.0 * sin(iGlobalTime*0.8), 110.0 * cos(iGlobalTime));
    pos = gl_FragCoord.xy - mid + offset;
    d2 = dot(pos, pos);
    r = 80.0;
    a1 = smoothstep(r * r, (r + 1.0) * (r + 1.0), d2);

    // Circle 2
    offset = vec2(100.0 * sin(iGlobalTime*1.1), -100.0 * cos(iGlobalTime*0.7));
    pos = gl_FragCoord.xy - mid + offset;
    d2 = dot(pos, pos);
    r = 80.0;
    a2 = smoothstep(r * r, (r + 1.0) * (r + 1.0), d2);

    // Calculate the final alpha
    float a = a0 + a1 + a2;
    a = abs(mod(a, 2.0) - 1.0);

    gl_FragColor = mix(bg, fg, a);
}
4

1 回答 1

3

增加片段着色器中操作的复杂性会对渲染时间产生非线性影响。在某些情况下,即使添加一个看起来很简单的分支操作也会使着色器慢 10 倍。

在 iOS 设备上的片段着色器中,循环尤其可怕,所以我会不惜一切代价避免它们。我敢打赌,如果您将该循环展开为针对您的统一值的一系列检查,它会执行得更好。

然而,对你的制服运行 10 次检查,这听起来像是涉及步骤或平滑步骤,当应用于帧缓冲区中的每个像素时将非常昂贵。这也相当浪费,因为屏幕的很大一部分不会被任何特定的圆圈覆盖。

无需使用单独的glDrawElements()调用来绘制单独的圆圈,或者通过绘制屏幕大小的四边形来绘制。我在这个答案中描述了我用来在我的开源应用程序中绘制球体冒名顶替者的过程,我可以在最新的 iOS 设备上以 60 FPS 的速度在屏幕上绘制数千个圆圈(球体)。为此,我为每个足够大的圆圈传入一个四边形,该圆圈包含该圆圈并且不大于该圆圈。这些四边形都聚集在一个数组中并立即绘制。每个圆的附加参数作为属性与顶点数据一起传入。例如,我不需要指定半径,因为我在顶点旁边使用从 (-1, -1) 到 (1, 1) 的冒名顶替空间坐标,并进行简单的计算以确定一个点是否在圆内。

如果您只绘制每个圆圈所需的片段,而不是更多,您将减轻管道片段处理部分的大量负载。您仍然需要启用混合模式,但是四边形大小的减小,加上片段着色器中执行的操作的简化,将带来更好的整体性能。

于 2012-10-30T03:47:31.177 回答