2017年9月12日

UE4 にグローバル シェーダーを追加してみよう

作成 Rolando Caloca Olivares

アンリアル エンジン 4 のグローバル シェーダーは、ポストプロセス エフェクトのレンダリング、演算シェーダーのディスパッチ、画面のクリアな どを実行するために C++ 側から使用することができるシェーダー (メタルやメッシュ上では動かないシェーダー) です。外観を思い通りに変更しようと すると、さらに高度な機能と、それに伴うカスタム シェーダー パスが必要になってしまうことがありますよね。実はこのプロセスをとても 簡単に行う方法があります。それを説明したいと思います。

アンリアル シェーダー ファイルの概要と使い方

UE4 はエンジン Engine/Shaders フォルダから.usf (Unreal Shader) ファイルを読み取ります。新しいシェーダーのソースファイルは、必ずここに 置かれることになります。バージョン 4.17 では、プラグイン (Plugin/Shaders) からシェーダーを読み取ることも可能です。お勧めとしては、 ConsoleVariables.ini ファイルの r.ShaderDevelopmentMode=1 を有効にしておくと開発が楽になります。詳細は こちらのドキュメント をご 覧ください。

では最初に、Engine/Shaders フォルダに新しい .usf ファイルを追加してみましょう。ファイル名は「MyTest.usf」とします。次 に、シンプルなパススルー頂点シェーダーとカスタム カラーを返すピクセル シェーダーを追加します。

// MyTest.usf 

// Simple pass-through vertex shader

void MainVS(
	in float4 InPosition :ATTRIBUTE0,
	out float4 Output :SV_POSITION
)
{
	Output = InPosition;
}

// Simple solid color pixel shader
float4 MyColor;
float4 MainPS() :SV_Target0
{
	return MyColor;
}

次に、UE4 がシェーダーを選択してコンパイルを開始するために C++ クラスを宣言します。頂点シェーダーから始めてみましょう。

// This can go on a header or cpp file
class FMyTestVS : public FGlobalShader
{
	DECLARE_EXPORTED_SHADER_TYPE(FMyTestVS, Global, /*MYMODULE_API*/);
 
	FMyTestVS() { }
	FMyTestVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
		:FGlobalShader(Initializer)
	{
	}
 
	static bool ShouldCache(EShaderPlatform Platform)
	{
		return true;
	}
};

ここでいくつか必要なことがあります。

  1. これはFGlobalShader のサブクラスなので、Global Shader Map   に入ります (つまり探すためのマテリアルは必要ありま せん)。
  2. DECLARE_EXPORTED_SHADER_TYPE() マクロを使うと、シェーダーのタイプのシリアル化に必要なエクスポートが生成されます。3 番目のパラメータは、シェーダーのモジュールが存在することになるコード モジュールの外部リンケージのタイプです。
  3. コンストラクタは、デフォルトとシリアライズ両方の 2 つです。
  4. 特定の状況でコンパイルする場合はShouldCache() 関数が必要です (例えば、非演算シェーダー対応の RHI 上で演算シェーダー のコンパイルは行いたくありません)。

クラスを宣言したので、UE4 のリストにシェーダーのタイプを登録することができます。

// This needs to go on a cpp file
IMPLEMENT_SHADER_TYPE(, FMyTestVS, TEXT("MyTest"), TEXT("MainVS"), SF_Vertex);

このマクロにより、.usf ファイル (MyTest.usf)、シェーダー エントリ ポイント (MainVS)、周波数 / シェーダー ステージ (SF_Vertex) にタイプ (FMyTestVS) がマップされます。ShouldCache() メソッドが true を返す限り、シェーダーはコン パイル リストにも追加されます。

注意:FGlobalShader を追加するモジュールはすべて、エンジンを実際に起動する前にロードしておかなければなりませ ん。ロードしていないと “Shader type was loaded after engine init, use ELoadingPhase::PostConfigInit on your module to cause it to load earlier.” というアサートが出されます。ゲームまたはエディタを起動した後にロードされた動的モジュールを使って、独自のシェーダーのタイプを追 加することは現時点ではできません。

ではここで、ちょっと面白いピクセル シェーダーを宣言してみましょう。

class FMyTestPS : public FGlobalShader
{
	DECLARE_EXPORTED_SHADER_TYPE(FMyTestPS, Global, /*MYMODULE_API*/);
 
	FShaderParameter MyColorParameter;
 
	FMyTestPS() { }
	FMyTestPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
		:FGlobalShader(Initializer)
	{
		MyColorParameter.Bind(Initializer.ParameterMap, TEXT("MyColor"), SPF_Mandatory);
	}
 
	static void ModifyCompilationEnvironment(EShaderPlatform Platform, FShaderCompilerEnvironment& OutEnvironment)
	{
		FGlobalShader::ModifyCompilationEnvironment(Platform, OutEnvironment);
		// Add your own defines for the shader code
		OutEnvironment.SetDefine(TEXT("MY_DEFINE"), 1);
	}
 
	static bool ShouldCache(EShaderPlatform Platform)
	{
		// Could skip compiling for Platform == SP_METAL for example
		return true;
	}
 
	// FShader interface.
	virtual bool Serialize(FArchive& Ar) override
	{
		bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
		Ar << MyColorParameter;
		return bShaderHasOutdatedParameters;
	}
 
	void SetColor(FRHICommandList& RHICmdList, const FLinearColor& Color)
	{
		SetShaderValue(RHICmdList, GetPixelShader(), MyColorParameter, Color);
	}
};

// Same source file as before, different entry point
IMPLEMENT_SHADER_TYPE(, FMyTestPS, TEXT("MyTest"), TEXT("MainPS"), SF_Pixel);

このクラスでは、.usf ファイルからシェーダーのパラメータ MyColor を公開しています。

  • FShaderParameter MyColorParameter メンバがクラスに追加されると、ランタイムがバインドを見つけるための 情報が保持され、ランタイム時にパラメータの値を設定することができます。
  • コンストラクタのシリアライズ化において、パラメータを名前で ParameterMap に Bind() しますが、名前は .usf ファイルのファイル名と一致しなければなりません。
  • ビヘイビアの定義およびシェーダーによる #define 値の設定を同じ C++ クラスで行うことが可能な場合は、新しい ModifyCompilationEnvironment() 関数を使用します。
  • Serialize() メソッドが必要です。(コンストラクタのシリアライズ中に一致した) シェーダーのバインドから のコンパイル / クック時間の情報は、ランタイム時にここにロード / 格納されます。
  • 最後に、ランタイム時に特別な値を使って MyColor パラメータの設定方法を表示するSetColor() メ ソッドを使っています。

では、これらのシェーダーのタイプを使って全画面にクワッドを描画するシンプルな関数を描いてみましょう。

void RenderMyTest(FRHICommandList& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, const FLinearColor& Color)
{
	// Get the collection of Global Shaders
	auto ShaderMap = GetGlobalShaderMap(FeatureLevel);

	// Get the actual shader instances off the ShaderMap
	TShaderMapRef MyVS(ShaderMap);
	TShaderMapRef MyPS(ShaderMap);

	// Declare a bound shader state using those shaders and apply it to the command list
	static FGlobalBoundShaderState MyTestBoundShaderState;
	SetGlobalBoundShaderState(RHICmdList, FeatureLevel, MyTestBoundShaderState, GetVertexDeclarationFVector4(), *MyVS, *MyPS);

	// Call our function to set up parameters
	MyPS->SetColor(RHICmdList, Color);
 
	// Setup the GPU in prep for drawing a solid quad
	RHICmdList.SetRasterizerState(TStaticRasterizerState<fm_solid, cm_none="">::GetRHI());
	RHICmdList.SetBlendState(TStaticBlendState<>::GetRHI());
	RHICmdList.SetDepthStencilState(TStaticDepthStencilState<false, cf_always="">::GetRHI(), 0);
 
	// Setup the vertices
	FVector4 Vertices[4];
	Vertices[0].Set(-1.0f, 1.0f, 0, 1.0f);
	Vertices[1].Set(1.0f, 1.0f, 0, 1.0f);
	Vertices[2].Set(-1.0f, -1.0f, 0, 1.0f);
	Vertices[3].Set(1.0f, -1.0f, 0, 1.0f);

	// Draw the quad
	DrawPrimitiveUP(RHICmdList, PT_TriangleStrip, 2, Vertices, sizeof(Vertices[0]));
}
</false,></fm_solid,>

コードベースでテストする場合は、コンソール変数を宣言すると、ランタイム時に切り替えができます。

static TAutoConsoleVariable CVarMyTest(
	TEXT("r.MyTest"),
	0,
	TEXT("Test My Global Shader, set it to 0 to disable, or to 1, 2 or 3 for fun!"),
	ECVF_RenderThreadSafe
);
 
void FDeferredShadingSceneRenderer::RenderFinish(FRHICommandListImmediate& RHICmdList)
{
	[...]
	// ***
	// Inserted code, just before finishing rendering, so we can overwrite the screen’s contents!
	int32 MyTestValue = CVarMyTest.GetValueOnAnyThread();
	if (MyTestValue != 0)
	{
		FLinearColor Color(MyTestValue == 1, MyTestValue == 2, MyTestValue == 3, 1);
		RenderMyTest(RHICmdList, FeatureLevel, Color);
	}
	// End Inserted code
	// ***
	FSceneRenderer::RenderFinish(RHICmdList);
	[...]
}

これで新しいグローバル シェーダーをテストできる状態になりました!プロジェクトを実行し、チルダ (~) キーを使ってコンソールを呼び出し、 r.MyTest 1 と入力します。次に、r.MyTest 2 および / または r.MyTest 3 と入力して色を変 更します。パスを無効にするには r.MyTest 0 を使います。

生成されたソースのデバッグ方法

.usf ファイルのコンパイルのデバッグ、あるいは処理済みファイルを表示する方法は、ブログ記事Debugging the Shader Compiling Process ( シェーダーコンパイルのデバッグプロセス) をご覧ください。

さらに!

クックされていないゲーム / エディタの実行中に .usf ファイルの修正も可能です。Ctrl+Shift+. (period) を押す、 またはコンソールに 「recompileshaders changed」 と入力して、シェーダーを選択し再ビルドするとイタレーションが速くなります !

是非お試しください!
 

用語に関する注記

グローバル シェーダー- 演算シェーダーやポストプロセス シェーダーなど、マテリアル エディタを使って作成されていないシェ ーダー。Global、Material、Mesh などのメタ タイプの 1 つです。残りのメタ タイプについても追って説明します。
FShaderType - シェーダーのコードで指定したテンプレートまたはクラスです。コードで指定した物理 C++ クラスへマップします。
FShaderResource - コンパイル済みシェーダーのマクロコードとそのランタイム RHI リソースです。
FShader - FShaderType のコンパイル済みシェーダーのインスタンスです。FShaderResource の情報からビルドされています。
TShaderMap - メタタイプ (Global) は同じで FShaderTypes が異なるシェーダーのコレクションです。