UE4(虚幻)学习笔记--虚幻4.26自定义shadermodel

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

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值