Adding a new Shading Model
本文主要记录了UE5如何通过修改源代码添加自己的shadingmodel~本文会先梳理下添加ShadingModel所需文件及其相关用途再进行相关介绍,了解过的朋友可直接跳转第二章开始。
1.文件梳理
- EngineTypes.h : 存储引擎中类型集合,在这里需要添加ShaderingModel
- MaterialShader.cpp : 负责材质相关属性声明
- HLSLMaterialTranslator.cpp : 负责控制材质C++侧到hlsl侧的数据传递
- ShaderMaterial.h : 声明材质相关定义
- Material.cpp :Material 相关设置
- ShaderMaterialDerivedHelpers.cpp :声明处理相关材质parameter
- ShadingCommon.ush : Shading Common 数据 包括共用的ShadingModel 和 相关函数
- BasePassPixelShader.usf :Base Pass Pixel Shader 相关
- ShadingModelsMaterial.ush : 对GBuffer相关设置
- BasePassCommon.ush :BasePass相关共用数据声明
- DeferredShadingCommon.ush :声明GBuffer数据及相关DeferredRendering数据函数
- Definitions.usf : 定义各种材质的宏
- ClusteredDeferredShadingPixelShader.usf :Defer Rendering Pixel Shader相关光照处理
- 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 | case MP_CustomData0: |
在 ShaderMaterialDerivedHelpers.cpp 文件中修改Custom Node 相关映射。
1 | // Only some shader models actually need custom data. |
3.设置 GBuffer Shading Model ID
打开 ShadingCommone.ush 文件在Shading Model的定义中添加自己的定义
然后在GetShadingModelColor(uint ShadingModelID) 中修改 ShadingModel ID的颜色
然后在 BasePassPixelShader.usf 中找到GetMaterialShadingModel 并进行声明 ShadingModel
1 |
|
3.1将CustomData数据写入GBuffer
接下来打开 ShadingModelsMaterial.ush 。查看SetGBufferForShadingModel
函数。该函数控制不同shadingModel写入FGbufferData的数据。
1 |
|
这里使用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 | bool HasCustomGBufferData(int ShadingModelID) |
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
45FDirectLighting StylizedShadowBxDF(FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow)
{
half3 X = GBuffer.WorldTangent;
half3 Y = normalize(cross(N, X));
half3 X = 0;
half3 Y = 0;
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 ********************* //
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 ********************* //
FDirectLighting Lighting;
if (LightData.bRectLight)
{
FRect Rect = GetRect( ToLight, LightData );
Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos );
Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture);
}
else
{
FCapsuleLight Capsule = GetCapsule( ToLight, LightData );
Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, SVPos );
Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared );
}
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)
}
}
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