ゲームエンジンの設計-Ubershader-シェーダー管理設計[終了]


18

遅延シェーディングを備えた柔軟なUbershaderシステムを実装したい。私の現在のアイデアは、FlatTexture、BumpTexture、Displacement Mappingなどの特定の機能を処理するモジュールからシェーダーを作成することです。色をデコードしたり、トーンマッピングなどを行う小さなモジュールもあります。これには、 GPUがサポートしていない場合、特定のタイプのモジュールを交換します。そのため、現在のGPU機能に適応できます。このデザインが良いかどうかはわかりません。私は今、悪いデザインを選択し、後でそれを支払うことができるのではないかと恐れています。

私の質問は、シェーダー管理システムを効果的に実装する方法に関するリソース、例、記事はどこにありますか?ビッグゲームエンジンがこれを行う方法を知っている人はいますか?


3
本当の答えを得るには十分ではありません。MegaCity-Oneシェーダーを前もって構築するのではなく、小さく始めてニーズに応じて有機的に成長させれば、このアプローチでうまくいくでしょう。第一に、前もってあまりにも多くの設計を行い、うまくいかない場合に後で支払うという最大の心配を軽減し、第二に、決して慣れない余分な仕事をすることを避けます。
パトリックヒューズ

残念ながら、「リソースリクエスト」の質問は受け付けていません。
ネムロック

回答:


23

半一般的なアプローチは、私がシェーダーコンポーネントと呼んでいるものを、あなたがモジュールを呼んでいると思うものに似たものにすることです。

この考え方は、後処理グラフに似ています。必要な入力、生成された出力、および実際にそれらを操作するコードの両方を含むシェーダーコードのチャンクを記述します。どんな状況でもどのシェーダーを適用するかを示すリストがあります(このマテリアルがバンプマッピングコンポーネントを必要とするかどうか、遅延またはフォワードコンポーネントが有効かどうかなど)。

これで、このグラフを取得し、そこからシェーダーコードを生成できます。これは主に、チャンクのコードを所定の位置に「貼り付け」、グラフが既に必要な順序になっていることを確認してから、必要に応じてシェーダーの入力/出力に貼り付けることを意味します(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スペースで使用されます。最適な方を使用してください。

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