说明

本文基于UE5.4.3,通过添加自定义ShadingModel实现简单的卡渲效果。

UE5 内置ShadingModels

UE5内置了13种着色模型,定义在ShadingModels.ush中

// Engine\Shaders\Private\ShadingModels.ush

FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
    switch( GBuffer.ShadingModelID )
    {
        case SHADINGMODELID_DEFAULT_LIT:
        case SHADINGMODELID_SINGLELAYERWATER:
        case SHADINGMODELID_THIN_TRANSLUCENT:
            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 );
        default:
            return (FDirectLighting)0;
    }
}

其中,DefaultLitBxDF, SubsurfaceBxDF等就是对应的shading models的实现。

  • Unlit: 无光照,只有Emissive Color;
  • Default Unlit: 默认光照,Diffuse使用Lambertian,Specular使用的DGF分别为GGX法线分布、SmithJoint遮挡、Schlick近似;
  • Subsurface: 次表面散射,添加一层额外的diffuse layer来近似表现次表面散射现象;
  • Preintegrated Skin: 预计算皮肤,是Subsurface Model在皮肤情况下的特化,参数与Subsurface model一致;
  • Clear Coat: 清漆,多层材质的一种最简单的形式,增加一层额外的specular layer来模拟材质表面的一层薄薄的透明膜;
  • Subsurface Profile: Subsurface Profile也是用于渲染皮肤的模型,是Preintegrated Skin模型的加强版,效果更真实,数字人的皮肤渲染使用的即该光照模型;
  • Two Sided Foliage: 用于渲染较薄的次表面散射材质,例如树叶、花瓣等,它可以模拟光线穿过材质的效果,比Subsurface model更真实;
  • Hair: 用于模拟毛发的渲染效果,主要依据的是Marschner Model;
  • Cloth: 布料模型和其他几个ShadingModel不一样,因为布料的组成形式是纤维的互相堆叠,纤维之间还存在空隙,不符合微表面理论,模拟Cloth的BRDF模型大致可以分为三类:
    • 基于观察的empirical models
    • 基于微表面理论的模型
    • 微圆柱体模型(micro-cylinder model)
  • Eye: 用于模拟眼睛的表面,加入了控制虹膜的参数;
  • SingleLayerWater: 用于模拟透明水面的效果,降低使用透明模式混合的开销和复杂度,与Default Lit模型公用同一套BxDF公式,但参数比Default Lit多两个(Opacity, Refraction);
  • Thin Translucent: 用于模拟基于物理原理的半透明材质,能够更真实地还原高光和背景色,与Default Lit模型公用同一套BxDF公式,但参数比Default Lit多一个(Opacity);
  • From Material Expression: 将多个shading model合并到单个材质中。

添加自定义Shading Model

很多简单的光照都可以通过Unlit模式实现。但如果需要引擎的光影功能(比如需要阴影的情况)就需要通过增加ShadingModel实现。这对于高度风格化的游戏尤其重要。

添加Shading Model的总体步骤

  • C++部分
    1. 增加一个Shading Model枚举
    2. 增加对应的关键字
    3. 根据情况开启材质接口(可选)
  • Shader部分
    1. 在Shader中定义关键字
    2. 开启CustomData写入的关键字并写入(可选)
    3. 获取光源数据
    4. 开启着色模型混合(可选)
    5. 着色计算
    6. 透明模式支持体积光叠加(可选)

添加ToonShadingModel

C++部分

新增Shading Model枚举

在EngineTypes.h中的EMaterialShadingModel添加ToonShadingModel枚举。增加这个枚举,材质编辑器Details面板中会增加对应的选项。

// Engine\Source\Runtime\Engine\Classes\Engine\EngineTypes.h

/** 
 * Specifies the overal rendering/shading model for a material
 * @warning Check UMaterialInstance::Serialize if changed!
 */
UENUM()
enum EMaterialShadingModel : int
{
    (...)

    /*********** Toon Shading Model *******/
    MSM_MyToonShadingModel      UMETA(DisplayName="Toon"),
    /**************************************/
   
   (...)
};
增加对应的关键字

在HLSLMaterialTranslator.cpp和MaterialHLSLEmitter.cpp的GetMaterialEnvironment方法中添加宏,通过宏定义,后续在Shader中判断到底应该走什么分支,从而实现逻辑。

// Engine\Source\Runtime\Engine\Private\Materials\HLSLMaterialTranslator.cpp

void FHLSLMaterialTranslator::GetMaterialEnvironment(EShaderPlatform InPlatform, FShaderCompilerEnvironment& OutEnvironment)
{
    (...)
    
    if (EnvironmentDefines->bShadingModelsIsLit)
    {
        (...)
        /********************* Toon Shading Model ********************/
        if (EnvironmentDefines->HasShadingModel(MSM_Toon))
        {
            OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_TOON"), TEXT("1"));
        }
        /*************************************************************/
        (...)
    }
    
    (...)
}
// Engine\Source\Runtime\Engine\Private\Materials\MaterialHLSLEmitter.cpp

static void GetMaterialEnvironment(EShaderPlatform InPlatform,
    const FMaterial& InMaterial,
    const UE::HLSLTree::FEmitContext& EmitContext,
    const FMaterialCompilationOutput& MaterialCompilationOutput,
    bool bUsesEmissiveColor,
    bool bUsesAnisotropy,
    bool bIsFullyRough,
    FShaderCompilerEnvironment& OutEnvironment)
{
    (...)
   /********************* Toon Shading Model ********************/
    if(ShadingModels.HasShadingModel(MSM_Toon))
    {
        OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_TOON"), TEXT("1"));
        NumSetMaterials++;
    }
   /*************************************************************/
    (...)   
}

在ShaderMaterial.h的结构体FShaderMaterialPropertyDefines中添加宏,该参数用来在C++进行材质Shader的宏操作

// Engine\Source\Runtime\RenderCore\Public\ShaderMaterial.h

(...)
struct FShaderMaterialPropertyDefines
{
    (...)
    /****************** Toon Shading Model ***************/
    uint8 MATERIAL_SHADINGMODEL_TOON : 1;
    /*****************************************************/
    (...)
};
(...)

在ShaderGenerationUtil.cpp的ApplyFetchEnvironmentInternal里添加

// Engine\Source\Runtime\Engine\Private\ShaderCompiler\ShaderGenerationUtil.cpp

template<typename EnvironmentType>
void ApplyFetchEnvironmentInternal(FShaderMaterialPropertyDefines& SrcDefines, const EnvironmentType& Environment)
{
    (...)
    FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_DEFAULT_LIT);
    FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_SUBSURFACE);
    FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN);
    FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE);
    FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_CLEAR_COAT);
    FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE);
    FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_HAIR);
    FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_CLOTH);
    FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_EYE);
    FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_SINGLELAYERWATER);
    FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT);
    /************* Toon Shading Model ********/
    FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_TOON)
    /*****************************************/
    (...)
}

static void DetermineUsedMaterialSlots(
    EGBufferSlotUsage Slots[GBS_Num],
    const FShaderMaterialDerivedDefines& Dst,
    const FShaderMaterialPropertyDefines& Mat,
    const FShaderLightmapPropertyDefines& Lightmap,
    const FShaderGlobalDefines& SrcGlobal,
    const FShaderCompilerDefines& Compiler,
    ERHIFeatureLevel::Type FEATURE_LEVEL)
    {
    (...)
    /************ Toon Shading Model ********/
    if(Mat.MATERIAL_SHADINGMODEL_TOON)
    {
        SetStandardGBufferSlots(Slots, bWriteEmissive, bHasTangent, bHasVelocity, bWritesVelocity, bHasStaticLighting, bIsSubstrateMaterial);
        Slots[GBS_CustomData] = GetGBufferSlotUsage(bUseCustomData);
    }
    /****************************************/
    (...)
}

在MaterialShader.h中的GetShadingModelString函数添加

// Engine\Source\Runtime\Engine\Private\Materials\MaterialShader.cpp

/** Converts an EMaterialShadingModel to a string description. */
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_Hair:              ShadingModelName = TEXT("MSM_Hair"); 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;
        case MSM_ThinTranslucent:   ShadingModelName = TEXT("MSM_ThinTranslucent"); break;
        case MSM_Toon:              ShadingModelName = TEXT("MSM_Toon"); break; //**** Toon Shading Model
        default:                    ShadingModelName = TEXT("Unknown"); break;
    }
    return ShadingModelName;
}

在MaterialShared.h中的IsSubsurfaceShadingModel中添加

// Engine\Source\Runtime\Engine\Public\MaterialShared.h

inline bool IsSubsurfaceShadingModel(FMaterialShadingModelField ShadingModel)
{
    return ShadingModel.HasShadingModel(MSM_Subsurface) ||
        ShadingModel.HasShadingModel(MSM_PreintegratedSkin) ||
        ShadingModel.HasShadingModel(MSM_SubsurfaceProfile) ||
        ShadingModel.HasShadingModel(MSM_TwoSidedFoliage) ||
        ShadingModel.HasShadingModel(MSM_Cloth) ||
        ShadingModel.HasShadingModel(MSM_Eye) ||
        ShadingModel.HasShadingModel(MSM_Toon);
}

在ShaderMaterialDerivedHelpers.cpp中添加Mat.MATERIAL_SHADINGMODEL_TOON

// Engine\Source\Runtime\RenderCore\Private\ShaderMaterialDerivedHelpers.cpp

// 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_TOON));
根据情况开启材质接口(可选)

不同的ShadingModel开发的接口不尽相同,比如,可能需要材质编辑器的UI发生变化(比如材质的节点上多出几个Pin或禁用几个Pin)。

Material.cpp中的bool UMaterial::IsPropertyActive(EMaterialProperty InProperty)函数用于那些接口是否Active。真正的实现逻辑在Material.cpp中的IsPropertyActive_Internal函数。

为Toon Shading Model开放CustomData0和CustomData1两个接口,CustomData0和CustomData1均为float类型且范围在0-1之间。

// Engine\Source\Runtime\Engine\Private\Materials\Material.cpp

static bool IsPropertyActive_Internal(EMaterialProperty InProperty,
    EMaterialDomain Domain,
    EBlendMode BlendMode,
    FMaterialShadingModelField ShadingModels,
    ETranslucencyLightingMode TranslucencyLightingMode,
    bool bIsTessellationEnabled,
    bool bBlendableOutputAlpha,
    bool bUsesDistortion,
    bool bUsesShadingModelFromMaterialExpression,
    bool bIsTranslucencyWritingVelocity,
    bool bIsThinSurface,
    bool bIsSupported)
{
    (...)
    if (bSubstrateEnabled)
    {
        (...)
    }
    else
    {
        switch (InProperty)
        {
            (...)
        case MP_CustomData0:
            Active = ShadingModels.HasAnyShadingModel({ MSM_ClearCoat, MSM_Hair, MSM_Cloth, MSM_Eye, 
                                                        MSM_SubsurfaceProfile, MSM_Toon });
            break;
        case MP_CustomData1:
            Active = ShadingModels.HasAnyShadingModel({ MSM_ClearCoat, MSM_Eye, MSM_Toon });
            break;
            (...)
        }
    }
    (...)
}

修改这两个接口的名称

// Engine\Source\Runtime\Engine\Private\Materials\MaterialAttributeDefinitionMap.cpp

FText FMaterialAttributeDefinitionMap::GetAttributeOverrideForMaterial(const FGuid& AttributeID, UMaterial* Material)
{
    (...)
    case MP_CustomData0:
        CustomPinNames.Add({ MSM_ClearCoat, LOCTEXT("ClearCoat", "Clear Coat").ToString() });
        CustomPinNames.Add({ MSM_Hair, LOCTEXT("Backlit", "Backlit").ToString() });
        CustomPinNames.Add({ MSM_Cloth, LOCTEXT("Cloth", "Cloth").ToString() });
        CustomPinNames.Add({ MSM_Eye, LOCTEXT("IrisMask", "Iris Mask").ToString() });
        CustomPinNames.Add({ MSM_SubsurfaceProfile, LOCTEXT("Curvature", "Curvature").ToString() });
        /******************** Toon Shading Model ****************/
        CustomPinNames.Add({ MSM_Toon, LOCTEXT("Specular Range", "Specular Range").ToString() });
        /********************************************************/
        return FText::FromString(GetPinNameFromShadingModelField(Material->GetShadingModels(), CustomPinNames, LOCTEXT("CustomData0", "Custom Data 0").ToString()));
    case MP_CustomData1:
        CustomPinNames.Add({ MSM_ClearCoat, LOCTEXT("ClearCoatRoughness", "Clear Coat Roughness").ToString() });
        CustomPinNames.Add({ MSM_Eye, LOCTEXT("IrisDistance", "Iris Distance").ToString() });
        /******************** Toon Shading Model ****************/
        CustomPinNames.Add({ MSM_Toon, LOCTEXT("Offset", "Offset").ToString() });
        /********************************************************/
        return FText::FromString(GetPinNameFromShadingModelField(Material->GetShadingModels(), CustomPinNames, LOCTEXT("CustomData1", "Custom Data 1").ToString()));
    (...)
}

CustomPin

Shader部分

在Shader中定义关键字

在ShadingCommon中定义宏

// Engine\Shaders\Private\ShadingCommon.ush

///////////////////////////////////////////////////////////////////////////////
// Shading models

// 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
#define SHADINGMODELID_THIN_TRANSLUCENT     11
#define SHADINGMODELID_SUBSTRATE            12      // Temporary while we convert everything to Substrate
/****************** Toon Shading Model **************/
#define SHADINGMODELID_TOON                 13
/****************************************************/
// don't forget +1 to SHADINGMODELID_NUM
#define SHADINGMODELID_NUM                  14
#define SHADINGMODELID_MASK                 0xF     // 4 bits reserved for ShadingModelID

在GetShadingModelColor函数中添加可视化调试颜色,PS4不支持switch-case语句,因此使用if-else

// Engine\Shaders\Private\ShadingCommon.ush

// 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_TOON) return float3(0.7f, 0.2f, 0.2f); // Debugging Color for ToonShadingModel
    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_TOON: return float3(0.7f, 0.2f, 0.2f); // Debugging Color for ToonShadingModel
        default: return float3(1.0f, 1.0f, 1.0f); // White
    }
#endif
}

在Definitions.usf中定义宏

// Engine\Shaders\Private\Definitions.usf

(...)
/************* Toon Shading Model **********/
#ifndef MATERIAL_SHADINGMODEL_TOON
#define MATERIAL_SHADINGMODEL_TOON          0
#endif
/************* Toon Shading Model **********/
(...)
开启CustomData写入(可选)

为使CustomData数据写入G-Buffer,需要进行以下修改:

  • WRITES_CUSTOMDATA_TO_GBUFFER宏定义添加一个判断,即当ShadingModel为ToonShadingModel时也需要开启写入
// Engine\Shaders\Private\BasePassCommon.ush

#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 || MATERIAL_SHADINGMODEL_TOON))
  • DeferredShadingCommon.ush中的HasCustomGBufferData函数
// Engine\Shaders\Private\DeferredShadingCommon.ush

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_TOON; // ToonShadingModel
}
  • ClusteredDeferredShadingPixelShader.usf中的SetGBufferForShadingModel函数
// Engine\Shaders\Private\ClusteredDeferredShadingPixelShader.usf

void SetGBufferForShadingModel(
    in out FGBufferData GBuffer, 
    in out FMaterialPixelParameters MaterialParameters,
    const float Opacity,
    const half3 BaseColor,
    const half  Metallic,
    const half  Specular,
    const float Roughness,
    const float Anisotropy,
    const float3 SubsurfaceColor,
    const float SubsurfaceProfile,
    const float Dither,
    const uint ShadingModel)
{
    (...)
#if MATERIAL_SHADINGMODEL_TOON
    else if(ShadingModel == SHADINGMODELID_TOON)
    {
        GBuffer.CustomData.x = saturate( GetMaterialCustomData0(MaterialParameters) ); // SpecularRange
        GBuffer.CustomData.y = saturate( GetMaterialCustomData1(MaterialParameters) ); // Offset
    }
#endif
}
获取光源数据

在ClusteredDeferredShadingPixelShader.usf中的ClusteredShadingPixelShader函数中添加ToonShadingModel

// Engine\Shaders\Private\ClusteredDeferredShadingPixelShader.usf

void ClusteredShadingPixelShader(
    in noperspective float4 UVAndScreenPos : TEXCOORD0,
    in float4 SvPosition : SV_Position,
    out float4 OutColor : SV_Target0)
{
    (...)
    // Regular lights
#if USE_PASS_PER_SHADING_MODEL

    GET_LIGHT_GRID_LOCAL_LIGHTING_SINGLE_SM(SHADINGMODELID_DEFAULT_LIT,         PixelShadingModelID, CompositedLighting, ScreenUV, CulledLightGridData, Dither, FirstNonSimpleLightIndex);
    GET_LIGHT_GRID_LOCAL_LIGHTING_SINGLE_SM(SHADINGMODELID_SUBSURFACE,          PixelShadingModelID, CompositedLighting, ScreenUV, CulledLightGridData, Dither, FirstNonSimpleLightIndex);
    GET_LIGHT_GRID_LOCAL_LIGHTING_SINGLE_SM(SHADINGMODELID_PREINTEGRATED_SKIN,  PixelShadingModelID, CompositedLighting, ScreenUV, CulledLightGridData, Dither, FirstNonSimpleLightIndex);
    GET_LIGHT_GRID_LOCAL_LIGHTING_SINGLE_SM(SHADINGMODELID_CLEAR_COAT,          PixelShadingModelID, CompositedLighting, ScreenUV, CulledLightGridData, Dither, FirstNonSimpleLightIndex);
    GET_LIGHT_GRID_LOCAL_LIGHTING_SINGLE_SM(SHADINGMODELID_SUBSURFACE_PROFILE,  PixelShadingModelID, CompositedLighting, ScreenUV, CulledLightGridData, Dither, FirstNonSimpleLightIndex);
    GET_LIGHT_GRID_LOCAL_LIGHTING_SINGLE_SM(SHADINGMODELID_TWOSIDED_FOLIAGE,    PixelShadingModelID, CompositedLighting, ScreenUV, CulledLightGridData, Dither, FirstNonSimpleLightIndex);
    GET_LIGHT_GRID_LOCAL_LIGHTING_SINGLE_SM(SHADINGMODELID_HAIR,                PixelShadingModelID, CompositedLighting, ScreenUV, CulledLightGridData, Dither, FirstNonSimpleLightIndex);
    GET_LIGHT_GRID_LOCAL_LIGHTING_SINGLE_SM(SHADINGMODELID_CLOTH,               PixelShadingModelID, CompositedLighting, ScreenUV, CulledLightGridData, Dither, FirstNonSimpleLightIndex);
    GET_LIGHT_GRID_LOCAL_LIGHTING_SINGLE_SM(SHADINGMODELID_EYE,                 PixelShadingModelID, CompositedLighting, ScreenUV, CulledLightGridData, Dither, FirstNonSimpleLightIndex);
    GET_LIGHT_GRID_LOCAL_LIGHTING_SINGLE_SM(SHADINGMODELID_SINGLELAYERWATER         PixelShadingModelID, CompositedLighting, ScreenUV, CulledLightGridData, Dither, FirstNonSimpleLightIndex);
    GET_LIGHT_GRID_LOCAL_LIGHTING_SINGLE_SM(SHADINGMODELID_TOON,                PixelShadingModelID, CompositedLighting, ScreenUV, CulledLightGridData, Dither, FirstNonSimpleLightIndex);
    // SHADINGMODELID_THIN_TRANSLUCENT - skipping because it can not be opaque
#else // !USE_PASS_PER_SHADING_MODEL
    CompositedLighting += GetLightGridLocalLighting(GetScreenSpaceData(ScreenUV), CulledLightGridData, TranslatedWorldPosition, CameraVector, ScreenUV, SceneDepth, 0, Dither, FirstNonSimpleLightIndex);
#endif // USE_PASS_PER_SHADING_MODEL
    (...)
}
着色计算

在ShadingModels.ush的IntegrateBxDF函数中,添加ToonShadingModel的分支,并实现其对应的着色计算ToonBxDF

// Engine\Shaders\Private\ShadingModels.ush

/******************* Toon Shading Model ***************/
float3 ToonStep(float Range, float Input)
{
    return smoothstep(0.5 - Range, 0.5 + Range, Input);
}

FDirectLighting ToonBxDF(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) + 1e-5);

    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, .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;
}
/******************************************************/
透明模式支持体积光叠加(可选)

在BasePassPixelShader.usf中FPixelShaderInOut_MainPS函数中修改预处理条件:

// Engine\Shaders\Private\BasePassPixelShader.usf

void FPixelShaderInOut_MainPS(
    FVertexFactoryInterpolantsVSToPS Interpolants,
    FBasePassInterpolantsVSToPS BasePassInterpolants,
    in FPixelShaderIn In,
    inout FPixelShaderOut Out)
{
    (...)

    // Volume lighting for lit translucency
    // Add ToonShadingModel
#if (MATERIAL_SHADINGMODEL_DEFAULT_LIT || MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_TOON) && (MATERIALBLENDING_TRANSLUCENT || MATERIALBLENDING_ADDITIVE) && !FORWARD_SHADING
    if (GBuffer.ShadingModelID == SHADINGMODELID_DEFAULT_LIT || GBuffer.ShadingModelID == SHADINGMODELID_SUBSURFACE || GBuffer.ShadingModelID == SHADINGMODELID_TOON)
    {
        Color += GetTranslucencyVolumeLighting(MaterialParameters, PixelMaterialInputs, BasePassInterpolants, GBuffer, IndirectIrradiance);
    }
#endif
    
    (...)
}

效果

Debug模式下

DebugMode

新建材质

CreateMaterial

Final

Reference

Unreal Engine Documentation: Shading Models in Unreal Engine

虚幻引擎之自定义着色模型(ShadingModel)

在UE4中创建新的Shading Model

[UE4] Custom Shading Model