0%

Adding a new Shading Model in Unreal5

Adding a new Shading Model

本文主要记录了UE5如何通过修改源代码添加自己的shadingmodel~本文会先梳理下添加ShadingModel所需文件及其相关用途再进行相关介绍,了解过的朋友可直接跳转第二章开始。

1.文件梳理

  1. EngineTypes.h : 存储引擎中类型集合,在这里需要添加ShaderingModel
  2. MaterialShader.cpp : 负责材质相关属性声明
  3. HLSLMaterialTranslator.cpp : 负责控制材质C++侧到hlsl侧的数据传递
  4. ShaderMaterial.h : 声明材质相关定义
  5. Material.cpp :Material 相关设置
  6. ShaderMaterialDerivedHelpers.cpp :声明处理相关材质parameter
  7. ShadingCommon.ush : Shading Common 数据 包括共用的ShadingModel 和 相关函数
  8. BasePassPixelShader.usf :Base Pass Pixel Shader 相关
  9. ShadingModelsMaterial.ush : 对GBuffer相关设置
  10. BasePassCommon.ush :BasePass相关共用数据声明
  11. DeferredShadingCommon.ush :声明GBuffer数据及相关DeferredRendering数据函数
  12. Definitions.usf : 定义各种材质的宏
  13. ClusteredDeferredShadingPixelShader.usf :Defer Rendering Pixel Shader相关光照处理
  14. ShadingModels.ush : 负责处理不同ShadingModel分类

2.新增 MaterialShadingModel

EngineTypes.h 中新增自己的ShadingModel类型

添加后编译即可在ShadingModel中找到自己的ShadingModel

Note : 这里需要在MSM_NUM 之前进行添加,否则会报check(InShadingModel < MSM_NUM)检查导致没法Hack

然后在 MaterialShader.cpp 文件中GetShadingModelString中添加相关声明,Note:名字必须相同

HLSLMaterialTranslator.cpp 文件,这里需要找到FHLSLMaterialTranslator::GetMaterialEnvironment 函数并为shadingmodel添加相关defined

ShaderMaterial.h 中 FShaderMaterialPropertyDefines 声明映射:

3.开启CustomData 接口

在 Material.cpp 中找到IsPropertyActive_Internal函数,在switch中找到相关接口,并输入自己新shadingModel。

这一步是为了激活接口在不同ShadingModel中的使用情况。

2.1 设置CustomData接口名字

打开 MaterialShared.cpp ,在GetAttributeOverrideForMaterial函数中case MP_CustomData0中添加CustomPinNames。该函数控制在Editor中切换不同模式下Pin接口的可用情况。 这里Add第一个是ShadingModel , 第二个是接口展示的名字。

1
2
3
4
5
6
7
8
case MP_CustomData0:	
CustomPinNames.Add({ MSM_ClearCoat, "Clear Coat" });
CustomPinNames.Add({MSM_Hair, "Backlit"});
CustomPinNames.Add({MSM_Cloth, "Cloth"});
CustomPinNames.Add({MSM_Eye, "Iris Mask"});
CustomPinNames.Add({MSM_SubsurfaceProfile, "Curvature" });
CustomPinNames.Add({MSM_StylizedShadow, "MyStylizedShadow" });
return FText::FromString(GetPinNameFromShadingModelField(Material->GetShadingModels(), CustomPinNames, "Custom Data 0"));

ShaderMaterialDerivedHelpers.cpp 文件中修改Custom Node 相关映射。

1
2
// Only some shader models actually need custom data.
Dst.WRITES_CUSTOMDATA_TO_GBUFFER = (Dst.USES_GBUFFER && (Mat.MATERIAL_SHADINGMODEL_SUBSURFACE || Mat.MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN || Mat.MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE || Mat.MATERIAL_SHADINGMODEL_CLEAR_COAT || Mat.MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE || Mat.MATERIAL_SHADINGMODEL_HAIR || Mat.MATERIAL_SHADINGMODEL_CLOTH || Mat.MATERIAL_SHADINGMODEL_EYE || Mat.MATERIAL_SHADINGMODEL_STYLIZED_SHAODW));

3.设置 GBuffer Shading Model ID

打开 ShadingCommone.ush 文件在Shading Model的定义中添加自己的定义

然后在GetShadingModelColor(uint ShadingModelID) 中修改 ShadingModel ID的颜色

然后在 BasePassPixelShader.usf 中找到GetMaterialShadingModel 并进行声明 ShadingModel

1
2
3
4
5
#if MATERIAL_SHADINGMODEL_STYLIZED_SHADOW
uint ShadingModel = SHADINGMODELID_STYLIZED_SHADOW;
#else
uint ShadingModel = GetMaterialShadingModel(PixelMaterialInputs);
#endif

3.1将CustomData数据写入GBuffer

接下来打开 ShadingModelsMaterial.ush 。查看SetGBufferForShadingModel函数。该函数控制不同shadingModel写入FGbufferData的数据。

1
2
3
4
5
6
7
#if MATERIAL_SHADINGMODEL_STYLIZED_SHADOW
else if (ShadingModel == SHADINGMODEL_STYLIZED_SHADOW)
{
GBuffer.ShadingModelID = SHADINGMODELID_STYLIZED_SHADOW;
GBuffer.CustomData.x = GetMaterialCustomData0(MaterialParameters);
}
#endif

这里使用GetMaterialCustomData0来获取CustomData0的输入,并将其存放到GBuffer.CustomData中

然后在 BasePassCommon.ush 中的#define WRITES_CUSTOMDATA_TO_GBUFFER 添加 MATERIAL_SHADINGMODEL_STYLIZED_SHADOW

1
#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 || SHADINGMODELID_STYLIZED_SHADOW))

4.修改 CustomGBufferData 数据

DeferredShadingCommon.ush 修改HasCustomGBufferData函数,支持SHADINGMODELID_STYLIZED_SHADOW写入

1
2
3
4
5
6
7
8
9
10
11
12
bool HasCustomGBufferData(int ShadingModelID)
{
return ShadingModelID == SHADINGMODELID_SUBSURFACE
|| ShadingModelID == SHADINGMODELID_PREINTEGRATED_SKIN
|| ShadingModelID == SHADINGMODELID_CLEAR_COAT
|| ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE
|| ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE
|| ShadingModelID == SHADINGMODELID_HAIR
|| ShadingModelID == SHADINGMODELID_CLOTH
|| ShadingModelID == SHADINGMODELID_EYE
|| ShadingModelID == SHADINGMODELID_STYLIZED_SHADOW;
}

5.其他声明

打开 ShaderGenerationUtil.cpp 文件,找到ApplyFetchEnvironment 并在其中声明 Shader Compile

同时到 DetermineUsedMaterialSlots 函数中添加对象slot

然后在 Definitions.usf 中声明

6.光照修改

从这里开始计算光照部分的修改:

6.1获取光源数据

在 Engine\Shaders\Private\ClusteredDeferredShadingPixelShader.usf 的 ClusteredShadingPixelShader 函数中添加我们的着色模型。

6.2着色计算

打开 ShadingModels.ush 在其中的IntegrateBxDF 函数中添加相关着色类型:

接下来是StylizedShadowBxDF 相关渲染代码,这里直接参考dalao文章

  • code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    FDirectLighting StylizedShadowBxDF(FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow)
    {
    #if GBUFFER_HAS_TANGENT
    half3 X = GBuffer.WorldTangent;
    half3 Y = normalize(cross(N, X));
    #else
    half3 X = 0;
    half3 Y = 0;
    #endif

    BxDFContext Context;

    Init(Context, N, X, Y, V, L);

    SphereMaxNoH(Context, AreaLight.SphereSinAlpha, true);
    Context.NoV = saturate(abs(Context.NoV) + 0.00001);

    float SpecularOffset = 0.5;
    float SpecularRange = GBuffer.CustomData.x;

    float3 ShadowColor = 0;

    ShadowColor = GBuffer.DiffuseColor * ShadowColor;
    float offset = GBuffer.CustomData.y;
    float SoftScatterStrength = 0;

    offset = offset * 2 - 1;
    half3 H = normalize(V + L);
    float NoH = saturate(dot(N, H));
    NoL = (dot(N, L) + 1) / 2; // overwrite NoL To Get more range out Of it
    half NoLOffset = saturate(NoL + offset);

    FDirectLighting Lighting;
    Lighting.Diffuse = AreaLight.FalloffColor * (smoothstep(0, 1, NoLOffset) * Falloff) * Diffuse_Lambert(GBuffer.DiffuseColor) * 2.2;
    float InScatter = pow(saturate(dot(L, -V)), 12) * lerp(3, 0.1F, 1);
    float NormalContribution = saturate(dot(N, H));
    float BackScatter = GBuffer.GBufferAO * NormalContribution / (PI * 2);
    Lighting.Specular = ToonStep(SpecularRange, (saturate(D_GGX(SpecularOffset, NoH)))) * (AreaLight.FalloffColor * GBuffer.SpecularColor * Falloff * 8);
    float3 TransmissionSoft = AreaLight.FalloffColor * (Falloff * lerp(BackScatter, 1, InScatter)) * ShadowColor * SoftScatterStrength;
    float3 ShadowLightener = 0;
    ShadowLightener = (saturate(smoothstep(0, 1, saturate(1 - NoLOffset))) * ShadowColor * 0.1);

    Lighting.Transmission = (ShadowLightener + TransmissionSoft) * Falloff;
    return Lighting;
    }

打开DeferredLightingCommon.ush ,并找到GetDynamicLighting函数,在其中的 GetDynamicLightingSplit,添加相关case 进行分类

  • code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    /** Calculates lighting for a given position, normal, etc with a fully featured lighting model designed for quality. */
    FDeferredLightingSplit GetDynamicLightingSplit(
    float3 TranslatedWorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID,
    FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos, FRectTexture SourceTexture,
    inout float SurfaceShadow)
    {
    FLightAccumulator LightAccumulator = (FLightAccumulator)0;

    float3 V = -CameraVector;
    float3 N = GBuffer.WorldNormal;
    BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT && CLEAR_COAT_BOTTOM_NORMAL)
    {
    const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 2) - (256.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal);
    N = OctahedronToUnitVector(oct1);
    }

    float3 L = LightData.Direction; // Already normalized
    float3 ToLight = L;

    float LightMask = 1;
    if (LightData.bRadialLight)
    {
    LightMask = GetLocalLightAttenuation( TranslatedWorldPosition, LightData, ToLight, L );
    }

    LightAccumulator.EstimatedCost += 0.3f; // running the PixelShader at all has a cost

    BRANCH
    if( LightMask > 0 )
    {
    FShadowTerms Shadow;
    Shadow.SurfaceShadow = AmbientOcclusion;
    Shadow.TransmissionShadow = 1;
    Shadow.TransmissionThickness = 1;
    Shadow.HairTransmittance.OpaqueVisibility = 1;
    const float ContactShadowOpacity = GBuffer.CustomData.a;
    GetShadowTerms(GBuffer.Depth, GBuffer.PrecomputedShadowFactors, GBuffer.ShadingModelID, ContactShadowOpacity,
    LightData, TranslatedWorldPosition, L, LightAttenuation, Dither, Shadow);
    SurfaceShadow = Shadow.SurfaceShadow;

    LightAccumulator.EstimatedCost += 0.3f; // add the cost of getting the shadow terms

    BRANCH
    if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 )
    {
    const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
    float3 LightColor = LightData.Color;

    // ****************** start here ********************* //
    // STYLIZEDSHADOW SHADING
    float3 Attenuation = 1;
    BRANCH
    if (GBuffer.ShadingModelID == SHADINGMODELID_STYLIZED_SHADOW)
    {

    float offset = GBuffer.CustomData.x;
    float TerminatorRange = saturate(GBuffer.Roughness - 0.5);

    offset = offset * 2 - 1;

    BRANCH
    if (offset >= 1)
    {
    Attenuation = 1;
    }
    else
    {
    float NoL = (dot(N, L) + 1) / 2;
    float NoLOffset = saturate(NoL + offset);
    float LightAttenuationOffset = saturate( Shadow.SurfaceShadow + offset);
    float ToonSurfaceShadow = smoothstep(0.5 - TerminatorRange, 0.5 + TerminatorRange, LightAttenuationOffset);

    Attenuation = smoothstep(0.5 - TerminatorRange, 0.5 + TerminatorRange, NoLOffset) * ToonSurfaceShadow;
    }
    }
    // ****************** end here ********************* //
    #if NON_DIRECTIONAL_DIRECT_LIGHTING
    float Lighting;

    if( LightData.bRectLight )
    {
    FRect Rect = GetRect( ToLight, LightData );

    Lighting = IntegrateLight( Rect, SourceTexture);
    }
    else
    {
    FCapsuleLight Capsule = GetCapsule( ToLight, LightData );

    Lighting = IntegrateLight( Capsule, LightData.bInverseSquared );
    }

    float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting;
    // ****************** start here ********************* //
    LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, LightColor * LightMask * Shadow.SurfaceShadow * Attenuation, bNeedsSeparateSubsurfaceLightAccumulation);
    // ****************** end here ********************* //
    #else
    FDirectLighting Lighting;

    if (LightData.bRectLight)
    {
    FRect Rect = GetRect( ToLight, LightData );

    #if REFERENCE_QUALITY
    Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos );
    #else
    Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture);
    #endif
    }
    else
    {
    FCapsuleLight Capsule = GetCapsule( ToLight, LightData );

    #if REFERENCE_QUALITY
    Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, SVPos );
    #else
    Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared );
    #endif
    }

    Lighting.Specular *= LightData.SpecularScale;
    // ****************** start here ********************* //
    LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow * Attenuation, bNeedsSeparateSubsurfaceLightAccumulation );

    LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, LightColor * LightMask * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );
    // ****************** end here ********************* //

    LightAccumulator.EstimatedCost += 0.4f; // add the cost of the lighting computations (should sum up to 1 form one light)

    #endif
    }
    }


    return LightAccumulator_GetResultSplit(LightAccumulator);
    }

编译后即可看到效果

7. 总结

初步实现自定义ShadingModel材质渲染。后续可做进一步开发~

8.参考

Unreal Engine 4 Rendering Part 6: Adding a new Shading Model

GitHub - Eragon-Brisingr/ToonShader: cartoon plugins for unreal engine

在UE4中创建新的Shading Model_「已注销」的博客-CSDN博客

UE5自定义着色模型 Unreal Engine 5 custom Shading Model