为什么这个GLSL着色器这么慢?

sci*_*pie 3 performance shader raytracing glsl

我试图在片段着色器中的网格上进行光线跟踪.我已经编写了下面的着色器来执行此操作(顶点着色器只绘制一个screenquad).

#version 150

uniform mat4 mInvProj, mInvRot;
uniform vec4 vCamPos;

varying vec4 vPosition;

int test(vec3 p)
{
    if (p.x > -4.0 && p.x < 4.0
     && p.y > -4.0 && p.y < 4.0
     && ((p.z < -4.0 && p.z > -8.0) || (p.z > 4.0 && p.z < 8.0)))
        return 1;
    return 0;
}

void main(void) {
    vec4 cOut = vec4(0, 0, 0, 0);

    vec4 vWorldSpace = mInvRot * mInvProj * vPosition;
    vec3 vRayOrg = vCamPos.xyz;
    vec3 vRayDir = normalize(vWorldSpace.xyz);

    // http://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm
    vec3 adelta = abs(vRayDir);
    int increaser;
    vec3 gradient, sgradient;
    if (adelta.x > adelta.y && adelta.x > adelta.z)
    {
        increaser = 0;
        gradient = vec3(vRayDir.x > 0.0? 1.0: -1.0, vRayDir.y / vRayDir.x, vRayDir.z / vRayDir.x);
        sgradient = vec3(0.0, gradient.y > 0.0? 1.0: -1.0, gradient.z > 0.0? 1.0: -1.0);
    }
    else if (adelta.y > adelta.x && adelta.y > adelta.z) 
    {
        increaser = 1;
        gradient = vec3(vRayDir.x / vRayDir.y, vRayDir.y > 0.0? 1.0: -1.0, vRayDir.z / vRayDir.y);
        sgradient = vec3(gradient.x > 0.0? 1.0: -1.0, 0.0, gradient.z > 0.0? 1.0: -1.0);
    }
    else 
    {
        increaser = 2;
        gradient = vec3(vRayDir.x / vRayDir.z, vRayDir.y / vRayDir.z, vRayDir.z > 0.0? 1.0: -1.0);
        sgradient = vec3(gradient.x > 0.0? 1.0: -1.0, gradient.y > 0.0? 1.0: -1.0, 0.0);
    }
    vec3 walk = vRayOrg;
    for (int i = 0; i < 64; ++i)
    {
        vec3 fwalk = floor(walk);
        if (test(fwalk) > 0)
        {
            vec3 c = abs(fwalk) / 4.0;
            cOut = vec4(c, 1.0);
            break;
        }
        vec3 nextwalk = walk + gradient;
        vec3 fnextwalk = floor(nextwalk);

        bool xChanged = fnextwalk.x != fwalk.x;
        bool yChanged = fnextwalk.y != fwalk.y;
        bool zChanged = fnextwalk.z != fwalk.z;

        if (increaser == 0)
        {
            if ((yChanged && test(fwalk + vec3(0.0, sgradient.y, 0.0)) > 0)
             || (zChanged && test(fwalk + vec3(0.0, 0.0, sgradient.z)) > 0)
             || (yChanged && zChanged && test(fwalk + vec3(0.0, sgradient.y, sgradient.z)) > 0))
                {
                    vec3 c = abs(fwalk) / 4.0;
                    cOut = vec4(c, 1.0);
                    break;
                }
        }
        else if (increaser == 1)
        {
            if ((xChanged && test(fwalk + vec3(sgradient.x, 0.0, 0.0)) > 0)
             || (zChanged && test(fwalk + vec3(0.0, 0.0, sgradient.z)) > 0)
             || (xChanged && zChanged && test(fwalk + vec3(sgradient.x, 0.0, sgradient.z)) > 0))
                {
                    vec3 c = abs(fwalk) / 4.0;
                    cOut = vec4(c, 1.0);
                    break;
                }
        }
        else
        {
            if ((xChanged && test(fwalk + vec3(sgradient.x, 0.0, 0.0)) > 0)
             || (yChanged && test(fwalk + vec3(0.0, sgradient.y, 0.0)) > 0)
             || (xChanged && yChanged && test(fwalk + vec3(sgradient.x, sgradient.y, 0.0)) > 0))
                {
                    vec3 c = abs(fwalk) / 4.0;
                    cOut = vec4(c, 1.0);
                    break;
                }
        }

        walk = nextwalk;
    }

    gl_FragColor = cOut;
}
Run Code Online (Sandbox Code Playgroud)

只要我查看接近网格的项目,硬编码的帧速率,帧速率看起来可以接受(Geforce 680M上的400 + fps)(尽管比我目前所写的其他着色器要低,但是当我看起来时)在空虚时(所以循环一直到64),帧速率很差(40fps).当看到如此接近网格时,我得到大约1200 fps,每个像素都在同一个关闭的网格项目中结束.

虽然我知道为每个像素做这个循环是一些工作,它仍然是一些简单的基本数学,特别是现在我已经删除了纹理查找并且刚刚使用了一个简单的测试,所以我不明白为什么这有让一切都变得如此艰难.我的GPU有16个内核,运行速度为700 + Mhz.我渲染为960x540,518400像素.它应该能够处理比我想象的更多.

如果我删除上面的抗锯齿部分(我将根据增加器值测试一些额外相邻点的代码部分),它会好一点(100fps),但是通过这些计算,它应该不会有很大的不同!如果我拆分代码以便不使用增量器但是下面的代码是针对每个不同的部分完成的,则帧速率保持不变.如果我将一些int更改为浮点数,则没有任何更改.

之前我做过更多密集和/或复杂的着色器,为什么这个着色非常慢?任何人都可以告诉我做什么计算会让它变得如此之慢?

我没有设置未使用的制服或类似的东西,C代码也只是做渲染.这是我以前成功使用过100次的代码.

任何人?

sci*_*pie 12

简短的回答是:着色器中的分支和循环(可能)是邪恶的.但它远不止于此:阅读本主题以获取更多信息:着色器中分支的效率

它来到这个:

图形适配器具有一个或多个GPU,GPU具有多个核心.每个核心都设计为运行多个线程,但这些线程只能运行完全相同的代码(取决于实现).

因此,如果10个线程必须执行不同的循环,那么只要最大循环将运行,这10个线程都必须运行(取决于实现,循环可能会继续进行,或者线程可能会停止) .

与分支相同:如果线程具有if,则可能(取决于实现)执行两个分支并且使用其中一个分支的结果.

因此,总而言之,如果您希望根据某些条件删除某些计算,则可能(并且可能主要是)更好地进行更多数学和使用0因子,而不是写条件本身和分支.

例如:

(using useLighting = 0.0f or 1.0f)
return useLighting * cLightColor * cMaterialColor + (1.0 - useLighting) * cMaterialColor;
Run Code Online (Sandbox Code Playgroud)

可能比以下更好:

if (useLighting < 0.5)
  return cMaterialColor;
else
  return cLightColor * cMaterialColor;
Run Code Online (Sandbox Code Playgroud)

但有时它可能不会......性能测试是关键......