Files
UnrealEngineUWP/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessSubsurface.cpp
Tiantian Xie c280df72de Added experimental support for Burley Normalized Subsurface Scattering.
1. Burley is disabled by default. To preview it, you need to 1) set r.SubsurfaceScattering.Type to 1 and 2) check the enable burley box in the profile.
2. Burley parameters always update from Separable. To disable it, set r.SSS.Burley.AlwaysUpdateParametersFromSeparable to 0.

#rb krzysztof.narkowicz
#jira none

[CL 7289867 by Tiantian Xie in Dev-Rendering branch]
2019-07-12 11:50:25 -04:00

342 lines
13 KiB
C++

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
PostProcessSubsurface.cpp: Screenspace subsurface scattering implementation.
=============================================================================*/
#include "PostProcess/PostProcessSubsurface.h"
#include "PostProcess/SceneRenderTargets.h"
#include "PostProcess/SubsurfaceCommon.h"
#include "PostProcess/SubsurfaceBurleyNormalized.h"
#include "PostProcess/SubsurfaceSeparable.h"
#include "Engine/SubsurfaceProfile.h"
#include "CanvasTypes.h"
#include "RenderTargetTemp.h"
namespace
{
//only add ubber subsurface console variables here
static TAutoConsoleVariable<int32> CVarSubsurfaceScatteringType(
TEXT("r.SubsurfaceScattering.Type"),
0,
TEXT("Subsurface type:\n")
TEXT(" 0. Separable SSS (default).\n")
TEXT(" 1. Burley Normalized.")
TEXT("Else. Separable SSS. Make sure we always have SSS"),
ECVF_Scalability | ECVF_RenderThreadSafe
);
}
bool IsSubsurfaceEnabled()
{
static const auto CVarSubsurfaceScattering = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.SubsurfaceScattering"));
static const auto CVarSSSScale = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.SSS.Scale"));
check(CVarSubsurfaceScattering);
check(CVarSSSScale);
const bool bEnabled = CVarSubsurfaceScattering->GetValueOnAnyThread() != 0;
const bool bHasScale = CVarSSSScale->GetValueOnAnyThread() > 0.0f;
return (bEnabled && bHasScale);
}
bool IsSubsurfaceRequiredForView(const FViewInfo& View)
{
const bool bSimpleDynamicLighting = IsAnyForwardShadingEnabled(View.GetShaderPlatform());
const bool bSubsurfaceEnabled = IsSubsurfaceEnabled();
const bool bViewHasSubsurfaceMaterials = ((View.ShadingModelMaskInView & GetUseSubsurfaceProfileShadingModelMask()) != 0);
return (bSubsurfaceEnabled && bViewHasSubsurfaceMaterials && !bSimpleDynamicLighting);
}
uint32 GetSubsurfaceRequiredViewMask(const TArray<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)
{
// No checkerboard for Burley
if (CVarSubsurfaceScatteringType.GetValueOnRenderThread() == 1)
return false;
return IsSeparableSubsurfaceCheckerboardFormat(SceneColorFormat);
}
// 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_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::SM4);
}
};
IMPLEMENT_GLOBAL_SHADER(FSubsurfaceViewportCopyPS, "/Engine/Private/PostProcessSubsurface.usf", "SubsurfaceViewportCopyPS", SF_Pixel);
void ComputeSubsurfaceForView(
FRDGBuilder& GraphBuilder,
const FScreenPassViewInfo& ScreenPassView,
const FScreenPassTextureViewport& SceneViewport,
FRDGTextureRef SceneTexture,
FRDGTextureRef SceneTextureOutput,
ERenderTargetLoadAction SceneTextureLoadAction)
{
// Compute subsurface based on cmd configuration
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.SubsurfaceScattering.Type"));
check(CVar);
if (CVar->GetValueOnRenderThread() == 1) // Use Burley normalized SSS
{
ComputeBurleySubsurfaceForView(GraphBuilder, ScreenPassView, SceneViewport, SceneTexture, SceneTextureOutput, SceneTextureLoadAction);
}
else // Use separable SSS.
{
ComputeSeparableSubsurfaceForView(GraphBuilder, ScreenPassView, SceneViewport, SceneTexture, SceneTextureOutput, SceneTextureLoadAction);
}
}
FRDGTextureRef ComputeSubsurface(
FRDGBuilder& GraphBuilder,
FRDGTextureRef SceneTexture,
const TArray<FViewInfo>& Views)
{
const uint32 ViewCount = Views.Num();
const uint32 ViewMaskAll = (1 << ViewCount) - 1;
const uint32 ViewMask = GetSubsurfaceRequiredViewMask(Views);
// Return the original target if no views have subsurface applied.
if (!ViewMask)
{
return SceneTexture;
}
FRDGTextureRef SceneTextureOutput = GraphBuilder.CreateTexture(SceneTexture->Desc, TEXT("SceneColorSubsurface"));
ERenderTargetLoadAction SceneTextureLoadAction = ERenderTargetLoadAction::ENoAction;
const bool bHasNonSubsurfaceView = ViewMask != ViewMaskAll;
/**
* Since we are outputting to a new texture and certain views may not utilize subsurface scattering,
* we need to copy all non-subsurface views onto the destination texture.
*/
if (bHasNonSubsurfaceView)
{
FSubsurfaceViewportCopyPS::FParameters* PassParameters = GraphBuilder.AllocParameters<FSubsurfaceViewportCopyPS::FParameters>();
PassParameters->RenderTargets[0] = FRenderTargetBinding(SceneTextureOutput, ERenderTargetLoadAction::ENoAction, ERenderTargetStoreAction::EStore);
PassParameters->SubsurfaceInput0_Texture = SceneTexture;
PassParameters->SubsurfaceSampler0 = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
TShaderMapRef<FSubsurfaceViewportCopyPS> PixelShader(Views[0].ShaderMap);
const FIntPoint InputTextureSize = SceneTexture->Desc.Extent;
GraphBuilder.AddPass(
RDG_EVENT_NAME("SubsurfaceViewportCopy"),
PassParameters,
ERenderGraphPassFlags::None,
[&Views, ViewMask, ViewCount, PixelShader, InputTextureSize, PassParameters](FRHICommandListImmediate& RHICmdList)
{
for (uint32 ViewIndex = 0; ViewIndex < ViewCount; ++ViewIndex)
{
const uint32 ViewBit = 1 << ViewIndex;
const bool bIsNonSubsurfaceView = (ViewMask & ViewBit) == 0;
if (bIsNonSubsurfaceView)
{
const FViewInfo& View = Views[ViewIndex];
const FScreenPassViewInfo ScreenPassView(View);
const FScreenPassTextureViewport TextureViewport(View.ViewRect, InputTextureSize);
DrawScreenPass(RHICmdList, ScreenPassView, TextureViewport, TextureViewport, *PixelShader, *PassParameters);
}
}
});
// Subsequent render passes should load the texture contents.
SceneTextureLoadAction = 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];
const FScreenPassViewInfo ScreenPassView(View);
const FScreenPassTextureViewport SceneViewport(View.ViewRect, SceneTexture);
ComputeSubsurfaceForView(GraphBuilder, ScreenPassView, SceneViewport, SceneTexture, SceneTextureOutput, SceneTextureLoadAction);
// Subsequent render passes should load the texture contents.
SceneTextureLoadAction = ERenderTargetLoadAction::ELoad;
}
}
return SceneTextureOutput;
}
void VisualizeSubsurface(
FRDGBuilder& GraphBuilder,
const FScreenPassViewInfo& ScreenPassView,
const FScreenPassTextureViewport& SceneViewport,
FRDGTextureRef SceneTexture,
FRDGTextureRef SceneTextureOutput)
{
check(SceneTexture);
check(SceneTextureOutput);
check(SceneViewport.Extent == SceneTexture->Desc.Extent);
const FViewInfo& View = ScreenPassView.View;
FSubsurfaceVisualizePS::FParameters* PassParameters = GraphBuilder.AllocParameters<FSubsurfaceVisualizePS::FParameters>();
PassParameters->Subsurface = GetSubsurfaceCommonParameters(GraphBuilder.RHICmdList, View);
PassParameters->RenderTargets[0] = FRenderTargetBinding(SceneTextureOutput, ERenderTargetLoadAction::EClear, ERenderTargetStoreAction::EStore);
PassParameters->SubsurfaceInput0.Texture = SceneTexture;
PassParameters->SubsurfaceInput0.Viewport = GetScreenPassTextureViewportParameters(SceneViewport);
PassParameters->SubsurfaceSampler0 = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->MiniFontTexture = GetMiniFontTexture();
TShaderMapRef<FSubsurfaceVisualizePS> PixelShader(View.ShaderMap);
GraphBuilder.AddPass(
RDG_EVENT_NAME("SubsurfaceVisualize"),
PassParameters,
ERenderGraphPassFlags::None,
[ScreenPassView, SceneViewport, SceneTextureOutput, PixelShader, PassParameters](FRHICommandListImmediate& RHICmdList)
{
DrawScreenPass(RHICmdList, ScreenPassView, SceneViewport, SceneViewport, *PixelShader, *PassParameters);
// Draw debug text
{
const FViewInfo& LocalView = ScreenPassView.View;
const FSceneViewFamily& ViewFamily = *LocalView.Family;
FRenderTargetTemp TempRenderTarget(static_cast<FRHITexture2D*>(SceneTextureOutput->GetRHI()), SceneTextureOutput->Desc.Extent);
FCanvas Canvas(&TempRenderTarget, nullptr, ViewFamily.CurrentRealTime, ViewFamily.CurrentWorldTime, ViewFamily.DeltaWorldTime, LocalView.GetFeatureLevel());
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));
}
const bool bFlush = false;
const bool bInsideRenderPass = true;
Canvas.Flush_RenderThread(RHICmdList, bFlush, bInsideRenderPass);
}
});
}
//////////////////////////////////////////////////////////////////////////
//! Shim methods to hook into the legacy pipeline until the full RDG conversion is complete.
void ComputeSubsurfaceShim(FRHICommandListImmediate& RHICmdList, const TArray<FViewInfo>& Views)
{
FSceneRenderTargets& SceneRenderTargets = FSceneRenderTargets::Get(RHICmdList);
FRDGBuilder GraphBuilder(RHICmdList);
FRDGTextureRef SceneTexture = GraphBuilder.RegisterExternalTexture(SceneRenderTargets.GetSceneColor(), TEXT("SceneColor"));
FRDGTextureRef SceneTextureOutput = ComputeSubsurface(GraphBuilder, SceneTexture, Views);
// Extract the result texture out and re-assign it to the scene render targets blackboard.
TRefCountPtr<IPooledRenderTarget> SceneTarget;
GraphBuilder.QueueTextureExtraction(SceneTextureOutput, &SceneTarget, false);
GraphBuilder.Execute();
SceneRenderTargets.SetSceneColor(SceneTarget);
// The RT should be released as early as possible to allow sharing of that memory for other purposes.
// This becomes even more important with some limited VRam (XBoxOne).
SceneRenderTargets.SetLightAttenuation(nullptr);
}
FRenderingCompositeOutputRef VisualizeSubsurfaceShim(
FRHICommandListImmediate& InRHICmdList,
FRenderingCompositionGraph& Graph,
FRenderingCompositeOutputRef Input)
{
// we need the GBuffer, we release it Process()
FSceneRenderTargets::Get(InRHICmdList).AdjustGBufferRefCount(InRHICmdList, 1);
FRenderingCompositePass* SubsurfaceVisualizePass = Graph.RegisterPass(new(FMemStack::Get()) TRCPassForRDG<1, 1>(
[](FRenderingCompositePass* Pass, FRenderingCompositePassContext& CompositePassContext)
{
FRDGBuilder GraphBuilder(CompositePassContext.RHICmdList);
FRDGTextureRef SceneTexture = Pass->CreateRDGTextureForRequiredInput(GraphBuilder, ePId_Input0, TEXT("SceneColor"));
FRDGTextureRef SceneTextureOutput = Pass->FindOrCreateRDGTextureForOutput(GraphBuilder, ePId_Output0, SceneTexture->Desc, TEXT("SubsurfaceVisualize"));
const FScreenPassViewInfo ScreenPassView(CompositePassContext.View);
const FScreenPassTextureViewport SceneViewport(CompositePassContext.View.ViewRect, SceneTexture->Desc.Extent);
VisualizeSubsurface(GraphBuilder, ScreenPassView, SceneViewport, SceneTexture, SceneTextureOutput);
Pass->ExtractRDGTextureForOutput(GraphBuilder, ePId_Output0, SceneTextureOutput);
GraphBuilder.Execute();
FRHICommandListImmediate& RHICmdList = GraphBuilder.RHICmdList;
FSceneRenderTargets::Get(RHICmdList).AdjustGBufferRefCount(RHICmdList, -1);
}));
SubsurfaceVisualizePass->SetInput(ePId_Input0, Input);
return FRenderingCompositeOutputRef(SubsurfaceVisualizePass);
}
//////////////////////////////////////////////////////////////////////////