尝试在UE4.22中实现罪恶装备Xrd的卡通渲染

尝试在UE4.22中实现罪恶装备Xrd的卡通渲染

零、前言


最近尝试尝试给UE4添加一个用于三渲二的新的光照模型,主要参考了知乎文章和这篇博客教程,以及GDC2015罪恶装备Xrd制作团队的演讲及其PPT内容。

一、获取美术资源


由于是完全照抄罪恶装备Xrd的渲染方法因此首先要获取到相关的美术资源。需要的解包软件有:

  1. psarc——一个可以将PS3加密格式文件进行解密的工具。
  2. umodel——一个用于解包虚幻引擎制作的游戏的工具。
  3. 罪恶装备Xrd的PS3版本游戏文件。

资源获取方法,以win10+罪恶装备Xrd的PS3镜像文件为例:

1、首先打开镜像文件,在此目录下找到DATA00.PSARC文件,并将其拷贝到psarc.exe所在的目录:

2、在psarc.exe所在目录中,按住shift+鼠标右键打开Powershell:

3、接下来输入下图所示内容进行解密:

4、你会得到一个叫做REDGame的文件夹,接下来使用umodel打开这个文件夹(就是把这个文件夹拖到umodel上):

5、一般前三个字母代表角色名称、后面三个时资源类型,比如第一个文件就是AXL的编号为0108的材质文件,选中后点击Export可以获得纹理(模型也是相同的流程,后缀是MSH,解包出来后还需要导入到3DMax中再导出成FBX才能直接导入到UE4引擎中):

二、日式卡通渲染基础


这一章节简单讲讲如何去使用获得的美术资源,详细内容可以去看GDC2015的讲座以及PPT。

1、Vertexcolor(解包得到的模型资源似乎丢失了相关信息)

  • R通道:控制角色的阴影倾向系数,即:在光线与法线夹角相同的情况下,该值越大则该顶点附近的区域越容易处于阴影状态。
  • G通道:轮廓线随着摄像机距离的膨胀系数。
  • B通道:轮廓线的ZOffset值,该值越大描边越不可见。
  • A通道:控制描边的粗细程度。

2、SSS纹理、Base纹理、ILM纹理

  • Base纹理——提供角色的BaseColor信息。
  • SSS纹理——提供次表面散射的颜色信息。
  • ILM纹理——R通道:高光系数(Blinn-Phong光照模型中的镜面反射指数系数);G通道:阴影系数,与VertexColor的R通道共同工作;B通道:高光区域;A通道:内部轮廓线的颜色。

三、给UE4添加新的ShadingModel


1、首先我们要让材质编辑器知道有这么一个新的ShadingModel:

2、通知Shader我们添加了一个新的光照模型(给Shader声明了一个宏):

3、由于我们要传输大量的数据给Shader,而UE4的材质系统只给了我们两个half4的Custom接口以及UE4的延迟渲染的GBuffer只有一个float4允许你自由使用,所以显然并不够用,因此我使用了Metallic、Specular、Roughness、Custom0来传输ILM纹理的RGBA通道数据,使用Subsurface来传输SSS纹理数据:

第一张图的主要作用是设置材质系统认为我们的卡通渲染模型属于次表面渲染模型的一种,否则材质系统翻译材质蓝图的时候不会给Subsurface这个值进行赋值,第二张图是告诉材质系统我们要在材质蓝图中用custom0和SubsurfaceColor这两个接口。

为了方便在材质蓝图中辨认接口,我们可以改编接口的名称:

上面四张图可能有些不太清晰,简单说一下,其实就是找到对应接口名称的XXXPinName函数进行更改,由于Specular、Roughness没有相关的XXXPinName函数,于是我们可以自己声明对应的函数,自定义其名称。

四、在Shader中添加相应的ShadingModel计算逻辑


首先,我们要在Shader单中定义一个ShadingModel与C++中定义的枚举相对应:

然后要让自定义的光照模型允许使用GBufferD,即CustomData:

由于这句代码比较长,在这里给出完整代码:

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

既然已经允许往GBuuferD中写入数据,那么接下来就是决定要写什么内容:

(1)、

上图表示,我们首先要将传输到SubsurfaceColor接口的数据赋值给真正的输出变量。完整代码如下:

#if MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN || MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE || MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE || MATERIAL_SHADINGMODEL_CLOTH || MATERIAL_SHADINGMODEL_EYE || MATERIAL_SHADINGMODEL_TOON
{
    float4 SubsurfaceData = GetMaterialSubsurfaceData(PixelMaterialInputs);

    #if MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN || MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE
        SubsurfaceColor = SubsurfaceData.rgb * View.DiffuseOverrideParameter.w + View.DiffuseOverrideParameter.xyz;
    #elif MATERIAL_SHADINGMODEL_CLOTH || MATERIAL_SHADINGMODEL_TOON
        SubsurfaceColor = SubsurfaceData.rgb;
    #endif
        SubsurfaceProfile = SubsurfaceData.a;
}
#endif

(2)、

上图表示,将SubsurfaceColor与Custom0合并成一个float4,并赋值到GBufferD代表的CustomData。

接下来由于我们使用了Metallic、Specular、Roughness,所以会参与到间接反射的计算当中,我们要找到对应的代码修改一下:

由于间接反射与光照计算是叠加关系,所以直接输出(0.0,0.0,0.0)即可。

------------------------------------------------------------------------2019.5.17修改-------------------------------------------------------------------------------------------------

还要注意的是,Metallic在编码GBuffer之前参与到了其他渲染逻辑的计算,需要将对应代码进行修改(不修改的话,会发现调整SpecularStrength数值后,角色身上会出现色块),如图:

---------------------------------------------------------------------------------修改结束----------------------------------------------------------------------------------------------

终于到了最后一步,获取我们所需要的数据开始计算表面颜色:

完整代码如下:

BRANCH
if (GBuffer.ShadingModelID == SHADINGMODELID_TOON)
{
    float3 LightColor = LightData.Color;

    float3 SSS = float3(GBuffer.CustomData.r, GBuffer.CustomData.g, GBuffer.CustomData.b);
    float SpecStrength = GBuffer.Metallic;
    float ShadowThreshold = GBuffer.Specular;
    float SpecularSize = GBuffer.Roughness;
    float InnerLine = GBuffer.CustomData.a;

    // 纹理数据处理
    float3 BrightColor = GBuffer.BaseColor;
    float3 ShadowColor = GBuffer.BaseColor * SSS;

    // 这里我觉得内部描边的颜色不够明显,于是乘上了0.5
    if (InnerLine < 0.8f)
    {
        InnerLine *= 0.5f;
    }
    float3 InnerLineColor = float3(InnerLine, InnerLine, InnerLine);

    float NdotL = dot(L, N) * Shadow.SurfaceShadow;
    float NdotH = dot(normalize(L + V), N);

    ShadowThreshold = step(ShadowThreshold, NdotL);

    float3 Diffuse = InnerLineColor * LightColor * lerp(ShadowColor, BrightColor, ShadowThreshold);
    float3 Specular = LightColor * BrightColor * ShadowThreshold * InnerLineColor * step(0.2f, SpecularSize * pow(NdotH, SpecStrength));

    LightAccumulator.TotalLight = Diffuse + Specular;
}
else
{
    //原本的代码
    ......
}

五、外轮廓描边实现


罪恶装备的外部描边是通过模型膨胀法实现的,这种方式在Unity3D中很好实现,只需要在Shader中添加一个pass即可,可是在UE4中,由于顶点在提交给RHI渲染时,已经进行了合并,想要在Renderer中加一个Pass来做描边似乎是不可能的(太菜不知道怎么改),于是我想的办法是在蓝图中,给Character这个类的SkeletalMesh组件附加一个PoseableMesh组件,当作多Pass渲染,如图:

膨胀模型外描边顾名思义,要将模型根据法线方向进行膨胀,然后只绘制背面,其中第一步很好做,第二部由于UE4并不支持只绘制背面,所以我们要稍做修改:

首先,声明一个新的变量,方便我们在材质中直接设置:

第二步,由于UE4的Material相关的类都是继承自MaterialInterface,所以我们需要添加一个接口,方便在绘制逻辑中查询该材质是否只绘制背面:

接下来要做的事情就很简单了,在Material和MaterialInstance中的对应地方重写IsOnlyBackFace这个函数即可(一个很简单的方法,在对应的.h、.cpp文件中查找IsTwoSided,然后在下方添加对应的函数即可,这里为了缩减篇幅,就不给出截图了)。

最后一步,在合并模型之前,设置ReverseCulling这个变量,找到SkeletaMesh.cpp这个文件,在该文件的GetDynamicElementsSection函数中找到Mesh.ReverseCulling进行修改:

六、材质蓝图截图


光照渲染:

外轮廓描边:

七、效果



编辑于 2019-05-17