LoginSignup
24
17

More than 5 years have passed since last update.

UE4のShading Modelを拡張する

Last updated at Posted at 2018-10-14

Unreal Engine 4のShading Modelを拡張して独自のライティングマテリアルを追加します。

 この記事は2018年10月14日に開催される、Unreal Fest 2018の講演、「UE4のライティング解体新書~効果的なNPRのためにライティングの仕組みを理解しよう~」のスライドの補足記事です。
スライドはこちら

 スライドでコードの変更部分などを説明しようとするとかなりのページじなってしまうので、別記事として作成します。

 ベースとなるUE4のバージョンは4.20です。

Shading Model

 マテリアルのライティングの種類です。エンジンにもとから用意されているものには

  • Unlit
  • Default Lit
  • Subsurface
  • Eye
  • Hair

 などがありますが、基本的にはPBRなライティング用のモデルです。これらにない独自のシェーディングを実装したい場合に自分でShading Modelを追加する手法を解説します。

追加するShading Model

 サンプルとしてシンプルなハーフランバートシェーディングを作成します。
 ハーフランバートとは、通常、法線と光源の向きが90度以上になると明るさが0になり、真っ黒になるのを180度で真っ黒になるようにする手法です。物理的には正しくないですが、陰影が柔らかになります。
 最近のゲームエンジンでは真っ黒になった部分には間接光が加算されるので不要なのですが、間接光の計算が実装されていない大昔はアンビエントカラーを指定したり、ハーフランバートにしたり工夫していたものでした。

仕様、簡単です。

  • ピクセルの明るさを法線と光源ベクトルの内積に1を足して2で割ったものを使用する。

通常は法線と光源ベクトルの内積を0でクリップしたものを使用します。シェーダーコードでは
saturate(dot(N, L))
になりますが、これを
(dot(N,L)+1) * 0.5
にします。内積は1から-1の結果を返すので、それを1-0の範囲に変換して使います。

エンジンコードの変更

まずはエンジン側です。新しいShading Modelを定義します。
Shading ModelはEngineTypes.hで定義されています。

Source/Runtime/Engine/Classes/Engine/EngineTypes.h
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"),
// @UEFEST2018 BEGIN
    MSM_HalfLambert         UMETA(DisplayName = "Half Lambert"),
// @UEFEST2018 END
MSM_MAX,
};

ソースを改変するときは、このように検索しやすいコメントで囲っておくとエンジンのアップデート時のマージ作業が楽になります。

次はShading ModelのIDから文字列を返す部分。

Source/Runtime/Engine/Private/Materials/MaterialShader.cpp
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;
// @UEFEST2018 BEGIN
        case MSM_HalfLambert:       ShadingModelName = TEXT("MSM_HalfLambert"); break;
// @UEFEST2018 END
        default: ShadingModelName = TEXT("Unknown"); break;
    }
    return ShadingModelName;
}

Shading Modelの数を返す部分

Source/Runtime/Engine/Private/Materials/MaterialShader.cpp
void UpdateMaterialShaderCompilingStats(const FMaterial* Material)
{
/* 中略 */
    switch(Material->GetShadingModel())
    {
        case MSM_Unlit:
            INC_DWORD_STAT_BY(STAT_ShaderCompiling_NumUnlitMaterialShaders,1);
            break;
        case MSM_DefaultLit:
        case MSM_Subsurface:
        case MSM_PreintegratedSkin:
        case MSM_ClearCoat:
        case MSM_Cloth:
        case MSM_SubsurfaceProfile:
        case MSM_TwoSidedFoliage:
// @UEFEST2018 BEGIN
        case MSM_HalfLambert:
// @UEFEST2018 END
            INC_DWORD_STAT_BY(STAT_ShaderCompiling_NumLitMaterialShaders,1);
            break;
        default: break;
    };

Shader Codeに設定するdefineを生成する部分

Source/Runtime/Engine/Private/Materials/MaterialShared.cpp
void FMaterial::SetupMaterialEnvironment(
    EShaderPlatform Platform,
    const FUniformExpressionSet& InUniformExpressionSet,
    FShaderCompilerEnvironment& OutEnvironment
    ) const
{
/* 中略 */
    switch(GetShadingModel())
    {
        case MSM_Unlit:             OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_UNLIT"),               TEXT("1")); break;
        case MSM_DefaultLit:        OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_DEFAULT_LIT"),         TEXT("1")); break;
        case MSM_Subsurface:        OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_SUBSURFACE"),          TEXT("1")); break;
        case MSM_PreintegratedSkin: OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN"),  TEXT("1")); break;
        case MSM_SubsurfaceProfile: OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE"),  TEXT("1")); break;
        case MSM_ClearCoat:         OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_CLEAR_COAT"),          TEXT("1")); break;
        case MSM_TwoSidedFoliage:   OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE"),    TEXT("1")); break;
        case MSM_Hair:              OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_HAIR"),                TEXT("1")); break;
        case MSM_Cloth:             OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_CLOTH"),               TEXT("1")); break;
        case MSM_Eye:               OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_EYE"),                 TEXT("1")); break;
// @UEFEST2018 BEGIN
        case MSM_HalfLambert:       OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_HALF_LAMBERT"),        TEXT("1")); break;
// @UEFEST2018 END
        default:
            UE_LOG(LogMaterial, Warning, TEXT("Unknown material shading model: %u  Setting to MSM_DefaultLit"),(int32)GetShadingModel());
            OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_DEFAULT_LIT"),TEXT("1"));
    };

 実はこれだけです。ただ単純にShading Modelを追加するだけなら簡単です。

 もう少し凝り始めると、マテリアル入力ピンの名前を変えたり増やしたりという作業が必要になりますが、そのへんはまたの機会に。難しくはありません。

シェーダーコードの変更

 次は実際の描画を担うシェーダーコードに手を入れます。

まずはShading Model IDを追加。

Shaders/Private/DeferredShadingCommon.ush
#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
// @UEFEST2018 BEGIN
// ORG>#define SHADINGMODELID_NUM                   10
#define SHADINGMODELID_HALF_LAMBERT         10
#define SHADINGMODELID_NUM                  11
// @UEFEST2018 BEGIN
#define SHADINGMODELID_MASK                 0xF                 

これを見てわかるとおり、Shading Modelは15個までしか持てません。GBufferに4bitの領域を確保して書き込んでいる都合です。

次にエディタでGBuffer表示する場合のShading Modelごとの色を返す部分。適当な色を設定します。

Shaders/Private/DeferredShadingCommon.ush
float3 GetShadingModelColor(uint ShadingModelID)
{
/* 中略 */
    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);
// @UEFEST2018 BEGIN
        case SHADINGMODELID_HALF_LAMBERT: return float3(0.5f, 0.5f, 0.5f);
// @UEFEST2018 END
        default: return float3(1.0f, 1.0f, 1.0f); // White
    }

GBufferにShading Model IDを書き込みます。

Shaders/Private/ShadingModelsMaterial.ush
/* ファイルの最後の部分 */
#else
    GBuffer.Metallic = saturate(GetMaterialCustomData1(MaterialParameters));            // Iris Distance

    #if NUM_MATERIAL_OUTPUTS_GETTANGENTOUTPUT > 0
        float3 Tangent = GetTangentOutput0(MaterialParameters);
        GBuffer.CustomData.yz = UnitVectorToOctahedron( normalize(Tangent) ) * 0.5 + 0.5;
    #endif
#endif
// @UEFEST2018 BEGIN
#elif MATERIAL_SHADINGMODEL_HALF_LAMBERT
    GBuffer.ShadingModelID = SHADINGMODELID_HALF_LAMBERT;
// @UEFEST2018 END
#else
    // missing shading model, compiler should report ShadingModelID is not set
#endif
}

そして、最も重要な部分、実際に拡張したShading Modelで描画する部分。
下記の関数をShadingModels.ushに追加。

Shaders/Private/ShadingModels.ush
// @UEFEST2018 BEGIN
FDirectLighting HalfLambertBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
    FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
    NoL = (dot(N, L) + 1.f) * 0.5f;
    Lighting.Diffuse  = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Lambert( GBuffer.DiffuseColor );
    return Lighting;
}
// @UEFEST2018 END

呼び出し部分

Shaders/Private/ShadingModels.ush
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:
            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 );
// @UEFEST2018 BEGIN
        case SHADINGMODELID_HALF_LAMBERT:
            return HalfLambertBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
// @UEFEST2018 END
        default:
            return (FDirectLighting)0;
    }
}

結果確認

では実際に描画してみましょう。
左がハーフランバート、右が標準のDefault Litです。

ScreenShot00001.png

マテリアルエディタはこんな感じです。
image.png

Shading Modelの拡張自体はとても簡単です。
実際には影の扱い、独自のスペキュラ、サブサーフェスカラーの使用、独自の情報をGBufferに格納など、ここからやることが沢山ありますが、まずは入り口です。

さらに改造する場合のヒント

マテリアルノードの入力ピンの名前を変えたい場合は
Source/Editor/UnrealEd/Private/MaterialGraph.cppを修正します。
マテリアルノードの入力ピンの有効無効は
Source/Runtime/Engine/Private/Materials/Material.cppを修正。
サブサーフェスカラーを使う場合は、
Source/Runtime/Engine/Public/MaterialShared.hのIsSubsurfaceShadingModel()を修正。
Shaders/Private/BasePassCommon.ushの#define WRITES_CUSTOMDATA_TO_GBUFFERに定義を追加。

24
17
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
17