UE4(虚幻)学习笔记--虚幻4.26自定义shadermodel
渲染方面纯新手,记录一下学习过程中踩的坑
参考博客
这个博客是4.25的,变动不大,最接近4.26的修改方式
https://zhuanlan.zhihu.com/p/212785666
这个博客是4.19的,但是解释的更清楚一些
https://zhuanlan.zhihu.com/p/36840778
这个博客是介绍虚幻的shader开发技巧的
https://zhuanlan.zhihu.com/p/66288908
下载源码
虚幻想改shadermodel必须改源码版,不多说了
C++部分的源码修改
EngineTypes.h文件下添加一个枚举
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_SingleLayerWater UMETA(DisplayName="SingleLayerWater"),
MSM_ThinTranslucent UMETA(DisplayName="Thin Translucent"),
// 这里进行的修改
MSM_StylizedShadow UMETA(DisplayName="Stylized Shadow"),
/** Number of unique shading models. */
MSM_NUM UMETA(Hidden),
/** Shading model will be determined by the Material Expression Graph,
by utilizing the 'Shading Model' MaterialAttribute output pin. */
MSM_FromMaterialExpression UMETA(DisplayName="From Material Expression"),
MSM_MAX
};
HLSLMaterialTranslator.cpp文件下的GetMaterialEnvironment方法中添加一个宏
if (ShadingModels.HasShadingModel(MSM_StylizedShadow))
{
OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_STYLIZED_SHADOW"), TEXT("1"));
NumSetMaterials++;
}
Material.cpp中为我们的材质开放接口
case MP_CustomData0:
Active = ShadingModels.HasAnyShadingModel({ MSM_ClearCoat, MSM_Hair, MSM_Cloth, MSM_Eye,MSM_StylizedShadow });
break;
case MP_CustomData1:
Active = ShadingModels.HasAnyShadingModel({ MSM_ClearCoat, MSM_Eye,MSM_StylizedShadow });
break;
customdata0和customdata1都是0-1的float
MaterialShared.cpp中开放蓝图的针脚
if (ShadingModels.HasShadingModel(MSM_StylizedShadow))
{
OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_STYLIZED_SHADOW"), TEXT("1"));
NumSetMaterials++;
}
C++部分完成的最终效果
Shader部分
引擎的config目录下ConsoleVariables.ini文件将
r.ShaderDevelopmentMode=1
ShadingCommon.ush下添加一个宏并定义一个ShadingModel
#define SHADINGMODELID_STYLIZED_SHADOW 12
//PS4
else if (ShadingModelID == SHADINGMODELID_STYLIZED_SHADOW) return float3(.7f, 0.2f, 0.2f);
//其他
case SHADINGMODELID_STYLIZED_SHADOW: return float3(.7f, 0.2f, 0.2f);
测试用的话PS4的那个可以不改
查看ShadingModel
以上部分改完后在ShadingModel视图下应该是这个效果
然后在BasePassCommon.ush文件下修改宏,使得引擎可以将CustomData0和CustomData1写入GBuffer
#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_STYLIZED_SHADOW))
ShadingModelsMaterial.ush下,将数据赋值给GBuffer,GBuffer中的数据将由之后的BRDF调用
#if MATERIAL_SHADINGMODEL_STYLIZED_SHADOW
else if (ShadingModel == SHADINGMODELID_STYLIZED_SHADOW)
{
GBuffer.CustomData.x = saturate( GetMaterialCustomData0(MaterialParameters) ); // SpecularRange
GBuffer.CustomData.y = saturate( GetMaterialCustomData1(MaterialParameters) ); // Offstet
}
#endif
修改DeferredShadingCommon.ush的HasCustomGBufferData函数,加入之前定义的宏(我在这个地方踩坑了,参考博客里没有说修改这里,结果没把CustomData0,1的数据写入GBuffer,一直没做出效果)
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_STYLIZED_SHADOW;
}
ShadingModel.ush中定义BRDF并调用
定义
float3 ToonStep(float Range, float Input)
{
return smoothstep(0.5 - Range, 0.5 + Range, Input);
}
FDirectLighting StylizedShadowBxDF(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;
}
调用
case SHADINGMODELID_STYLIZED_SHADOW:
return StylizedShadowBxDF(GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow);
DeferredLightingCommon.ush中计算
/** Calculates lighting for a given position, normal, etc with a fully featured lighting model designed for quality. */
FDeferredLightingSplit GetDynamicLightingSplit(
float3 WorldPosition, 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(WorldPosition, 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;
GetShadowTerms(GBuffer, LightData, WorldPosition, 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;
//修改处1这里开始
float3 Attenuation = 1;
BRANCH
if (GBuffer.ShadingModelID == SHADINGMODELID_STYLIZED_SHADOW)
{
float offset = GBuffer.CustomData.y;
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;
}
}
//修改处1这里结束
#if NON_DIRECTIONAL_DIRECT_LIGHTING
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;
LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, LightColor * LightMask * Attenuation * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation);
#else
FDirectLighting Lighting;
if (LightData.bRectLight)
{
FRect Rect = GetRect(ToLight, LightData);
#if REFERENCE_QUALITY
Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos );
#else
Lighting = IntegrateBxDF(GBuffer, N, V, Rect, Shadow, SourceTexture);
#endif
}
else
{
FCapsuleLight Capsule = GetCapsule(ToLight, LightData);
#if REFERENCE_QUALITY
Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, SVPos );
#else
Lighting = IntegrateBxDF(GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared);
#endif
}
Lighting.Specular *= LightData.SpecularScale;
BRANCH
//修改处2这里开始
if (GBuffer.ShadingModelID == SHADINGMODELID_STYLIZED_SHADOW)
{
LightAccumulator_AddSplit(LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow * Attenuation * 0.25, bNeedsSeparateSubsurfaceLightAccumulation);
}
else
{
LightAccumulator_AddSplit(LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation);
}
//修改处2这里结束
LightAccumulator_AddSplit(LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, LightColor * LightMask * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation);
LightAccumulator.EstimatedCost += 0.4f; // add the cost of the lighting computations (should sum up to 1 form one light)
#endif
}
}
return LightAccumulator_GetResultSplit(LightAccumulator);
}
最后编译
编译shader快捷键是Ctrl+Shift+.
蓝图材质(记得SpecularRange和Offset两个变量不要调成0,要在0-1之间)
最后效果
最后吐槽一句,踩这个坑真的烦,少写了一句代码找了两天bug