Unreal Engine 4 渲染 (6) 添加新的着色模型 Adding a new Shading Model

https://medium.com/@lordned/ue4-rendering-part-6-adding-a-new-shading-model-e2972b40d72d

翻译:yarswang 转载请保留

添加新的着色模型

Unreal支持几种常见的着色模型,可满足大多数游戏的需求。Unreal支持广义的微面高光(microfacet specular)作为其默认的光照模型,但也具有支持高端头发和眼睛效果的光照模型。 这些着色模型可能不适合你的游戏,你可能希望对它们进行调整或添加全新的模型,尤其是对于高度风格化的游戏。

正在进行着色模型的工作,使用阶梯式照明

 

集成一个新的照明模型的代码是令人惊讶的少,但是需要耐心(因为它将需要(几乎)完整编译引擎和所有着色器)。 一旦你决定开始进行增量更改,请务必查看迭代章节,因为这可以帮助减少约10分钟的迭代时间。

 

本文中的大多数代码都基于FelixK在其博客系列中的优秀(但有些过时)信息,以及评论对各个帖子的一些更正。 非常推荐FelixK的博客,我已经浏览了一些着色器代码更改来更多地了解该过程以及我们为什么这样做。

 

我们需要修改引擎的三个不同区域以支持新的着色模型:材质编辑器,材质本身和现有的着色器代码。 我们将同时处理所有这些更改。

 

修改材质编辑器

我们的第一站是EngineTypes.h内的EMaterialShadingModel枚举。 此枚举确定什么内容在“材质编辑器”内的“着色模型(Shading Model)”下拉列表中显示。 我们将在MSM_MAX之前添加新的枚举条目MSM_StylizedShadow到枚举中

 

// Note: Check UMaterialInstance::Serialize if changed!
UENUM()
enum EMaterialShadingModel
{
// … Previous entries omitted for brevity
MSM_Eye UMETA(DisplayName=”Eye”),
MSM_StylizedShadow UMETA(DisplayName=”Stylized Shadow”),
MSM_MAX,
};

 

枚举似乎是按名称序列化的(是这样吗?),但是对于引擎的任何可能按整数值序列化的部分,则应该添加到列表的末尾。

 

Epic在EMaterialShadingModel枚举上方留下了一条注释,警告开发人员在我们更改枚举时检查UMaterialInstance::Serialize函数。 添加一个新的着色模型似乎没有任何东西需要改变,所以我们可以忽略它并继续。 (如果你对该函数的作用感到好奇,看起来它们确实在某一点上更改了枚举值的顺序,因此该函数有一些代码可以根据被加载资源的版本来修复它。)

添加一个下拉列表是如此的容易

 

完成此更改后,如果我们编译它,新的着色模型将显示在“材质编辑器”内的“着色模型(Shading Model)”下拉列表中,但它没有任何功能! FelixK使用Custom Data 0引脚允许美术同学来设置光衰减范围的大小。我们需要修改代码以使自定义着色模型启用Custom Data 0引脚。

 

打开Material.cpp(不要与Lightmass项目中同名文件混淆)并查找UMaterial::IsPropertyActive函数。 此函数会针对于材质上的每个可能的引脚执行。如果你尝试修改材质区域(例如贴花decal,后期处理post processing等),则需要特别注意此函数的第一部分,它们会查看每个域并简单确定哪些引脚被启用。如果你像我们一样修改着色模型,那么它有点复杂 – 对于每个引脚,如果在给定其他属性时应该是活动的,则有一个switch语句为其返回true

 

在我们的例子中,我们想要启用MP_CustomData0引脚,所以向下滚动到MP_CustomData0上的部分并添加 || ShadingModel == MSM_StylizedShadow到它的结尾。将“着色模型”更改为 ”Stylized Shadow” 时,此引脚应启用,允许你将材质图连接到该引脚。

 

switch (InProperty)

{

// Other cases omitted for brevity

case MP_CustomData0:

Active = ShadingModel == MSM_ClearCoat || ShadingModel == MSM_Hair || ShadingModel == MSM_Cloth || ShadingModel == MSM_Eye || ShadingModel == MSM_StylizedShadow;

break;

}

 

重要的是要了解此代码仅更改材质编辑器中的UI,你仍需要确保使用着色器中提供给这些引脚的数据。

 

附注:Custom Data 0 Custom Data 1 是单通道浮点属性,对于自定义着色模型,不确定是否能提供足够的额外数据。 Javad Kouchakzadeh向我指出,你可以创建全新的引脚,让你可以选择如何为它们生成HLSL代码。 不幸的是,这个范围有点超出了本教程,但可能是未来教程的主题。 如果你喜欢冒险,可以查看MaterialShared.cppInitializeAttributeMap() 函数!

 

修改HLSL预处理器定义

一旦修改了材质编辑器以便能够选择我们的着色模型,我们需要确保着色器知道它们何时被设置为使用我们的着色模型!

 

打开MaterialShared.cpp并查找FMaterial :: SetupMaterialEnvironment(EShaderPlatform Platform, const FUniformExpressionSet& InUniformExpressionSet, FShaderCompilerEnvironment& OutEnvironment) const函数。 此函数允许你查看各种配置因素(例如材料上的属性),然后通过添加其他定义来修改OutEnvironment变量。

 

向下滚动到GetShadingModel()的switch语句并添加我们的MSM_StylizedShadow case(来自EngineTypes.h)并在现有模式后面给它一个字符串名称。

 

switch(GetShadingModel())

{

// Other cases omitted for brevity

case MSM_Eye:

OutEnvironment.SetDefine(TEXT(“MATERIAL_SHADINGMODEL_EYE”), TEXT(“1”)); break;

case MSM_StylizedShadow:

OutEnvironment.SetDefine(TEXT(“MATERIAL_SHADINGMODEL_STYLIZED_SHADOW”), TEXT(“1”)); break;

}

 

现在,当材质的着色模型设置为MSM_StylizedShadow时,HLSL编译器会将MATERIAL_SHADINGMODEL_STYLIZED_SHADOW设置为预处理器定义。 这将允许我们稍后在HLSL代码中使用#if MATERIAL_SHADINGMODEL_STYLIZED_SHADOW来制作仅适用于使用我们的着色模型的着色器排列的东西。

 

综述

以上是C++代码所需的修改。 我们已经将我们的渲染模型添加到编辑器的下拉列表中,我们已经更改了用户可以插入数据的引脚,并且确保了生成的着色器可以辨识何时处于该模式。 编译引擎,来上一杯咖啡 - 修改EngineTypes.h将导致大部分C++代码被重新编译。 在对.ush / .usf文件进行更改之前,不要运行编辑器,因为修改它们会导致所有着色器重新编译!

 

更新GBuffer着色模型ID

现在可以确定何时构建使用我们的光照模型的着色器排列(通过MATERIAL_SHADINGMODEL_STYLIZED_SHADOW,我们可以开始对着色器进行更改)。我们需要做的第一件事是将新的着色模型ID写入 GBuffer。这允许DeferredLightPixelShader知道在运行光照计算时要尝试使用哪种着色模型。

 

打开DeferredShadingCommon.ush,中间有一个以#define SHADINGMODELID_UNLIT开头的部分。 在它的末尾添加自己的着色模型ID,然后更新SHADINGMODELID_NUM

 

#define SHADINGMODELID_EYE 9

#define SHADINGMODELID_STYLIZED_SHADOW 10

#define SHADINGMODELID_NUM 11

 

我们需要告诉着色器将此着色模型ID写入GBuffer,但在离开此文件之前,我们应该更新缓冲区可视化>着色模型颜色,以便你可以使用着色模型判断场景中的哪些像素。 在文件的底部应该是float3 GetShadingModelColor(uint ShadingModelID)

 

我们将在#if PS4_PROFILE部分中添加一个条目,并在现有模式之后添加switch (ShadingModelID) 。 我们选择紫色只是因为原始教程也是如此。

 

switch(ShadingModelID)

{

// Omitted for brevity

case SHADINGMODELID_EYE: return float3(0.3f, 1.0f, 1.0f);

case SHADINGMODELID_STYLIZED_SHADOW: return float3(0.4f, 0.0f, 0.8f); // Purple

}

 

float3(0.4f, 0.0f, 0.8f)

 

现在我们需要告诉BasePassPixelShader将正确的ID写入Shading Model ID纹理。 打开ShadingModelsMaterial.ush并查看SetGBufferForShadingModel函数。 此函数允许每个着色模型选择如何将各种PBR数据通道写入FGBufferData结构。 你唯一需要做的就是确保GBuffer.ShadingModelID被赋值。 如果我们希望使用材质编辑器中的Custom Data 0通道,那么就在此处查询该值并将其写入GBuffer。

 

#elif MATERIAL_SHADINGMODEL_EYE

GBuffer.ShadingModelID = SHADINGMODELID_EYE;

// Omitted for brevity

#elif MATERIAL_SHADINGMODEL_STYLIZED_SHADOW

GBuffer.ShadingModelID = SHADINGMODELID_STYLIZED_SHADOW;

GBuffer.CustomData.x = GetMaterialCustomData0(MaterialParameters);

#else

// missing shading model, compiler should report ShadingModelID is not set

#endif

 

我们之前通过更改C++代码在编辑器中启用了Custom Data 0引脚。 调用GetMaterialCustomData0(...) 实际获取值并将其存储在GBuffer中,以便稍后在我们的着色模型中读取它。 如果你正在使用GBuffer的CustomData部分,则需要打开BasePassCommon.ush并将你的MATERIAL_SHADINGMODEL_STYLIZED_SHADOW添加到#define WRITES_CUSTOMDATA_TO_GBUFFER部分的末尾。 这是一种优化,如果着色模型不使用它,Unreal将忽略对自定义数据缓冲区的写入或采样。

 

改变衰减计算

到目前为止,我们只专注于添加新的着色模型并处理添加它所需的各种样板代码。 现在我们将着眼于修改使用阴影模型时如何计算光衰减。 为此,我们将打开DeferredLightingCommon.ush并找到GetDynamicLighting函数。

 

Unreal使用以下计算来确定最终光倍数:LightColor *(NoL * SurfaceAttenuation)。 NoL(N点乘L)产生平滑的梯度,这不是我们想要的。 我们将创建一个新的光衰减变量,并根据我们的着色模型修改该值。 然后我们将更新现有的函数调用以使用我们的新衰减变量来避免重复代码。 通过函数的大部分方式应该是在两次调用LightAccumulator_Add之前调用AreaLightSpecular函数的部分(一次用于累积曲面,一次用于累积次曲面)。 我们将添加以下代码块:

 

float3 AttenuationColor = 0.f;

BRANCH

if(ShadingModelID == SHADINGMODELID_STYLIZED_SHADOW)

{

float Range = GBuffer.CustomData.x * 0.5f;

AttenuationColor = LightColor * ((DistanceAttenuation * LightRadiusMask * SpotFalloff) * smoothstep(0.5f — Range, 0.5f + Range, SurfaceShadow) * 0.1f);

}

else

{

AttenuationColor = LightColor * (NoL * SurfaceAttenuation);

}

 

然后我们需要替换对LightAccumulator_Add的调用以使用我们新的AttenuationColor变量。

 

// accumulate surface

{

float3 SurfaceLighting = SurfaceShading(GBuffer, LobeRoughness, LobeEnergy, L, V, N, Random);

LightAccumulator_Add(LightAccumulator, SurfaceLighting, (1.0/PI), AttenuationColor, bNeedsSeparateSubsurfaceLightAccumulation);

}

 

你会注意到我们必须使用动态分支(if语句)而不是预处理器定义。 DeferredLightingCommon.ush中的像素着色器针对每个灯光运行,这些灯光可以影响具有多个着色模型的多个对象; 这阻止我们使用预处理器定义,因此我们不得不使用动态分支来检查GBuffer纹理通道中的ID。

 

更改表面着色

现在我们已经声明了新的照明模型,你还应该修改表面着色功能。 如果你没有声明新的表面着色模型,它会将像素视为黑色,因此你至少需要在switch函数内添加case以使用标准着色。

 

打开ShadingModels.ush并转到底部的SurfaceShading函数。 我们将在交换机中添加一个新条目,然后声明该函数以供使用。

 

float3 SurfaceShading( FGBufferData GBuffer, float3 LobeRoughness, float3 LobeEnergy, float3 L, float3 V, half3 N, uint2 Random )

{

 switch( GBuffer.ShadingModelID )

 {

  case SHADINGMODELID_UNLIT:

  return StandardShading( GBuffer.DiffuseColor, GBuffer.SpecularColor, LobeRoughness, LobeEnergy, L, V, N );

  // Others omitted for brevity

  case SHADINGMODELID_STYLIZED_SHADOW:

  return StylizedShadowShading(GBuffer, LobeRoughness, L, V, N);

 }

}

 

然后我们声明我们的StylizedShadowShading函数:

 

float3 StylizedShadowShading( FGBufferData GBuffer, float3 Roughness, float3 L, float3 V, half3 N)

{

 float Range = GBuffer.CustomData.x * 0.5f;

 float3 H = normalize(V+L);

 float NoH = saturate( dot(N, H));

 return GBuffer.DiffuseColor + saturate(smoothstep(0.5f — Range, 0.5f + Range, D_GGX(Roughness.y, NoH)) * GBuffer.SpecularColor);

}

 

支持点亮半透明

如果希望我们的着色器适用于必须为其添加特定支持的半透明对象 - 打开BasePassPixelShader.usf并找到带有注释“// Volume lighting for lit translucency”的部分,并将着色模型添加到#if语句中。

 

//Volume lighting for lit translucency

#if (MATERIAL_SHADINGMODEL_DEFAULT_LIT || MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_STYLIZED_SHADOW) && (MATERIALBLENDING_TRANSLUCENT || MATERIALBLENDING_ADDITIVE) && !SIMPLE_FORWARD_SHADING && !FORWARD_SHADING)

Color += GetTranslucencyVolumeLighting(MaterialParameters, PixelMaterialInputs, BasePassInterpolants, GBuffer, IndirectIrradiance);

#endif

 

此时重新启动编辑器并重新编译所有着色器是个好主意。 如果你想继续调整着色器,最好查看Iteration文章来了解如何减少着色器重新编译时间!

 

综述

我们修改了BasePassPixelShaders,以便将正确的ID写入GBuffer的ID通道,然后我们通过对GBuffer中的ID进行采样来修改光照衰减和表面着色以使用我们的新着色模型。

 

下章

在下一个教程中,我们将介绍如何通过向延迟基本传递添加几何着色器来创建轮廓着色器。 这包括如何添加着色器舞台以及如何修改现有的基本着色器着色器以处理从“顶点着色器”到“像素着色器”的操作,还可以处理“顶点着色器”到“几何着色器”到“像素着色器”。

 

你可能感兴趣的:(Unreal,Engine,4,Rending)