28 KiB
Raw Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
GBuffer&Material&BasePass 2023-12-08 17:34:58

# GBuffer

目前UE5.3会调用

  • WriteGBufferInfoAutogen()
    • EncodeGBufferToMRT() 动态生成BasePassPixelShader.usf中的EncodeGBufferToMRT() 的代码并且会生成一个AutogenShaderHeaders.ush文件。其路径为 Engine\Intermediate\ShaderAutogen\PCD3D_SM5或者Engine\Intermediate\ShaderAutogen\PCD3D_ES3_1
  1. 给FGBufferData添加结构体数据时需要在此添加额外代码逻辑
  2. GBuffer精度在FetchLegacyGBufferInfo()设置。
  3. 是否往GBuffer中写入Velocity主要靠这个宏WRITES_VELOCITY_TO_GBUFFER。具体决定其数值的逻辑位于FShaderGlobalDefines FetchShaderGlobalDefines。主要还是靠r.VelocityOutputPass进行开启。
    1. PS. MSAA以及VR绝对不会开启Velocity输出选项。还有就是r.Velocity.ForceOutput但经过测试不开启r.VelocityOutputPass依然无法输出。以及FPrimitiveSceneProxy的bAlwaysHasVelocity与bHasWorldPositionOffsetVelocity。
    2. 其他相关FSR、TSR
  4. 如何添加GBuffer
    1. https://zhuanlan.zhihu.com/p/568775542
    2. https://zhuanlan.zhihu.com/p/677772284

UE5 GBuffer内容

UE GBuffer存储数据

OutGBufferA(MRT1) = WorldNormal/PerObjectGBufferData                                        (GBT_Float_16_16_16_16/GBT_Unorm_11_11_10/GBT_Unorm_8_8_8_8)                                   
OutGBufferB(MRT2) = Metallic/Specular/Roughness/EncodeShadingModelIdAndSelectiveOutputMask  (GBT_Float_16_16_16_16/GBT_Unorm_8_8_8_8)
OutGBufferC(MRT3) = BaseColor/GBufferAO                                                     (GBT_Unorm_8_8_8_8)
OutGBufferD = GBuffer.CustomData                                                            (GBT_Unorm_8_8_8_8)
OutGBufferE = GBuffer.PrecomputedShadowFactors                                              (GBT_Unorm_8_8_8_8)
TargetVelocity / OutGBufferF = velocity / tangent                                           (默认不开启 带有深度<开启Lumen与距离场 或者 开启光线追踪> GBC_Raw_Float_16_16_16_16 不带深度  GBC_Raw_Float_16_16)
TargetSeparatedMainDirLight = SingleLayerWater相关                                          (SingleLayerWater才会开启 GBC_Raw_Float_11_11_10)

// 0..1, 2 bits, use CastContactShadow(GBuffer) or HasDynamicIndirectShadowCasterRepresentation(GBuffer) to extract  
half PerObjectGBufferData;

GBuffer相关信息精度、顺序可以参考FetchLegacyGBufferInfo()。

  • 不存在Velocity与Tangent:
    • OutGBufferD(MRT4)
    • OutGBufferD(MRT5)
    • TargetSeparatedMainDirLight(MRT6)
  • 存在Velocity
    • TargetVelocity(MRT4)
    • OutGBufferD(MRT5)
    • OutGBufferE(MRT6)
    • TargetSeparatedMainDirLight(MRT7)
  • 存在Tangent
    • OutGBufferF(MRT4)
    • OutGBufferD(MRT5)
    • OutGBufferE(MRT6)
    • TargetSeparatedMainDirLight(MRT7)

几个动态MRT的存在条件与Shader判断宏

  • OutGBufferE(PrecomputedShadowFactors)r.AllowStaticLighting = 1
    • GBUFFER_HAS_PRECSHADOWFACTOR
    • WRITES_PRECSHADOWFACTOR_ZERO
    • WRITES_PRECSHADOWFACTOR_TO_GBUFFER
  • TargetVelocity(IsUsingBasePassVelocity(Platform) || Layout == GBL_ForceVelocity) ? 1 : 0;//r.VelocityOutputPass = 1
    • r.VelocityOutputPass = 1时会对骨骼物体以及WPO材质物体输出速度。因为大概率会使用距离场阴影以及VSM所以会占用GBuffer Velocity所有通道。
    • GBUFFER_HAS_VELOCITY
    • WRITES_VELOCITY_TO_GBUFFER
  • SingleLayerWater
    • 默认不会写入GBuffer需要符合以下条件const bool bNeedsSeparateMainDirLightTexture = IsWaterDistanceFieldShadowEnabled(Parameters.Platform) || IsWaterVirtualShadowMapFilteringEnabled(Parameters.Platform);
      • r.Water.SingleLayer.ShadersSupportDistanceFieldShadow = 1
      • r.Water.SingleLayer.ShadersSupportVSMFiltering = 1
    • const bool bIsSingleLayerWater = Parameters.MaterialParameters.ShadingModels.HasShadingModel(MSM_SingleLayerWater);
  • Tangentfalse目前单独使用另一组MRT来存储。
    • ~GBUFFER_HAS_TANGENT`

ToonGBuffer修改&数据存储

OutGBufferA:PerObjectGBufferData => 可以存储额外的有关Tonn渲染功能参数
OutGBufferB:Metallic/Specular/Roughness => 
			? / SpcularPower(控制高光亮度与Mask) / ? / ?
			//ToonHairMask OffsetShadowMask/SpcularMask/SpecularValue	
OutGBufferC:GBufferAO =>
			ToonAO
OutGBufferD:CustomData.xyzw => 
			ShadowColor.rgb / NoLOffset //ShadowColor这里可以在Material里通过主光向量、ShadowStep、Shadow羽化计算多层阴影效果。
OutGBufferE:GBuffer.PrecomputedShadowFactors.xyzw => 
			ToonDataID/ ToonOutlineDataID / OutlineMask(控制Outline绘制以及Outline强度) / ToonObjectID(判断是否是一个物体)
TargetVelocity / OutGBufferF = velocity / tangent //目前先不考虑输出Velocity的情况
			? / ? / ? / ?

ToonDataID在材质编辑器中会存在SubsurfaceColor.a中ToonOutlineDataID在材质编辑器中会存在CustomData1引脚名为ToonBufferB考虑到Subsurface有一个CurvatureMap需要使用CustomData0所以这里使用了CustomData1

蓝色协议的方案 !蓝色协议的方案#GBuffer

额外添加相关宏逻辑位于ShaderCompiler.cpp

  • GBUFFER_HAS_TOONDATA

修改GBuffer格式

BasePassRendering.cpp中ModifyBasePassCSPSCompilationEnvironment()

void ModifyBasePassCSPSCompilationEnvironment()
{
...
	const bool bOutputVelocity = (GBufferLayout == GBL_ForceVelocity) ||  
	    FVelocityRendering::BasePassCanOutputVelocity(Parameters.Platform);  
	if (bOutputVelocity)  
	{  
	    // As defined in BasePassPixelShader.usf. Also account for Strata setting velocity in slot 1 as described in FetchLegacyGBufferInfo.  
	    const int32 VelocityIndex = Strata::IsStrataEnabled() ? 1 : (IsForwardShadingEnabled(Parameters.Platform) ? 1 : 4);  
	    OutEnvironment.SetRenderTargetOutputFormat(VelocityIndex, PF_G16R16);  
	}
...
	const bool bNeedsSeparateMainDirLightTexture = IsWaterDistanceFieldShadowEnabled(Parameters.Platform) || IsWaterVirtualShadowMapFilteringEnabled(Parameters.Platform);
	if (bIsSingleLayerWater && bNeedsSeparateMainDirLightTexture)
	{
		// See FShaderCompileUtilities::FetchGBufferParamsRuntime for the details
		const bool bHasTangent = false;
		static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
		bool bHasPrecShadowFactor = (CVar ? (CVar->GetValueOnAnyThread() != 0) : 1);

		uint32 TargetSeparatedMainDirLight = 5;
		if (bOutputVelocity == false && bHasTangent == false)
		{
			TargetSeparatedMainDirLight = 5;
			if (bHasPrecShadowFactor)
			{
				TargetSeparatedMainDirLight = 6;
			}
		}
		else if (bOutputVelocity)
		{
			TargetSeparatedMainDirLight = 6;
			if (bHasPrecShadowFactor)
			{
				TargetSeparatedMainDirLight = 7;
			}
		}
		else if (bHasTangent)
		{
			TargetSeparatedMainDirLight = 6;
			if (bHasPrecShadowFactor)
			{
				TargetSeparatedMainDirLight = 7;
			}
		}
		OutEnvironment.SetRenderTargetOutputFormat(TargetSeparatedMainDirLight, PF_FloatR11G11B10);
...
}

GBufferInfo.cpp中的FetchLegacyGBufferInfo()

控制GBuffer精度以及数据打包情况。

ShaderMaterialDerivedHelpers.cpp中的CalculateDerivedMaterialParameters()

else if (Mat.IS_BASE_PASS)
	{
		Dst.PIXELSHADEROUTPUT_BASEPASS = 1;
		if (Dst.USES_GBUFFER)
		{
			Dst.PIXELSHADEROUTPUT_MRT0 = (!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || Dst.NEEDS_BASEPASS_VERTEX_FOGGING || Mat.USES_EMISSIVE_COLOR || SrcGlobal.ALLOW_STATIC_LIGHTING || Mat.MATERIAL_SHADINGMODEL_SINGLELAYERWATER);
			Dst.PIXELSHADEROUTPUT_MRT1 = ((!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || !Mat.MATERIAL_SHADINGMODEL_UNLIT));
			Dst.PIXELSHADEROUTPUT_MRT2 = ((!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || !Mat.MATERIAL_SHADINGMODEL_UNLIT));
			Dst.PIXELSHADEROUTPUT_MRT3 = ((!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || !Mat.MATERIAL_SHADINGMODEL_UNLIT));
			if (SrcGlobal.GBUFFER_HAS_VELOCITY || SrcGlobal.GBUFFER_HAS_TANGENT)
			{
				Dst.PIXELSHADEROUTPUT_MRT4 = Dst.WRITES_VELOCITY_TO_GBUFFER || SrcGlobal.GBUFFER_HAS_TANGENT;
				Dst.PIXELSHADEROUTPUT_MRT5 = (!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || Dst.WRITES_CUSTOMDATA_TO_GBUFFER);
				Dst.PIXELSHADEROUTPUT_MRT6 = (Dst.GBUFFER_HAS_PRECSHADOWFACTOR && (!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || (Dst.WRITES_PRECSHADOWFACTOR_TO_GBUFFER && !Mat.MATERIAL_SHADINGMODEL_UNLIT)));
			}
			else
			{
				Dst.PIXELSHADEROUTPUT_MRT4 = (!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || Dst.WRITES_CUSTOMDATA_TO_GBUFFER);
				Dst.PIXELSHADEROUTPUT_MRT5 = (Dst.GBUFFER_HAS_PRECSHADOWFACTOR && (!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || (Dst.WRITES_PRECSHADOWFACTOR_TO_GBUFFER && !Mat.MATERIAL_SHADINGMODEL_UNLIT)));
			}
		}
		else
		{
			Dst.PIXELSHADEROUTPUT_MRT0 = true;
			// we also need MRT for thin translucency due to dual blending if we are not on the fallback path
			Dst.PIXELSHADEROUTPUT_MRT1 = (Dst.WRITES_VELOCITY_TO_GBUFFER || (Mat.DUAL_SOURCE_COLOR_BLENDING_ENABLED && Dst.MATERIAL_WORKS_WITH_DUAL_SOURCE_COLOR_BLENDING));
		}
	}
}

位于FShaderCompileUtilities::ApplyDerivedDefines()新版本逻辑遍历数据由GBufferInfo.cpp中的FetchLegacyGBufferInfo()处理。

#if 1
	static bool bTestNewVersion = true;
	if (bTestNewVersion)
	{
		//if (DerivedDefines.USES_GBUFFER)
		{
			for (int32 Iter = 0; Iter < FGBufferInfo::MaxTargets; Iter++)
			{
				if (bTargetUsage[Iter])
				{
					FString TargetName = FString::Printf(TEXT("PIXELSHADEROUTPUT_MRT%d"), Iter);
					OutEnvironment.SetDefine(TargetName.GetCharArray().GetData(), TEXT("1"));
				}
			}
		}
	}
	else
	{
		// This uses the legacy logic from CalculateDerivedMaterialParameters(); Just keeping it around momentarily for testing during the transition.
		SET_COMPILE_BOOL_IF_TRUE(PIXELSHADEROUTPUT_MRT0)
		SET_COMPILE_BOOL_IF_TRUE(PIXELSHADEROUTPUT_MRT1)
		SET_COMPILE_BOOL_IF_TRUE(PIXELSHADEROUTPUT_MRT2)
		SET_COMPILE_BOOL_IF_TRUE(PIXELSHADEROUTPUT_MRT3)
		SET_COMPILE_BOOL_IF_TRUE(PIXELSHADEROUTPUT_MRT4)
		SET_COMPILE_BOOL_IF_TRUE(PIXELSHADEROUTPUT_MRT5)
		SET_COMPILE_BOOL_IF_TRUE(PIXELSHADEROUTPUT_MRT6)
	}
#endif

MaterialTemplate.ush

MaterialTemplate.ush中定义许多模版函数里面的具体内容会在HLSLMaterialTranslator.h中的GetMaterialShaderCode() 中添加。最后这些函数会在BassPassPixelShader.usf中调用。

bool bEnableExecutionFlow的作用为是否使用新的材质HLSL生成器默认为0。

static TAutoConsoleVariable<int32> CVarMaterialEnableNewHLSLGenerator(  
    TEXT("r.MaterialEnableNewHLSLGenerator"),  
    0,  
    TEXT("Enables the new (WIP) material HLSL generator.\n")  
    TEXT("0 - Don't allow\n")  
    TEXT("1 - Allow if enabled by material\n")  
    TEXT("2 - Force all materials to use new generator\n"),  
    ECVF_RenderThreadSafe | ECVF_ReadOnly);

这个和新版材质HLSL生成器有关相关生成代码为MaterialEmitHLSL()=>调用GenerateMaterialTemplateHLSL()

bCompileForComputeShader = Material->IsLightFunction(); GetPerInstanceCustomDataX分为Vertex与Pixel版本。

FMaterialAttributes

MaterialTemplate.ush有一处/** Material declarations */之后会生成对应FMaterialAttributes结构体可以在材质编辑器的HLSL中查看生成结果。这与

  • MaterialAttributeDefinitionMap.cppFMaterialAttributeDefinitionMap::InitializeAttributeMap()中定义属性。
  • HLSLMaterialTranslator.cppGetMaterialShaderCode()中的for (const FGuid& AttributeID : OrderedVisibleAttributes):生成对应属性结构体以及属性获取函数。

DerivativeAutogen.GenerateUsedFunctions()

{  
    FString DerivativeHelpers = DerivativeAutogen.GenerateUsedFunctions(*this);  
    FString DerivativeHelpersAndResources = DerivativeHelpers + ResourcesString;  
    //LazyPrintf.PushParam(*ResourcesString);  
    LazyPrintf.PushParam(*DerivativeHelpersAndResources);  
}

GetMaterialEmissiveForCS()以及其他函数

if (bCompileForComputeShader)  
{  
    LazyPrintf.PushParam(*GenerateFunctionCode(CompiledMP_EmissiveColorCS, BaseDerivativeVariation));  
}  
else  
{  
    LazyPrintf.PushParam(TEXT("return 0"));  
}

{  
    FLinearColor Extinction = Material->GetTranslucentMultipleScatteringExtinction();  
    LazyPrintf.PushParam(*FString::Printf(TEXT("return MaterialFloat3(%.5f, %.5f, %.5f)"), Extinction.R, Extinction.G, Extinction.B));  
}  
LazyPrintf.PushParam(*FString::Printf(TEXT("return %.5f"), Material->GetOpacityMaskClipValue()));  
{  
    const FDisplacementScaling DisplacementScaling = Material->GetDisplacementScaling();  
    LazyPrintf.PushParam(*FString::Printf(TEXT("return %.5f"), FMath::Max(0.0f, DisplacementScaling.Magnitude)));  
    LazyPrintf.PushParam(*FString::Printf(TEXT("return %.5f"), FMath::Clamp(DisplacementScaling.Center, 0.0f, 1.0f)));  
}  
  
LazyPrintf.PushParam(!bEnableExecutionFlow ? *GenerateFunctionCode(MP_WorldPositionOffset, BaseDerivativeVariation) : TEXT("return Parameters.MaterialAttributes.WorldPositionOffset"));  
LazyPrintf.PushParam(!bEnableExecutionFlow ? *GenerateFunctionCode(CompiledMP_PrevWorldPositionOffset, BaseDerivativeVariation) : TEXT("return 0.0f"));  
LazyPrintf.PushParam(!bEnableExecutionFlow ? *GenerateFunctionCode(MP_CustomData0, BaseDerivativeVariation) : TEXT("return 0.0f"));  
LazyPrintf.PushParam(!bEnableExecutionFlow ? *GenerateFunctionCode(MP_CustomData1, BaseDerivativeVariation) : TEXT("return 0.0f"));

%.5f表示按浮点数输出小数点后面取5位其余的舍弃例如5/2 “%.5f”输出为2.50000

MaterialCustomizedUVs & CustomInterpolators

  • for (uint32 CustomUVIndex = 0; CustomUVIndex < NumUserTexCoords; CustomUVIndex++)
  • for (UMaterialExpressionVertexInterpolator* Interpolator : CustomVertexInterpolators

添加ToonDataAssetID 与 ToonOutlineDataAssetID笔记

  1. FMaterialRenderProxy::UpdateDeferredCachedUniformExpressions()
  2. FMaterialRenderProxy::EvaluateUniformExpressions()
  3. FUniformExpressionSet::FillUniformBuffer()
  4. EvaluatePreshader()
  5. EvaluateParameter()
  6. Context.MaterialRenderProxy->GetParameterValue()

可以看得出关键数据在UniformExpressionSet中这里的ParameterIndex则通过EvaluateParameter(Stack, UniformExpressionSet, ReadPreshaderValue<uint16>(Data), Context);进行计算。

const FMaterialNumericParameterInfo& Parameter = UniformExpressionSet->GetNumericParameter(ParameterIndex);
bool bFoundParameter = false;

// First allow proxy the chance to override parameter
if (Context.MaterialRenderProxy)
{
	FMaterialParameterValue ParameterValue;
	if (Context.MaterialRenderProxy->GetParameterValue(Parameter.ParameterType, Parameter.ParameterInfo, ParameterValue, Context))
	{
		Stack.PushValue(ParameterValue.AsShaderValue());
		bFoundParameter = true;
	}
}

bool FMaterialInstanceResource::GetParameterValue(EMaterialParameterType Type, const FHashedMaterialParameterInfo& ParameterInfo, FMaterialParameterValue& OutValue, const FMaterialRenderContext& Context) const
{
	checkSlow(IsInParallelRenderingThread());

	bool bResult = false;

	// Check for hard-coded parameters
	if (Type == EMaterialParameterType::Scalar && ParameterInfo.Name == GetSubsurfaceProfileParameterName())
	{
		check(ParameterInfo.Association == EMaterialParameterAssociation::GlobalParameter);
		const USubsurfaceProfile* MySubsurfaceProfileRT = GetSubsurfaceProfileRT();
		OutValue = GetSubsurfaceProfileId(MySubsurfaceProfileRT);
		bResult = true;
	}
	else if (Type == EMaterialParameterType::Scalar && NumSpecularProfileRT() > 0)
	{
		for (uint32 It=0,Count=NumSpecularProfileRT();It<Count;++It)
		{
			if (ParameterInfo.Name == SpecularProfileAtlas::GetSpecularProfileParameterName(GetSpecularProfileRT(It)))
			{
				check(ParameterInfo.Association == EMaterialParameterAssociation::GlobalParameter);
				OutValue = SpecularProfileAtlas::GetSpecularProfileId(GetSpecularProfileRT(It));
				bResult = true;
				break;
			}
		}
	}

BasePass EncodeGBufferToMRT/DecodeGBufferDataDirect逻辑笔记

主要逻辑位于FShaderCompileUtilities::WriteGBufferInfoAutogen():

void FShaderCompileUtilities::WriteGBufferInfoAutogen(EShaderPlatform TargetPlatform, ERHIFeatureLevel::Type FeatureLevel = ERHIFeatureLevel::SM5)
{
	FGBufferParams DefaultParams = FetchGBufferParamsPipeline(TargetPlatform, GBL_Default);
	FScopeLock MapLock(&GCriticalSection);

	// For now, the logic always calculates the new GBuffer, and if it's the first time, write it, otherwise check it hasn't changed. We are doing this for
	// debugging, and in the near future it will only calculate the GBuffer on the first time only.
	FGBufferInfo DefaultBufferInfo = FetchFullGBufferInfo(DefaultParams);
	FString AutoGenDirectory = GetAutoGenDirectory(TargetPlatform);
	FString AutogenHeaderFilename = AutoGenDirectory / TEXT("AutogenShaderHeaders.ush");
	FString AutogenHeaderFilenameTemp = AutoGenDirectory / TEXT("AutogenShaderHeaders_temp.ush");

	if (GLastGBufferIsValid[TargetPlatform])
	{
		const bool bSame = IsGBufferInfoEqual(GLastGBufferInfo[TargetPlatform], DefaultBufferInfo);//判断GBufferInfo是否相同不同则触发断言
		check(bSame);
	}
	else
	{
		GLastGBufferIsValid[TargetPlatform] = true;
		// should cache this properly, and serialize it, but this is a temporary fix.
		GLastGBufferInfo[TargetPlatform] = DefaultBufferInfo;

		FString OutputFileData;
		OutputFileData += TEXT("// Copyright Epic Games, Inc. All Rights Reserved.\n");
		OutputFileData += TEXT("\n");
		OutputFileData += TEXT("#pragma once\n");
		OutputFileData += TEXT("\n");

		OutputFileData += TEXT("#if FEATURE_LEVEL >= FEATURE_LEVEL_SM5\n");
		OutputFileData += TEXT("float SampleDeviceZFromSceneTexturesTempCopy(float2 UV)\n");
		OutputFileData += TEXT("{\n");
		OutputFileData += TEXT("\treturn SceneDepthTexture.SampleLevel(SceneDepthTextureSampler, UV, 0).r;\n");
		OutputFileData += TEXT("}\n");
		OutputFileData += TEXT("#endif\n");
		OutputFileData += TEXT("\n");

		OutputFileData += TEXT("#ifndef GBUFFER_LAYOUT\n");
		OutputFileData += TEXT("#define GBUFFER_LAYOUT 0\n");
		OutputFileData += TEXT("#endif\n");
		OutputFileData += TEXT("\n");

		for (uint32 Layout = 0; Layout < GBL_Num; ++Layout)
		{
			FGBufferParams Params = FetchGBufferParamsPipeline(TargetPlatform, (EGBufferLayout)Layout);
			FGBufferInfo BufferInfo = FetchFullGBufferInfo(Params);

			OutputFileData.Appendf(TEXT("#if GBUFFER_LAYOUT == %u\n\n"), Layout);
			OutputFileData += CreateGBufferEncodeFunction(BufferInfo);

			OutputFileData += TEXT("\n");

			OutputFileData += CreateGBufferDecodeFunctionDirect(BufferInfo);

			OutputFileData += TEXT("\n");
			//OutputFileData += TEXT("#if SHADING_PATH_DEFERRED\n");
			OutputFileData += TEXT("#if FEATURE_LEVEL >= FEATURE_LEVEL_SM5\n");
			OutputFileData += TEXT("\n");

			OutputFileData += CreateGBufferDecodeFunctionVariation(BufferInfo, EGBufferDecodeType::CoordUV, FeatureLevel);
			OutputFileData += TEXT("\n");

			OutputFileData += CreateGBufferDecodeFunctionVariation(BufferInfo, EGBufferDecodeType::CoordUInt, FeatureLevel);

			OutputFileData += TEXT("\n");

			OutputFileData += CreateGBufferDecodeFunctionVariation(BufferInfo, EGBufferDecodeType::SceneTextures, FeatureLevel);
			OutputFileData += TEXT("\n");

			OutputFileData += CreateGBufferDecodeFunctionVariation(BufferInfo, EGBufferDecodeType::SceneTexturesLoad, FeatureLevel);
			OutputFileData += TEXT("\n");

			OutputFileData += TEXT("#endif\n");
			OutputFileData += TEXT("\n");

			OutputFileData += TEXT("#endif\n");
			OutputFileData += TEXT("\n");
		}
	...
}

写入内容与这2句获取的FGbufferInfo有关FGBufferParams Params = FetchGBufferParamsPipeline(TargetPlatform, (EGBufferLayout)Layout);FGBufferInfo BufferInfo = FetchFullGBufferInfo(Params);

!ShaderGenerationUtil_CreateGBufferEncodeFunction.png

是否需要Toon

在材质中:

FMaterialRelevance UMaterialInterface::GetRelevance_Internal(const UMaterial* Material, ERHIFeatureLevel::Type InFeatureLevel) const
{
	if(Material)  
	{
		//YivanLee's Modify 这里仅仅针对人物因为它决定了是否开启ToonGBuffer但是对于ToonLevelToonFoliageToonGrass这里并不需要开启  
		bool bUseToonData = MaterialResource->GetShadingModels().HasAnyShadingModel({ MSM_ToonStandard, MSM_ToonSkin, MSM_ToonHair, MSM_ToonFace, MSM_ToonEyeBrow });
	}

	···
	MaterialRelevance.bUsesToonData = bUseToonData;
	···
}

在渲染管线中:

//RenderUtils.cpp
bool IsUsingToonRendering(const FStaticShaderPlatform Platform)  
{  
    static FShaderPlatformCachedIniValue<int32> PerPlatformCVar(TEXT("r.ToonRendering.Enable"));  
    if (IsMobilePlatform(Platform) || IsForwardShadingEnabled(Platform))//目前不考虑VR与移动端  
    {  
        return false;  
    }  
    else  
    {  
        return (PerPlatformCVar.Get(Platform) == 1);  
    }  
}  
  
bool IsUsingToonOutline(const FStaticShaderPlatform Platform)  
{  
    static FShaderPlatformCachedIniValue<int32> PerPlatformCVar(TEXT("r.ToonRendering.ToonOutline"));  
    return (PerPlatformCVar.Get(Platform) == 1) && IsUsingToonRendering(Platform);  
}  
  
bool IsUsingToonRimLighting(const FStaticShaderPlatform Platform)  
{  
    static FShaderPlatformCachedIniValue<int32> PerPlatformCVar(TEXT("r.ToonRendering.ToonRimLighting"));  
    return (PerPlatformCVar.Get(Platform) == 1) && IsUsingToonRendering(Platform);  
}

李兄的ToonBuffer判断逻辑

bool FDeferredShadingSceneRenderer::ShouldRenderToonDataPass() const
{
	if (!SupportsToonDataMaterials(FeatureLevel, ShaderPlatform))
	{
		return false;
	}
	if (IsForwardShadingEnabled(GetFeatureLevelShaderPlatform(FeatureLevel)))
	{
		return false;
	}
	for (auto& View : Views)
	{
		if (View.ShouldRenderView() && View.ParallelMeshDrawCommandPasses[EMeshPass::ToonDataPass].HasAnyDraw())
		{
			return true;
		}
	}
	return false;
}

Toon PerObjectGBufferData具体功能表

从3开始0、1、2已被占用。

  • ?

ToonBufferData

  • ToonObjectID
struct FSceneDataIntermediates  
{  
    uint PrimitiveId;  
    uint InstanceId;  
    uint ViewIndex;  
    uint CullingFlags;  
    // Index from which we load the instance info, needed for the   
	uint InstanceIdLoadIndex;  
    FInstanceSceneData InstanceData;  
    FPrimitiveSceneData Primitive;  
};

struct FVertexFactoryIntermediatesCommon  
{  
    /** Cached primitive and instance data */  
    FSceneDataIntermediates SceneData;  
#if USE_INSTANCING || USE_INSTANCE_CULLING  
    FVertexFactoryInstanceInput InstanceInput;  
#endif  
#if USE_SPLINEDEFORM  
    FSplineMeshShaderParams SplineMeshParams;  
#endif  
};

FPrimitiveSceneData GetPrimitiveData(FVertexFactoryIntermediatesCommon Intermediates)  
{  
    return Intermediates.SceneData.Primitive;  
}

高光

  • PBR高光使用Roughness控制是否可行是否需要传入GBuffer一个Mask贴图
  • 自定义高光:高光贴图、高光颜色、参数化高光形状、多层高光

BasePassPixelShader

Velocity相关代码段

	#if USES_GBUFFER
		// -0.5 .. 0.5, could be optimzed as lower quality noise would be sufficient
		float QuantizationBias = PseudoRandom( MaterialParameters.SvPosition.xy ) - 0.5f;

		GBuffer.IndirectIrradiance = IndirectIrradiance;

		// this is the new encode, the older encode is the #else, keeping it around briefly until the new version is confirmed stable.
		#if 1
		{
			// change this so that we can pack everything into the gbuffer, but leave this for now
			#if GBUFFER_HAS_DIFFUSE_SAMPLE_OCCLUSION
				GBuffer.GenericAO = float(GBuffer.DiffuseIndirectSampleOcclusion) * (1.0f / 255.0f);
			#elif ALLOW_STATIC_LIGHTING
				// No space for AO. Multiply IndirectIrradiance by AO instead of storing.
				GBuffer.GenericAO = EncodeIndirectIrradiance(GBuffer.IndirectIrradiance * GBuffer.GBufferAO) + QuantizationBias * (1.0 / 255.0);	// Stationary sky light path
			#else
				GBuffer.GenericAO = GBuffer.GBufferAO;	// Movable sky light path
			#endif

			EncodeGBufferToMRT(Out, GBuffer, QuantizationBias);

			if (GBuffer.ShadingModelID == SHADINGMODELID_UNLIT && !STRATA_ENABLED) // Do not touch what strata outputs
			{
				Out.MRT[1] = 0;
				SetGBufferForUnlit(Out.MRT[2]);
				Out.MRT[3] = 0;
				Out.MRT[GBUFFER_HAS_VELOCITY ? 5 : 4] = 0;
				Out.MRT[GBUFFER_HAS_VELOCITY ? 6 : 5] = 0;
			}

		#if SINGLE_LAYER_WATER_SEPARATED_MAIN_LIGHT
			// In deferred, we always output the directional light in a separated buffer.
			// This is used to apply distance field shadows or light function to the main directional light.
			// Strata also writes it through MRT because this is faster than through UAV.
			#if STRATA_ENABLED && STRATA_INLINE_SINGLELAYERWATER
				Out.MRT[(GBUFFER_HAS_VELOCITY ? 2 : 1) + (GBUFFER_HAS_PRECSHADOWFACTOR ? 1 : 0)] = float4(SeparatedWaterMainDirLightLuminance * View.PreExposure, 1.0f);
			#else
			if (GBuffer.ShadingModelID == SHADINGMODELID_SINGLELAYERWATER)
			{
				Out.MRT[(GBUFFER_HAS_VELOCITY ? 6 : 5) + (GBUFFER_HAS_PRECSHADOWFACTOR ? 1 : 0)] = float4(SeparatedWaterMainDirLightLuminance * View.PreExposure, 1.0f);
			}
			#endif
		#endif
		}

顶点色

蓝色协议

用于存储一些低精度数据,插值即可

  • 顶点色:
    • R:阴影区域控制(强度) 0~1
    • G:描边宽度
    • B:ToonAO
  • 第二套顶点色UV Channel1
    • R:深度偏移
    • G:用来区分内轮廓不同部位的ID

蓝色协议的R:阴影区域标记 与 B:AO而罪恶装备使用贴图来传递信息。

罪恶装备

对阴影判断阈值的偏移。见前面着色部分顶点AO+手绘修正) R:阴影偏移 G:轮廓线根据与相机的距离扩大多少的系数 B:等高线 Z 轴偏移值

罪恶装备

8,G为阴影控AOR为高光强度参数金属和光滑材质的部分设置的更大一些。B通道用于照明控制。最大值为高光反之值越小高光越淡。

https://zhuanlan.zhihu.com/p/360229590一文中介绍了崩坏3与原神的计算方式

崩坏3的LightMap计算方式

half4 baseColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv.xy);
half4 LightMapColor = SAMPLE_TEXTURE2D(_LightMap, sampler_LightMap, input.uv.xy);
half3 ShadowColor = baseColor.rgb * _ShadowMultColor.rgb;
half3 DarkShadowColor = baseColor.rgb * _DarkShadowMultColor.rgb;

//如果SFactor = 0,ShallowShadowColor为一级阴影色,否则为BaseColor。
float SWeight = (LightMapColor.g * input.color.r + input.lambert) * 0.5 + 1.125;
float SFactor = floor(SWeight - _ShadowArea);
half3 ShallowShadowColor = SFactor * baseColor.rgb + (1 - SFactor) * ShadowColor.rgb;

二级阴影计算:

//如果SFactor = 0,DarkShadowColor为二级阴影色,否则为一级阴影色。
SFactor = floor(SWeight - _DarkShadowArea);
DarkShadowColor = SFactor * (_FixDarkShadow * ShadowColor + (1 - _FixDarkShadow) * ShallowShadowColor) + (1 - SFactor) * DarkShadowColor;

// 平滑阴影边缘
half rampS = smoothstep(0, _ShadowSmooth, input.lambert - _ShadowArea);
half rampDS = smoothstep(0, _DarkShadowSmooth, input.lambert - _DarkShadowArea);
ShallowShadowColor.rgb = lerp(ShadowColor, baseColor.rgb, rampS);
DarkShadowColor.rgb = lerp(DarkShadowColor.rgb, ShadowColor, rampDS);

//如果SFactor = 0,FinalColor为二级阴影否则为一级阴影。
SFactor = floor(LightMapColor.g * input.color.r + 0.9f);
half4 FinalColor;
FinalColor.rgb = SFactor * ShallowShadowColor + (1 - SFactor) * DarkShadowColor;

罪恶装备 对阴影判断阈值的偏移。见前面着色部分顶点AO+手绘修正) G : 轮廓线根据与相机的距离扩大多少的系数 B : 等高线 Z 轴偏移值 A : 轮廓厚度系数。0.5为标准1为最大厚度0为无等高线

蓝色协议

蓝色协议的方案

米哈游