このドキュメントはプラグイン システムを用いてシェーダーを新規作成する方法の説明です。HLSL コード、または GPU への負荷の少ないシェーダーを書く方法の説明 ではありません。
Unreal Engine 4 (UE4) で使用するシェーダーはプラグイン システムを使って追加することができます。プラグイン システムを使ってシェーダーを作成すると、シェーダーを迅速かつ簡単に共有することができます。このドキュメントでは、EU4 のプラグインでのシェーダー作成に必要な作業の概要を説明します。
//Engine/Plugins/Compositing/LensDistortion プラグインを直接見ると理解しやすくなります。
プラグイン作成のコツ
プラグインを新規作成する場合、以下の点に注意してください。
-
プラグイン ウィザードを使用してください。プラグインに必要なすべてのファイルとフォルダを簡単に作成することができます。
-
現段階では、新たなシェーダーモデルの追加といったマテリアル エディタへの大幅な変更の追加はプラグイン経由では行うことができません。
-
必ずすべてのファイルとフォルダを必要な場所に追加して、Visual Studio ソリューション ファイルを再生成してください。
-
ProjectName.uplugin で、モジュールの LoadingPhase to PostConfigInit (シェーダーが実装されるモジュールのみ) が以下のようになるようにしてください。
{
"FileVersion" :3,
"Version" :1,
"VersionName" :"1.0",
"FriendlyName" :"Foo",
"Description" :"Plugin to play around with shaders.",
"Category" :"Sandbox",
"CreatedBy" :"Epic Games, Inc.",
"CreatedByURL" : "http://epicgames.com",
"DocsURL" : "",
"MarketplaceURL" : "",
"SupportURL" : "",
"EnabledByDefault" : false,
"CanContainContent" : true,
"IsBetaVersion" : false,
"Installed" : false,
"Modules" :
[
{
"Name" :"Foo",
"Type" :"Developer",
"LoadingPhase" :"PostConfigInit"
}
]
}
レンダリング スレッド
ゲーム側の API とは対照的に、RHI レンダー コマンドは専用スレッドのレンダリング スレッドによってキューに加えられます。レンダリング スレッドはゲーム スレッドの従スレッドです。RHI は ENQUEUE_RENDER_COMMAND 経由で API に FIFO (first in, first out) コマンドをキューに追加します。レンダリング スレッドは 0 またはゲーム スレッドより 1 フレーム遅くなります。CPU パフォーマンスのためにも、プロダクション ランタイム中は API と RHI 間の同期は絶対に避けなければなりません。プラグインの C++ 関数を正しいスレッドで呼び出すために、check (IsInGameThread()); や check (IsInRenderingThread()); などの複数のアサートを追加して、スレッドのロバスト性を改善することができます。
Unreal シェーダ ファイル
シェーダー ファイルは 2 つの異なる種類があります。UE4 で使用するシェーダーの開発中はこのことに注意しなければなりません。2 つのシェーダーファイルにはそれぞれ次のような目的があります。
- アンリアル シェーダー ヘッダ (.USH)
- 他の USH または USF ファイルによってのみインクルードされます。
-
アンリアル シェーダー フォーマット (.USF)
-
プライベート データのみ格納されます。
- Private ディレクトリでの下位互換性が保証されます。
-
シェーダー エントリ ポイントが含まれます。
-
シェーダー ファイル プリプロセシングと仮想ファイル パス
HLSL 言語がベースとなっている USF シェーダー ファイルは、マルチプラットフォーム シェーダー コードを含むアンリアル エンジン シェーダー ファイル フォーマットです。このマルチプラットフォーム サポートを実現するために、エンジンのシェーダー コンパイラのプラットフォーム固有のシェーダー コンパイラ (FXC、GLSL クロス コンパイル用 HLSLCC など) の前に、プラットフォームに依存しないソースファイルの前処理用のパスを追加しました。結果として、すべての #define と#if は、一番最初のこの前処理で解決されます。もちろん、各プラットフォームには、シェーダーの前処理のターゲット プラットフォームがどれか分かるように #define が組み込まれています (VULKAN_PROFILE など)。
C/C++ ファイルと同じように、HelloWorld.usf を #include でインクルードすることができます。これにより、#include を書き込んだ USF ファイルと同じディレクトリに格納されている「HelloWorld.usf」というファイルもインクルードされます。同じファイルの重複インクルードを避けるために、前処理済みのディレクティブ#pragma をファイルの最上部に追加することができます。例えば、以下があります。
- FooCommon.usf
// File shared across all Plugin's shaders
#pragma once
#include "/Engine/Public/Platform.ush"
// ...
- FooBar.usf
// File containing all foobar-related functions and structures.
#pragma once
#include "FooCommon.usf"
// ...
- FooBarComputeShader.usf
// Compute shader that does foobar on the GPU
#include "FooCommon.usf"
#include "FooBar.usf"
// ...
以下のいずれかの方法で、プラグインまたはプロジェクト モジュールのシェーダーから USF ファイルをインクルードすることができます。
-
エンジン内で #include
/Engine/<FilePath>
を使用する (<FilePath>
は//Engine/Shaders/
ディレクトリに相当するファイル パス)。 -
または #include
/Plugin/<PluginName>/<PluginFilePath>
で別のプラグインを使用する (<PluginName>
は アクティベートされた プラグインの名前、<PluginFilePath>
はプラグインのShaders/
ディレクトリに相当するファイル パス)。依存関係を .uplugin の正しいプラグインへ追加するのはデベロッパーの担当です。
First Global Shader
以下の方法で FGlobalShader から継承したグローバル シェーダー
class FLensDistortionUVGenerationShader : public FGlobalShader
{
public:
// This function determines whether or not the shader should be compiled for a given platform.
// In this case, the shader will not be usable without SM4 support.
static bool ShouldCache(EShaderPlatform Platform)
{
return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM4);
}
// Compile-time constants for shader can be defined in this function:
static void ModifyCompilationEnvironment(EShaderPlatform Platform, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Platform, OutEnvironment);
OutEnvironment.SetDefine(TEXT("GRID_SUBDIVISION_X"), kGridSubdivisionX);
OutEnvironment.SetDefine(TEXT("GRID_SUBDIVISION_Y"), kGridSubdivisionY);
}
// Default constructor.
FLensDistortionUVGenerationShader() {}
// Constructor using an initializer object.We can bind parameters here so that our C++ code
// can interface with USF, enabling us to set shader parameters from code.
FLensDistortionUVGenerationShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
:FGlobalShader(Initializer)
{
PixelUVSize.Bind(Initializer.ParameterMap, TEXT("PixelUVSize"));
RadialDistortionCoefs.Bind(Initializer.ParameterMap, TEXT("RadialDistortionCoefs"));
TangentialDistortionCoefs.Bind(Initializer.ParameterMap, TEXT("TangentialDistortionCoefs"));
DistortedCameraMatrix.Bind(Initializer.ParameterMap, TEXT("DistortedCameraMatrix"));
UndistortedCameraMatrix.Bind(Initializer.ParameterMap, TEXT("UndistortedCameraMatrix"));
OutputMultiplyAndAdd.Bind(Initializer.ParameterMap, TEXT("OutputMultiplyAndAdd"));
}
// All members must be serialized here.This function is run at load and save time, and is used
// to put the shader into the DDC and pak files.
virtual bool Serialize(FArchive& Ar) override
{
bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
Ar << PixelUVSize << RadialDistortionCoefs << TangentialDistortionCoefs << DistortedCameraMatrix << UndistortedCameraMatrix << OutputMultiplyAndAdd;
return bShaderHasOutdatedParameters;
}
// This function is an example of how shader parameters can be precomputed based on data specific
// to the shader.In this case, the shader requires several matrices that can be computed from
// a few parameters, and that would be inefficient to compute inside the shader itself.Note that
// this function is not an override; it is custom to this class, and is called when this feature's
// specific implementation requires it.
template<typename TShaderRHIParamRef>
void SetParameters(
FRHICommandListImmediate& RHICmdList,
const TShaderRHIParamRef ShaderRHI,
const FCompiledCameraModel& CompiledCameraModel,
const FIntPoint& DisplacementMapResolution)
{
FVector2D PixelUVSizeValue(
1.f / float(DisplacementMapResolution.X), 1.f / float(DisplacementMapResolution.Y));
FVector RadialDistortionCoefsValue(
CompiledCameraModel.OriginalCameraModel.K1,
CompiledCameraModel.OriginalCameraModel.K2,
CompiledCameraModel.OriginalCameraModel.K3);
FVector2D TangentialDistortionCoefsValue(
CompiledCameraModel.OriginalCameraModel.P1,
CompiledCameraModel.OriginalCameraModel.P2);
SetShaderValue(RHICmdList, ShaderRHI, PixelUVSize, PixelUVSizeValue);
SetShaderValue(RHICmdList, ShaderRHI, DistortedCameraMatrix, CompiledCameraModel.DistortedCameraMatrix);
SetShaderValue(RHICmdList, ShaderRHI, UndistortedCameraMatrix, CompiledCameraModel.UndistortedCameraMatrix);
SetShaderValue(RHICmdList, ShaderRHI, RadialDistortionCoefs, RadialDistortionCoefsValue);
SetShaderValue(RHICmdList, ShaderRHI, TangentialDistortionCoefs, TangentialDistortionCoefsValue);
SetShaderValue(RHICmdList, ShaderRHI, OutputMultiplyAndAdd, CompiledCameraModel.OutputMultiplyAndAdd);
}
private:
// Shader parameters.
FShaderParameter PixelUVSize;
FShaderParameter RadialDistortionCoefs;
FShaderParameter TangentialDistortionCoefs;
FShaderParameter DistortedCameraMatrix;
FShaderParameter UndistortedCameraMatrix;
FShaderParameter OutputMultiplyAndAdd;
};
// This macro exposes the shader to the Engine.Note the absolute virtual source file path.
IMPLEMENT_SHADER_TYPE(, FLensDistortionUVGenerationVS, TEXT("/Plugin/LensDistortion/Private/UVGeneration.usf"), TEXT("MainVS"), SF_Vertex)
Engine/Public/Platform.usf
シェーダーをすべての UE4 プラットフォームにコンパイルするには、すべてのシェーダー ファイルに (直接または間接的に) /Engine/Public/Platform.usf を含む必要があります。
シェーダー開発のコツ
ConsoleVariables.ini をローカルでカスタマイズしてレンダラーの設定を一部変更して、シェーダーの書き出し時のイタレーション処理を速めることができます。例えば、以下の ConsoleVariables は、シェーダーの動作に関する詳細なデバッグ情報の取得に役立ちます。
-
r.ShaderDevelopmentMode = 1 シェーダー コンパイルの詳細ログおよびエラー時のリトライの機会を取得します。
-
r.DumpShaderDebugInfo = 1 「Saved」フォルダのプリプロセスされたシェーダーをダンプします。
警告:しばらくの間この状態のままにすると、小さなファイルとフォルダでハードドライブが一杯になってしまうので、完了したら必ず無効にしてください。
トラブルシューティング
シェーダーのコンパイルまたは UE4 エディタでの表示に問題がある場合は、以下の操作を試してください。
-
Can't compile:
/Plugin/<MyPluginName>/<MyFile>
not found というエラーが出る場合:プラグインのモジュールの LoadingPhase が PostConfigInit に設定されていること、およびプラグインのシェーダー / ディレクトリ名にタイポがないことを確認してください。
-
Can't #include
"/Plugin/<ParentPluginName>/<MyFile>"
というエラーが出る場合:親プラグインがアクティベートしていることを確認してください。また、このエラーは .uplugin または .uproject のプラグイン依存の欠如を意味するので、プラグインの依存も確認してください。
既存レンダラの規則
レンダラでは、シェーダー クラスとシェーダー エントリー ポイントに命名規則があります。シェーダー ドメインの接尾辞は以下のようになります。
シェーダー ドメイン | 接尾辞 |
---|---|
Vertex Shader | VS |
Hull Shader | HS |
Domain Shader | DS |
Geometry Shader | GS |
Pixel Shader | PS |
Compute Shader | CS |
例えば、C++ ファイルで FLensDistortionUVGenerationVS へのコールには最後に頂点シェーダーの VS シグナルが付きます。USF ファイル内では void MainVS(...) は VS シグナルで終わります。HLSL で構造体を処理する場合、構造体名は、例えば FBasePassInterpolators のように F で始めます。
UE4 のコーディング基準についての詳細は、「UE4 コーディング基準のドキュメント」を参照してください。
その他の参照リンク
以下のリンクでは、UE4 を使ったグローバル シェーダー開発に関する詳細が提供されています。