iPhone GLSL 动态分支问题

Posted

技术标签:

【中文标题】iPhone GLSL 动态分支问题【英文标题】:iPhone GLSL dynamic branching issue 【发布时间】:2013-04-03 13:59:37 【问题描述】:

我试图将 vec3 数组作为统一传递,然后在每个像素上迭代它们。数组的大小因情况而异,因此我无法以恒定的迭代次数进行循环。

代码如下:

precision highp float;
precision highp int;

varying vec4 v_fragmentColor;

varying vec4 v_pos;

uniform int u_numberOfParticles;

const int numberOfAccumsToCapture = 3;
const float threshold = 0.15;              
const float gooCoeff = 1.19;

uniform mat4 u_MVPMatrix;
uniform vec3 u_waterVertices[100];

void main()

    vec4 finalColor = vec4(0.0, 0.0, 0.0, 0.0);

    vec2 currPos = v_pos.xy;

    float accum = 0.0;
    vec3 normal = vec3(0, 0, 0);

    for ( int i = 0; i < u_numberOfParticles; ++i )
    
        vec2 dir2 = u_waterVertices[i].xy - currPos.xy;
        vec3 dir3 = vec3(dir2, 0.1);
        float q = dot(dir2, dir2);

        accum += u_waterVertices[i].z / q;
    

    float normalizeToEdge = 1.0 - (accum - threshold) / 2.0;

    if (normalizeToEdge < 0.4)
        finalColor = vec4( 0.1, normalizeToEdge + 0.5, 0.9-normalizeToEdge*0.4, 1.0);

    if ( normalizeToEdge < 0.2 )
    
        finalColor = vec4( 120.0/255.0, 245.0/255.0, 245.0/255.0, 1.0);
        float shade = mix( 0.7, 1.0, normal.x);
        finalColor *= shade;
    

    gl_FragColor = vec4(finalColor);

问题出在这里:

for ( int i = 0; i < u_numberOfParticles; ++i )

    vec2 dir2 = u_waterVertices[i].xy - currPos.xy;
    vec3 dir3 = vec3(dir2, 0.1);
    float q = dot(dir2, dir2);

    accum += u_waterVertices[i].z / q;

当我像这样制作for循环时

for ( int i = 0; i < 2; ++i )

    //...

即使 u_numberOfParticles 也是 2,我的帧速率也会增加一倍

变成这样

for ( int i = 0; i < 100; ++i )

    if (i == u_numberOfParticles)
        break;
    //...

没有改善。

我知道应对这种情况的唯一方法是创建多个着色器。但是数组的大小可能从 1 到 40 不等,仅仅因为 for 循环而制作 40 个不同的着色器是愚蠢的。任何帮助或想法如何处理这种情况?

【问题讨论】:

u_numberOfParticles 多久更改一次?可以在运行时设置一次吗? @Kimi,当我添加新粒子时它会改变,所以经常..它可以是 15,然后是 20,然后是 30,然后是 15,我可以将它始终设置为 40,例如但那是低效的。 将迭代次数设置为较小的常量时获得更好结果的原因是编译器循环展开和常量数组索引。如果您有动态中断子句,则无法展开循环。您是否在片段着色器 (gl_FragClolor) 中运行提供的代码?如果是,那么计算顶点着色器中的值并将 accum 作为变量传递会更有效。 @Kimi,是的,这是一个片段着色器。我正在渲染一个全屏四边形,所以我无法计算顶点的值。我认为有一些解决方案可以解决如何在着色器上实现这种行为,但更有效。我实际上可以保留常量 = 40,但是如果有 10 个粒子,它将进行 30 次迭代。这对性能。 另外,我建议在真实设备上进行测试,而忽略您在模拟器中获得的任何性能。 sim 与帧速率无关。它可以更快或更慢。即使在两代不同的设备之间,您也会看到性能差异很大。 iPhone 5 比 iPhone 4 甚至 4S 快得多。 【参考方案1】:

我同意 @badweasel 的观点,即你的方法并不适合着色器。

据我了解,您正在计算从当前像素到每个粒子的距离,总结并使用结果确定颜色。

也许您可以改为为每个粒子渲染一个点精灵并通过智能混合确定颜色。

您可以使用gl_PointSize 在顶点着色器中设置点精灵的大小。在片段着色器中,您可以使用gl_PointCoord.xy(在纹理坐标中,即[0..1])来确定当前像素在点精灵中的位置。通过知道点精灵的大小,您可以计算当前像素到粒子中心的距离并将颜色设置为某种东西。通过另外启用混合,您可能能够在循环中实现求和,但帧速率要高得多。

以下是我用于通过点精灵渲染“假”球体的顶点和片段着色器,作为如何使用点精灵的示例。

VS:

#version 150
in vec3 InPosition;

uniform mat4 ModelViewProjectionMatrix;
uniform int Radius = 10;

void main()

    vec4 Vertex = vec4(InPosition, 1.0);
    gl_Position = ModelViewProjectionMatrix * Vertex;
    gl_PointSize = Radius;

FS:

#version 150
out vec4 FragColor;

void main()

    // calculate normal, i.e. vector pointing from point sprite center to current fragment
    vec3 normal;
    normal.xy = gl_PointCoord * 2 - vec2(1);
    float r2 = dot(normal.xy, normal.xy);
    // skip pixels outside the sphere
    if (r2 > 1) discard;
    // set "fake" z normal to simulate spheres
    normal.z = sqrt(1 - r2);
    // visualize per pixel eye-space normal
    FragColor = vec4(gl_PointCoord, normal.z, 1.0);

注意,您需要启用:GL_POINT_SPRITEGL_PROGRAM_POINT_SIZE 才能使用点精灵。

【讨论】:

现在我使用你建议的方法。我渲染了一些“特殊”纹理,然后在混合后,我分析每个像素的 alpha 值并决定该像素应该是什么样子。由于在片段着色器中删除了这些循环,这似乎要快得多。不过,谢谢。

以上是关于iPhone GLSL 动态分支问题的主要内容,如果未能解决你的问题,请参考以下文章

在 Bamboo 中构建 repo 时动态选择分支

git:合并两个分支:啥方向?

五大常用算法:分治动态规划贪心回溯和分支界定

动态规划回溯搜索分治算法分支限界算法

TFS 2013 使用动态解决方案计数构建定义

case学习:使用VTI解决分支机构动态IP与总部互联问题