環境
UE4.27.0
概要
Material Expressionの実装が知りたくて追っていました。
docを読むだけで使い方は分かるものの、具体的な実装までは分からないことが多いためです。
Material FunctionなんかはEditorから実装が見れてお手軽ですね!
Material Expressionの例
図のような緑のノード。ダブルクリックしても実装が見れません。
Desaturationのdocは以下。
Color Expressions | Unreal Engine Documentation
Material Functionの例
図のような青のノード。ダブルクリックすると実装が見れます。
Material Expression自体はかなりの種類があります。
詳細は以下のdocに目を通ると良いでしょう。(以下はUE5のdocです)
Unreal Engine Material Expressions Reference | Unreal Engine Documentation
Material Expressionはどこから見れるの?
1. HLSL Codeから追う
Material EditorのWindow -> Shader Code -> HLSL Codeを開けばMaterial 全体のHLSLが見れます。
適当なEditorにコピペして「desaturation」を検索しますがヒットしません。
これは困った。
実際にはちゃんと実装されているんですが、それは後程解説します。
因みにIncludeされている .ush ですが、これらはUnreal Shader Fileの略称(だと思います)で、色々と便利な関数等が登録されています。
2. MaterialExpression.cpp を追う
実際のMaterial Expressionの実装についてはMaterialExpression.cpp を追うのが一番確実でしょう。
Engine\Source\Runtime\Engine\Private\Materials\MaterialExpression.cpp 内に記述があります。
Desaturationの実装については、UMaterialExpressionDesaturation() を追っていきます。
UMaterialExpressionDesaturation::UMaterialExpressionDesaturation(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Structure to hold one-time initialization struct FConstructorStatics { FText NAME_Color; FText NAME_Utility; FConstructorStatics() : NAME_Color(LOCTEXT( "Color", "Color" )) , NAME_Utility(LOCTEXT( "Utility", "Utility" )) { } }; static FConstructorStatics ConstructorStatics; LuminanceFactors = FLinearColor(0.3f, 0.59f, 0.11f, 0.0f); #if WITH_EDITORONLY_DATA MenuCategories.Add(ConstructorStatics.NAME_Color); MenuCategories.Add(ConstructorStatics.NAME_Utility); #endif } #if WITH_EDITOR int32 UMaterialExpressionDesaturation::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) { if(!Input.GetTracedInput().Expression) return Compiler->Errorf(TEXT("Missing Desaturation input")); int32 Color = Compiler->ForceCast(Input.Compile(Compiler), MCT_Float3, MFCF_ExactMatch|MFCF_ReplicateValue), Grey = Compiler->Dot(Color,Compiler->Constant3(LuminanceFactors.R,LuminanceFactors.G,LuminanceFactors.B)); if(Fraction.GetTracedInput().Expression) return Compiler->Lerp(Color,Grey,Fraction.Compile(Compiler)); else return Grey; } #endif // WITH_EDITOR
パッと見は何をしているのかよくわからないかもしれませんが、実装自体は非常にシンプルです。
UMaterialExpressionDesaturation::Compile() 内の一部分だけを見ればよいです。
int32 UMaterialExpressionDesaturation::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) { ... int32 Color = Compiler->ForceCast(Input.Compile(Compiler), MCT_Float3, MFCF_ExactMatch|MFCF_ReplicateValue), Grey = Compiler->Dot(Color,Compiler->Constant3(LuminanceFactors.R,LuminanceFactors.G,LuminanceFactors.B)); if(Fraction.GetTracedInput().Expression) return Compiler->Lerp(Color,Grey,Fraction.Compile(Compiler)); else return Grey; }
入力されたColorにDot() でLuminanceFactorsを指定し
入力されたFractionがある場合にはLerpして、ない場合にはDot() の結果を返しています。
LuminanceFactors はUMaterialExpressionDesaturation() で定義されており、カラーからモノトーンな色に変換するための適当な係数です。
LuminanceFactors = FLinearColor(0.3f, 0.59f, 0.11f, 0.0f);
3. HLSL Codeでは追えないの?
1. でコピーしたHLSLをもう一度見てみます。
3000行近いコードが生成されて何がなんだか……という感じなのですが、実際に見るべきは
CalcPixelMaterialInputs() 内を見れば良いです。
void CalcPixelMaterialInputs(in out FMaterialPixelParameters Parameters, in out FPixelMaterialInputs PixelMaterialInputs) { // Initial calculations (required for Normal) // The Normal is a special case as it might have its own expressions and also be used to calculate other inputs, so perform the assignment here PixelMaterialInputs.Normal = MaterialFloat3(0.00000000,0.00000000,1.00000000); // Note that here MaterialNormal can be in world space or tangent space float3 MaterialNormal = GetMaterialNormal(Parameters, PixelMaterialInputs); #if MATERIAL_TANGENTSPACENORMAL #if SIMPLE_FORWARD_SHADING Parameters.WorldNormal = float3(0, 0, 1); #endif #if FEATURE_LEVEL >= FEATURE_LEVEL_SM4 // Mobile will rely on only the final normalize for performance MaterialNormal = normalize(MaterialNormal); #endif // normalizing after the tangent space to world space conversion improves quality with sheared bases (UV layout to WS causes shrearing) // use full precision normalize to avoid overflows Parameters.WorldNormal = TransformTangentNormalToWorld(Parameters.TangentToWorld, MaterialNormal); #else //MATERIAL_TANGENTSPACENORMAL Parameters.WorldNormal = normalize(MaterialNormal); #endif //MATERIAL_TANGENTSPACENORMAL #if MATERIAL_TANGENTSPACENORMAL // flip the normal for backfaces being rendered with a two-sided material Parameters.WorldNormal *= Parameters.TwoSidedSign; #endif Parameters.ReflectionVector = ReflectionAboutCustomWorldNormal(Parameters, Parameters.WorldNormal, false); #if !PARTICLE_SPRITE_FACTORY Parameters.Particle.MotionBlurFade = 1.0f; #endif // !PARTICLE_SPRITE_FACTORY // Now the rest of the inputs MaterialFloat3 Local0 = lerp(MaterialFloat3(0.00000000,0.00000000,0.00000000),Material.VectorExpressions[1].rgb,MaterialFloat(Material.ScalarExpressions[0].x)); MaterialFloat Local1 = MaterialStoreTexCoordScale(Parameters, Parameters.TexCoords[0].xy, 0); MaterialFloat4 Local2 = ProcessMaterialLinearColorTextureLookup(Texture2DSampleBias(Material.Texture2D_0, Material.Texture2D_0Sampler,Parameters.TexCoords[0].xy,View.MaterialTextureMipBias)); MaterialFloat Local3 = MaterialStoreTexSample(Parameters, Local2, 0); MaterialFloat Local4 = dot(Local2.rgb, MaterialFloat3(0.30000001,0.58999997,0.11000000)); MaterialFloat3 Local5 = lerp(Local2.rgb,MaterialFloat3(Local4,Local4,Local4),MaterialFloat(0.75000000)); PixelMaterialInputs.EmissiveColor = Local0; PixelMaterialInputs.Opacity = 1.00000000; PixelMaterialInputs.OpacityMask = 1.00000000; PixelMaterialInputs.BaseColor = Local5; PixelMaterialInputs.Metallic = 0.00000000; PixelMaterialInputs.Specular = 0.50000000; PixelMaterialInputs.Roughness = 0.50000000; PixelMaterialInputs.Anisotropy = 0.00000000; PixelMaterialInputs.Tangent = MaterialFloat3(1.00000000,0.00000000,0.00000000); PixelMaterialInputs.Subsurface = 0; PixelMaterialInputs.AmbientOcclusion = 1.00000000; PixelMaterialInputs.Refraction = 0; PixelMaterialInputs.PixelDepthOffset = 0.00000000; PixelMaterialInputs.ShadingModel = 1; #if MATERIAL_USES_ANISOTROPY Parameters.WorldTangent = CalculateAnisotropyTangent(Parameters, PixelMaterialInputs); #else Parameters.WorldTangent = 0; #endif }
またしても長くて何がなんだか……という感じなのですが、実際に見るべき箇所はたったの2行です。
MaterialFloat Local4 = dot(Local2.rgb, MaterialFloat3(0.30000001,0.58999997,0.11000000)); MaterialFloat3 Local5 = lerp(Local2.rgb,MaterialFloat3(Local4,Local4,Local4),MaterialFloat(0.75000000));
この処理をよく見るとDesaturation() の記述はないものの、実行している処理は先程MaterialExpression.cpp で見たものと同じです。
入力されたColor に対してdot() で LuminanceFactors = FLinearColor(0.3f, 0.59f, 0.11f, 0.0f);と同じ係数を使用して
入力されたFractionに対してLerp() を使用しているだけです。
まとめ
MathExpressionの実装がどうなっているのか知りたくて色々と追ってみましたが
上記のようにEngine側のCodeとHLSL Codeから追うことができます。
何かしらの参考になれば幸いです!
以上です。