3

フラグメント シェーダーのグリッドでレイトレースを実行しようとしています。これを行うために、以下のシェーダーを作成しました (頂点シェーダーはスクリーンクワッドを描画するだけです)。

#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;
}

ハードコーディングされた近いグリッド アイテムを見ている限り、フレームレートは許容できるように見えます (Geforce 680M で 400+fps) (これまでに書いた他のシェーダーと比較すると予想よりも低いですが)。空の場合 (ループは 64 まで続く)、フレームレートはひどいものです (40fps)。グリッドを非常に近くで見ると、すべてのピクセルが同じ閉じたグリッド アイテムになってしまうため、約 1200 fps になります。

すべてのピクセルに対してこのループを実行するのはある程度の作業であることは理解していますが、特にテクスチャルックアップを削除して簡単なテストを使用しただけなので、簡単な基​​本的な計算であるため、なぜこれが必要なのかわかりませんすべてを非常に遅くします。私の GPU には 16 コアがあり、700+Mhz で動作します。960x540、518400 ピクセルでレンダリングしています。これは、私が考えるよりもはるかに多くを処理できるはずです。

上記のアンチエイリアシング部分 (増加値に基づいていくつかの余分な隣接ポイントをテストするコードの部分) を削除すると、少し良くなります (100fps) が、これらの計算ではそうではありません。大きな違いを生む!コードを分割して増量剤を使用しないようにしますが、以下のコードを異なる部分ごとに実行すると、フレームレートは変わりません。一部の int を float に変更しても、何も変わりません。

以前にもっと集中的で複雑なシェーダーを行ったことがありますが、なぜこれは非常に遅いのですか? 私が行う計算が非常に遅くなる理由を誰か教えてもらえますか?

使用されていないユニフォームなどを設定しているわけではありません。C コードもレンダリング以外のことは何もしていません。これは、私が何百回も前に使用したコードです。

誰?

4

1 に答える 1

12

簡単に言えば、シェーダーでの分岐とループは悪です (悪である可能性があります)。しかし、それだけではありません。詳細については、このトピックをお読みください:シェーダーでの分岐の効率

これは次のようになります。

グラフィックス アダプターには 1 つ以上の GPU があり、GPU には複数のコアがあります。すべてのコアは複数のスレッドを実行するように設計されていますが、それらのスレッドはまったく同じコードしか実行できません (実装によって異なります)。

したがって、10 個のスレッドが別のループを実行する必要がある場合、それらの 10 個のスレッドはすべて、最大のループの実行にかかる限り実行する必要があります (実装によっては、ループが必要以上に継続されるか、スレッドが停止する可能性があります)。 .

分岐も同様です。スレッドに if がある場合、(実装によっては) 両方の分岐が実行され、そのうちの 1 つの結果が使用される可能性があります。

したがって、結論として、条件自体を記述して分岐するよりも、いくつかの条件に応じて一部の計算を削除する場合は、より多くの計算を行い、0 係数を使用する方が良いかもしれません (そしておそらくほとんどの場合そうです)。

例えば:

(using useLighting = 0.0f or 1.0f)
return useLighting * cLightColor * cMaterialColor + (1.0 - useLighting) * cMaterialColor;

次よりも優れている可能性があります。

if (useLighting < 0.5)
  return cMaterialColor;
else
  return cLightColor * cMaterialColor;

しかし、そうでない場合もあります...パフォーマンステストが重要です...

于 2013-05-07T09:06:49.730 に答える