はじめに
こんにちは。
UnrealEngine5が気になるプログラマーの村田です。
残念ながらUE5のお話ではなく、今回もUE4のお話になりますが、最後までご覧頂けると幸いです。
タイトルの通り、マテリアルノードを作ってみたお話になります。
UE4の標準でシェーダの組み込み関数に対応する各ノードが実装されておりますが、中にはノード化されていないものもあります。
その中でも今回は「Smoothstep」関数のノードを作成してみました!
Smoothstep関数についてはこちらをご覧ください。
※使用したバージョンはUE4.25.0になります。
※エディタの言語環境は日本語になります。
実装
まず、Smoothstepノード用のクラスを追加しましょう。
UE4.25.0/Engine/Source/Runtime/Engine/Classes/Materials階層にMaterialExpressionSmoothstep.hファイルを追加し、クラスの定義を行います。
UCLASS(MinimalAPI)
class UMaterialExpressionSmoothstep : public UMaterialExpression
{
GENERATED_UCLASS_BODY()
UPROPERTY()
FExpressionInput Input;
UPROPERTY()
FExpressionInput Min;
UPROPERTY()
FExpressionInput Max;
//~ Begin UMaterialExpression Interface
#if WITH_EDITOR
virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
virtual void GetCaption(TArray<FString>& OutCaptions) const override;
#endif
//~ End UMaterialExpression Interface
};
実装はMaterialExpression.cppに記述します。
※エンジン側のソースコードを編集する場合は、UE4のコーディングルールに則りコメントを必ず記述しましょう!
詳しくは
こちらをご覧ください!
// @Logicalbeat Murata - BEGIN Smoothstepノード追加
#include "Materials/MaterialExpressionSmoothstep.h"
// @Logicalbeat Murata - END
︙(略)
︙
// @Logicalbeat Murata - BEGIN Smoothstepノード追加
//
// UMaterialExpressionSmoothstep
//
UMaterialExpressionSmoothstep::UMaterialExpressionSmoothstep(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// Structure to hold one-time initialization
struct FConstructorStatics
{
FText NAME_LB;
FConstructorStatics()
: NAME_LB(LOCTEXT( "Logicalbeat", "Logicalbeat" )) // カテゴリー名を設定。指定しない場合は「その他」に追加される。
{
}
};
static FConstructorStatics ConstructorStatics;
#if WITH_EDITORONLY_DATA
MenuCategories.Add(ConstructorStatics.NAME_LB); // ↑で指定したカテゴリーを反映。
#endif
}
#if WITH_EDITOR
// Shaderコードへ変換
int32 UMaterialExpressionSmoothstep::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)
{
if(!Input.GetTracedInput().Expression || !Min.GetTracedInput().Expression || !Max.GetTracedInput().Expression)
{
return Compiler->Error(TEXT("Missing Smoothstep Input")); // Inputが繋がっていない時
}
return Compiler->Smoothstep(Min.Compile(Compiler), Max.Compile(Compiler), Input.Compile(Compiler));
}
// マテリアルノードの見出し部分の表示文言
void UMaterialExpressionSmoothstep::GetCaption(TArray<FString>& OutCaptions) const
{
FString Caption = TEXT( "Smoothstep" );
OutCaptions.Add(Caption);
}
#endif // WITH_EDITOR
// @Logicalbeat Murata - END
続いて上記コードの38行目のShaderコードに変換するためのSmoothstep関数を実装していきます。
FMaterialCompilerクラスは抽象クラスで派生クラスに実装を任せています。
なので、FMaterialCompilerクラスには純粋仮想関数を追加しておきます。
同ファイルにFMaterialCompilerを継承したFProxyMaterialCompilerが存在するので先程の関数をオーバーライドしておきます。
今回はこのクラスは特に関係ないので中身は気にしなくてもOKです。
class FMaterialCompiler
{
public:
︙(略)
︙
// @Logicalbeat Murata - BEGIN Smoothstepコンパイル純粋仮想関数追加
virtual int32 Smoothstep(int32 A, int32 B, int32 X) = 0;
// @Logicalbeat Murata - END
};
/**
* A proxy for the material compiler interface which by default passes all function calls unmodified.
* Note: Any functions of FMaterialCompiler that change the internal compiler state must be routed!
*/
class FProxyMaterialCompiler : public FMaterialCompiler
{
public:
︙(略)
︙
// @Logicalbeat Murata - BEGIN Smoothstepのコンパイル関数をオーバーライド
virtual int32 Smoothstep(int32 A, int32 B, int32 X) override
{
return Compiler->Smoothstep(A, B, X);
}
// @Logicalbeat Murata - END
protected:
FMaterialCompiler* Compiler;
};
続いてHLSLMaterialTranslator.hと.cppにもFMaterialCompilerを継承したFHLSLMaterialTranslatorクラスが存在するのでオーバライドしていきます。
今回大事なのはこっちのクラスになります。
(ヘッダーファイルの方は宣言だけなので割愛します。)
// @Logicalbeat Murata - BEGIN Smoothsteのコンパイル関数を追加
int32 FHLSLMaterialTranslator::Smoothstep(int32 A, int32 B, int32 X)
{
if(A == INDEX_NONE || B == INDEX_NONE || X == INDEX_NONE)
{
return INDEX_NONE;
}
FMaterialUniformExpression* ExpressionA = GetParameterUniformExpression(A); // Min
FMaterialUniformExpression* ExpressionB = GetParameterUniformExpression(B); // Max
FMaterialUniformExpression* ExpressionX = GetParameterUniformExpression(X); // X
EMaterialValueType ResultType = GetParameterType(X); // 戻り値の型
if(ExpressionA && ExpressionB && ExpressionX)
{
return AddUniformExpression(new FMaterialUniformExpressionSmoothstep(ExpressionA, ExpressionB, ExpressionX), ResultType, TEXT("smoothstep(%s,%s,%s)"), *CoerceParameter(A, ResultType), *CoerceParameter(B, ResultType), *GetParameterCode(X));
}
else
{
return AddCodeChunk(ResultType, TEXT("smoothstep(%s,%s,%s)"), *CoerceParameter(A, ResultType), *CoerceParameter(B, ResultType), *GetParameterCode(X));
}
}
// @Logicalbeat Murata - END
コードの編集は次で最後です。
MaterialUniformExpression.hにSmoothstep用のクラスを追加と.cppにStaticTypeを定義するマクロを追加すれば完成です!
// @Logicalbeat Murata - BEGIN Smoothstep用MaterialUniformExpressionクラスを追加
/**
*/
class FMaterialUniformExpressionSmoothstep : public FMaterialUniformExpression
{
DECLARE_MATERIALUNIFORMEXPRESSION_TYPE(FMaterialUniformExpressionSmoothstep);
public:
FMaterialUniformExpressionSmoothstep(){}
FMaterialUniformExpressionSmoothstep(FMaterialUniformExpression* InMin, FMaterialUniformExpression* InMax, FMaterialUniformExpression* InInput)
: Min(InMin)
, Max(InMax)
, Input(InInput)
{}
// FMaterialUniformExpression interface.
virtual bool IsConstant() const
{
return Input->IsConstant() && Min->IsConstant() && Max->IsConstant();
}
virtual bool IsIdentical(const FMaterialUniformExpression* OtherExpression) const
{
if (GetType() != OtherExpression->GetType())
{
return false;
}
FMaterialUniformExpressionSmoothstep* OtherSmoothstep = (FMaterialUniformExpressionSmoothstep*)OtherExpression;
return Input->IsIdentical(OtherSmoothstep->Input) && Min->IsIdentical(OtherSmoothstep->Min) && Max->IsIdentical(OtherSmoothstep->Max);
}
private:
TRefCountPtr<FMaterialUniformExpression> Min;
TRefCountPtr<FMaterialUniformExpression> Max;
TRefCountPtr<FMaterialUniformExpression> Input;
};
// @Logicalbeat Murata - END
// @Logicalbeat Murata - BEGIN FMaterialUniformExpressionSmoothstep追加
IMPLEMENT_MATERIALUNIFORMEXPRESSION_TYPE(FMaterialUniformExpressionSmoothstep);
// @Logicalbeat Murata - END
確認
実装が完了したので、実際に動作を確認してみます。
値をグラフ化して確認してみましたが問題なさそうですね!
念の為に変換されたShaderコードも確認してみます。
float Local0 = smoothstep(Material_ScalarExpressions[0].y,Material_ScalarExpressions[0].x,Parameters.TexCoords[0].xy.r);
float Local1 = (1.00000000 - Parameters.TexCoords[0].xy.g);
float Local2 = (Local0 - Local1);
float Local3 = abs(Local2);
float Local4 = ((abs(Local3 - Material_ScalarExpressions[0].z) > 0.00001000) ? (Local3 >= Material_ScalarExpressions[0].z ? 0.00000000 : 1.00000000) : 1.00000000);
float3 Local5 = lerp( float3 (0.00000000,0.00000000,0.00000000),Material_VectorExpressions[3].rgb, float (Local4));
float3 Local6 = lerp(Local5,Material_VectorExpressions[4].rgb, float (Material_ScalarExpressions[0].w));
PixelMaterialInputs.EmissiveColor = Local6;
ちゃんとSmoothstep関数が呼ばれてますね!
最後に
やってみると案外簡単にノードを追加できました。
UE4はソースが公開されているので、こういう拡張がしやすくて助かりますね。
今回紹介した方法の他にもCustomノードを使えば、ノード化されていない関数を呼び出すことが可能です。
Customノードだと関数の呼び出しが増えるので、今回の手法より少しだけ負荷が高くなる気がします。
(ちゃんと計測したわけではないです。。)
今回の内容が少しでも皆様の開発の参考になれば幸いです。
最後までご覧いただき、ありがとうございました。
【免責事項】
本サイトでの情報を利用することによる損害等に対し、
株式会社ロジカルビートは一切の責任を負いません。