
前期已经做过单pass效果这次不讲其他功能注重多pass部分一、创建新文件Engine/Source/Runtime/Renderer/Private/PostProcess/LearningBloom/ LearningBloom.h LearningBloom.cpp Engine/Shaders/Private/PostProcess/LearningBloom/ LearningBloom.usfLearningBloom.h// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include ScreenPass.h class FRDGBuilder; class FViewInfo; struct FLearningBloomInputs { FScreenPassTexture SceneColor; }; FScreenPassTexture AddLearningBloomPass( FRDGBuilder GraphBuilder, const FViewInfo View, const FLearningBloomInputs Inputs);LearningBloom.cpp// Copyright Epic Games, Inc. All Rights Reserved. #include PostProcess/LearningBloom/LearningBloom.h #include DataDrivenShaderPlatformInfo.h #include PixelShaderUtils.h #include PostProcess/PostProcessWeightedSampleSum.h #include SceneRendering.h namespace { TAutoConsoleVariableint32 CVarLearningBloomEnable( TEXT(r.LearningBloom.Enable), 0, TEXT(Enable the learning bloom pass.\n) TEXT(0: Disabled\n) TEXT(1: Enabled), ECVF_RenderThreadSafe); TAutoConsoleVariablefloat CVarLearningBloomThreshold( TEXT(r.LearningBloom.Threshold), 1.0f, TEXT(HDR luminance threshold.), ECVF_RenderThreadSafe); TAutoConsoleVariablefloat CVarLearningBloomBlurSize( TEXT(r.LearningBloom.BlurSize), 2.0f, TEXT(Gaussian blur kernel size, as percentage of screen width.), ECVF_RenderThreadSafe); TAutoConsoleVariablefloat CVarLearningBloomIntensity( TEXT(r.LearningBloom.Intensity), 1.0f, TEXT(Intensity applied during composition.), ECVF_RenderThreadSafe); TAutoConsoleVariableint32 CVarLearningBloomDebug( TEXT(r.LearningBloom.Debug), 0, TEXT(Debug output.\n) TEXT(0: Final composite\n) TEXT(1: Threshold result\n) TEXT(2: Blurred result), ECVF_RenderThreadSafe); class FLearningBloomThresholdPS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FLearningBloomThresholdPS); SHADER_USE_PARAMETER_STRUCT( FLearningBloomThresholdPS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_REF( FViewUniformShaderParameters, View) SHADER_PARAMETER_RDG_TEXTURE( Texture2D, InputTexture) SHADER_PARAMETER_SAMPLER( SamplerState, InputSampler) SHADER_PARAMETER( FScreenTransform, SvPositionToInputTextureUV) SHADER_PARAMETER(float, Threshold) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation( const FGlobalShaderPermutationParameters Parameters) { return IsFeatureLevelSupported( Parameters.Platform, ERHIFeatureLevel::SM5); } }; IMPLEMENT_GLOBAL_SHADER( FLearningBloomThresholdPS, /Engine/Private/PostProcess/LearningBloom/LearningBloom.usf, LearningBloomThresholdPS, SF_Pixel); class FLearningBloomCompositePS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FLearningBloomCompositePS); SHADER_USE_PARAMETER_STRUCT( FLearningBloomCompositePS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_TEXTURE( Texture2D, SceneColorTexture) SHADER_PARAMETER_SAMPLER( SamplerState, SceneColorSampler) SHADER_PARAMETER_RDG_TEXTURE( Texture2D, BloomTexture) SHADER_PARAMETER_SAMPLER( SamplerState, BloomSampler) SHADER_PARAMETER( FScreenTransform, SvPositionToSceneColorUV) SHADER_PARAMETER( FScreenTransform, SvPositionToBloomUV) SHADER_PARAMETER(float, Intensity) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation( const FGlobalShaderPermutationParameters Parameters) { return IsFeatureLevelSupported( Parameters.Platform, ERHIFeatureLevel::SM5); } }; IMPLEMENT_GLOBAL_SHADER( FLearningBloomCompositePS, /Engine/Private/PostProcess/LearningBloom/LearningBloom.usf, LearningBloomCompositePS, SF_Pixel); FScreenTransform GetSvPositionToTextureUV( const FScreenPassTextureViewport OutputViewport, const FScreenPassTextureViewport InputViewport) { return FScreenTransform::ChangeTextureBasisFromTo( OutputViewport, FScreenTransform::ETextureBasis::TexelPosition, FScreenTransform::ETextureBasis::ViewportUV) * FScreenTransform::ChangeTextureBasisFromTo( InputViewport, FScreenTransform::ETextureBasis::ViewportUV, FScreenTransform::ETextureBasis::TextureUV); } FScreenPassTexture AddThresholdPass( FRDGBuilder GraphBuilder, const FViewInfo View, const FScreenPassTexture Input, float Threshold) { FScreenPassRenderTarget Output FScreenPassRenderTarget::CreateFromInput( GraphBuilder, Input, View.GetOverwriteLoadAction(), TEXT(LearningBloom.Threshold)); const FScreenPassTextureViewport InputViewport(Input); const FScreenPassTextureViewport OutputViewport(Output); FLearningBloomThresholdPS::FParameters* PassParameters GraphBuilder.AllocParameters FLearningBloomThresholdPS::FParameters(); PassParameters-View View.ViewUniformBuffer; PassParameters-InputTexture Input.Texture; PassParameters-InputSampler TStaticSamplerState SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp::GetRHI(); PassParameters-Threshold Threshold; PassParameters-SvPositionToInputTextureUV GetSvPositionToTextureUV( OutputViewport, InputViewport); PassParameters-RenderTargets[0] Output.GetRenderTargetBinding(); TShaderMapRefFLearningBloomThresholdPS PixelShader( View.ShaderMap); FPixelShaderUtils::AddFullscreenPass( GraphBuilder, View.ShaderMap, RDG_EVENT_NAME(LearningBloom Threshold), PixelShader, PassParameters, Output.ViewRect); return FScreenPassTexture(Output); } FScreenPassTexture AddCompositePass( FRDGBuilder GraphBuilder, const FViewInfo View, const FScreenPassTexture SceneColor, const FScreenPassTexture Bloom, float Intensity) { FScreenPassRenderTarget Output FScreenPassRenderTarget::CreateFromInput( GraphBuilder, SceneColor, View.GetOverwriteLoadAction(), TEXT(LearningBloom.Composite)); const FScreenPassTextureViewport OutputViewport(Output); const FScreenPassTextureViewport SceneColorViewport(SceneColor); const FScreenPassTextureViewport BloomViewport(Bloom); FLearningBloomCompositePS::FParameters* PassParameters GraphBuilder.AllocParameters FLearningBloomCompositePS::FParameters(); FRHISamplerState* BilinearClampSampler TStaticSamplerState SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp::GetRHI(); PassParameters-SceneColorTexture SceneColor.Texture; PassParameters-SceneColorSampler BilinearClampSampler; PassParameters-BloomTexture Bloom.Texture; PassParameters-BloomSampler BilinearClampSampler; PassParameters-Intensity Intensity; PassParameters-SvPositionToSceneColorUV GetSvPositionToTextureUV( OutputViewport, SceneColorViewport); PassParameters-SvPositionToBloomUV GetSvPositionToTextureUV( OutputViewport, BloomViewport); PassParameters-RenderTargets[0] Output.GetRenderTargetBinding(); TShaderMapRefFLearningBloomCompositePS PixelShader( View.ShaderMap); FPixelShaderUtils::AddFullscreenPass( GraphBuilder, View.ShaderMap, RDG_EVENT_NAME(LearningBloom Composite), PixelShader, PassParameters, Output.ViewRect); return FScreenPassTexture(Output); } } FScreenPassTexture AddLearningBloomPass( FRDGBuilder GraphBuilder, const FViewInfo View, const FLearningBloomInputs Inputs) { check(Inputs.SceneColor.IsValid()); if (CVarLearningBloomEnable.GetValueOnRenderThread() 0) { return Inputs.SceneColor; } const float Threshold FMath::Max( 0.0f, CVarLearningBloomThreshold.GetValueOnRenderThread()); const float BlurSize FMath::Clamp( CVarLearningBloomBlurSize.GetValueOnRenderThread(), 0.1f, 20.0f); const float Intensity FMath::Max( 0.0f, CVarLearningBloomIntensity.GetValueOnRenderThread()); const int32 DebugMode CVarLearningBloomDebug.GetValueOnRenderThread(); const FScreenPassTexture ThresholdOutput AddThresholdPass( GraphBuilder, View, Inputs.SceneColor, Threshold); if (DebugMode 1) { return ThresholdOutput; } FGaussianBlurInputs BlurInputs; BlurInputs.NameX TEXT(LearningBloom Blur X); BlurInputs.NameY TEXT(LearningBloom Blur Y); BlurInputs.Filter FScreenPassTextureSlice::CreateFromScreenPassTexture( GraphBuilder, ThresholdOutput); BlurInputs.TintColor FLinearColor::White; BlurInputs.KernelSizePercent BlurSize; BlurInputs.UseMirrorAddressMode false; const FScreenPassTexture BlurOutput AddGaussianBlurPass( GraphBuilder, View, BlurInputs); if (DebugMode 2) { return BlurOutput; } return AddCompositePass( GraphBuilder, View, Inputs.SceneColor, BlurOutput, Intensity); }这里注意上段代码有多个PS的IMPLEMENT_GLOBAL_SHADER里面绑定的是同一个.usf文件的不同函数FScreenPassTextureSlice是什么它只有两个核心成员FRDGTextureSRVRef TextureSRV; FIntRect ViewRect;纹理1920 × 1080 ┌──────────────┬──────────────┐ │ │ │ │ 其他视图 │ 当前 ViewRect │ │ │ │ └──────────────┴──────────────┘所以TextureSRV 从哪张纹理、哪个 array slice 读取 ViewRect 在这个 slice 中处理哪个矩形区域BlurInputs.TintColor FLinearColor::White; BlurInputs.KernelSizePercent BlurSize; BlurInputs.UseMirrorAddressMode false;1. TintColor模糊结果的颜色倍率BlurInputs.TintColor FLinearColor::White;White等于FLinearColor(1, 1, 1, 1)意思是不改变模糊结果的颜色和强度最终模糊颜色 GaussianBlur结果 × TintColorBlurSize 2.0f; 屏幕宽度 1920;模糊半径Radius 1920 × 2% × 0.5 19.2 像素创建 LearningBloom.usf// Copyright Epic Games, Inc. All Rights Reserved. #include Common.ush #include ScreenPass.ush Texture2D InputTexture; SamplerState InputSampler; FScreenTransform SvPositionToInputTextureUV; float Threshold; void LearningBloomThresholdPS( float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0) { const float2 UV ApplyScreenTransform( SvPosition.xy, SvPositionToInputTextureUV); const float3 PreExposedColor Texture2DSample(InputTexture, InputSampler, UV).rgb; const float3 AbsoluteColor PreExposedColor * View.OneOverPreExposure; const float LuminanceValue dot( AbsoluteColor, float3(0.2126f, 0.7152f, 0.0722f)); const float BrightMask step(Threshold, LuminanceValue); const float3 BrightColor AbsoluteColor * BrightMask * View.PreExposure; OutColor float4(BrightColor, 0.0f); } // Composite pass Texture2D SceneColorTexture; SamplerState SceneColorSampler; Texture2D BloomTexture; SamplerState BloomSampler; FScreenTransform SvPositionToSceneColorUV; FScreenTransform SvPositionToBloomUV; float Intensity; void LearningBloomCompositePS( float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0) { const float2 SceneColorUV ApplyScreenTransform( SvPosition.xy, SvPositionToSceneColorUV); const float2 BloomUV ApplyScreenTransform( SvPosition.xy, SvPositionToBloomUV); const float4 SceneColor Texture2DSample( SceneColorTexture, SceneColorSampler, SceneColorUV); const float3 BloomColor Texture2DSample( BloomTexture, BloomSampler, BloomUV).rgb; OutColor float4( SceneColor.rgb BloomColor * Intensity, SceneColor.a); }SvPosition不是 C 手动传进来的而是 GPU 光栅化阶段自动传给 Pixel Shader 的。AddFullscreenPass()会绘制一个覆盖整个画面的全屏三角形流程是C AddFullscreenPass ↓ 全屏 Vertex Shader ↓ 输出顶点 SV_POSITION ↓ GPU 光栅化器生成像素 ↓ Pixel Shader 接收到当前像素的 SV_POSITION ↓ LearningBloomThresholdPSApplyScreenTransform()是 Unreal Engine Shader 自带的辅助函数。PreExposure可以直白理解成UE 在写入 HDR SceneColor 之前先把颜色整体乘一个系数让 GPU 中间纹理里的数字不要过大或过小。它主要是数值稳定手段不是额外的美术曝光效果。为什么需要它真实 HDR 场景颜色范围可能非常夸张黑暗房间0.001 普通物体1 太阳、高亮反射100000如果全部直接写进 SceneColor大数值可能溢出小数值精度不足R11G11B10 等格式更容易出现问题Temporal History 跨帧处理也更困难。所以 UE 提前缩放SceneColor中存储的颜色 原始场景颜色 × PreExposureTonemap 时会除掉 PreExposure。假设太阳像素的原始 HDR 颜色是100000相机最终曝光是Exposure 0.001不使用 PreExposureSceneColor 先保存100000Tonemap 前再计算100000 × 0.001 100最终数字没问题但 SceneColor、TAA、Bloom、MotionBlur 等中间过程一直在处理100000。可能出现HDR 格式范围或精度问题高亮计算不稳定Blur 累加大数值Temporal History 精度问题。把 Pass 插入 PostProcessing.cpp打开PostProcessing.cpp:在 include 区域添加#include PostProcess/LearningBloom/LearningBloom.h然后找到大约 1400 行附近SceneColorBeforeTonemapSlice SceneColorSlice; if (PassSequence.IsEnabled(EPass::Tonemap))在它前面插入{ FLearningBloomInputs LearningBloomInputs; LearningBloomInputs.SceneColor FScreenPassTexture::CopyFromSlice( GraphBuilder, SceneColorSlice); const FScreenPassTexture LearningBloomOutput AddLearningBloomPass( GraphBuilder, View, LearningBloomInputs); SceneColorSlice FScreenPassTextureSlice::CreateFromScreenPassTexture( GraphBuilder, LearningBloomOutput); } SceneColorBeforeTonemapSlice SceneColorSlice;