半一般的なアプローチは、私がシェーダーコンポーネントと呼んでいるものを、あなたがモジュールを呼んでいると思うものに似たものにすることです。
この考え方は、後処理グラフに似ています。必要な入力、生成された出力、および実際にそれらを操作するコードの両方を含むシェーダーコードのチャンクを記述します。どんな状況でもどのシェーダーを適用するかを示すリストがあります(このマテリアルがバンプマッピングコンポーネントを必要とするかどうか、遅延またはフォワードコンポーネントが有効かどうかなど)。
これで、このグラフを取得し、そこからシェーダーコードを生成できます。これは主に、チャンクのコードを所定の位置に「貼り付け」、グラフが既に必要な順序になっていることを確認してから、必要に応じてシェーダーの入力/出力に貼り付けることを意味します(GLSLでは、 、out、および均一変数)。
これは、ユーバーシェーダーアプローチとは異なります。Ubershaderは、すべてに必要なすべてのコードを1つのシェーダーセットに配置する場所です。#ifdefやユニフォームなどを使用して、コンパイルまたは実行時に機能をオンまたはオフにします。私は個人的にubershaderアプローチを軽deしていますが、いくつかのかなり印象的なAAAエンジンがそれらを使用しています(特にCrytekが思い浮かびます)。
シェーダーチャンクはいくつかの方法で処理できます。最も高度な方法-GLSL、HLSL、およびコンソールのサポートを計画している場合に便利-は、シェーダー言語のパーサーを作成することです(おそらく、開発者が最大限に「理解しやすい」ように、HLSL / CgまたはGLSLに近いものにします) )その後、ソースからソースへの変換に使用できます。別のアプローチは、シェーダーチャンクをXMLファイルなどにまとめるだけです。
<shader name="example" type="pixel">
<input name="color" type="float4" source="vertex" />
<output name="color" type="float4" target="output" index="0" />
<glsl><![CDATA[
output.color = vec4(input.color.r, 0, 0, 1);
]]></glsl>
</shader>
このアプローチでは、異なるAPIの複数のコードセクションを作成したり、コードセクションのバージョンを作成したりできることに注意してください(GLSL 1.20バージョンとGLSL 3.20バージョンを使用できます)。グラフは、互換性のあるコードセクションを持たないシェーダーチャンクを自動的に除外することもできるため、古いハードウェアでセミグレースフルデグレードを得ることができます(そのため、通常のマッピングや、プログラマが必要とせずにサポートできない古いハードウェアで除外されるもの)一連の明示的なチェックを行います)。
XMlサンプルは、次のようなものを生成できます(これが無効なGLSLである場合は謝罪します。そのAPIを使用してからしばらく経ちました)。
layout (location=0) in vec4 input_color;
layout (location=0) out vec4 output_color;
struct Input {
vec4 color;
};
struct Output {
vec4 color;
}
void main() {
Input input;
input.color = input_color;
Output output;
// Source: example.shader
#line 5
output.color = vec4(input.color.r, 0, 0, 1);
output_color = output.color;
}
少し賢くなり、より「効率的な」コードを生成できますが、正直なところ、完全に不要なシェーダーコンパイラは、生成されたコードから冗長性を削除します。おそらく新しいGLSLを使用すると、#line
コマンドにファイル名を追加することもできますが、古いバージョンは非常に不十分であり、それをサポートしていないことがわかります。
複数のチャンクがある場合、それらの入力(ツリーの祖先チャンクによって出力として提供されない)は、出力と同様に入力ブロックに連結され、コードは連結されます。ステージが一致するように(頂点とフラグメント)、頂点属性の入力レイアウトが「正しく機能する」ようにするために、少し余分な作業が行われます。このアプローチのもう1つの優れた利点は、GLSLの古いバージョンではサポートされていない明示的なユニフォームおよび入力属性バインディングインデックスを記述し、シェーダー生成/バインディングライブラリでこれらを処理できることです。同様に、VBOとglVertexAttribPointer
呼び出しの設定にメタデータを使用して、互換性を確保し、すべてが「正常に機能する」ようにすることができます。
残念ながら、すでにこのような優れたクロスAPIライブラリはありません。Cgは少し近づいていますが、AMDカードでOpenGLをサポートしているため、最も基本的なコード生成機能以外を使用すると非常に遅くなる可能性があります。DirectXエフェクトフレームワークも機能しますが、もちろんHLSL以外の言語のサポートはありません。GLSLには、DirectXライブラリを模倣する不完全/バグのあるライブラリがいくつかありますが、最後にチェックしたときにそれらの状態が与えられているので、自分で作成します。
ubershaderのアプローチとは、特定の機能に対して「よく知られた」プリプロセッサディレクティブを定義し、さまざまな構成のさまざまなマテリアルに再コンパイルすることを意味します。たとえば、法線マップを持つマテリアルの場合、定義することができUSE_NORMAL_MAPPING=1
、ピクセルステージのubershaderには次のものがあります。
#if USE_NORMAL_MAPPING
vec4 normal;
// all your normal mapping code
#else
vec4 normal = normalize(in_normal);
#endif
ここでの大きな問題は、使用中のすべての組み合わせをプリコンパイルする必要があるプリコンパイルHLSLでこれを処理することです。GLSLを使用しても、使用中のすべてのプリプロセッサディレクティブのキーを適切に生成して、同一のシェーダーの再コンパイル/キャッシュを回避する必要があります。ユニフォームを使用すると複雑さを軽減できますが、プリプロセッサのユニフォームとは異なり、命令数は削減されず、パフォーマンスに若干の影響が残る可能性があります。
明確にするために、両方のアプローチ(および大量のシェーダーバリエーションを手動で作成するだけでなく)はすべてAAAスペースで使用されます。最適な方を使用してください。