Files
UnrealEngine/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessSubsurface.cpp
2025-05-18 13:04:45 +08:00

1637 lines
68 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
PostProcessceSetupce.cpp: Screenspace subsurface scattering implementation.
Indirect dispatch implementation high level description
1. Initialize counters
2. Setup pass: record the tiles that need to draw Burley and Separable in two different buffer.
3. Indirect dispatch Burley.
4. Indirect dispatch Separable.
5. Recombine.
=============================================================================*/
#include "PostProcess/PostProcessSubsurface.h"
#include "PostProcess/SceneRenderTargets.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "Engine/SubsurfaceProfile.h"
#include "CanvasTypes.h"
#include "ScenePrivate.h"
#include "GenerateMips.h"
#include "ClearQuad.h"
#include "Substrate/Substrate.h"
#include "PostProcess/TemporalAA.h"
#include "SubsurfaceTiles.h"
#include "UnrealEngine.h"
namespace
{
// Subsurface common parameters
TAutoConsoleVariable<int32> CVarSubsurfaceScattering(
TEXT("r.SubsurfaceScattering"),
1,
TEXT(" 0: disabled\n")
TEXT(" 1: enabled (default)"),
ECVF_RenderThreadSafe | ECVF_Scalability);
TAutoConsoleVariable<float> CVarSSSScale(
TEXT("r.SSS.Scale"),
1.0f,
TEXT("Affects the Screen space Separable subsurface scattering pass ")
TEXT("(use shadingmodel SubsurfaceProfile, get near to the object as the default)\n")
TEXT("is human skin which only scatters about 1.2cm)\n")
TEXT(" 0: off (if there is no object on the screen using this pass it should automatically disable the post process pass)\n")
TEXT("<1: scale scatter radius down (for testing)\n")
TEXT(" 1: use given radius form the Subsurface scattering asset (default)\n")
TEXT(">1: scale scatter radius up (for testing)"),
ECVF_Scalability | ECVF_RenderThreadSafe);
TAutoConsoleVariable<int32> CVarSSSHalfRes(
TEXT("r.SSS.HalfRes"),
1,
TEXT(" 0: full quality (Combined Burley and Separable pass. Separable is not optimized, as reference)\n")
TEXT(" 1: parts of the algorithm runs in half resolution which is lower quality but faster (default, Separable only)"),
ECVF_RenderThreadSafe | ECVF_Scalability);
TAutoConsoleVariable<int32> CVarSSSHalfResForceSeparable(
TEXT("r.SSS.HalfRes.ForceSeparable"),
1,
TEXT(" 0: Use the implementation hint from the subsurface profile.\n")
TEXT(" 1: Force to use separable filter in HalfRes mode (default)."),
ECVF_RenderThreadSafe | ECVF_Scalability);
TAutoConsoleVariable<int32> CVarSSSQuality(
TEXT("r.SSS.Quality"),
0,
TEXT("Defines the quality of the recombine pass when using the SubsurfaceScatteringProfile shading model\n")
TEXT(" 0: low (faster, default)\n")
TEXT(" 1: high (sharper details but slower)\n")
TEXT("-1: auto, 1 if TemporalAA is disabled (without TemporalAA the quality is more noticable)"),
ECVF_RenderThreadSafe | ECVF_Scalability);
TAutoConsoleVariable<int32> CVarSSSFilter(
TEXT("r.SSS.Filter"),
1,
TEXT("Defines the filter method for Screenspace Subsurface Scattering feature.\n")
TEXT(" 0: point filter (useful for testing, could be cleaner)\n")
TEXT(" 1: bilinear filter"),
ECVF_RenderThreadSafe | ECVF_Scalability);
TAutoConsoleVariable<int32> CVarSSSSampleSet(
TEXT("r.SSS.SampleSet"),
2,
TEXT("Defines how many samples we use for Separable Screenspace Subsurface Scattering feature.\n")
TEXT(" 0: lowest quality (6*2+1)\n")
TEXT(" 1: medium quality (9*2+1)\n")
TEXT(" 2: high quality (13*2+1) (default)"),
ECVF_RenderThreadSafe | ECVF_Scalability);
TAutoConsoleVariable<int32> CVarSSSBurleyUpdateParameter(
TEXT("r.SSS.Burley.AlwaysUpdateParametersFromSeparable"),
0,
TEXT("0: Will not update parameters when the program loads. (default)")
TEXT("1: Always update from the separable when the program loads. (Correct only when Subsurface color is 1)."),
ECVF_RenderThreadSafe | ECVF_Scalability
);
TAutoConsoleVariable<int32> CVarSSSCheckerboard(
TEXT("r.SSS.Checkerboard"),
2,
TEXT("Enables or disables checkerboard rendering for subsurface profile rendering.\n")
TEXT("This is necessary if SceneColor does not include a floating point alpha channel (e.g 32-bit formats)\n")
TEXT(" 0: Disabled (high quality). If the rendertarget format does not have an alpha channel (e.g., PF_FloatR11G11B10), it leads to over-washed SSS. \n")
TEXT(" 1: Enabled (low quality). Surface lighting will be at reduced resolution.\n")
TEXT(" 2: Automatic. Non-checkerboard lighting will be applied if we have a suitable rendertarget format\n"),
ECVF_RenderThreadSafe);
TAutoConsoleVariable<int32> CVarSSSCheckerboardNeighborSSSValidation(
TEXT("r.SSS.Checkerboard.NeighborSSSValidation"),
0,
TEXT("Enable or disable checkerboard neighbor subsurface scattering validation.\n")
TEXT("This validation can remove border light leakage into subsurface scattering, creating a sharpe border with correct color")
TEXT(" 0: Disabled (default)")
TEXT(" 1: Enabled. Add 1 subsurface profile id query/pixel (low quality), 4 id query/pixel (high quality) at recombine pass"),
ECVF_RenderThreadSafe);
TAutoConsoleVariable<int32> CVarSSSBurleyQuality(
TEXT("r.SSS.Burley.Quality"),
1,
TEXT("0: Fallback mode. Burley falls back to run scattering in Separable with transmission in Burley for better performance. Separable parameters are automatically fitted.")
TEXT("1: Automatic. The subsurface will only switch to separable in half resolution. (default)"),
ECVF_RenderThreadSafe | ECVF_Scalability
);
TAutoConsoleVariable<int32> CVarSSSBurleyNumSamplesOverride(
TEXT("r.SSS.Burley.NumSamplesOverride"),
0,
TEXT("When zero, Burley SSS adaptively determines the number of samples. When non-zero, this value overrides the sample count.\n"),
ECVF_RenderThreadSafe);
TAutoConsoleVariable<int32> CVarSSSBurleyEnableProfileIdCache(
TEXT("r.SSS.Burley.EnableProfileIdCache"),
1,
TEXT("0: Disable profile id cache using in the sampling pass.\n")
TEXT("1: Consumes 1 byte per pixel more memory to make Burley pass much faster. (default)\n"),
ECVF_RenderThreadSafe);
TAutoConsoleVariable<int32> CVarSSSBurleyBilateralFilterKernelFunctionType(
TEXT("r.SSS.Burley.BilateralFilterKernelFunctionType"),
1,
TEXT("0: Depth Only. It is more performant (x2 faster for close view).")
TEXT("1: Depth and normal. It leads to better quality in regions like eyelids. (default)"),
ECVF_RenderThreadSafe);
TAutoConsoleVariable<int32> CVarSSSMipmapsMinTileCount(
TEXT("r.SSS.Burley.MinGenerateMipsTileCount"),
4000,
TEXT("4000. (default) The minimal number of tiles to trigger subsurface radiance mip generation. Set to zero to always generate mips (Experimental value)"),
ECVF_RenderThreadSafe);
TAutoConsoleVariable<float> CVarSSSSubpixelThreshold(
TEXT("r.SSS.Subpixel.Threshold"),
1.0f,
TEXT("If a scattering is always within 1 pixel footage, there is no need to scatter. This value determines at which percentage the scattering is considered all inside\n")
TEXT("the texel itself. Default is below 1. Once it reaches this value, we stop doing subsurface scattering. The logic is per tile."),
ECVF_RenderThreadSafe);
TAutoConsoleVariable<float> CVarSubSurfaceColorAsTannsmittanceAtDistance(
TEXT("r.SSS.SubSurfaceColorAsTansmittanceAtDistance"),
0.15f,
TEXT("Normalized distance (0..1) at which the surface color is interpreted as transmittance color to compute extinction coefficients."),
ECVF_RenderThreadSafe);
DECLARE_GPU_STAT(SubsurfaceScattering);
}
// Define to use a custom ps to clear UAV.
#define USE_CUSTOM_CLEAR_UAV
enum class ESubsurfaceMode : uint32
{
// Performs a full resolution scattering filter.
FullRes,
// Performs a half resolution scattering filter.
HalfRes,
// Reconstructs lighting, but does not perform scattering.
Bypass,
MAX
};
const TCHAR* GetEventName(ESubsurfaceMode SubsurfaceMode)
{
static const TCHAR* const kEventNames[] = {
TEXT("FullRes"),
TEXT("HalfRes"),
TEXT("Bypass"),
};
static_assert(UE_ARRAY_COUNT(kEventNames) == int32(ESubsurfaceMode::MAX), "Fix me");
return kEventNames[int32(SubsurfaceMode)];
}
// Returns the [0, N] clamped value of the 'r.SSS.Scale' CVar.
float GetSubsurfaceRadiusScale()
{
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.SSS.Scale"));
check(CVar);
return FMath::Max(0.0f, CVar->GetValueOnRenderThread());
}
int32 GetSSSFilter()
{
return CVarSSSFilter.GetValueOnRenderThread();
}
int32 GetSSSSampleSet()
{
return CVarSSSSampleSet.GetValueOnAnyThread();
}
int32 GetSSSQuality()
{
return CVarSSSQuality.GetValueOnAnyThread();
}
int32 GetSSSBurleyBilateralFilterKernelFunctionType()
{
return CVarSSSBurleyBilateralFilterKernelFunctionType.GetValueOnAnyThread();
}
// Returns the current subsurface mode required by the current view.
ESubsurfaceMode GetSubsurfaceModeForView(const FViewInfo& View)
{
const float Radius = GetSubsurfaceRadiusScale();
const bool bShowSubsurfaceScattering = Radius > 0 && View.Family->EngineShowFlags.SubsurfaceScattering;
if (bShowSubsurfaceScattering)
{
const bool bHalfRes = CVarSSSHalfRes.GetValueOnRenderThread() != 0;
if (bHalfRes)
{
return ESubsurfaceMode::HalfRes;
}
else
{
return ESubsurfaceMode::FullRes;
}
}
else
{
return ESubsurfaceMode::Bypass;
}
}
// A shader parameter struct for a single subsurface input texture.
BEGIN_SHADER_PARAMETER_STRUCT(FSubsurfaceInput, )
SHADER_PARAMETER_STRUCT_INCLUDE(FScreenPassTextureViewportParameters, Viewport)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, Texture)
END_SHADER_PARAMETER_STRUCT()
BEGIN_SHADER_PARAMETER_STRUCT(FSubsurfaceSRVInput, )
SHADER_PARAMETER_STRUCT_INCLUDE(FScreenPassTextureViewportParameters, Viewport)
SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, Texture)
END_SHADER_PARAMETER_STRUCT();
// Set of common shader parameters shared by all subsurface shaders.
BEGIN_SHADER_PARAMETER_STRUCT(FSubsurfaceParameters, )
SHADER_PARAMETER(FVector4f, SubsurfaceParams)
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSceneTextureUniformParameters, SceneTextures)
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, ViewUniformBuffer)
END_SHADER_PARAMETER_STRUCT()
FSubsurfaceParameters GetSubsurfaceCommonParameters(FRDGBuilder& GraphBuilder, const FViewInfo& View, TRDGUniformBufferRef<FSceneTextureUniformParameters> SceneTextures)
{
const float DistanceToProjectionWindow = View.ViewMatrices.GetProjectionMatrix().M[0][0];
const float SSSScaleZ = DistanceToProjectionWindow * GetSubsurfaceRadiusScale();
const float SSSScaleX = SSSScaleZ / SUBSURFACE_KERNEL_SIZE * 0.5f;
const float SSSOverrideNumSamples = float(CVarSSSBurleyNumSamplesOverride.GetValueOnRenderThread());
const float MinGenerateMipsTileCount = FMath::Max(0.0f, CVarSSSMipmapsMinTileCount.GetValueOnRenderThread());
FSubsurfaceParameters Parameters;
Parameters.SubsurfaceParams = FVector4f(SSSScaleX, SSSScaleZ, SSSOverrideNumSamples, MinGenerateMipsTileCount);
Parameters.ViewUniformBuffer = View.ViewUniformBuffer;
Parameters.SceneTextures = SceneTextures;
return Parameters;
}
FSubsurfaceInput GetSubsurfaceInput(FRDGTextureRef Texture, const FScreenPassTextureViewportParameters& ViewportParameters)
{
FSubsurfaceInput Input;
Input.Texture = Texture;
Input.Viewport = ViewportParameters;
return Input;
}
FSubsurfaceSRVInput GetSubsurfaceSRVInput(FRDGTextureSRVRef Texture, const FScreenPassTextureViewportParameters& ViewportParameters)
{
FSubsurfaceSRVInput Input;
Input.Texture = Texture;
Input.Viewport = ViewportParameters;
return Input;
}
bool IsSubsurfaceEnabled()
{
const bool bEnabled = CVarSubsurfaceScattering.GetValueOnAnyThread() != 0;
const bool bHasScale = CVarSSSScale.GetValueOnAnyThread() > 0.0f;
return (bEnabled && bHasScale);
}
bool IsSubsurfaceRequiredForView(const FViewInfo& View)
{
const bool bSimpleDynamicLighting = IsForwardShadingEnabled(View.GetShaderPlatform());
const bool bSubsurfaceEnabled = IsSubsurfaceEnabled();
const bool bViewHasSubsurfaceMaterials = ((View.ShadingModelMaskInView & GetUseSubsurfaceProfileShadingModelMask()) != 0);
return (bSubsurfaceEnabled && bViewHasSubsurfaceMaterials && !bSimpleDynamicLighting);
}
bool IsProfileIdCacheEnabled()
{
return UE::PixelFormat::HasCapabilities(PF_R8_UINT, EPixelFormatCapabilities::TypedUAVLoad)
&& CVarSSSBurleyEnableProfileIdCache.GetValueOnRenderThread() != 0;
}
uint32 GetSubsurfaceRequiredViewMask(TArrayView<const FViewInfo> Views)
{
const uint32 ViewCount = Views.Num();
uint32 ViewMask = 0;
// Traverse the views to make sure we only process subsurface if requested by any view.
for (uint32 ViewIndex = 0; ViewIndex < ViewCount; ++ViewIndex)
{
const FViewInfo& View = Views[ViewIndex];
if (IsSubsurfaceRequiredForView(View))
{
const uint32 ViewBit = 1 << ViewIndex;
ViewMask |= ViewBit;
}
}
return ViewMask;
}
bool IsSubsurfaceCheckerboardFormat(EPixelFormat SceneColorFormat, const FViewInfo& View)
{
if (Substrate::IsOpaqueRoughRefractionEnabled(View.GetShaderPlatform()))
{
// With this mode, specular and subsurface colors are correctly separated so checkboard is not required.
return false;
}
int CVarValue = CVarSSSCheckerboard.GetValueOnRenderThread();
if (CVarValue == 0)
{
return false;
}
else if (CVarValue == 1)
{
return true;
}
else if (CVarValue == 2)
{
switch (SceneColorFormat)
{
case PF_A32B32G32R32F:
case PF_FloatRGBA:
return false;
default:
return true;
}
}
return true;
}
// Base class for a subsurface shader.
class FSubsurfaceShader : public FGlobalShader
{
public:
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("SUBSURFACE_RADIUS_SCALE"), SUBSURFACE_RADIUS_SCALE);
OutEnvironment.SetDefine(TEXT("SUBSURFACE_KERNEL_SIZE"), SUBSURFACE_KERNEL_SIZE);
}
FSubsurfaceShader() = default;
FSubsurfaceShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FGlobalShader(Initializer)
{}
};
// Encapsulates the post processing subsurface scattering common pixel shader.
class FSubsurfaceVisualizePS : public FSubsurfaceShader
{
public:
DECLARE_GLOBAL_SHADER(FSubsurfaceVisualizePS);
SHADER_USE_PARAMETER_STRUCT(FSubsurfaceVisualizePS, FSubsurfaceShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSubstrateGlobalUniformParameters, Substrate)
SHADER_PARAMETER_STRUCT_INCLUDE(FSubsurfaceParameters, Subsurface)
SHADER_PARAMETER_STRUCT(FSubsurfaceInput, SubsurfaceInput0)
SHADER_PARAMETER_TEXTURE(Texture2D, MiniFontTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, SubsurfaceSampler0)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_GLOBAL_SHADER(FSubsurfaceVisualizePS, "/Engine/Private/PostProcessSubsurface.usf", "VisualizePS", SF_Pixel);
// Encapsulates a simple copy pixel shader.
class FSubsurfaceViewportCopyPS : public FSubsurfaceShader
{
DECLARE_GLOBAL_SHADER(FSubsurfaceViewportCopyPS);
SHADER_USE_PARAMETER_STRUCT(FSubsurfaceViewportCopyPS, FSubsurfaceShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SubsurfaceInput0_Texture)
SHADER_PARAMETER_SAMPLER(SamplerState, SubsurfaceSampler0)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT();
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
};
IMPLEMENT_GLOBAL_SHADER(FSubsurfaceViewportCopyPS, "/Engine/Private/PostProcessSubsurface.usf", "SubsurfaceViewportCopyPS", SF_Pixel);
//-------------------------------------------------------------------------------------------
// Indirect dispatch class and functions
//-------------------------------------------------------------------------------------------
// Subsurface uniform buffer layout
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FSubsurfaceUniformParameters, )
SHADER_PARAMETER(uint32, MaxGroupCount)
SHADER_PARAMETER(uint32, bRectPrimitive)
END_GLOBAL_SHADER_PARAMETER_STRUCT()
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FSubsurfaceUniformParameters, "SubsurfaceUniformParameters");
typedef TUniformBufferRef<FSubsurfaceUniformParameters> FSubsurfaceUniformRef;
// Return a uniform buffer with values filled and with single frame lifetime
FSubsurfaceUniformRef CreateUniformBuffer(FViewInfo const& View, int32 MaxGroupCount)
{
FSubsurfaceUniformParameters Parameters;
Parameters.MaxGroupCount = MaxGroupCount;
Parameters.bRectPrimitive = GRHISupportsRectTopology;
return FSubsurfaceUniformRef::CreateUniformBufferImmediate(Parameters, UniformBuffer_SingleFrame);
}
class FSubsurfaceInitValueBufferCS : public FSubsurfaceShader
{
public:
DECLARE_GLOBAL_SHADER(FSubsurfaceInitValueBufferCS);
SHADER_USE_PARAMETER_STRUCT(FSubsurfaceInitValueBufferCS, FSubsurfaceShader);
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FSubsurfaceShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("SUBSURFACE_BURLEY_COMPUTE"), 1);
}
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, RWTileTypeCountBuffer)
END_SHADER_PARAMETER_STRUCT()
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
};
IMPLEMENT_GLOBAL_SHADER(FSubsurfaceInitValueBufferCS, "/Engine/Private/PostProcessSubsurface.usf", "InitValueBufferCS", SF_Compute);
class FFillMipsConditionBufferCS : public FSubsurfaceShader
{
public:
DECLARE_GLOBAL_SHADER(FFillMipsConditionBufferCS);
SHADER_USE_PARAMETER_STRUCT(FFillMipsConditionBufferCS, FSubsurfaceShader);
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FSubsurfaceShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("SUBSURFACE_BURLEY_COMPUTE"), 1);
}
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(uint32, MinGenerateMipsTileCount)
SHADER_PARAMETER(uint32, Offset)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint>, TileTypeCountBuffer)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, RWMipsConditionBuffer)
END_SHADER_PARAMETER_STRUCT()
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
};
IMPLEMENT_GLOBAL_SHADER(FFillMipsConditionBufferCS, "/Engine/Private/PostProcessSubsurface.usf", "FillMipsConditionBufferCS", SF_Compute);
class FSubsurfaceBuildIndirectDispatchArgsCS : public FSubsurfaceShader
{
public:
DECLARE_GLOBAL_SHADER(FSubsurfaceBuildIndirectDispatchArgsCS);
SHADER_USE_PARAMETER_STRUCT(FSubsurfaceBuildIndirectDispatchArgsCS, FSubsurfaceShader)
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FSubsurfaceShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("SUBSURFACE_BURLEY_COMPUTE"), 1);
}
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_REF(FSubsurfaceUniformParameters, SubsurfaceUniformParameters)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, RWIndirectDispatchArgsBuffer)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, RWIndirectDrawArgsBuffer)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint>, TileTypeCountBuffer)
END_SHADER_PARAMETER_STRUCT()
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
};
IMPLEMENT_GLOBAL_SHADER(FSubsurfaceBuildIndirectDispatchArgsCS, "/Engine/Private/PostProcessSubsurface.usf", "BuildIndirectDispatchArgsCS", SF_Compute);
class FSubsurfaceIndirectDispatchSetupCS : public FSubsurfaceShader
{
public:
DECLARE_GLOBAL_SHADER(FSubsurfaceIndirectDispatchSetupCS);
SHADER_USE_PARAMETER_STRUCT(FSubsurfaceIndirectDispatchSetupCS, FSubsurfaceShader)
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FSubsurfaceShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("SUBSURFACE_BURLEY_COMPUTE"), 1);
}
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSubstrateGlobalUniformParameters, Substrate)
SHADER_PARAMETER_STRUCT_INCLUDE(FSubsurfaceParameters, Subsurface)
SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Output)
SHADER_PARAMETER_STRUCT(FSubsurfaceInput, SubsurfaceInput0)
SHADER_PARAMETER_SAMPLER(SamplerState, SubsurfaceSampler0)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, SetupTexture)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint>, GroupBuffer)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, RWSeparableGroupBuffer)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, RWBurleyGroupBuffer)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, RWAllGroupBuffer)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, RWTileTypeCountBuffer)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, ProfileIdTexture)
SHADER_PARAMETER_STRUCT_REF(FSubsurfaceUniformParameters, SubsurfaceUniformParameters)
RDG_BUFFER_ACCESS(IndirectDispatchArgsBuffer, ERHIAccess::IndirectArgs)
END_SHADER_PARAMETER_STRUCT()
// Controls the quality of lighting reconstruction.
enum class EQuality : uint32
{
Low,
High,
MAX
};
enum class ESubsurfaceSetupPass : uint32
{
PassOneMain, // Subsurface Setup. Write to Setup texture
PassTwoPassThrough, // PassThrough to final texture before recombine (due to Subpixel).
MAX
};
static const TCHAR* GetEventName(EQuality Quality)
{
static const TCHAR* const kEventNames[] = {
TEXT("Low"),
TEXT("High"),
};
static_assert(UE_ARRAY_COUNT(kEventNames) == int32(EQuality::MAX), "Fix me");
return kEventNames[int32(Quality)];
}
static const TCHAR* GetPassName(ESubsurfaceSetupPass Pass)
{
static const TCHAR* const kEventNames[] = {
TEXT("Main"),
TEXT("Passthrough"),
};
static_assert(UE_ARRAY_COUNT(kEventNames) == int32(ESubsurfaceSetupPass::MAX), "Fix me");
return kEventNames[int32(Pass)];
}
class FDimensionHalfRes : SHADER_PERMUTATION_BOOL("SUBSURFACE_HALF_RES");
class FDimensionCheckerboard : SHADER_PERMUTATION_BOOL("SUBSURFACE_PROFILE_CHECKERBOARD");
class FRunningInSeparable : SHADER_PERMUTATION_BOOL("SUBSURFACE_FORCE_SEPARABLE");
class FDimensionEnableProfileIDCache : SHADER_PERMUTATION_BOOL("ENABLE_PROFILE_ID_CACHE");
class FDimensionQuality : SHADER_PERMUTATION_ENUM_CLASS("SUBSURFACE_RECOMBINE_QUALITY", EQuality);
class FDimensionSetupPass :SHADER_PERMUTATION_ENUM_CLASS("SUBSURFACE_SETUP_PASS", ESubsurfaceSetupPass);
using FPermutationDomain = TShaderPermutationDomain<FDimensionHalfRes, FDimensionCheckerboard, FRunningInSeparable, FDimensionEnableProfileIDCache, FDimensionQuality, FDimensionSetupPass>;
};
IMPLEMENT_GLOBAL_SHADER(FSubsurfaceIndirectDispatchSetupCS, "/Engine/Private/PostProcessSubsurface.usf", "SetupIndirectCS", SF_Compute);
class FSubsurfaceIndirectDispatchCS : public FSubsurfaceShader
{
public:
DECLARE_GLOBAL_SHADER(FSubsurfaceIndirectDispatchCS);
SHADER_USE_PARAMETER_STRUCT(FSubsurfaceIndirectDispatchCS, FSubsurfaceShader);
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FSubsurfaceShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("SUBSURFACE_BURLEY_COMPUTE"), 1);
OutEnvironment.SetDefine(TEXT("ENABLE_VELOCITY"), 1);
OutEnvironment.SetDefine(TEXT("SUBSURFACE_GROUP_SIZE"), FSubsurfaceTiles::TileSize);
}
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSubstrateGlobalUniformParameters, Substrate)
SHADER_PARAMETER_STRUCT_INCLUDE(FSubsurfaceParameters, Subsurface)
SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Output)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, SSSColorUAV)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, HistoryUAV)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint>, GroupBuffer)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint>, TileTypeCountBuffer)
RDG_BUFFER_ACCESS(IndirectDispatchArgsBuffer, ERHIAccess::IndirectArgs)
SHADER_PARAMETER_STRUCT(FSubsurfaceInput, SubsurfaceInput0)
SHADER_PARAMETER_SAMPLER(SamplerState, SubsurfaceSampler0)
SHADER_PARAMETER_STRUCT(FSubsurfaceInput, SubsurfaceInput1) // History
SHADER_PARAMETER_SAMPLER(SamplerState, SubsurfaceSampler1)
SHADER_PARAMETER_STRUCT(FSubsurfaceInput, SubsurfaceInput2) // Profile mask | Velocity
SHADER_PARAMETER_SAMPLER(SamplerState, SubsurfaceSampler2)
SHADER_PARAMETER_STRUCT(FSubsurfaceInput, SubsurfaceInput3) // Control Variates
SHADER_PARAMETER_SAMPLER(SamplerState, SubsurfaceSampler3)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, ProfileIdTexture)
END_SHADER_PARAMETER_STRUCT()
// Direction of the 1D separable filter.
enum class EDirection : uint32
{
Horizontal,
Vertical,
MAX
};
enum class ESubsurfacePass : uint32
{
PassOne, // Burley sampling (or Horizontal) pass pass one
PassTwo, // Variance updating (or Vertical) pass pass two
MAX
};
// Controls the quality (number of samples) of the blur kernel.
enum class EQuality : uint32
{
Low,
Medium,
High,
MAX
};
enum class EBilateralFilterKernelFunctionType : uint32
{
Depth,
DepthAndNormal,
MAX
};
enum class ESubsurfaceType : uint32
{
BURLEY,
SEPARABLE,
MAX
};
static const TCHAR* GetEventName(EDirection Direction)
{
static const TCHAR* const kEventNames[] = {
TEXT("Horizontal"),
TEXT("Vertical"),
};
static_assert(UE_ARRAY_COUNT(kEventNames) == int32(EDirection::MAX), "Fix me");
return kEventNames[int32(Direction)];
}
static const TCHAR* GetEventName(ESubsurfacePass SubsurfacePass)
{
static const TCHAR* const kEventNames[] = {
TEXT("PassOne"),
TEXT("PassTwo"),
};
static_assert(UE_ARRAY_COUNT(kEventNames) == int32(ESubsurfacePass::MAX), "Fix me");
return kEventNames[int32(SubsurfacePass)];
}
static const TCHAR* GetEventName(EQuality Quality)
{
static const TCHAR* const kEventNames[] = {
TEXT("Low"),
TEXT("Medium"),
TEXT("High"),
};
static_assert(UE_ARRAY_COUNT(kEventNames) == int32(EQuality::MAX), "Fix me");
return kEventNames[int32(Quality)];
}
static const TCHAR* GetEventName(ESubsurfaceType SubsurfaceType)
{
static const TCHAR* const kEventNames[] = {
TEXT("Burley"),
TEXT("Separable"),
};
static_assert(UE_ARRAY_COUNT(kEventNames) == int32(ESubsurfaceType::MAX), "Fix me");
return kEventNames[int32(SubsurfaceType)];
}
class FSubsurfacePassFunction : SHADER_PERMUTATION_ENUM_CLASS("SUBSURFACE_PASS", ESubsurfacePass);
class FDimensionQuality : SHADER_PERMUTATION_ENUM_CLASS("SUBSURFACE_QUALITY", EQuality);
class FBilateralFilterKernelFunctionType : SHADER_PERMUTATION_ENUM_CLASS("BILATERAL_FILTER_KERNEL_FUNCTION_TYPE", EBilateralFilterKernelFunctionType);
class FSubsurfaceType : SHADER_PERMUTATION_ENUM_CLASS("SUBSURFACE_TYPE", ESubsurfaceType);
class FDimensionHalfRes : SHADER_PERMUTATION_BOOL("SUBSURFACE_HALF_RES");
class FRunningInSeparable : SHADER_PERMUTATION_BOOL("SUBSURFACE_FORCE_SEPARABLE");
class FDimensionEnableProfileIDCache : SHADER_PERMUTATION_BOOL("ENABLE_PROFILE_ID_CACHE");
using FPermutationDomain = TShaderPermutationDomain<FSubsurfacePassFunction, FDimensionQuality,
FBilateralFilterKernelFunctionType, FSubsurfaceType, FDimensionHalfRes, FRunningInSeparable, FDimensionEnableProfileIDCache>;
static EShaderPermutationPrecacheRequest ShouldPrecachePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
FPermutationDomain PermutationVector(Parameters.PermutationId);
if (PermutationVector.Get<FDimensionQuality>() != GetQuality())
{
return EShaderPermutationPrecacheRequest::NotUsed;
}
if (PermutationVector.Get<FBilateralFilterKernelFunctionType>() != GetBilateralFilterKernelFunctionType())
{
return EShaderPermutationPrecacheRequest::NotUsed;
}
return EShaderPermutationPrecacheRequest::Precached;
}
// Returns the sampler state based on the requested SSS filter CVar setting and half resolution setting.
static FRHISamplerState* GetSamplerState(bool bHalfRes)
{
if (GetSSSFilter())
{ // Trilinear is used for mipmap sampling in full resolution
if (bHalfRes)
{
return TStaticSamplerState<SF_Bilinear, AM_Border, AM_Border, AM_Border>::GetRHI();//SF_Bilinear
}
else
{
return TStaticSamplerState<SF_Trilinear, AM_Border, AM_Border, AM_Border>::GetRHI();//SF_Bilinear
}
}
else
{
return TStaticSamplerState<SF_Point, AM_Border, AM_Border, AM_Border>::GetRHI();
}
}
// Returns the SSS quality level requested by the SSS SampleSet CVar setting.
static EQuality GetQuality()
{
return static_cast<FSubsurfaceIndirectDispatchCS::EQuality>(
FMath::Clamp(
GetSSSSampleSet(),
static_cast<int32>(FSubsurfaceIndirectDispatchCS::EQuality::Low),
static_cast<int32>(FSubsurfaceIndirectDispatchCS::EQuality::High)));
}
static EBilateralFilterKernelFunctionType GetBilateralFilterKernelFunctionType()
{
return static_cast<FSubsurfaceIndirectDispatchCS::EBilateralFilterKernelFunctionType>(
FMath::Clamp(
GetSSSBurleyBilateralFilterKernelFunctionType(),
static_cast<int32>(FSubsurfaceIndirectDispatchCS::EBilateralFilterKernelFunctionType::Depth),
static_cast<int32>(FSubsurfaceIndirectDispatchCS::EBilateralFilterKernelFunctionType::DepthAndNormal)));
}
static FSubsurfaceTiles::ETileType ToSubsurfaceTileType(ESubsurfaceType Type, bool bForceSeparable)
{
if (bForceSeparable)
{
return FSubsurfaceTiles::ETileType::AllNonPassthrough;
}
FSubsurfaceTiles::ETileType TileType = FSubsurfaceTiles::ETileType::AFIS;
switch (Type)
{
case ESubsurfaceType::BURLEY: TileType = FSubsurfaceTiles::ETileType::AFIS; break;
case ESubsurfaceType::SEPARABLE: TileType = FSubsurfaceTiles::ETileType::SEPARABLE; break;
}
return TileType;
}
};
IMPLEMENT_GLOBAL_SHADER(FSubsurfaceIndirectDispatchCS, "/Engine/Private/PostProcessSubsurface.usf", "MainIndirectDispatchCS", SF_Compute);
// resolve textures that is not SRV
// Encapsulates a simple copy pixel shader.
class FSubsurfaceSRVResolvePS : public FSubsurfaceShader
{
DECLARE_GLOBAL_SHADER(FSubsurfaceSRVResolvePS);
SHADER_USE_PARAMETER_STRUCT(FSubsurfaceSRVResolvePS, FSubsurfaceShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SubsurfaceInput0_Texture)
RDG_BUFFER_ACCESS(IndirectDispatchArgsBuffer, ERHIAccess::IndirectArgs)
SHADER_PARAMETER_SAMPLER(SamplerState, SubsurfaceSampler0)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT();
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
};
IMPLEMENT_GLOBAL_SHADER(FSubsurfaceSRVResolvePS, "/Engine/Private/PostProcessSubsurface.usf", "SubsurfaceViewportCopyPS", SF_Pixel);
FRDGTextureRef CreateBlackUAVTexture(FRDGBuilder& GraphBuilder, FRDGTextureDesc SRVDesc, const TCHAR* Name, const FViewInfo& View,
const FScreenPassTextureViewport& SceneViewport)
{
#ifdef USE_CUSTOM_CLEAR_UAV
SRVDesc.Flags |= TexCreate_ShaderResource | TexCreate_UAV;
FRDGTextureRef SRVTextureOutput = GraphBuilder.CreateTexture(SRVDesc, Name);
FSubsurfaceSRVResolvePS::FParameters* PassParameters = GraphBuilder.AllocParameters<FSubsurfaceSRVResolvePS::FParameters>();
PassParameters->RenderTargets[0] = FRenderTargetBinding(SRVTextureOutput, ERenderTargetLoadAction::ENoAction);
PassParameters->SubsurfaceInput0_Texture = GraphBuilder.RegisterExternalTexture(GSystemTextures.BlackDummy);
PassParameters->SubsurfaceSampler0 = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
TShaderMapRef<FSubsurfaceSRVResolvePS> PixelShader(View.ShaderMap);
AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("SSS::ClearUAV"), View, SceneViewport, SceneViewport, PixelShader, PassParameters);
#else
FRDGTextureRef SRVTextureOutput = GraphBuilder.CreateTexture(SRVDesc, Name);
FRDGTextureUAVDesc UAVClearDesc(SRVTextureOutput, 0);
ClearUAV(GraphBuilder, FRDGEventName(TEXT("SSS::ClearUAV")), GraphBuilder.CreateUAV(UAVClearDesc), FLinearColor::Black);
#endif
return SRVTextureOutput;
}
// create a black UAV only when CondtionBuffer[Offset] > 0.
FRDGTextureRef CreateBlackUAVTexture(FRDGBuilder& GraphBuilder, FRDGTextureDesc SRVDesc, const TCHAR* TextureName, FRDGEventName&& PassName,
const FScreenPassTextureViewport& SceneViewport, FRDGBufferRef ConditionBuffer, uint32 Offset)
{
SRVDesc.Flags |= TexCreate_ShaderResource | TexCreate_UAV;
FRDGTextureRef TextureOutput = GraphBuilder.CreateTexture(SRVDesc, TextureName);
AddConditionalClearBlackUAVPass(
GraphBuilder,
Forward<FRDGEventName>(PassName),
TextureOutput,
SceneViewport,
ConditionBuffer,
Offset);
return TextureOutput;
}
// Helper function to use external textures for the current GraphBuilder.
// When the texture is null, we use BlackDummy.
FRDGTextureRef RegisterExternalRenderTarget(FRDGBuilder& GraphBuilder, TRefCountPtr<IPooledRenderTarget>* PtrExternalTexture, FIntPoint CurentViewExtent, const TCHAR* Name)
{
FRDGTextureRef RegisteredTexture = NULL;
if (!PtrExternalTexture || !(*PtrExternalTexture))
{
RegisteredTexture = GraphBuilder.RegisterExternalTexture(GSystemTextures.BlackDummy, Name);
}
else
{
if (CurentViewExtent != (*PtrExternalTexture)->GetDesc().Extent)
{
RegisteredTexture = GraphBuilder.RegisterExternalTexture(GSystemTextures.BlackDummy, Name);
}
else
{
RegisteredTexture = GraphBuilder.RegisterExternalTexture(*PtrExternalTexture, Name);
}
}
return RegisteredTexture;
}
// Encapsulates the post processing subsurface recombine pixel shader.
class FSubsurfaceRecombinePS : public FSubsurfaceShader
{
DECLARE_GLOBAL_SHADER(FSubsurfaceRecombinePS);
SHADER_USE_PARAMETER_STRUCT(FSubsurfaceRecombinePS, FSubsurfaceShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSubstrateGlobalUniformParameters, Substrate)
SHADER_PARAMETER_STRUCT_INCLUDE(FSubsurfaceParameters, Subsurface)
SHADER_PARAMETER_STRUCT_INCLUDE(FSubsurfaceTilePassVS::FParameters, TileParameters)
SHADER_PARAMETER_STRUCT(FSubsurfaceInput, SubsurfaceInput0)
SHADER_PARAMETER_STRUCT(FSubsurfaceInput, SubsurfaceInput1)
SHADER_PARAMETER_SAMPLER(SamplerState, SubsurfaceSampler0)
SHADER_PARAMETER_SAMPLER(SamplerState, SubsurfaceSampler1)
SHADER_PARAMETER(uint32, CheckerboardNeighborSSSValidation)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT();
// Controls the quality of lighting reconstruction.
enum class EQuality : uint32
{
Low,
High,
MAX
};
static const TCHAR* GetEventName(EQuality Quality)
{
static const TCHAR* const kEventNames[] = {
TEXT("Low"),
TEXT("High"),
};
static_assert(UE_ARRAY_COUNT(kEventNames) == int32(EQuality::MAX), "Fix me");
return kEventNames[int32(Quality)];
}
class FDimensionMode : SHADER_PERMUTATION_ENUM_CLASS("SUBSURFACE_RECOMBINE_MODE", ESubsurfaceMode);
class FDimensionQuality : SHADER_PERMUTATION_ENUM_CLASS("SUBSURFACE_RECOMBINE_QUALITY", EQuality);
class FDimensionCheckerboard : SHADER_PERMUTATION_BOOL("SUBSURFACE_PROFILE_CHECKERBOARD");
class FDimensionHalfRes : SHADER_PERMUTATION_BOOL("SUBSURFACE_HALF_RES");
class FRunningInSeparable : SHADER_PERMUTATION_BOOL("SUBSURFACE_FORCE_SEPARABLE");
using FPermutationDomain = TShaderPermutationDomain<FDimensionMode, FDimensionQuality, FDimensionCheckerboard, FDimensionHalfRes, FRunningInSeparable>;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return GetMaxSupportedFeatureLevel(Parameters.Platform) >= ERHIFeatureLevel::SM5;
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FSubsurfaceShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("SUBSURFACE_RECOMBINE"), 1);
}
// Returns the Recombine quality level requested by the SSS Quality CVar setting.
static EQuality GetQuality(const FViewInfo& View)
{
const uint32 QualityCVar = GetSSSQuality();
EMainTAAPassConfig MainTAAConfig = GetMainTAAPassConfig(View);
// Low quality is really bad with modern temporal upscalers.
bool bAllowLowQuality = MainTAAConfig == EMainTAAPassConfig::Disabled || MainTAAConfig == EMainTAAPassConfig::TAA;
if (!bAllowLowQuality)
{
return EQuality::High;
}
// Quality is forced to high when the CVar is set to 'auto' and TAA is NOT enabled.
// TAA improves quality through temporal filtering and clamping box, making it less necessary to use
// high quality mode.
if (QualityCVar == -1)
{
if (MainTAAConfig == EMainTAAPassConfig::TAA)
{
return EQuality::Low;
}
else
{
return EQuality::High;
}
}
if (QualityCVar == 1)
{
return EQuality::High;
}
else
{
return EQuality::Low;
}
}
static uint32 GetCheckerBoardNeighborSSSValidation(bool bCheckerBoard)
{
bool bValidation = CVarSSSCheckerboardNeighborSSSValidation.GetValueOnRenderThread() > 0 ? true : false;
return (bCheckerBoard && bValidation) ? 1u : 0u;
}
};
IMPLEMENT_GLOBAL_SHADER(FSubsurfaceRecombinePS, "/Engine/Private/PostProcessSubsurface.usf", "SubsurfaceRecombinePS", SF_Pixel);
// copy write back additively to the scene color texture
class FSubsurfaceRecombineCopyPS : public FSubsurfaceShader
{
DECLARE_GLOBAL_SHADER(FSubsurfaceRecombineCopyPS);
SHADER_USE_PARAMETER_STRUCT(FSubsurfaceRecombineCopyPS, FSubsurfaceShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSubstrateGlobalUniformParameters, Substrate)
SHADER_PARAMETER_STRUCT_INCLUDE(FSubsurfaceParameters, Subsurface)
SHADER_PARAMETER_STRUCT_INCLUDE(FSubsurfaceTilePassVS::FParameters, TileParameters)
SHADER_PARAMETER_STRUCT(FSubsurfaceInput, SubsurfaceInput0)
SHADER_PARAMETER_SAMPLER(SamplerState, SubsurfaceSampler0)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT();
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
};
IMPLEMENT_GLOBAL_SHADER(FSubsurfaceRecombineCopyPS, "/Engine/Private/PostProcessSubsurface.usf", "SubsurfaceRecombineCopyPS", SF_Pixel);
void AddSubsurfaceViewPass(
FRDGBuilder& GraphBuilder,
const FViewInfo& View,
const FSceneTextures& SceneTextures,
FRDGTextureRef SceneColorTextureOutput,
ERenderTargetLoadAction SceneColorTextureLoadAction)
{
FRDGTextureRef SceneColorTexture = SceneTextures.Color.Target;
const FScreenPassTextureViewport SceneViewport(SceneColorTexture, View.ViewRect);
check(SceneColorTextureOutput);
const FSceneViewFamily* ViewFamily = View.Family;
const FRDGTextureDesc& SceneColorTextureDesc = SceneColorTexture->Desc;
const ESubsurfaceMode SubsurfaceMode = GetSubsurfaceModeForView(View);
const bool bHalfRes = (SubsurfaceMode == ESubsurfaceMode::HalfRes);
const bool bCheckerboard = IsSubsurfaceCheckerboardFormat(SceneColorTextureDesc.Format, View);
const uint32 ScaleFactor = bHalfRes ? 2 : 1;
//We run in separable mode under two conditions: 1) Run Burley fallback mode. 2) when the screen is in half resolution.
const bool bForceRunningInSeparable = CVarSSSBurleyQuality.GetValueOnRenderThread() == 0 ||
(bHalfRes && CVarSSSHalfResForceSeparable.GetValueOnRenderThread() != 0 );
const bool bUseProfileIdCache = IsProfileIdCacheEnabled();
const int32 MinGenerateMipsTileCount = FMath::Max(0, CVarSSSMipmapsMinTileCount.GetValueOnRenderThread());
/**
* All subsurface passes within the screen-space subsurface effect can operate at half or full resolution,
* depending on the subsurface mode. The values are precomputed and shared among all Subsurface textures.
*/
const FScreenPassTextureViewport SubsurfaceViewport = GetDownscaledViewport(SceneViewport, ScaleFactor);
const FRDGTextureDesc SceneColorTextureDescriptor = FRDGTextureDesc::Create2D(
SceneViewport.Extent,
PF_FloatRGBA,
FClearValueBinding(),
TexCreate_RenderTargetable | TexCreate_ShaderResource | TexCreate_UAV);
const FRDGTextureDesc SubsurfaceTextureDescriptor = FRDGTextureDesc::Create2D(
SubsurfaceViewport.Extent,
PF_FloatRGBA,
FClearValueBinding(),
TexCreate_RenderTargetable | TexCreate_ShaderResource | TexCreate_UAV);
const FRDGTextureDesc ProfileIdTextureDescriptor = FRDGTextureDesc::Create2D(
SubsurfaceViewport.Extent,
PF_R8_UINT,
FClearValueBinding(),
TexCreate_RenderTargetable | TexCreate_ShaderResource | TexCreate_UAV);
// Create texture desc with 6 mips if possible, otherwise clamp number of mips to match the viewport resolution
const FRDGTextureDesc SubsurfaceTextureWith6MipsDescriptor = FRDGTextureDesc::Create2D(
SubsurfaceViewport.Extent,
PF_FloatRGBA,
FClearValueBinding(),
TexCreate_RenderTargetable | TexCreate_ShaderResource | TexCreate_UAV,
FMath::Min(6u, 1 + FMath::FloorLog2((uint32)SubsurfaceViewport.Extent.GetMin())));
const FRDGSystemTextures& SystemTextures = FRDGSystemTextures::Get(GraphBuilder);
const FSubsurfaceParameters SubsurfaceCommonParameters = GetSubsurfaceCommonParameters(GraphBuilder, View, SceneTextures.UniformBuffer);
const FScreenPassTextureViewportParameters SubsurfaceViewportParameters = GetScreenPassTextureViewportParameters(SubsurfaceViewport);
const FScreenPassTextureViewportParameters SceneViewportParameters = GetScreenPassTextureViewportParameters(SceneViewport);
const bool bReadSeparatedSubSurfaceSceneColor = Substrate::IsOpaqueRoughRefractionEnabled(View.GetShaderPlatform());
const bool bWriteSeparatedOpaqueRoughRefractionSceneColor = Substrate::IsOpaqueRoughRefractionEnabled(View.GetShaderPlatform());
FRDGTextureRef SeparatedSubSurfaceSceneColor = View.SubstrateViewData.SceneData->SeparatedSubSurfaceSceneColor;
FRDGTextureRef SeparatedOpaqueRoughRefractionSceneColor = View.SubstrateViewData.SceneData->SeparatedOpaqueRoughRefractionSceneColor;
FRDGTextureRef SetupTexture = SceneColorTexture;
FRDGTextureRef SubsurfaceSubpassOneTex = nullptr;
FRDGTextureRef SubsurfaceSubpassTwoTex = nullptr;
FRDGTextureRef ProfileIdTexture = nullptr;
FRHISamplerState* PointClampSampler = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
FRHISamplerState* BilinearBorderSampler = TStaticSamplerState<SF_Bilinear, AM_Border, AM_Border, AM_Border>::GetRHI();
//History texture
FSceneViewState* ViewState = (FSceneViewState*)View.State;
TRefCountPtr<IPooledRenderTarget>* QualityHistoryState = ViewState ? &ViewState->SubsurfaceScatteringQualityHistoryRT : NULL;
//allocate/reallocate the quality history texture.
FRDGTextureRef QualityHistoryTexture = RegisterExternalRenderTarget(GraphBuilder, QualityHistoryState, SubsurfaceTextureDescriptor.Extent, TEXT("Subsurface.QualityHistoryTexture"));
FRDGTextureRef NewQualityHistoryTexture = nullptr;
RDG_EVENT_SCOPE_STAT(GraphBuilder, SubsurfaceScattering, "SubsurfaceScattering");
RDG_GPU_STAT_SCOPE(GraphBuilder, SubsurfaceScattering);
FSubsurfaceTiles Tiles;
/**
* When in bypass mode, the setup and convolution passes are skipped, but lighting
* reconstruction is still performed in the recombine pass.
*/
if (SubsurfaceMode != ESubsurfaceMode::Bypass)
{
// Support mipmaps in full resolution only. Need to clear the setup texture due to tiling logic.
SetupTexture = CreateBlackUAVTexture(GraphBuilder,
bForceRunningInSeparable ? SubsurfaceTextureDescriptor:SubsurfaceTextureWith6MipsDescriptor,
TEXT("Subsurface.SetupTexture"),
View,
SubsurfaceViewport);
// profile cache to accelerate sampling
if (bUseProfileIdCache)
{
// This path was designed to get used when r.SSS.Burley.EnableProfileIdCache is true.
ProfileIdTexture = GraphBuilder.CreateTexture(ProfileIdTextureDescriptor, TEXT("Subsurface.ProfileIdTexture"));
}
else
{
ProfileIdTexture = SystemTextures.Black;
}
FRDGTextureRef VelocityTexture = GetIfProduced(SceneTextures.Velocity, SystemTextures.Black);
Tiles = ClassifySSSTiles(GraphBuilder, View, SceneTextures, SceneViewportParameters, SubsurfaceViewportParameters, bHalfRes);
// Pre-allocate black UAV for the second buffer if all tiles are rendered in half resolution.
// Need to access this texture to directly setup passthrough pixel.
if (bHalfRes)
{
SubsurfaceSubpassTwoTex = CreateBlackUAVTexture(
GraphBuilder,
SubsurfaceTextureDescriptor,
TEXT("Subsurface.SubpassTwoTex"),
RDG_EVENT_NAME("SSS::ClearUAV(%s, %s.TileCount > 0)", TEXT("Subsurface.SubpassTwoTex"), ToString(FSubsurfaceTiles::ETileType::All)),
SubsurfaceViewport,
Tiles.TileTypeCountBuffer,
ToIndex(FSubsurfaceTiles::ETileType::All));
}
else
{
SubsurfaceSubpassTwoTex = GraphBuilder.CreateTexture(SubsurfaceTextureDescriptor, TEXT("Subsurface.SubpassTwoTex"));
}
FSubsurfaceUniformRef UniformBuffer = CreateUniformBuffer(View, Tiles.TileCount);
// Call the indirect setup (with tile classification)
{
typedef FSubsurfaceIndirectDispatchSetupCS SHADER;
struct FSubsurfaceSetupPassInfo
{
FSubsurfaceSetupPassInfo(FSubsurfaceTiles::ETileType InTileType, FRDGTextureRef InOutput, SHADER::ESubsurfaceSetupPass InPass)
: TileType(InTileType), Output(InOutput), Pass(InPass)
{}
FSubsurfaceTiles::ETileType TileType;
FRDGTextureRef Output;
SHADER::ESubsurfaceSetupPass Pass;
};
const int NumOfSubsurfaceSetupPass = 2;
const FSubsurfaceSetupPassInfo SubsurfaceSetupPassInfos[NumOfSubsurfaceSetupPass] =
{
{FSubsurfaceTiles::ETileType::AllNonPassthrough, SetupTexture , SHADER::ESubsurfaceSetupPass::PassOneMain}, // Main setup
{FSubsurfaceTiles::ETileType::PASSTHROUGH, SubsurfaceSubpassTwoTex, SHADER::ESubsurfaceSetupPass::PassTwoPassThrough} // Passthrough SSS for subpixel SSS
};
for (int PassIndex = 0; PassIndex < NumOfSubsurfaceSetupPass; ++PassIndex)
{
FSubsurfaceSetupPassInfo PassInfo = SubsurfaceSetupPassInfos[PassIndex];
SHADER::FParameters* PassParameters = GraphBuilder.AllocParameters<SHADER::FParameters>();
PassParameters->Subsurface = SubsurfaceCommonParameters;
PassParameters->Output = SubsurfaceViewportParameters;
PassParameters->SubsurfaceInput0 = GetSubsurfaceInput(bReadSeparatedSubSurfaceSceneColor ? SeparatedSubSurfaceSceneColor : SceneColorTexture, SceneViewportParameters);
PassParameters->SubsurfaceSampler0 = PointClampSampler;
PassParameters->SetupTexture = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(PassInfo.Output, 0));
if (bUseProfileIdCache)
{
PassParameters->ProfileIdTexture = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(ProfileIdTexture));
}
PassParameters->RWBurleyGroupBuffer = GraphBuilder.CreateUAV(Tiles.GetTileBuffer(FSubsurfaceTiles::ETileType::AFIS), EPixelFormat::PF_R32_UINT);
PassParameters->RWSeparableGroupBuffer = GraphBuilder.CreateUAV(Tiles.GetTileBuffer(FSubsurfaceTiles::ETileType::SEPARABLE), EPixelFormat::PF_R32_UINT);
PassParameters->RWTileTypeCountBuffer = GraphBuilder.CreateUAV(Tiles.TileTypeCountBuffer, EPixelFormat::PF_R32_UINT);
PassParameters->IndirectDispatchArgsBuffer = Tiles.TileIndirectDispatchBuffer;
PassParameters->GroupBuffer = Tiles.GetTileBufferSRV(PassInfo.TileType);
PassParameters->SubsurfaceUniformParameters = UniformBuffer;
PassParameters->Substrate = Substrate::BindSubstrateGlobalUniformParameters(View);
SHADER::EQuality Quality = static_cast<SHADER::EQuality>(FSubsurfaceRecombinePS::GetQuality(View));
SHADER::FPermutationDomain ComputeShaderPermutationVector;
ComputeShaderPermutationVector.Set<SHADER::FDimensionHalfRes>(bHalfRes);
ComputeShaderPermutationVector.Set<SHADER::FDimensionCheckerboard>(bCheckerboard);
ComputeShaderPermutationVector.Set<SHADER::FRunningInSeparable>(bForceRunningInSeparable);
ComputeShaderPermutationVector.Set<SHADER::FDimensionEnableProfileIDCache>(bUseProfileIdCache);
ComputeShaderPermutationVector.Set<SHADER::FDimensionQuality>(Quality);
ComputeShaderPermutationVector.Set<SHADER::FDimensionSetupPass>(PassInfo.Pass);
TShaderMapRef<SHADER> ComputeShader(View.ShaderMap, ComputeShaderPermutationVector);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("SSS::Setup(%s%s%s %s %s) %dx%d",
ComputeShaderPermutationVector.Get<SHADER::FDimensionHalfRes>() ? TEXT(" HalfRes") : TEXT(""),
ComputeShaderPermutationVector.Get<SHADER::FDimensionCheckerboard>() ? TEXT(" Checkerboard") : TEXT(""),
ComputeShaderPermutationVector.Get<SHADER::FRunningInSeparable>() ? TEXT(" RunningInSeparable") : TEXT(""),
SHADER::GetPassName(ComputeShaderPermutationVector.Get<SHADER::FDimensionSetupPass>()),
SHADER::GetEventName(ComputeShaderPermutationVector.Get<SHADER::FDimensionQuality>()),
SubsurfaceViewport.Extent.X,
SubsurfaceViewport.Extent.Y),
ComputeShader,
PassParameters,
PassParameters->IndirectDispatchArgsBuffer,
Tiles.GetIndirectDispatchArgOffset(PassInfo.TileType));
}
}
if (!bForceRunningInSeparable)
{
// Setup the indirect dispatch & draw arguments if not running in separable mode.
{
typedef FSubsurfaceBuildIndirectDispatchArgsCS ARGSETUPSHADER;
ARGSETUPSHADER::FParameters* PassParameters = GraphBuilder.AllocParameters<ARGSETUPSHADER::FParameters>();
PassParameters->SubsurfaceUniformParameters = UniformBuffer;
PassParameters->RWIndirectDispatchArgsBuffer = GraphBuilder.CreateUAV(Tiles.TileIndirectDispatchBuffer, EPixelFormat::PF_R32_UINT);
PassParameters->RWIndirectDrawArgsBuffer = GraphBuilder.CreateUAV(Tiles.TileIndirectDrawBuffer, EPixelFormat::PF_R32_UINT);
PassParameters->TileTypeCountBuffer = GraphBuilder.CreateSRV(Tiles.TileTypeCountBuffer, EPixelFormat::PF_R32_UINT);
TShaderMapRef<ARGSETUPSHADER> ComputeShader(View.ShaderMap);
FComputeShaderUtils::AddPass(GraphBuilder, FRDGEventName(TEXT("SSS::BuildIndirectArgs(Dispatch & Draw)")), ComputeShader, PassParameters, FIntVector(1, 1, 1));
}
// Separable does not need this mipmap.
{
FRDGBufferRef MipsConditionBuffer = GraphBuilder.CreateBuffer(
FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), FSubsurfaceTiles::TileTypeCount), TEXT("Subsurface.MipsConditionBuffer"));
const int32 Offset = ToIndex(FSubsurfaceTiles::ETileType::AFIS);
{
typedef FFillMipsConditionBufferCS SHADER;
TShaderMapRef<SHADER> ComputeShader(View.ShaderMap);
SHADER::FParameters* PassParameters = GraphBuilder.AllocParameters<SHADER::FParameters>();
PassParameters->MinGenerateMipsTileCount = MinGenerateMipsTileCount;
PassParameters->Offset = Offset;
PassParameters->TileTypeCountBuffer = GraphBuilder.CreateSRV(Tiles.TileTypeCountBuffer, EPixelFormat::PF_R32_UINT);
PassParameters->RWMipsConditionBuffer = GraphBuilder.CreateUAV(MipsConditionBuffer, EPixelFormat::PF_R32_UINT);
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("SSS::FillMipsConditionBufferCS"), ComputeShader, PassParameters, FIntVector(1, 1, 1));
}
// Generate mipmap for the diffuse scene color and depth, use bilinear filter conditionally
FGenerateMips::ExecuteCompute(GraphBuilder, View.FeatureLevel, SetupTexture, BilinearBorderSampler, MipsConditionBuffer, Offset);
}
}
// Allocate additional auxiliary buffers
{
const int32 Offset = ToIndex(bForceRunningInSeparable ? FSubsurfaceTiles::ETileType::AllNonPassthrough : FSubsurfaceTiles::ETileType::SEPARABLE);
// Pre-allocate black UAV if we have separable tiles.
SubsurfaceSubpassOneTex = CreateBlackUAVTexture(
GraphBuilder,
SubsurfaceTextureDescriptor,
TEXT("Subsurface.SubpassOneTex"),
RDG_EVENT_NAME("SSS::ClearUAV(%s, %s.TileCount > 0)",TEXT("Subsurface.SubpassOneTex"), ToString(FSubsurfaceTiles::ETileType::SEPARABLE)),
SubsurfaceViewport,
Tiles.TileTypeCountBuffer,
Offset);
if (!bForceRunningInSeparable)
{
NewQualityHistoryTexture = GraphBuilder.CreateTexture(SubsurfaceTextureDescriptor, TEXT("Subsurface.QualityHistoryState"));
}
}
// Major pass to combine Burley and Separable
{
typedef FSubsurfaceIndirectDispatchCS SHADER;
FRHISamplerState* SubsurfaceSamplerState = SHADER::GetSamplerState(bHalfRes);
struct FSubsurfacePassInfo
{
FSubsurfacePassInfo(const TCHAR* InName, FRDGTextureRef InInput, FRDGTextureRef InOutput,
SHADER::ESubsurfaceType InSurfaceType, SHADER::ESubsurfacePass InSurfacePass)
: Name(InName), Input(InInput), Output(InOutput), SurfaceType(InSurfaceType), SubsurfacePass(InSurfacePass)
{}
const TCHAR* Name;
FRDGTextureRef Input;
FRDGTextureRef Output;
SHADER::ESubsurfaceType SurfaceType;
SHADER::ESubsurfacePass SubsurfacePass;
};
const int NumOfSubsurfacePass = 4;
const FSubsurfacePassInfo SubsurfacePassInfos[NumOfSubsurfacePass] =
{
{ TEXT("SSS::PassOne_Burley"), SetupTexture, SubsurfaceSubpassOneTex, SHADER::ESubsurfaceType::BURLEY , SHADER::ESubsurfacePass::PassOne}, //Burley main pass
{ TEXT("SSS::PassTwo_SepHon"), SetupTexture, SubsurfaceSubpassOneTex, SHADER::ESubsurfaceType::SEPARABLE, SHADER::ESubsurfacePass::PassOne}, //Separable horizontal
{ TEXT("SSS::PassThree_SepVer"), SubsurfaceSubpassOneTex, SubsurfaceSubpassTwoTex, SHADER::ESubsurfaceType::SEPARABLE, SHADER::ESubsurfacePass::PassTwo}, //Separable Vertical
{ TEXT("SSS::PassFour_BVar"), SubsurfaceSubpassOneTex, SubsurfaceSubpassTwoTex, SHADER::ESubsurfaceType::BURLEY , SHADER::ESubsurfacePass::PassTwo} //Burley Variance
};
const FRDGBufferSRVRef SubsurfaceBufferUsage[] = {
Tiles.GetTileBufferSRV(FSubsurfaceTiles::ETileType::AFIS),
Tiles.GetTileBufferSRV(bForceRunningInSeparable ? FSubsurfaceTiles::ETileType::AllNonPassthrough : FSubsurfaceTiles::ETileType::SEPARABLE) };
//Dispatch the two phase for both SSS
for (int PassIndex = 0; PassIndex < NumOfSubsurfacePass; ++PassIndex)
{
const FSubsurfacePassInfo& PassInfo = SubsurfacePassInfos[PassIndex];
const SHADER::ESubsurfaceType SubsurfaceType = PassInfo.SurfaceType;
const auto SubsurfacePassFunction = PassInfo.SubsurfacePass;
const int SubsurfaceTypeIndex = static_cast<int>(SubsurfaceType);
FRDGTextureRef TextureInput = PassInfo.Input;
FRDGTextureRef TextureOutput = PassInfo.Output;
FRDGTextureUAVDesc SSSColorUAVDesc(TextureOutput, 0);
FRDGTextureSRVDesc InputSRVDesc = FRDGTextureSRVDesc::Create(TextureInput);
SHADER::FParameters* PassParameters = GraphBuilder.AllocParameters<SHADER::FParameters>();
PassParameters->Subsurface = SubsurfaceCommonParameters;
PassParameters->Output = SubsurfaceViewportParameters;
PassParameters->SSSColorUAV = GraphBuilder.CreateUAV(SSSColorUAVDesc);
PassParameters->SubsurfaceInput0 = GetSubsurfaceInput(TextureInput, SubsurfaceViewportParameters);
PassParameters->SubsurfaceSampler0 = SubsurfaceSamplerState;
PassParameters->GroupBuffer = SubsurfaceBufferUsage[SubsurfaceTypeIndex];
PassParameters->TileTypeCountBuffer = GraphBuilder.CreateSRV(Tiles.TileTypeCountBuffer,EPixelFormat::PF_R32_UINT);
PassParameters->IndirectDispatchArgsBuffer = Tiles.TileIndirectDispatchBuffer;
PassParameters->Substrate = Substrate::BindSubstrateGlobalUniformParameters(View);
if (SubsurfacePassFunction == SHADER::ESubsurfacePass::PassOne && SubsurfaceType == SHADER::ESubsurfaceType::BURLEY)
{
PassParameters->SubsurfaceInput1 = GetSubsurfaceInput(QualityHistoryTexture, SubsurfaceViewportParameters);
PassParameters->SubsurfaceSampler1 = PointClampSampler;
PassParameters->SubsurfaceInput2 = GetSubsurfaceInput(VelocityTexture, SubsurfaceViewportParameters);
PassParameters->SubsurfaceSampler2 = PointClampSampler;
}
if (SubsurfacePassFunction == SHADER::ESubsurfacePass::PassTwo && SubsurfaceType == SHADER::ESubsurfaceType::BURLEY)
{
// we do not write to history in separable mode.
if (!bForceRunningInSeparable)
{
PassParameters->HistoryUAV = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(NewQualityHistoryTexture, 0));
PassParameters->SubsurfaceInput3 = GetSubsurfaceInput(SetupTexture, SubsurfaceViewportParameters);
PassParameters->SubsurfaceSampler3 = TStaticSamplerState<SF_Bilinear, AM_Border, AM_Border, AM_Border>::GetRHI();
}
PassParameters->SubsurfaceInput1 = GetSubsurfaceInput(QualityHistoryTexture, SubsurfaceViewportParameters);
PassParameters->SubsurfaceSampler1 = PointClampSampler;
PassParameters->SubsurfaceInput2 = GetSubsurfaceInput(VelocityTexture, SubsurfaceViewportParameters);
PassParameters->SubsurfaceSampler2 = PointClampSampler;
}
if (bUseProfileIdCache)
{
PassParameters->ProfileIdTexture = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(ProfileIdTexture));
}
SHADER::FPermutationDomain ComputeShaderPermutationVector;
ComputeShaderPermutationVector.Set<SHADER::FSubsurfacePassFunction>(SubsurfacePassFunction);
ComputeShaderPermutationVector.Set<SHADER::FDimensionQuality>(SHADER::GetQuality());
ComputeShaderPermutationVector.Set<SHADER::FBilateralFilterKernelFunctionType>(SHADER::GetBilateralFilterKernelFunctionType());
ComputeShaderPermutationVector.Set<SHADER::FSubsurfaceType>(SubsurfaceType);
ComputeShaderPermutationVector.Set<SHADER::FDimensionHalfRes>(bHalfRes);
ComputeShaderPermutationVector.Set<SHADER::FRunningInSeparable>(bForceRunningInSeparable);
ComputeShaderPermutationVector.Set<SHADER::FDimensionEnableProfileIDCache>(bUseProfileIdCache);
TShaderMapRef<SHADER> ComputeShader(View.ShaderMap, ComputeShaderPermutationVector);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("%s(%s %s %s %s%s)",
PassInfo.Name,
SHADER::GetEventName(ComputeShaderPermutationVector.Get<SHADER::FSubsurfacePassFunction>()),
SHADER::GetEventName(ComputeShaderPermutationVector.Get<SHADER::FDimensionQuality>()),
SHADER::GetEventName(ComputeShaderPermutationVector.Get<SHADER::FSubsurfaceType>()),
ComputeShaderPermutationVector.Get<SHADER::FDimensionHalfRes>() ? TEXT(" HalfRes") : TEXT(""),
ComputeShaderPermutationVector.Get<SHADER::FRunningInSeparable>() ? TEXT(" RunningInSeparable") : TEXT("")),
ComputeShader,
PassParameters,
PassParameters->IndirectDispatchArgsBuffer,
Tiles.GetIndirectDispatchArgOffset(SHADER::ToSubsurfaceTileType(SubsurfaceType,bForceRunningInSeparable)));
}
}
}
// Recombines scattering result with scene color, and updates only SSS region using specialized vertex shader.
{
FRDGTextureRef SubsurfaceIntermediateTexture = GraphBuilder.CreateTexture(SceneColorTextureDesc, TEXT("Subsurface.Recombines"));
// When bypassing subsurface scattering, we use the directly full screen pass instead of building the tile
// and apply the tile based copy
const bool bShouldFallbackToFullScreenPass = SubsurfaceMode == ESubsurfaceMode::Bypass;
FRHIDepthStencilState* DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
// Recombine into intermediate textures before copying back.
{
FSubsurfaceTiles::ETileType TileType = FSubsurfaceTiles::ETileType::All;
FSubsurfaceRecombinePS::FParameters* PassParameters = GraphBuilder.AllocParameters<FSubsurfaceRecombinePS::FParameters>();
PassParameters->Subsurface = SubsurfaceCommonParameters;
// Add dynamic branch parameters for recombine pass only.
PassParameters->CheckerboardNeighborSSSValidation = FSubsurfaceRecombinePS::GetCheckerBoardNeighborSSSValidation(bCheckerboard);
if (SubsurfaceMode != ESubsurfaceMode::Bypass)
{
PassParameters->TileParameters = GetSubsurfaceTileParameters(SubsurfaceViewport, Tiles, TileType);
}
PassParameters->RenderTargets[0] = FRenderTargetBinding(SubsurfaceIntermediateTexture, SceneColorTextureLoadAction);
PassParameters->SubsurfaceInput0 = GetSubsurfaceInput(bReadSeparatedSubSurfaceSceneColor ? SeparatedSubSurfaceSceneColor : SceneColorTexture, SceneViewportParameters);
PassParameters->SubsurfaceSampler0 = BilinearBorderSampler;
PassParameters->Substrate = Substrate::BindSubstrateGlobalUniformParameters(View);
// Scattering output target is only used when scattering is enabled.
if (SubsurfaceMode != ESubsurfaceMode::Bypass)
{
PassParameters->SubsurfaceInput1 = GetSubsurfaceInput(SubsurfaceSubpassTwoTex, SubsurfaceViewportParameters);
PassParameters->SubsurfaceSampler1 = BilinearBorderSampler;
}
const FSubsurfaceRecombinePS::EQuality RecombineQuality = FSubsurfaceRecombinePS::GetQuality(View);
FSubsurfaceRecombinePS::FPermutationDomain PixelShaderPermutationVector;
PixelShaderPermutationVector.Set<FSubsurfaceRecombinePS::FDimensionMode>(SubsurfaceMode);
PixelShaderPermutationVector.Set<FSubsurfaceRecombinePS::FDimensionQuality>(RecombineQuality);
PixelShaderPermutationVector.Set<FSubsurfaceRecombinePS::FDimensionCheckerboard>(bCheckerboard);
PixelShaderPermutationVector.Set<FSubsurfaceRecombinePS::FDimensionHalfRes>(bHalfRes);
PixelShaderPermutationVector.Set<FSubsurfaceRecombinePS::FRunningInSeparable>(bForceRunningInSeparable);
TShaderMapRef<FSubsurfaceRecombinePS> PixelShader(View.ShaderMap, PixelShaderPermutationVector);
TShaderMapRef<FSubsurfaceTilePassVS> VertexShader(View.ShaderMap);
FRHIBlendState* BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_Zero, BO_Add, BF_One, BF_Zero>::GetRHI();
/**
* See the related comment above in the prepare pass. The scene viewport is used as both the target and
* texture viewport in order to ensure that the correct pixel is sampled for checkerboard rendering.
*/
AddSubsurfaceTiledScreenPass(
GraphBuilder,
RDG_EVENT_NAME("SSS::Recombine(%s %s%s%s%s%s%s) %dx%d",
GetEventName(PixelShaderPermutationVector.Get<FSubsurfaceRecombinePS::FDimensionMode>()),
FSubsurfaceRecombinePS::GetEventName(PixelShaderPermutationVector.Get<FSubsurfaceRecombinePS::FDimensionQuality>()),
PixelShaderPermutationVector.Get<FSubsurfaceRecombinePS::FDimensionCheckerboard>() ? TEXT(" Checkerboard") : TEXT(""),
FSubsurfaceRecombinePS::GetCheckerBoardNeighborSSSValidation(bCheckerboard) ? TEXT("-Validation") : TEXT(""),
PixelShaderPermutationVector.Get<FSubsurfaceRecombinePS::FDimensionHalfRes>() ? TEXT(" HalfRes") : TEXT(""),
PixelShaderPermutationVector.Get<FSubsurfaceRecombinePS::FRunningInSeparable>() ? TEXT(" RunningInSeparable") : TEXT(""),
!bShouldFallbackToFullScreenPass ? TEXT(" Tiled") : TEXT(""),
View.ViewRect.Width(),
View.ViewRect.Height()),
View,
PassParameters,
VertexShader,
PixelShader,
BlendState,
DepthStencilState,
SceneViewport,
TileType,
bShouldFallbackToFullScreenPass);
}
//Write back to the SceneColor texture
{
FSubsurfaceTiles::ETileType TileType = FSubsurfaceTiles::ETileType::All;
FSubsurfaceRecombineCopyPS::FParameters* PassParameters = GraphBuilder.AllocParameters<FSubsurfaceRecombineCopyPS::FParameters>();
PassParameters->Subsurface = SubsurfaceCommonParameters;
if (SubsurfaceMode != ESubsurfaceMode::Bypass)
{
PassParameters->TileParameters = GetSubsurfaceTileParameters(SubsurfaceViewport, Tiles, TileType);
}
PassParameters->RenderTargets[0] = bWriteSeparatedOpaqueRoughRefractionSceneColor ?
FRenderTargetBinding(SeparatedOpaqueRoughRefractionSceneColor, ERenderTargetLoadAction::ELoad) :
FRenderTargetBinding(SceneColorTexture, SceneColorTextureLoadAction);
PassParameters->SubsurfaceInput0 = GetSubsurfaceInput(SubsurfaceIntermediateTexture, SceneViewportParameters);
PassParameters->SubsurfaceSampler0 = PointClampSampler;
PassParameters->Substrate = Substrate::BindSubstrateGlobalUniformParameters(View);
TShaderMapRef<FSubsurfaceRecombineCopyPS> PixelShader(View.ShaderMap);
TShaderMapRef<FSubsurfaceTilePassVS> VertexShader(View.ShaderMap);
FRHIBlendState* BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_Zero, BO_Add, BF_One, BF_Zero>::GetRHI();
if (bWriteSeparatedOpaqueRoughRefractionSceneColor)
{
BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One>::GetRHI();
}
AddSubsurfaceTiledScreenPass(
GraphBuilder,
RDG_EVENT_NAME("SSS::CopyToSceneColor%s %dx%d",
!bShouldFallbackToFullScreenPass ? TEXT("( Tiled)") : TEXT(""),
View.ViewRect.Width(),
View.ViewRect.Height()),
View,
PassParameters,
VertexShader,
PixelShader,
BlendState,
DepthStencilState,
SceneViewport,
TileType,
bShouldFallbackToFullScreenPass);
}
}
if (SubsurfaceMode != ESubsurfaceMode::Bypass && QualityHistoryState && !bForceRunningInSeparable)
{
GraphBuilder.QueueTextureExtraction(NewQualityHistoryTexture, QualityHistoryState);
}
}
FRDGTextureRef AddSubsurfacePass(
FRDGBuilder& GraphBuilder,
TArrayView<const FViewInfo> Views,
const uint32 ViewMask,
const FSceneTextures& SceneTextures,
FRDGTextureRef SceneColorTextureOutput)
{
const uint32 ViewCount = Views.Num();
const uint32 ViewMaskAll = (1 << ViewCount) - 1;
check(ViewMask);
FRDGTextureRef SceneColorTexture = SceneTextures.Color.Target;
ERenderTargetLoadAction SceneColorTextureLoadAction = ERenderTargetLoadAction::ELoad;
for (uint32 ViewIndex = 0; ViewIndex < ViewCount; ++ViewIndex)
{
const uint32 ViewBit = 1 << ViewIndex;
const bool bIsSubsurfaceView = (ViewMask & ViewBit) != 0;
if (bIsSubsurfaceView)
{
RDG_EVENT_SCOPE(GraphBuilder, "SubsurfaceScattering(ViewId=%d)", ViewIndex);
const FViewInfo& View = Views[ViewIndex];
AddSubsurfaceViewPass(GraphBuilder, View, SceneTextures, SceneColorTextureOutput, SceneColorTextureLoadAction);
SceneColorTextureLoadAction = ERenderTargetLoadAction::ELoad;
}
}
return SceneColorTextureOutput;
}
FScreenPassTexture AddVisualizeSubsurfacePass(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FVisualizeSubsurfaceInputs& Inputs)
{
check(Inputs.SceneColor.IsValid());
FScreenPassRenderTarget Output = Inputs.OverrideOutput;
if (!Output.IsValid())
{
Output = FScreenPassRenderTarget::CreateFromInput(GraphBuilder, Inputs.SceneColor, View.GetOverwriteLoadAction(), TEXT("VisualizeSubsurface"));
}
const FScreenPassTextureViewport InputViewport(Inputs.SceneColor);
FSubsurfaceVisualizePS::FParameters* PassParameters = GraphBuilder.AllocParameters<FSubsurfaceVisualizePS::FParameters>();
PassParameters->Subsurface = GetSubsurfaceCommonParameters(GraphBuilder, View, Inputs.SceneTextures);
PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding();
PassParameters->SubsurfaceInput0.Texture = Inputs.SceneColor.Texture;
PassParameters->SubsurfaceInput0.Viewport = GetScreenPassTextureViewportParameters(InputViewport);
PassParameters->SubsurfaceSampler0 = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->MiniFontTexture = GetMiniFontTexture();
PassParameters->Substrate = Substrate::BindSubstrateGlobalUniformParameters(View);
TShaderMapRef<FSubsurfaceVisualizePS> PixelShader(View.ShaderMap);
RDG_EVENT_SCOPE(GraphBuilder, "VisualizeSubsurface");
AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("SSS::Visualizer"), View, FScreenPassTextureViewport(Output), InputViewport, PixelShader, PassParameters);
Output.LoadAction = ERenderTargetLoadAction::ELoad;
AddDrawCanvasPass(GraphBuilder, RDG_EVENT_NAME("SSS::Text"), View, Output, [](FCanvas& Canvas)
{
float X = 30;
float Y = 28;
const float YStep = 14;
FString Line = FString::Printf(TEXT("Visualize Screen Space Subsurface Scattering"));
Canvas.DrawShadowedString(X, Y += YStep, *Line, GetStatsFont(), FLinearColor(1, 1, 1));
Y += YStep;
uint32 Index = 0;
while (GSubsurfaceProfileTextureObject.GetEntryString(Index++, Line))
{
Canvas.DrawShadowedString(X, Y += YStep, *Line, GetStatsFont(), FLinearColor(1, 1, 1));
}
});
return MoveTemp(Output);
}
void AddSubsurfacePass(
FRDGBuilder& GraphBuilder,
FSceneTextures& SceneTextures,
TArrayView<const FViewInfo> Views)
{
const uint32 ViewMask = GetSubsurfaceRequiredViewMask(Views);
if (!ViewMask)
{
return;
}
checkf(!SceneTextures.Color.IsSeparate(), TEXT("Subsurface rendering requires the deferred renderer."));
AddSubsurfacePass(GraphBuilder, Views, ViewMask, SceneTextures, SceneTextures.Color.Target);
SceneTextures.Color.Resolve = SceneTextures.Color.Target;
}