クイック スタート
シェーダーで作業をする場合、r.ShaderDevelopmentMode を必ず 1 に設定して有効にしてください。 最も簡単な方法は、ConsoleVariables.ini を編集して、ロード時に毎回有効になるようにします。 これで、エラー時のリトライ、シェーダー開発関連のログ、および警告が有効になります。
recompileshaders changed コマンドを実行する Ctrl+Shift+. を使用します。このコマンドの実行は、Unreal Shader (.usf) ファイルへの変更を保存した後に行います。
多くのシェーダーに含まれるファイル (例:common.usf) を変更した場合は、再コンパイルの時間がしばらくかかることがあります。マテリアルでイテレートする場合、マテリアルに若干の変更を加えて (例:ノードの移動) 、マテリアル エディタで 'Apply' を使用することでマテリアルの再コンパイルをトリガーできます。
シェーダーとマテリアル
グローバル シェーダー
グローバル シェーダーは、(フルスクリーン クアッドのように) 固定ジオメトリで機能するシェーダーであり、マテリアルとのインターフェースを必要としません。例としては、シャドウのフィルタリングやポスト プロセスがあります。任意のグローバル シェーダーのタイプに対して 1 つのシェーダーのみがメモリに存在します。
マテリアルとメッシュ タイプ
マテリアルは、マテリアルがどのようにレンダリングされるのかを制御する状態のセット (ブレンド モード、二面など) とマテリアルがさまざまなレンダリング パス (ベースカラー、ラフネス、法線など) とどのように相互作用するのかを制御するマテリアル入力のセットによって定義されます。
頂点ファクトリ
マテリアルはさまざまなメッシュ タイプへの適用をサポートしなければなりません。これは頂点ファクトリによって実現します。FVertexFactoryType は、固有のメッシュ タイプを表し、FVertexFactory インスタンスはインスタンスごとのデータを格納して固有のメッシュ タイプをサポートします。たとえば、FGPUSkinVertexFactory はスキニングに必要なボーン マトリックスだけでなく、GPU スキン頂点ファクトリ シェーダー コードが入力として必要とするさまざまな頂点バッファへの参照も格納します。頂点ファクトリ シェーダー コードは、暗黙的なインターフェースであり、さまざまなパス シェーダーによってメッシュ タイプの違いを抽象化するために使用されます。頂点ファクトリは、主に頂点シェーダー コードから構成されますが、一部のピクセル シェーダー コードも含まれます。 頂点ファクトリ シェーダー コードの重要なコンポーネントは以下のとおりです。
| 関数 | 説明 |
|---|---|
| FVertexFactoryInput | 頂点シェーダーへの入力として頂点ファクトリが何を必要とするのかを定義します。これらは、C++ 側の FVertexFactory の頂点の宣言と一致しなければなりません。 |
| FVertexFactoryIntermediates | 複数の頂点ファクトリ関数で使用されるキャッシュされた中間データを格納するために使用します。 一般的な例は、TangentToLocal マトリックスであり、パックされない頂点入力から計算されなくてはなりません。 |
| FVertexFactoryInterpolantsVSToPS | 頂点シェーダーからピクセル シェーダーに渡される頂点ファクトリ データ。 |
| VertexFactoryGetWorldPosition | ワールド空間頂点位置を得るために頂点シェーダーから呼び出されます。スタティックメッシュでは、これは単に LocalToWorld マトリックスを使用して頂点バッファからのローカル空間の位置をワールド空間に変換します。GPU スキン メッシュでは、この位置は最初にスキニングされ、次にワールド空間に変換されます。 |
| VertexFactoryGetInterpolantsVSToPS | FVertexFactoryInput を FVertexFactoryInterpolants に変換します。これは、ピクセル シェーダーに渡される前にグラフィックス ハードウェアによって補間されます。 |
| GetMaterialPixelParameters | これはピクセル シェーダーで呼び出され、頂点ファクトリ固有の補間式 (FVertexFactoryInterpolants) を、そのパスのピクセル シェーダーが使用する FMaterialPixelParameters 構造体に変換します。 |
マテリアル シェーダー
FMaterialShaderType を使用するシェーダーは、パス固有のシェーダーであり、いくつかのマテリアルの属性にアクセスする必要があります。したがって、各マテリアルに対してコンパイルする必要がありますが、メッシュの属性にアクセスする必要はありません。ライト関数のパス シェーダーは、FMaterialShaderType の一例です。
FMeshMaterialShaderType を使用するシェーダーは、パス固有のシェーダーであり、マテリアルの属性およびメッシュのタイプに依存します。したがって、各マテリアル/頂点ファクトリの組み合わせに対してコンパイルされなければなりません。たとえば、TBasePassVS/TBasePassPS はすべてのマテリアル入力を順送りのレンダリング パスで評価する必要があります。
マテリアルの必須シェーダー一式は FMaterialShaderMap にあります。 以下のようなものです。
FMaterialShaderMap
FLightFunctionPixelShader - FMaterialShaderType
FLocalVertexFactory - FVertexFactoryType
TDepthOnlyPS - FMeshMaterialShaderType
TDepthOnlyVS - FMeshMaterialShaderType
TBasePassPS - FMeshMaterialShaderType
TBasePassVS - FMeshMaterialShaderType
Etc
FGPUSkinVertexFactory - FVertexFactoryType
Etc
頂点ファクトリは、マテリアルの用途に依存する ShouldCache 関数に基づきこのマトリックスに含まれます。たとえば、bUsedWithSkeletalMesh が true だと GPU スキン頂点ファクトリが含まれます。FMeshMaterialShaderType は、マテリアルと頂点ファクトリの属性に依存する ShouldCache 関数に基づきこのマトリックスに含まれます。これはシェーダーをキャッシングするための疎行列 (sparse matrix) アプローチであり、非常に速くシェーダーが多数になり、メモリを消費し、コンパイル時間が長くなります。実際に必要なシェーダーのリストを保存するのに比べて主な利点は、リストを生成する必要がなく、コンソールでのランタイム前に必要なシェーダーは常にコンパイル済みであるという点です。Unreal Engine では、シェーダーのメモリ問題をシェーダーの圧縮によって、またコンパイル時間の問題をマルチコアのシェーダーのコンパイルで軽減しています。
マテリアル シェーダーを作成する
マテリアル シェーダーのタイプは DECLARE_SHADER_TYPE マクロで作成されます。
class FLightFunctionPixelShader : public FShader { DECLARE_SHADER_TYPE(FLightFunctionPixelShader,Material);
これは、マテリアル シェーダーのタイプに対して必要なメタデータと関数を宣言します。マテリアル シェーダーのタイプは IMPLEMENT_MATERIAL_SHADER_TYPE によってインスタンス化されます。
IMPLEMENT_MATERIAL_SHADER_TYPE(,FLightFunctionPixelShader,TEXT("LightFunctionPixelShader")
これは、マテリアル シェーダーのタイプのグローバル メタデータを生成します。これにより、ランタイム時に任意のシェーダーのタイプを使用してすべてのシェーダーをイテレートするといったことが可能になります。
通常のマテリアルのピクセル シェーダーのタイプは、GetMaterialPixelParameters 頂点ファクトリ関数を呼び出すことで、最初に FMaterialPixelParameters 構造体を作成します。GetMaterialPixelParameters は、頂点ファクトリ固有の入力をどのパスでもアクセスするであろう WorldPosition、TangentNormal などのプロパティに変換します。次にマテリアル シェーダーは CalcMaterialParameters を呼び出します。これは、FMaterialPixelParameters のメンバーの残りを書き出し、その後、FMaterialPixelParameters が完全に初期化されます。次にマテリアル シェーダーは MaterialTemplate.usf (たとえば、マテリアルのエミッシブ入力のための GetMaterialEmissive) の関数を通してマテリアルの入力のいくつかにアクセスし、シェーディングを行い、そのパスの最終カラーを出力します。
特殊なエンジンのマテリアル
UMaterial には bUsedAsSpecialEngineMaterial と呼ばれる設定があり、どの頂点ファクトリ タイプでもマテリアルを使用できるようにします。そのため、すべての頂点ファクトリはマテリアルとコンパイルされ、非常に大きなセットになります。bUsedAsSpecialEngineMaterial は以下に対して有用です。
- ライティングのみなどのレンダリング ビューモードと合わせて使用するマテリアル。
- コンパイル エラーがある場合にフォールバックとして使用するマテリアル (DefaultDecalMaterial、DefaultMaterial など)。
- キャッシュしなければならないシェーダー数を削減するために、他のマテリアルをレンダリングする場合に使用されるシェーダーのマテリアル。たとえば、不透明なマテリアルの深度専用のシェーダーは、DefaultMaterial と同じ深さ出力を生成します。そのため、代わりに DefaultMaterial のシェーダーが使用され、不透明なマテリアルはデプス専用のシェーダーのキャッシュをスキップします。
シェーダーのコンパイル
Unreal Engine では、ストリーミング システムを使用してシェーダーを非同期でコンパイルします。 コンパイル要求は、キャッシュされたシェーダー マップを持たないマテリアルがロードしたときにエンキューされます。利用可能な状態になるとコンパイル結果がエンジンをブロックすることなく適用されます。 これはロード時間とコンパイルのスループットの観点では最適です。しかし、実際のプラットフォームのシェーダーのコンパイルとそれを要求したマテリアルとの間には多くのレイヤーがあることを意味します。
プラットフォーム シェーダー コンパイル関数 (D3DCompile) は多くの場合、単一プロセス内でのマルチコアのスケーリングを妨ぐクリティカルなセクションを内部に含むため、実際のコンパイル作業は、Shader Compile Workers と呼ばれるヘルパー プロセスで行われます。
シェーダー コンパイラのデバッグ
コンパイルをどのように行うかを制御する設定がいくつかあります。これにより、シェーダー コンパイラのデバッグを単純化できます。 これは、BaseEngine.ini の [DevOptions.Shaders] セクションにあります。
| 設定 | 説明 |
|---|---|
| bAllowCompilingThroughWorkers | SCW を起動してコンパイラの DLL を呼び出すか、Unreal Engine がコンパイラの DLL を直接呼び出すかを設定します。 無効な場合、コンパイルはシングル コアになります。 |
| bAllowAsynchronousShaderCompiling | Unreal Engine 内の別のスレッドでコンパイルを行うかどうかを設定します。 |
Unreal Engine から直接シェーダー コンパイラの DLL にステップインする場合 (たとえば CompileD3D11Shader) では、これらを両方とも false に設定します。 しかしコンパイルには長時間かかります。したがって、他のすべてのシェーダーがキャッシュ済みであるようにしてください。
コンパイラ エラーでリトライする
r.ShaderDevelopmentMode が有効な状態では、シェーダー コンパイル エラーでリトライする機会があります。 これは、コンパイルが失敗すると致命的なエラーになるグローバル シェーダーの場合に特に重要です。
デバッグでは、デバッガーがアタッチされた状態でブレークポイントになり、Visual Studio の出力ウィンドウでコンパイル エラーになります。 次にエラー ログを ダブルクリック して問題のある行に直接移動します。
上記が該当しない場合は、Yes/No ダイアログになります。
シェーダーのキャッシシングとクッキング
シェーダーがコンパイルされると、派生データ キャッシュ (DDC) に入ります。 DDC には、そのキーにシェーダー ソース ファイルを含むコンパイルへのすべての入力のハッシュが含まれます。 つまり、シェーダー ソース ファイルへの変更はエンジンを再起動するか、「recompileshaders changed」 を行うたびに自動的にピックアップされます。
FShader Serialize 関数を修正する場合、後方互換性を処理する必要はなく、そのシェーダーに含まれるシェーダー ファイルにスペースを追加するだけです。
アセットをクックする場合、マテリアル シェーダーはマテリアルのパッケージにインライン化され、グローバル シェーダーはグローバル シェーダー ファイルに別個に格納されます。これにより、エンジンの起動時に速くロードできるようになります。
デバッグ
シェーダーのデバッグの主な方法は、シェーダーに中間ファイルを出力させるようにシェーダーを修正し、それを適切な VisualizeTexture コマンドでビジュアル化します。 これにより高速イテレーションが実現します。エンジンを再起動することなくオンザフライでコンパイルできるからです。 たとえば、以下のようにして WorldPosition が正しいことを確認できます。
OutColor = frac(WorldPosition / 1000);
次にスケールが正しいことと、結果が視点依存になっていないことを確認します。 しかし、この方法ではデータ構造をビルドするような複雑なシェーダーはうまくスケーリングしません。
デバッグ情報をダンプする
r.DumpShaderDebugInfo=1 を使用してコンパイルするすべてのシェーダーに対してすべてのファイルをディスクに保存することができます。 r.ShaderDevelopmentMode のような ConsoleVariables.ini にこれを設定する場合に役立ちます。 以下を含むファイルは、GameName/Saved/ShaderDebugInfo に格納されます。
- ソース ファイルとインクルード ファイル
- シェーダーのプリプロセスされたバージョン
- 使用したコンパイラと同等のコマンドライン オプションを持つプリプロセス バージョンをコンパイルするためのバッチファイル
この設定をオンにしたままにすると、HD が多くの小さなファイルとフォルダでいっぱいになることがあります。
イテレーションのベスト プラクティス
グローバル シェーダーで作業をしている場合、recompileshaders changed または Ctrl+Shift+. はイテレートする最速の方法です。 シェーダーのコンパイルに長時間かかる場合は、CFLAG_StandardOptimization を、シェーダーの ModifyCompilationEnvironment のコンパイル フラグとして指定することを検討します。
マテリアル シェーダーで作業している場合、BasePassPixelShader.usf のように単一のマテリアルでのイテレーションがはるかに速くなります。 マテリアル エディタで [Apply (適用)] ボタンをクリックするたびに、ディスクからシェーダー ファイルを再読み込みし、そのマテリアルだけを再コンパイルします。
クロス コンパイラ
HLSL クロス コンパイラ を使用して、OpenGL プラットフォーム向けに HLSL を GLSL に自動的に変換します。これにより、シェーダーをすべてのプラットフォームに対して一度だけオーサリングできます。 オフラインのシェーダーのコンパイル時に実行し、OpenGL ドライバでは行われないことが多いコードに対するさまざまな最適化を行います。
AsyncCompute
AsyncCompute は、特定の GPU で機能する API で利用可能なハードウェア機能です。GPU のハードウェア ユニットをより効率的に活用できるようにインターリーブします。