IFステートメントで1つではなく2つのシェーダーを使用する


9

私は比較的大きなopengl ES 1.1ソースをES 2.0に移植する作業をしています。

OpenGL ES 2.0(つまり、すべてがシェーダーを使用する)では、ティーポットを3回描画したいと思います。

  1. 最初のものは、均一な色(古いglColor4f)です。

  2. 2つ目は、頂点ごとのカラーです(ティーポットにも頂点カラーの配列があります)。

  3. 3つ目は、頂点ごとのテクスチャです。

  4. そして、おそらく4番目の1つで、頂点ごとのテクスチャと色の両方があります。そして、多分5番目のもので、法線も含まれます。

私が知る限り、実装には2つの選択肢があります。1つ目は、動作を変更するように設定されたユニフォームを使用して、上記のすべてをサポートするシェーダーを作成することです(たとえば、単一カラーユニフォームまたは頂点ごとのカラーユニフォームを使用します)。

2番目の選択肢は、状況ごとに異なるシェーダーを作成することです。一部のカスタムシェーダーの前処理では、それほど複雑ではありませんが、描画オブジェクト間でシェーダーを切り替える際のパフォーマンスコストが問題になります。私はそれがささいに小さくないことを読みました。

つまり、これを行う最善の方法は、両方を構築して測定することですが、入力を聞くのは良いことです。

回答:


10

分岐のパフォーマンスコストもそれほど小さくありません。あなたの場合、描画されているすべての頂点とフラグメントは、シェーダーを介して同じパスをたどるので、最新のデスクトップハードウェアでは、それほど悪くはありませんが、ES2を使用しているため、最新のものを使用していませんデスクトップハードウェア。

分岐の最悪の場合は次のようになります。

  • ブランチの両側が評価されます。
  • 「ミックス」または「ステップ」命令がシェーダーコンパイラーによって生成され、使用する側を決定するためにコードに挿入されます。

そして、これらの追加命令はすべて、描画する頂点またはフラグメントごとに実行されます。これは、シェーダーの変更のコストと比較して、数百万の追加の指示になる可能性があります。

Appleの「OpenGL ESプログラミングガイドiOS用)」(ターゲットハードウェアの代表と見なすことができます)では、分岐について次のように述べています。

分岐を避ける

ブランチは、3Dグラフィックスプロセッサ上で並列に操作を実行する機能を低下させる可能性があるため、シェーダーでは推奨されません。シェーダーでブランチを使用する必要がある場合は、次の推奨事項に従ってください。

  • 最高のパフォーマンス:シェーダーのコンパイル時に既知の定数で分岐します。
  • 許容:均一変数で分岐します。
  • 潜在的に遅い:シェーダー内で計算された値での分岐。

多くのノブとレバーを備えた大きなシェーダーを作成する代わりに、特定のレンダリングタスクに特化した小さなシェーダーを作成します。シェーダーのブランチの数を減らすことと、作成するシェーダーの数を増やすことの間にはトレードオフがあります。さまざまなオプションをテストし、最速のソリューションを選択してください。

ここの「Acceptable」スロットにいることに満足している場合でも、4つまたは5つのケースを選択することを検討する必要があり、シェーダーの命令カウントを増やすことになります。ターゲットハードウェアの命令数の制限に注意し、上記のAppleリンクから再度引用して、それらを超えないようにする必要があります。

OpenGL ESの実装は、これらの制限を超えたときにソフトウェアフォールバックを実装する必要はありません。代わりに、シェーダーは単にコンパイルまたはリンクに失敗します。

これは、分岐がニーズに最適なソリューションではないということではありません。両方のアプローチをプロファイリングする必要があるという事実を正しく特定したので、それが最後の推奨事項です。ただし、シェーダーがより複雑になるにつれて、分岐ベースのソリューションは、いくつかのシェーダーの変更よりもはるかに高いオーバーヘッドをもたらす可能性があることに注意してください。


3

シェーダーをバインドするコストは簡単ではないかもしれませんが、同じシェーダーを使用するすべてのオブジェクトをバッチ処理せずに数千のアイテムをレンダリングしない限り、ボトルネックになることはありません。

これがモバイルデバイスに当てはまるかどうかはわかりませんが、条件が定数とユニフォームの間にある場合、GPUがブランチでひどく遅くなることはありません。どちらも有効です。どちらも過去に使用されたものであり、今後も使用されます。どちらの場合も、どちらが適切であるかを選択してください。

さらに、これを実現する方法は他にもいくつかあります。「Uber-shaders」と、OpenGLシェーダープログラムのリンク方法のちょっとしたトリックです。

「Uber-shaders」は基本的に最初の選択ですが、分岐はありませんが、複数のシェーダーがあります。ifステートメントを使用する代わりに、プリプロセッサを使用します- #define#ifdef#else、、#endif適切などの異なるバージョン、コンパイル#defineに必要なもののために秒。

vec4 color;
#ifdef PER_VERTEX_COLOR
color = in_color;
#else
color = obj_color;
#endif

シェーダーを個別の関数に分割することもできます。すべての関数のプロトタイプを定義して呼び出すシェーダーを1つ用意し、適切な実装を含む追加のシェーダーの束をリンクします。このトリックをシャドウマッピングに使用して、すべてのシェーダーを変更しなくても、すべてのオブジェクトでフィルタリングがどのように行われるかを簡単に交換できるようにしました。

//ins, outs, uniforms

float getShadowCoefficient();

void main()
{
    //shading stuff goes here

    gl_FragColor = color * getShadowCoefficient();
}

次に、他のシェーダーファイルを複数定義して、 getShadowCoefficient()、必要なユニフォームます。たとえば、次のものshadow_none.glslが含まれます。

float getShadowCoefficient()
{
    return 1;
}

そして shadow_simple.glsl含まれています(CSMを実装する私のシェーダーから簡略化):

in vec4 eye_position;

uniform sampler2DShadow shad_tex;
uniform mat4 shad_mat;

float getShadowCoefficient()
{
    vec4 shad_coord = shad_mat * eye_position;
    return texture(shad_tex, shad_coord).x;
}

また、別のshadow_*シェーダーをリンクすることで、シェーディングが必要かどうかを簡単に選択できます。このソリューションはオーバーヘッドが非常に大きくなる可能性がありますが、GLSLコンパイラは他の方法でオーバーヘッドを最適化するのに十分であると考えたいと思います。私はこれについてテストを実行していませんが、それは私がそれを行うのが好きな方法です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.