こんにちは!!!クライアントエンジニアの小林です。
今回はトゥーン・セル表現を題材にエンジン改造に触れていきます。
UnrealEngineでトゥーン表現をする場合、大体はUnlitシェーダかポストエフェクト、エンジン改造をすると思います。
なんで面倒なエンジン改造をすることが多いのかというのは、以下の表を見ると分かると思います。
Unlitシェーダ | ポストエフェクト | エンジン改造 | |
---|---|---|---|
影(Shadow)を落とせるか | × | △ (疑似的) | 〇 |
ディレクショナルライト以外を反映できるか | × | △ (疑似的) | 〇 |
特殊な演出を組み込みたい | △ | △ | 〇 |
Unlitシェーダもポストエフェクトもやろうと思えばできないことはないのですが、
エンジン改造に比べると面倒だったり制約が多くなる印象です。
そんな中でエンジン改造は極論、リソースの許す限りあらゆる表現を実装できます。
2022年の直近でいうとCEDECでのブループロトコルや、アンリアルフェスでのアイドルマスター、鬼滅などなど。
なんかもうリリースタイトルは大体改造してますね。
2022/09/17:ブルプロさんはいつリリースするんだろう。
ということでエンジン改造の推奨バージョン5.0.3がリリースされてから早数か月。
そろそろ手を付けてもいいだろうということでトゥーン表現向けのエンジン改造をやっていきます。
現状は3本構成で、シェーディングモデルの追加、マテリアルインプットの追加、Gバッファの拡張です。
- 作業環境
- 改造内容
- 改造ルール
- \Engine\Source
- \Runtime\Engine\Classes\Engine\EngineTypes.h
- \Runtime\Engine\Classes\Materials\MaterialExpressionShadingModel.h
- \Runtime\Engine\Private\Materials\HLSLMaterialTranslator.cpp
- \Engine\Source\Runtime\Engine\Private\Materials\MaterialHLSLEmitter.cpp
- \Runtime\Engine\Private\Materials\MaterialShader.cpp
- \Runtime\Engine\Private\ShaderCompiler\ShaderGenerationUtil.cpp
- \Engine\Source\Runtime\RenderCore\Public\ShaderMaterial.h
- \Engine\Shaders
- 追加したシェーディングモデルを使ってみる
- おわり
作業環境
・windows10
・visual studio 2019
・visual studio code
・UnrealEngine 5.0.3 - release
改造内容
ToonLitというトゥーン表現専用のシェーディングモデルを追加します。
ライティングはディフューズカラーのみ反映します。
スペキュラやリムライトを考慮してもいいのですが、エンジン改造がメインのため適当にします。
また、Lightmass、Strata、PixelInspectorなどが非対応です。
改造ルール
UnrealEngineのエンジンコードに手を加える場合は、コメントを付けることが推奨されています。
推奨されているのですが記述フォーマットがおそらく決まっていません。
これに関しては正直決めてほしかったのですが、私の場合はこんな感じに書いています。
//// Add ShadingModel 2022/09/17 //// // 日付の前が改造内容 float A = 0.0f; //// Add ShadingModel 2022/09/17 //// //// Add ShadingModel 2022/09/17 //// #if 0 float B = 0.0f; #else // #if 0 ~ #else がUnrealEngineさんのオリジナルコード // #else ~ #endifが改造コード // オリジナルコードを残しておきたい場合にこの記述をしてます float B = 1.0f; #endif //// Add ShadingModel 2022/09/17 ////
\Engine\Source
\Runtime\Engine\Classes\Engine\EngineTypes.h
ToonLitをシェーディングモデル列挙体に追加しています。
UE5.0からMSM_Strata
というPBRに特化したシェーディングモデルが追加されました。
特別な理由が無ければデフォルトのシェーディングモデルの下に追加していきます。
MSM_SingleLayerWater UMETA(DisplayName="SingleLayerWater"), MSM_ThinTranslucent UMETA(DisplayName="Thin Translucent"), MSM_Strata UMETA(DisplayName="Strata", Hidden), //// Add ShadingModel:ToonLit 2022/09/17 //// MSM_ToonLit UMETA(DisplayName="Toon Lit"), //// Add ShadingModel:ToonLit 2022/09/17 //// /** Number of unique shading models. */ MSM_NUM UMETA(Hidden), /** Shading model will be determined by the Material Expression Graph,
\Runtime\Engine\Classes\Materials\MaterialExpressionShadingModel.h
シェーディングモデルをFrom Material Expression
に選択した際に使用するShadingModelノードの対応をしています。
virtual void GetCaption(TArray<FString>& OutCaptions) const override; #endif public: //// Add ShadingModel:ToonLit 2022/09/17 //// UPROPERTY(EditAnywhere, Category=ShadingModel, meta=(ValidEnumValues="MSM_DefaultLit, MSM_Subsurface, MSM_PreintegratedSkin, MSM_ClearCoat, MSM_SubsurfaceProfile, MSM_TwoSidedFoliage, MSM_Hair, MSM_Cloth, MSM_Eye, MSM_ToonLit")) //// Add ShadingModel:ToonLit 2022/09/17 //// TEnumAsByte<enum EMaterialShadingModel> ShadingModel = MSM_DefaultLit; //~ End UMaterialExpression Interface };
\Runtime\Engine\Private\Materials\HLSLMaterialTranslator.cpp
そのマテリアル・シェーダーがToonLitか否かのフラグを立てています。
bMaterialRequestsDualSourceBlending = true; } //// Add ShadingModel:ToonLit 2022/09/17 //// if (ShadingModels.HasShadingModel(MSM_ToonLit)) { OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_TOON_LIT"), TEXT("1")); NumSetMaterials++; } //// Add ShadingModel:ToonLit 2022/09/17 //// if (ShadingModels.HasShadingModel(MSM_SingleLayerWater) && FDataDrivenShaderPlatformInfo::GetRequiresDisableForwardLocalLights(Platform)) {
\Engine\Source\Runtime\Engine\Private\Materials\MaterialHLSLEmitter.cpp
bMaterialRequestsDualSourceBlending = true; } //// Add ShadingModel:ToonLit 2022/09/17 //// if (ShadingModels.HasShadingModel(MSM_ToonLit)) { OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_TOON_LIT"), TEXT("1")); NumSetMaterials++; } //// Add ShadingModel:ToonLit 2022/09/17 //// if (ShadingModels.HasShadingModel(MSM_SingleLayerWater) && FDataDrivenShaderPlatformInfo::GetRequiresDisableForwardLocalLights(InPlatform)) {
\Runtime\Engine\Private\Materials\MaterialShader.cpp
ログなどで出力されるシェーディングモデル名と負荷計測です。
case MSM_Eye: ShadingModelName = TEXT("MSM_Eye"); break; case MSM_SingleLayerWater: ShadingModelName = TEXT("MSM_SingleLayerWater"); break; case MSM_ThinTranslucent: ShadingModelName = TEXT("MSM_ThinTranslucent"); break; //// Add ShadingModel:ToonLit 2022/09/17 //// case MSM_ToonLit: ShadingModelName = TEXT("MSM_ToonLit"); break; //// Add ShadingModel:ToonLit 2022/09/17 //// default: ShadingModelName = TEXT("Unknown"); break; } return ShadingModelName;
{ INC_DWORD_STAT_BY(STAT_ShaderCompiling_NumUnlitMaterialShaders, 1); } //// Add ShadingModel:ToonLit 2022/09/17 //// else if (ShadingModels.HasAnyShadingModel({ MSM_DefaultLit, MSM_Subsurface, MSM_PreintegratedSkin, MSM_ClearCoat, MSM_Cloth, MSM_SubsurfaceProfile, MSM_TwoSidedFoliage, MSM_SingleLayerWater, MSM_ThinTranslucent, MSM_ToonLit })) //// Add ShadingModel:ToonLit 2022/09/17 //// { INC_DWORD_STAT_BY(STAT_ShaderCompiling_NumLitMaterialShaders, 1); }
\Runtime\Engine\Private\ShaderCompiler\ShaderGenerationUtil.cpp
どのGバッファを使うかなどを指定しています。
FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_EYE); FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_SINGLELAYERWATER); FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT); //// Add ShadingModel:ToonLit 2022/09/17 //// FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_TOON_LIT); //// Add ShadingModel:ToonLit 2022/09/17 //// FETCH_COMPILE_BOOL(MATERIAL_FULLY_ROUGH);
FETCH_COMPILE_BOOL(MATERIAL_FULLY_ROUGH); { } //// Add ShadingModel:ToonLit 2022/09/17 //// if (Mat.MATERIAL_SHADINGMODEL_TOON_LIT) { SetStandardGBufferSlots(Slots, bWriteEmissive, bHasTangent, bHasVelocity, bHasStaticLighting, bIsStrataMaterial); } //// Add ShadingModel:ToonLit 2022/09/17 //// } void FShaderCompileUtilities::ApplyDerivedDefines(FShaderCompilerEnvironment& OutEnvironment, FShaderCompilerEnvironment * SharedEnvironment, const EShaderPlatform Platform)
\Engine\Source\Runtime\RenderCore\Public\ShaderMaterial.h
uint8 MATERIAL_SHADINGMODEL_EYE : 1; uint8 MATERIAL_SHADINGMODEL_SINGLELAYERWATER : 1; uint8 MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT : 1; //// Add ShadingModel:ToonLit 2022/09/17 //// uint8 MATERIAL_SHADINGMODEL_TOON_LIT : 1; //// Add ShadingModel:ToonLit 2022/09/17 //// uint8 TRANSLUCENCY_LIGHTING_VOLUMETRIC_NONDIRECTIONAL : 1; uint8 TRANSLUCENCY_LIGHTING_VOLUMETRIC_DIRECTIONAL : 1;
\Engine\Shaders
\Private\DeferredLightingCommon.ush
ToonLitに落ちる影(Shadow)を2値化しています。
厳密にはBRANCH if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 )
の前に2値化すると不都合が生じるのですが、シェーディングモデルの追加が目的なので一旦思考放棄します。
LightAccumulator.EstimatedCost += 0.3f; // add the cost of getting the shadow terms //// Add ShadingModel:ToonLit 2022/09/17 //// BRANCH if (GBuffer.ShadingModelID == SHADINGMODELID_TOON_LIT) { Shadow.SurfaceShadow = Shadow.SurfaceShadow > 0.5f ? 1.0f : 0.0f; } //// Add ShadingModel:ToonLit 2022/09/17 //// BRANCH if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 ) {
\Private\Definitions.usf
ソース側で対応したToonLitか否かのフラグ受け渡し場所です。
#ifndef ~ #endif
で囲われているので、ソース側から特に指示が無ければToonLitではないよ。というフラグが立ちます。
#define MATERIAL_SHADINGMODEL_UNLIT 0 #endif //// Add ShadingModel:ToonLit 2022/09/17 //// #ifndef MATERIAL_SHADINGMODEL_TOON_LIT #define MATERIAL_SHADINGMODEL_TOON_LIT 0 #endif //// Add ShadingModel:ToonLit 2022/09/17 //// #ifndef MATERIAL_SINGLE_SHADINGMODEL #define MATERIAL_SINGLE_SHADINGMODEL 0
\Private\ShadingCommon.ush
#define SHADINGMODELID_SINGLELAYERWATER 10 #define SHADINGMODELID_THIN_TRANSLUCENT 11 #define SHADINGMODELID_STRATA 12 // Temporary while we convert everything to Strata //// Add ShadingModel:ToonLit 2022/09/17 //// #define SHADINGMODELID_TOON_LIT 13 #define SHADINGMODELID_NUM 14 //// Add ShadingModel:ToonLit 2022/09/17 //// #define SHADINGMODELID_MASK 0xF // 4 bits reserved for ShadingModelID
else if (ShadingModelID == SHADINGMODELID_SINGLELAYERWATER) return float3(0.5f, 0.5f, 1.0f); else if (ShadingModelID == SHADINGMODELID_THIN_TRANSLUCENT) return float3(1.0f, 0.8f, 0.3f); else if (ShadingModelID == SHADINGMODELID_STRATA) return float3(1.0f, 1.0f, 0.0f); //// Add ShadingModel:ToonLit 2022/09/17 //// else if (ShadingModelID == SHADINGMODELID_TOON_LIT) return float3(0.3f, 0.3f, 0.3f); //// Add ShadingModel:ToonLit 2022/09/17 //// else return float3(1.0f, 1.0f, 1.0f); // White #else switch(ShadingModelID)
case SHADINGMODELID_SINGLELAYERWATER: return float3(0.5f, 0.5f, 1.0f); case SHADINGMODELID_THIN_TRANSLUCENT: return float3(1.0f, 0.8f, 0.3f); case SHADINGMODELID_STRATA: return float3(1.0f, 1.0f, 0.0f); //// Add ShadingModel:ToonLit 2022/09/17 //// case SHADINGMODELID_TOON_LIT: return float3(0.3f, 0.3f, 0.3f); //// Add ShadingModel:ToonLit 2022/09/17 //// default: return float3(1.0f, 1.0f, 1.0f); // White } #endif
\Private\ShadingModels.ush
ToonLitモデルのシェーディング計算箇所です。
return Lighting; } //// Add ShadingModel:ToonLit 2022/09/17 //// FDirectLighting ToonLitBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) { BxDFContext Context; FDirectLighting Lighting; bool bHasAnisotropy = false; float NoV, VoH, NoH; { Init(Context, N, V, L); NoV = Context.NoV; VoH = Context.VoH; NoH = Context.NoH; SphereMaxNoH(Context, AreaLight.SphereSinAlpha, true); } Context.NoV = saturate(abs( Context.NoV ) + 1e-5); Lighting.Diffuse = Diffuse_Lambert(GBuffer.DiffuseColor) * AreaLight.FalloffColor * Falloff; Lighting.Specular = 0.0f; Lighting.Transmission = 0.0f; return Lighting; } //// Add ShadingModel:ToonLit 2022/09/17 //// FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) { switch( GBuffer.ShadingModelID )
return ClothBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); case SHADINGMODELID_EYE: return EyeBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); //// Add ShadingModel:ToonLit 2022/09/17 //// case SHADINGMODELID_TOON_LIT: return ToonLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); //// Add ShadingModel:ToonLit 2022/09/17 //// default: return (FDirectLighting)0; }
\Private\SkyLightingDiffuseShared.ush
本来スカイライトパスでは法線を考慮しますが、トゥーン表現とは微妙に相性が悪いのでベタ塗りにしています。
DiffuseColor += ClothFuzz * GBuffer.CustomData.a; } //// Add ShadingModel:ToonLit 2022/09/17 //// BRANCH if (GBuffer.ShadingModelID == SHADINGMODELID_TOON_LIT) { Lighting = Diffuse_Lambert(DiffuseColor) * View.SkyLightColor.rgb; } else { // Compute the preconvolved incoming lighting with the bent normal direction float3 DiffuseLookup = GetSkySHDiffuse(SkyVisData.SkyDiffuseLookUpNormal) * View.SkyLightColor.rgb; // And accumulate the lighting Lighting += (SkyVisData.SkyDiffuseLookUpMul * DiffuseLookup + SkyVisData.SkyDiffuseLookUpAdd) * DiffuseColor * DiffuseWeight; } //// Add ShadingModel:ToonLit 2022/09/17 //// Lighting *= View.PreExposure; //Lighting = (Texture2DSampleLevel(BentNormalAOTexture, BentNormalAOSampler, UV, 0).xyz);
追加したシェーディングモデルを使ってみる
マテリアルのシェーディングモデルをToon Litに変更して、ベースカラーに出力させたいテクスチャなどを繋げます。
Unlitシェーダやポストエフェクトは違い、AdditionalLightの反映や影(Shadow)が落ちているのも確認できました。
おわり
お疲れさまでした!!!