--- title: GBuffer&Material&BasePass date: 2023-12-08 17:34:58 excerpt: tags: rating: ⭐ --- # # 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存储数据]] ```c# 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); - Tangent:false,目前单独使用另一组MRT来存储。 - ~~GBUFFER_HAS_TANGENT~` ### ToonGBuffer修改&数据存储 ```c# 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格式 - [[#ShaderMaterialDerivedHelpers.cpp中的CalculateDerivedMaterialParameters()]]控制在BasePassPixelShader.usf中的MRT宏是否为true。 - [[#BasePassRendering.cpp中ModifyBasePassCSPSCompilationEnvironment()]]控制Velocity与SingleLayerWater相关的RT精度。 - [[#GBufferInfo.cpp中的FetchLegacyGBufferInfo()]]控制GBuffer精度以及数据打包情况。 #### BasePassRendering.cpp中ModifyBasePassCSPSCompilationEnvironment() ```c++ 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() ```c++ 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()处理。 ```c++ #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中调用。 ### 是否需要Toon 在材质中: ```c++ FMaterialRelevance UMaterialInterface::GetRelevance_Internal(const UMaterial* Material, ERHIFeatureLevel::Type InFeatureLevel) const { if(Material) { //YivanLee's Modify 这里仅仅针对人物,因为它决定了是否开启ToonGBuffer,但是对于ToonLevel,ToonFoliage,ToonGrass这里并不需要开启 bool bUseToonData = MaterialResource->GetShadingModels().HasAnyShadingModel({ MSM_ToonStandard, MSM_ToonSkin, MSM_ToonHair, MSM_ToonFace, MSM_ToonEyeBrow }); } ··· MaterialRelevance.bUsesToonData = bUseToonData; ··· } ``` 在渲染管线中: ```c++ //RenderUtils.cpp bool IsUsingToonRendering(const FStaticShaderPlatform Platform) {     static FShaderPlatformCachedIniValue 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 PerPlatformCVar(TEXT("r.ToonRendering.ToonOutline"));     return (PerPlatformCVar.Get(Platform) == 1) && IsUsingToonRendering(Platform); } bool IsUsingToonRimLighting(const FStaticShaderPlatform Platform) {     static FShaderPlatformCachedIniValue PerPlatformCVar(TEXT("r.ToonRendering.ToonRimLighting"));     return (PerPlatformCVar.Get(Platform) == 1) && IsUsingToonRendering(Platform); } ``` 李兄的ToonBuffer判断逻辑: ```c++ 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已被占用。 - ? ## ToonData ## 高光 - PBR高光(使用Roughness控制是否可行?是否需要传入GBuffer一个Mask贴图) - 自定义高光:高光贴图、高光颜色、参数化高光形状、多层高光 # BasePassPixelShader Velocity相关代码段: ```c++ #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 轴偏移值 # 罪恶装备 ![](https://pic2.zhimg.com/80/v2-56012886fafbaf36932f03b0ad65a165_720w.jpg)8,G为阴影控(AO),R为高光强度参数,金属和光滑材质的部分设置的更大一些。B通道:用于照明控制。最大值为高光,反之,值越小高光越淡。![](https://pic4.zhimg.com/80/v2-748ebbdd4da3efe74054c8215be8b023_720w.jpg) ![](https://pic2.zhimg.com/80/v2-74e1a9fba264af2b18e66616d9f86831_720w.jpg) https://zhuanlan.zhihu.com/p/360229590一文中介绍了崩坏3与原神的计算方式 崩坏3的LightMap计算方式: ```c++ 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; ``` 二级阴影计算: ```c++ //如果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为无等高线 # 蓝色协议 [[蓝色协议的方案]] # 米哈游