UE4移动端卡通渲染

前期准备

最近学习UE4,尝试改UE4引擎,做一个卡通渲染的shading model。使用的UE4版本是4.19。

首先需要有源码版的UE4引擎,根据这篇UE4官方文档,去Github把源码pull下来。然后根据这篇官方文档从源码构建引擎。我这里在vs上踩了坑,除了要装c++桌面开发的unreal相关外,还要在.NET桌面开发中勾上.NET Framework 4.6.2。以及可以根据这篇博文做一些修改,装一些实用工具,其中还有六篇教程,对新手入门改UE4源码非常有帮助。

接下来主要参考这篇知乎的文章,实际上他也是根据上面Medium的教程做的。但是这篇教程是针对PC端的,PC端和Mobile使用的是完全不一样的渲染管线,这里新做一个shading model参考一下他们的做法,然后自己研究了一下改mobile。

添加新的shading model

在以下文件中分别做如下修改

EngineTypes.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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"),
MSM_Toon UMETA(DisplayName = "Toon"),
MSM_MAX,
};

MaterialShared.cpp

1
2
3
4
5
6
7
8
switch(GetShadingModel())
{
...
case MSM_Eye: OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_EYE"), TEXT("1")); break;
case MSM_Toon: OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_TOON"), TEXT("1")); break;
default:
...
};

DeferredShadingCommon.ush

1
2
3
4
5
6
7
8
9
10
11
12
13
#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_TOON 10
#define SHADINGMODELID_NUM 11
#define SHADINGMODELID_MASK 0xF

这样就可以使用自己的shading model了

接口名称修改

在Material.cpp中想要的接口处添加自己的shading model,如想要customdata0接口,这样添加这个接口就被激活了

1
2
case MP_CustomData0:
Active = ShadingModel == MSM_ClearCoat || ShadingModel == MSM_Hair || ShadingModel == MSM_Cloth || ShadingModel == MSM_Eye || ShadingModel == MSM_Toon;

然后修改接口的名字,在MaterialGragh.cpp中,比如修改metallic接口名,找到这个函数,改成这样

1
2
3
4
5
6
7
8
9
10
FText UMaterialGraph::GetMetallicPinName() const
{
switch (Material->GetShadingModel())
{
case MSM_Toon:
return LOCTEXT("ShadowStrength", "Shadow Strength");
default:
return Material->GetShadingModel() == MSM_Hair ? LOCTEXT("Scatter", "Scatter") : LOCTEXT("Metallic", "Metallic");
}
}

想改specular或roughness的话,由于没有这个函数,需要自己添加,添加后在此处调用函数

1
2
3
4
MaterialInputs.Add( FMaterialInputInfo( GetBaseColorPinName(),  MP_BaseColor, LOCTEXT( "BaseColorToolTip", "Defines the overall color of the  Material. Each channel is automatically clamped between 0 and 1" ) ) );
MaterialInputs.Add( FMaterialInputInfo( GetMetallicPinName(), MP_Metallic, LOCTEXT( "MetallicToolTip", "Controls how \"metal-like\" your surface looks like") ) );
MaterialInputs.Add( FMaterialInputInfo( GetSpecularPinName(), MP_Specular, LOCTEXT("SpecularToolTip", "Used to scale the current amount of specularity on non-metallic surfaces and is a value between 0 and 1, default at 0.5") ) );
MaterialInputs.Add( FMaterialInputInfo( GetRoughnessPinName(), MP_Roughness, LOCTEXT( "RoughnessToolTip", "Controls how rough the Material is. Roughness of 0 (smooth) is a mirror reflection and 1 (rough) is completely matte or diffuse" ) ) );

另外在MaterialGragh.h中需要添加新函数的声明,这样接口就变成了这样

贴图采样

mobile使用的就是前向渲染,主要实现基本在MobileBasePassPixelShader.usf中,用宏MATERIAL_SHADINGMODEL_TOON控制toon要进行的计算和不需要进行的,关键的几个数据N、L、V、Shadow原本代码里就已经算过了,可以直接用,关键在于如何自己采样贴图,想办法获取到从接口输入的贴图。

先顺着basecolor的来源,发现来源于PixelMaterialInputs中的数据

1
2
3
4
half3 GetMaterialBaseColorRaw(FPixelMaterialInputs PixelMaterialInputs)
{
return PixelMaterialInputs.BaseColor;
}

然后发现PixelMaterialInputs的定义是这样的

1
2
3
4
struct FPixelMaterialInputs
{
%s
};

%s处是根据用户的蓝图填充进去的,于是自己连了一个材质,看了一下生成的hlsl,找到一处关键代码

1
2
MaterialFloat4 Local0 = ProcessMaterialColorTextureLookup(Texture2DSampleBias(Material.Texture2D_0,Material.Texture2D_0Sampler,Parameters.TexCoords[0].xy,View.MaterialTextureMipBias));
PixelMaterialInputs.BaseColor = Local0.rgb;

于是在MobileBasePassPixelShader.usf中尝试把Material.Texture2D_0当贴图用,结果真的可以直接用,后面的编号应该是内存中贴图的顺序,先被接入贴图的节点中的贴图排前面,比如emissive节点先被接入一张贴图,那么它就是_0,后面接入新的贴图节点只是给原来的_0贴图重新赋值。所以使用这个shading model的话要注意贴图的连入顺序。应该是有个地方有绑定之类的操作,但这样改起来非常复杂,可以直接给引擎组的人提需求了……

最后就可以使用了,这是一个使用ramp图做暗部映射,另外添加一个边缘光的卡通shading model