UE4.24でShading Model独自のShading Modelを追加してみた
表題の通りUE4はエンジン改造によって独自のShading Modelを追加することができます
今回はそちらを4.24.2をベースとしてやってみたという記事になります、誤っている点などありましたらご指摘頂けると幸いです
なお、コードの拡張部分にはその範囲を示すために以下のようなコメントを追加してあります(実際のプロジェクトでEngine改造をする際には、よりプロジェクトに適したコメントが望ましいと思います)
// begin
Custom code ...
// end
/* begin */ Custom code ... /* end */
C++側
列挙型の定義
今回はCelShadingという名前のShading Modelを追加していきます
UENUM()
enum EMaterialShadingModel
{
MSM_Unlit UMETA(DisplayName="Unlit"),
MSM_DefaultLit UMETA(DisplayName="Default Lit"),
MSM_Subsurface UMETA(DisplayName="Subsurface"),
MSM_PreintegratedSkin UMETA(DisplayName="Preintegrated Skin"),
MSM_ClearCoat UMETA(DisplayName="Clear Coat"),
MSM_SubsurfaceProfile UMETA(DisplayName="Subsurface Profile"),
MSM_TwoSidedFoliage UMETA(DisplayName="Two Sided Foliage"),
MSM_Hair UMETA(DisplayName="Hair"),
MSM_Cloth UMETA(DisplayName="Cloth"),
MSM_Eye UMETA(DisplayName="Eye"),
MSM_SingleLayerWater UMETA(DisplayName="SingleLayerWater"),
// begin
MSM_CelShading UMETA(DisplayName="CelShading"),
// end
/** Number of unique shading models. */
MSM_NUM UMETA(Hidden),
/** Shading model will be determined by the Material Expression Graph,
by utilizing the 'Shading Model' MaterialAttribute output pin. */
MSM_FromMaterialExpression UMETA(DisplayName="From Material Expression"),
MSM_MAX
};
列挙型の値から文字列を取得する部分
FString GetShadingModelString(EMaterialShadingModel ShadingModel)
{
FString ShadingModelName;
switch(ShadingModel)
{
case MSM_Unlit: ShadingModelName = TEXT("MSM_Unlit"); break;
case MSM_DefaultLit: ShadingModelName = TEXT("MSM_DefaultLit"); break;
case MSM_Subsurface: ShadingModelName = TEXT("MSM_Subsurface"); break;
case MSM_PreintegratedSkin: ShadingModelName = TEXT("MSM_PreintegratedSkin"); break;
case MSM_ClearCoat: ShadingModelName = TEXT("MSM_ClearCoat"); break;
case MSM_SubsurfaceProfile: ShadingModelName = TEXT("MSM_SubsurfaceProfile"); break;
case MSM_TwoSidedFoliage: ShadingModelName = TEXT("MSM_TwoSidedFoliage"); break;
case MSM_Cloth: ShadingModelName = TEXT("MSM_Cloth"); break;
case MSM_Eye: ShadingModelName = TEXT("MSM_Eye"); break;
case MSM_SingleLayerWater: ShadingModelName = TEXT("MSM_SingleLayerWater"); break;
// begin
case MSM_CelShading: ShadingModelName = TEXT("MSM_CelShading"); break;
// end
default: ShadingModelName = TEXT("Unknown"); break;
}
return ShadingModelName;
}
stat用の条件式に追加したShading Modelを追加します
else if (ShadingModels.HasAnyShadingModel({ MSM_DefaultLit, MSM_Subsurface, MSM_PreintegratedSkin, MSM_ClearCoat, MSM_Cloth, MSM_SubsurfaceProfile, MSM_TwoSidedFoliage, MSM_SingleLayerWater, /* begin */ MSM_CelShading /* end */}))
{
INC_DWORD_STAT_BY(STAT_ShaderCompiling_NumLitMaterialShaders, 1);
}
Shader側で使用する変数名を設定します
if (ShadingModels.HasShadingModel(MSM_Eye))
{
OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_EYE"), TEXT("1"));
NumSetMaterials++;
}
if (ShadingModels.HasShadingModel(MSM_SingleLayerWater))
{
OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_SINGLELAYERWATER"), TEXT("1"));
NumSetMaterials++;
}
// begin
if (ShadingModels.HasShadingModel(MSM_CelShading))
{
OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_CELSHADING"), TEXT("1"));
NumSetMaterials++;
}
// end
ここまで書いてビルドをすれば、とりあえずMaterial EditorのShading ModelにCelShadingが追加されていることが確認できるはずです
Shader側
ここからは実際にCelShadingというShading ModelがどのようにシェーディングされるのかをShader側で書いていきます
まずエンジンのShader(*.ush/*.usf)を編集する前にConsoleVariables.iniを編集してShader開発をしやすいようにします
r.ShaderDevelopmentMode=1の前のセミコロンを外すことでShader Compile時のエラー詳細と、その際にRecompileするかどうかのDialogが出るようになるのでShader開発がしやすくなるはずです
またエディタ起動時にCtrl + Shift + .
でShaderの再コンパイルも可能です
; Uncomment to get detailed logs on shader compiles and the opportunity to retry on errors
r.ShaderDevelopmentMode=1
; Uncomment to dump shaders in the Saved folder
; Warning: leaving this on for a while will fill your hard drive with many small files and folders
;r.DumpShaderDebugInfo=1
; When this is enabled, SCW crashes will print out the list of jobs in the current worker
;r.DumpSCWQueuedJobs=1
; When this is enabled, when dumping shaders an additional file to use with ShaderCompilerWorker -direct mode will be generated
;r.DumpShaderDebugWorkerCommandLine=1
IDを設定します、コメントにあるように4bitのみShadingModelID用に確保されているので、ShadingModelは最大15までとなります
// SHADINGMODELID_* occupy the 4 low bits of an 8bit channel and SKIP_* occupy the 4 high bits
#define SHADINGMODELID_UNLIT 0
#define SHADINGMODELID_DEFAULT_LIT 1
#define SHADINGMODELID_SUBSURFACE 2
#define SHADINGMODELID_PREINTEGRATED_SKIN 3
#define SHADINGMODELID_CLEAR_COAT 4
#define SHADINGMODELID_SUBSURFACE_PROFILE 5
#define SHADINGMODELID_TWOSIDED_FOLIAGE 6
#define SHADINGMODELID_HAIR 7
#define SHADINGMODELID_CLOTH 8
#define SHADINGMODELID_EYE 9
#define SHADINGMODELID_SINGLELAYERWATER 10
// begin
#define SHADINGMODELID_CELSHADING 11
#define SHADINGMODELID_NUM 12
// end
#define SHADINGMODELID_MASK 0xF // 4 bits reserved for ShadingModelID
Debug表示用の色を設定
PS4用に別途ifでの分岐が設けられるようになってますね
// for debugging and to visualize
float3 GetShadingModelColor(uint ShadingModelID)
{
// TODO: PS4 doesn't optimize out correctly the switch(), so it thinks it needs all the Samplers even if they get compiled out
// This will get fixed after launch per Sony...
#if PS4_PROFILE
if (ShadingModelID == SHADINGMODELID_UNLIT) return float3(0.1f, 0.1f, 0.2f); // Dark Blue
else if (ShadingModelID == SHADINGMODELID_DEFAULT_LIT) return float3(0.1f, 1.0f, 0.1f); // Green
else if (ShadingModelID == SHADINGMODELID_SUBSURFACE) return float3(1.0f, 0.1f, 0.1f); // Red
else if (ShadingModelID == SHADINGMODELID_PREINTEGRATED_SKIN) return float3(0.6f, 0.4f, 0.1f); // Brown
else if (ShadingModelID == SHADINGMODELID_CLEAR_COAT) return float3(0.1f, 0.4f, 0.4f);
else if (ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE) return float3(0.2f, 0.6f, 0.5f); // Cyan
else if (ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE) return float3(0.2f, 0.2f, 0.8f); // Blue
else if (ShadingModelID == SHADINGMODELID_HAIR) return float3(0.6f, 0.1f, 0.5f);
else if (ShadingModelID == SHADINGMODELID_CLOTH) return float3(0.7f, 1.0f, 1.0f);
else if (ShadingModelID == SHADINGMODELID_EYE) return float3(0.3f, 1.0f, 1.0f);
else if (ShadingModelID == SHADINGMODELID_SINGLELAYERWATER) return float3(0.5f, 0.5f, 1.0f);
// begin
else if (ShadingModelID == SHADINGMODELID_CELSHADING) return float3(0.5f, 0.5f, 0.5f);
// end
else return float3(1.0f, 1.0f, 1.0f); // White
#else
switch(ShadingModelID)
{
case SHADINGMODELID_UNLIT: return float3(0.1f, 0.1f, 0.2f); // Dark Blue
case SHADINGMODELID_DEFAULT_LIT: return float3(0.1f, 1.0f, 0.1f); // Green
case SHADINGMODELID_SUBSURFACE: return float3(1.0f, 0.1f, 0.1f); // Red
case SHADINGMODELID_PREINTEGRATED_SKIN: return float3(0.6f, 0.4f, 0.1f); // Brown
case SHADINGMODELID_CLEAR_COAT: return float3(0.1f, 0.4f, 0.4f); // Brown
case SHADINGMODELID_SUBSURFACE_PROFILE: return float3(0.2f, 0.6f, 0.5f); // Cyan
case SHADINGMODELID_TWOSIDED_FOLIAGE: return float3(0.2f, 0.2f, 0.8f); // Cyan
case SHADINGMODELID_HAIR: return float3(0.6f, 0.1f, 0.5f);
case SHADINGMODELID_CLOTH: return float3(0.7f, 1.0f, 1.0f);
case SHADINGMODELID_EYE: return float3(0.3f, 1.0f, 1.0f);
case SHADINGMODELID_SINGLELAYERWATER: return float3(0.5f, 0.5f, 1.0f);
// begin
case SHADINGMODELID_CELSHADING: return float3(0.5f, 0.5f, 0.5f);
// end
default: return float3(1.0f, 1.0f, 1.0f); // White
}
#endif
}
実際に拡張したShading Modelをどう描画するかの部分と呼び出し部分
今回はNoLを利用して段階的に色が分かれるようなShadingModelとしました、Stepの数を増やせば段階数も増えます
// begin
FDirectLighting CelShadingBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
float Step = 3.0f;
FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
Lighting.Diffuse = AreaLight.FalloffColor * Falloff * Diffuse_Lambert(GBuffer.DiffuseColor) * saturate(floor(NoL * Step) / Step);
return Lighting;
}
// end
FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
switch( GBuffer.ShadingModelID )
{
case SHADINGMODELID_DEFAULT_LIT:
case SHADINGMODELID_SINGLELAYERWATER:
return DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_SUBSURFACE:
return SubsurfaceBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_PREINTEGRATED_SKIN:
return PreintegratedSkinBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_CLEAR_COAT:
return ClearCoatBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_SUBSURFACE_PROFILE:
return SubsurfaceProfileBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_TWOSIDED_FOLIAGE:
return TwoSidedBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_HAIR:
return HairBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_CLOTH:
return ClothBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_EYE:
return EyeBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
// begin
case SHADINGMODELID_CELSHADING:
return CelShadingBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
// end
default:
return (FDirectLighting)0;
}
}
Custom Dataについて
先程の状態だとShadingModels.ushのCelShadingBxDFのStepを変更しないと段階数が変更できず非常に不便です
なので更に拡張して、Material InputにあるCustom Data 0を使用できるようにした上で、そちらを利用してStepを変更できるようにしていこうと思います
C++側
まずCelShadingでCustom Data 0の入力を有効化します
static bool IsPropertyActive_Internal(EMaterialProperty InProperty,
EMaterialDomain Domain,
EBlendMode BlendMode,
FMaterialShadingModelField ShadingModels,
ETranslucencyLightingMode TranslucencyLightingMode,
EDecalBlendMode DecalBlendMode,
bool bBlendableOutputAlpha,
bool bHasTessellation,
bool bHasRefraction,
bool bUsesShadingModelFromMaterialExpression)
{
/ *...* /
case MP_SubsurfaceColor:
Active = ShadingModels.HasAnyShadingModel({ MSM_Subsurface, MSM_PreintegratedSkin, MSM_TwoSidedFoliage, MSM_Cloth });
break;
// begin
case MP_CustomData0:
Active = ShadingModels.HasAnyShadingModel({ MSM_ClearCoat, MSM_Hair, MSM_Cloth, MSM_Eye, MSM_CelShading });
break;
// end
次にCustom Data 0の名前をわかりやすいようにInv Step(Stepの逆数)へと変更します
逆数にしている理由としては、格納する対象のGBufferのフォーマットがHDRフォーマットではないので0~1までの範囲しか渡すことができないからです
ですので今回はStepの逆数を渡すようにしています
FText UMaterialGraph::GetCustomDataPinName( uint32 Index ) const
{
if( Index == 0 )
{
TArray<TKeyValuePair<EMaterialShadingModel, FString>> CustomPinNames(
{{MSM_ClearCoat, "Clear Coat"},
{MSM_Hair, "Backlit"},
{MSM_Cloth, "Cloth"},
{MSM_Eye, "Iris Mask"},
// begin
{MSM_CelShading, "Inv Step"}
// end
});
Shader側
Custom DataをGBufferに書き込むための定義を追加します
#define WRITES_CUSTOMDATA_TO_GBUFFER (USES_GBUFFER && (MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN || MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE || MATERIAL_SHADINGMODEL_CLEAR_COAT || MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE || MATERIAL_SHADINGMODEL_HAIR || MATERIAL_SHADINGMODEL_CLOTH || MATERIAL_SHADINGMODEL_EYE || /* begin */ MATERIAL_SHADINGMODEL_CELSHADING /* end */))
Custom Data 0を取得してGBuffer.CustomData.xへと代入します
ですのでここにStepの逆数であるInvStepが代入されることになります
#endif
}
#endif
// begin
#if MATERIAL_SHADINGMODEL_CELSHADING
else if (ShadingModel == SHADINGMODELID_CELSHADING)
{
GBuffer.CustomData.x = saturate(GetMaterialCustomData0(MaterialParameters));
}
#endif
// end
逆数を渡されるので、それに合わせてシェーディングの計算式も変更します
// begin
FDirectLighting CelShadingBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
// float Step = 3.0f;
float InvStep = GBuffer.CustomData.x;
FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
// Lighting.Diffuse = AreaLight.FalloffColor * Falloff * Diffuse_Lambert(GBuffer.DiffuseColor) * saturate(floor(NoL * Step) / Step);
Lighting.Diffuse = AreaLight.FalloffColor * Falloff * Diffuse_Lambert(GBuffer.DiffuseColor) * saturate(floor(NoL / InvStep) * InvStep);
return Lighting;
}
// end
このような変更をしてきた上でMaterial Graphを下の画像のように組むことで、正しくInv Stepが反映されていること確認できます
お疲れさまでした
参考文献
下記文献には大変お世話になりました、この場を借りてお礼申し上げます