【问题标题】:Optimize WebGL shader?优化 WebGL 着色器?
【发布时间】:2017-07-03 07:01:04
【问题描述】:

我编写了以下着色器来渲染带有一堆同心圆的图案。最终我想让每个旋转的球体成为一个发光体,沿着these lines 创造一些东西。

当然,现在我只是在做最基本的部分来渲染不同的对象。

不幸的是,着色器速度非常慢(在高端 macbook 上为 16fps 全屏)。我很确定这是由于我在着色器中有许多 for 循环和分支。我想知道如何以更优化的方式实现我想要实现的几何图形:

编辑:您可以在此处运行着色器:https://www.shadertoy.com/view/lssyRH

我缺少的一个明显优化是,目前所有片段都针对整个 24 个周围的圆圈进行检查。通过检查片段是否与图表的外部边界相交来完全放弃这些检查将非常快速和容易。我想我只是想了解如何做这样的事情的最佳实践。

#define N 10
#define M 5
#define K 24
#define M_PI 3.1415926535897932384626433832795

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    float aspectRatio = iResolution.x / iResolution.y;

    float h = 1.0;
    float w = aspectRatio;

    vec2 uv = vec2(fragCoord.x / iResolution.x * aspectRatio, fragCoord.y / iResolution.y); 

    float radius = 0.01;
    float orbitR = 0.02;
    float orbiterRadius = 0.005;
    float centerRadius = 0.002;
    float encloseR = 2.0 * orbitR;
    float encloserRadius = 0.002;
    float spacingX = (w / (float(N) + 1.0));
    float spacingY = h / (float(M) + 1.0);
    float x = 0.0;
    float y = 0.0;
    vec4 totalLight = vec4(0.0, 0.0, 0.0, 1.0);
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            // compute the center of the diagram
            vec2 center = vec2(spacingX * (float(i) + 1.0), spacingY * (float(j) + 1.0));
            x =  center.x + orbitR * cos(iGlobalTime);
            y =  center.y + orbitR * sin(iGlobalTime);
            vec2 bulb = vec2(x,y);
            if (length(uv - center) < centerRadius) {
                // frag intersects white center marker                   
                fragColor = vec4(1.0);
                return;               
            } else if (length(uv - bulb) < radius) {
                // intersects rotating "light"
                fragColor = vec4(uv,0.5+0.5*sin(iGlobalTime),1.0);
                return;
            } else {
                // intersects one of the enclosing 24 cylinders
                for(int k = 0; k < K; k++) {
                    float theta = M_PI * 2.0 * float(k)/ float(K);
                    x = center.x + cos(theta) * encloseR;
                    y = center.y + sin(theta) * encloseR;
                    vec2 encloser = vec2(x,y);
                    if (length(uv - encloser) < encloserRadius) {
                        fragColor = vec4(uv,0.5+0.5*sin(iGlobalTime),1.0);
                    return;
                    }
                }   
            }
        }
    }


}

【问题讨论】:

  • 你能预先计算出无数的 sin() 和 cos() 并以某种方式将它们发送到着色器,而不是在着色器中计算它们吗?
  • 您的着色器甚至无法正常工作,至少对我来说有很多伪影,并且您有很多未使用的变量...

标签: opengl-es glsl webgl shader raytracing


【解决方案1】:

请记住,您要优化片段着色器,并且只优化片段着色器:

  1. sin(iGlobalTime)cos(iGlobalTime) 移出循环,它们在整个绘制调用中保持静态,因此无需在每次循环迭代时重新计算它们。
  2. GPU 在可能的情况下采用矢量化指令集 (SIMD),并利用这一点。您通过执行多个标量操作来浪费大量周期,您可以在其中使用单个向量指令(请参阅带注释的代码) [在这里我聪明了三年:我不确定这个关于现代 GPU 如何处理指令的说法是否正确,但它确实有助于提高可读性,甚至可能给编译器一两个提示]
  3. 您的半径检查平方,保存 sqrt(length) 以备您真正需要时使用
  4. 用浮点常量替换浮点常量(你的循环限制)(智能着色器编译器已经这样做了,但不是可以指望的东西)
  5. 在着色器中没有未定义的行为(不写入 gl_FragColor)

这是您的着色器的优化和注释版本(仍然包含未定义的行为,就像您提供的一样)。注解的形式为:

// annotation
// old code, if any
new code
#define N 10
// define float constant N
#define fN 10.
#define M 5
// define float constant M
#define fM 5.
#define K 24
// define float constant K
#define fK 24.
#define M_PI 3.1415926535897932384626433832795
// predefine 2 times PI
#define M_PI2 6.28318531

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    float aspectRatio = iResolution.x / iResolution.y;

    // we dont need these separate
    // float h = 1.0;
    // float w = aspectRatio;

    // use vector ops(2 divs 1 mul => 1 div 1 mul)
    // vec2 uv = vec2(fragCoord.x / iResolution.x * aspectRatio, fragCoord.y / iResolution.y); 
    vec2 uv = fragCoord.xy / iResolution.xy;
    uv.x *= aspectRatio;

    // most of the following declarations should be predefined  or marked as "const"...

    float radius = 0.01;
    // precalc squared radius
    float radius2 = radius*radius;
    float orbitR = 0.02;
    float orbiterRadius = 0.005;
    float centerRadius = 0.002;
    // precalc squared center radius
    float centerRadius2 = centerRadius * centerRadius;
    float encloseR = 2.0 * orbitR;
    float encloserRadius = 0.002;
    // precalc squared encloser radius
    float encloserRadius2 = encloserRadius * encloserRadius;

    // Use float constants and vector ops here(2 casts 2 adds 2 divs => 1 add 1 div)
    // float spacingX = w / (float(N) + 1.0);
    // float spacingY = h / (float(M) + 1.0);
    vec2 spacing = vec2(aspectRatio, 1.0) / (vec2(fN, fM)+1.);

    // calc sin and cos of global time
    // saves N*M(sin,cos,2 muls) 
    vec2 stct = vec2(sin(iGlobalTime), cos(iGlobalTime));
    vec2 orbit = orbitR * stct;

    // not needed anymore
    // float x = 0.0;
    // float y = 0.0;

    // was never used
    // vec4 totalLight = vec4(0.0, 0.0, 0.0, 1.0);

    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            // compute the center of the diagram
            // Use vector ops
            // vec2 center = vec2(spacingX * (float(i) + 1.0), spacingY * (float(j) + 1.0));
            vec2 center = spacing * (vec2(i,j)+1.0);

            // Again use vector opts, use precalced time trig(orbit = orbitR * stct)
            // x = center.x + orbitR * cos(iGlobalTime);
            // y = center.y + orbitR * sin(iGlobalTime);
            // vec2 bulb = vec2(x,y);
            vec2 bulb = center + orbit;
            // calculate offsets
            vec2 centerOffset = uv - center;
            vec2 bulbOffset = uv - bulb;
            // use squared length check
            // if (length(uv - center) < centerRadius) {
            if (dot(centerOffset, centerOffset) < centerRadius2) {
                // frag intersects white center marker                   
                fragColor = vec4(1.0);
                return;               
            // use squared length check
            // } else if (length(uv - bulb) < radius) {
            } else if (dot(bulbOffset, bulbOffset) < radius2) {
                // Use precalced sin global time in stct.x
                // intersects rotating "light"
                fragColor = vec4(uv,0.5+0.5*stct.x,1.0);
                return;
            } else {
                // intersects one of the enclosing 24 cylinders
                for(int k = 0; k < K; k++) {
                    // use predefined 2*PI and float K
                    float theta = M_PI2 * float(k) / fK;
                    // Use vector ops(2 muls 2 adds => 1 mul 1 add)
                    // x = center.x + cos(theta) * encloseR;
                    // y = center.y + sin(theta) * encloseR;
                    // vec2 encloser = vec2(x,y);
                    vec2 encloseOffset = uv - (center + vec2(cos(theta),sin(theta)) * encloseR);
                    if (dot(encloseOffset,encloseOffset) < encloserRadius2) {
                        fragColor = vec4(uv,0.5+0.5*stct.x,1.0);
                        return;
                    }
                }   
            }
        }
    }
}

【讨论】:

  • 谢谢 LJ - 这很有帮助。对计算肯定有很多改进。
【解决方案2】:

我做了更多的思考......我意识到优化它的最佳方法是实际更改逻辑,以便在对小圆圈进行交叉测试之前检查圆圈组的边界。这让它以 60fps 运行:

这里的例子: https://www.shadertoy.com/view/lssyRH

【讨论】:

    猜你喜欢
    • 2021-10-06
    • 2019-03-04
    • 2019-02-17
    • 2013-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多