Files
UnrealEngineUWP/Engine/Source/Runtime/Renderer/Private/PathTracing.cpp
chris kulla 0281af418c Avoid wave operations in path compaction shader which appears to give a slight speedup despite additional contention on the atomic and does not require running with SM6.
Implement tiled dispatch in the path tracer to reduce the likelyhood of GPU timeouts when rendering at high resolution. This also reduces the memory requirements for path state when running with path compaction enabled.

Change from a uint buffer to a structured buffer for storing path states which gives a small speedup.

Add indirect dispatch support to launch less work for compacted bounces (off by default as it does not seem to provide a speedup so far)

#jira TM-6595
#rb Juan.Canada
#preflight 61b27c6a2b48d03df526ce85
#preflight 61b28773ee0de9822e0f02de

#ROBOMERGE-AUTHOR: chris.kulla
#ROBOMERGE-SOURCE: CL 18426885 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v897-18405271)
#ROBOMERGE[STARSHIP]: UE5-Release-Engine-Staging Release-5.0

[CL 18426911 by chris kulla in ue5-release-engine-test branch]
2021-12-09 18:36:38 -05:00

1729 lines
76 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PathTracing.h"
#include "RHI.h"
#include "PathTracingDenoiser.h"
PathTracingDenoiserFunction* GPathTracingDenoiserFunc = nullptr;
TAutoConsoleVariable<int32> CVarPathTracing(
TEXT("r.PathTracing"),
1,
TEXT("Enables the path tracing renderer (to guard the compilation of path tracer specific material permutations)"),
ECVF_RenderThreadSafe | ECVF_ReadOnly
);
#if RHI_RAYTRACING
#include "RendererPrivate.h"
#include "GlobalShader.h"
#include "DeferredShadingRenderer.h"
#include "HAL/PlatformApplicationMisc.h"
#include "RayTracingTypes.h"
#include "RayTracingDefinitions.h"
#include "PathTracingDefinitions.h"
#include "RayTracing/RayTracingMaterialHitShaders.h"
#include "RenderCore/Public/GenerateMips.h"
#include "HairStrands/HairStrandsData.h"
#include <limits>
TAutoConsoleVariable<int32> CVarPathTracingCompaction(
TEXT("r.PathTracing.Compaction"),
1,
TEXT("Enables path compaction to improve GPU occupancy for the path tracer (default: 1 (enabled))"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingIndirectDispatch(
TEXT("r.PathTracing.IndirectDispatch"),
0,
TEXT("Enables indirect dispatch (if supported by the hardware) for compacted path tracing (default: 0 (disabled))"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingDispatchSize(
TEXT("r.PathTracing.DispatchSize"),
2048,
TEXT("Controls the tile size used when rendering the image. Reducing this value may prevent GPU timeouts for heavy renders. (default = 2048)"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingMaxBounces(
TEXT("r.PathTracing.MaxBounces"),
-1,
TEXT("Sets the maximum number of path tracing bounces (default = -1 (driven by postprocesing volume))"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingSamplesPerPixel(
TEXT("r.PathTracing.SamplesPerPixel"),
-1,
TEXT("Sets the maximum number of samples per pixel (default = -1 (driven by postprocesing volume))"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<float> CVarPathTracingFilterWidth(
TEXT("r.PathTracing.FilterWidth"),
-1,
TEXT("Sets the anti-aliasing filter width (default = -1 (driven by postprocesing volume))"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<float> CVarPathTracingAbsorptionScale(
TEXT("r.PathTracing.AbsorptionScale"),
0.01,
TEXT("Sets the inverse distance at which BaseColor is reached for transmittance in refractive glass (default = 1/100 units)\n")
TEXT("Setting this value to 0 will disable absorption handling for refractive glass\n"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingMISMode(
TEXT("r.PathTracing.MISMode"),
2,
TEXT("Selects the sampling technique for light integration (default = 2 (MIS enabled))\n")
TEXT("0: Material sampling\n")
TEXT("1: Light sampling\n")
TEXT("2: MIS betwen material and light sampling (default)\n"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingMISCompensation(
TEXT("r.PathTracing.MISCompensation"),
1,
TEXT("Activates MIS compensation for skylight importance sampling. (default = 1 (enabled))\n")
TEXT("This option only takes effect when r.PathTracing.MISMode = 2\n"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingSkylightCaching(
TEXT("r.PathTracing.SkylightCaching"),
1,
TEXT("Attempts to re-use skylight data between frames. (default = 1 (enabled))\n")
TEXT("When set to 0, the skylight texture and importance samping data will be regenerated every frame. This is mainly intended as a benchmarking and debugging aid\n"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingVisibleLights(
TEXT("r.PathTracing.VisibleLights"),
0,
TEXT("Should light sources be visible to camera rays? (default = 0 (off))\n")
TEXT("0: Hide lights from camera rays (default)\n")
TEXT("1: Make all lights visible to camera\n")
TEXT("2: Make skydome only visible to camera\n"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingMaxSSSBounces(
TEXT("r.PathTracing.MaxSSSBounces"),
256,
TEXT("Sets the maximum number of bounces inside subsurface materials. Lowering this value can make subsurface scattering render too dim, while setting it too high can cause long render times. (default = 256)"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<float> CVarPathTracingMaxPathIntensity(
TEXT("r.PathTracing.MaxPathIntensity"),
-1,
TEXT("When positive, light paths greater that this amount are clamped to prevent fireflies (default = -1 (driven by postprocesing volume))"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingApproximateCaustics(
TEXT("r.PathTracing.ApproximateCaustics"),
1,
TEXT("When non-zero, the path tracer will approximate caustic paths to reduce noise. This reduces speckles and noise from low-roughness glass and metals. (default = 1 (enabled))"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingEnableEmissive(
TEXT("r.PathTracing.EnableEmissive"),
-1,
TEXT("Indicates if emissive materials should contribute to scene lighting (default = -1 (driven by postprocesing volume)"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingEnableCameraBackfaceCulling(
TEXT("r.PathTracing.EnableCameraBackfaceCulling"),
1,
TEXT("When non-zero, the path tracer will skip over backfacing triangles when tracing primary rays from the camera. (default = 1 (enabled))"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingFrameIndependentTemporalSeed(
TEXT("r.PathTracing.FrameIndependentTemporalSeed"),
1,
TEXT("Indicates to use different temporal seed for each sample across frames rather than resetting the sequence at the start of each frame\n")
TEXT("0: off\n")
TEXT("1: on (default)\n"),
ECVF_RenderThreadSafe
);
// See PATHTRACER_SAMPLER_* defines
TAutoConsoleVariable<int32> CVarPathTracingSamplerType(
TEXT("r.PathTracing.SamplerType"),
PATHTRACER_SAMPLER_DEFAULT,
TEXT("Controls the way the path tracer generates its random numbers\n")
TEXT("0: use a different high quality random sequence per pixel\n")
TEXT("1: optimize the random sequence across pixels to reduce visible error at the target sample count\n")
TEXT("2: share random seeds across pixels to improve coherence of execution on the GPU. This trades some correlation across the image in exchange for better performance.\n"),
ECVF_RenderThreadSafe
);
#if 0
// TODO: re-enable this when multi-gpu is supported again
// r.PathTracing.GPUCount is read only because ComputeViewGPUMasks results cannot change after UE has been launched
TAutoConsoleVariable<int32> CVarPathTracingGPUCount(
TEXT("r.PathTracing.GPUCount"),
1,
TEXT("Sets the amount of GPUs used for computing the path tracing pass (default = 1 GPU)"),
ECVF_RenderThreadSafe | ECVF_ReadOnly
);
#endif
TAutoConsoleVariable<int32> CVarPathTracingWiperMode(
TEXT("r.PathTracing.WiperMode"),
0,
TEXT("Enables wiper mode to render using the path tracer only in a region of the screen for debugging purposes (default = 0, wiper mode disabled)"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingProgressDisplay(
TEXT("r.PathTracing.ProgressDisplay"),
0,
TEXT("Enables an in-frame display of progress towards the defined sample per pixel limit. The indicator dissapears when the maximum is reached and sample accumulation has stopped (default = 0)\n")
TEXT("0: off (default)\n")
TEXT("1: on\n"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingLightGridResolution(
TEXT("r.PathTracing.LightGridResolution"),
256,
TEXT("Controls the resolution of the 2D light grid used to cull irrelevant lights from lighting calculations (default = 256)\n"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingLightGridMaxCount(
TEXT("r.PathTracing.LightGridMaxCount"),
128,
TEXT("Controls the maximum number of lights per cell in the 2D light grid. The minimum of this value and the number of lights in the scene is used. (default = 128)\n"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingLightGridVisualize(
TEXT("r.PathTracing.LightGridVisualize"),
0,
TEXT("Enables a visualization mode of the light grid density where red indicates the maximum light count has been reached (default = 0)\n")
TEXT("0: off (default)\n")
TEXT("1: light count heatmap (red - close to overflow, increase r.PathTracing.LightGridMaxCount)\n")
TEXT("2: unique light lists (colors are a function of which lights occupy each cell)\n")
TEXT("3: area light visualization (green: point light sources only, blue: some area light sources)\n"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarPathTracingDenoiser(
TEXT("r.PathTracing.Denoiser"),
-1,
TEXT("Enable denoising of the path traced output (if a denoiser plugin is active) (default = -1 (driven by postprocesing volume))\n")
TEXT("-1: inherit from PostProcessVolume\n")
TEXT("0: disable denoiser\n")
TEXT("1: enable denoiser (if a denoiser plugin is active)\n"),
ECVF_RenderThreadSafe
);
BEGIN_SHADER_PARAMETER_STRUCT(FPathTracingData, )
SHADER_PARAMETER(float, BlendFactor)
SHADER_PARAMETER(uint32, Iteration)
SHADER_PARAMETER(uint32, TemporalSeed)
SHADER_PARAMETER(uint32, MaxSamples)
SHADER_PARAMETER(uint32, MaxBounces)
SHADER_PARAMETER(uint32, MaxSSSBounces)
SHADER_PARAMETER(uint32, MISMode)
SHADER_PARAMETER(uint32, ApproximateCaustics)
SHADER_PARAMETER(uint32, EnableCameraBackfaceCulling)
SHADER_PARAMETER(uint32, EnableDirectLighting)
SHADER_PARAMETER(uint32, EnableEmissive)
SHADER_PARAMETER(uint32, SamplerType)
SHADER_PARAMETER(uint32, VisualizeLightGrid)
SHADER_PARAMETER(float, MaxPathIntensity)
SHADER_PARAMETER(float, MaxNormalBias)
SHADER_PARAMETER(float, FilterWidth)
SHADER_PARAMETER(float, AbsorptionScale)
SHADER_PARAMETER(float, CameraFocusDistance)
SHADER_PARAMETER(float, CameraLensRadius)
END_SHADER_PARAMETER_STRUCT()
// Store the rendering options used on the previous frame so we can correctly invalidate when things change
struct FPathTracingConfig
{
FPathTracingData PathTracingData;
FIntRect ViewRect;
int LightShowFlags;
int LightGridResolution;
int LightGridMaxCount;
bool VisibleLights;
bool UseMISCompensation;
bool LockedSamplingPattern;
int DenoiserMode; // NOTE: does not require path tracing invalidation
bool IsDifferent(const FPathTracingConfig& Other) const
{
// If any of these parameters if different, we will need to restart path tracing accuulation
return
PathTracingData.MaxSamples != Other.PathTracingData.MaxSamples ||
PathTracingData.MaxBounces != Other.PathTracingData.MaxBounces ||
PathTracingData.MaxSSSBounces != Other.PathTracingData.MaxSSSBounces ||
PathTracingData.MISMode != Other.PathTracingData.MISMode ||
PathTracingData.SamplerType != Other.PathTracingData.SamplerType ||
PathTracingData.ApproximateCaustics != Other.PathTracingData.ApproximateCaustics ||
PathTracingData.EnableCameraBackfaceCulling != Other.PathTracingData.EnableCameraBackfaceCulling ||
PathTracingData.EnableDirectLighting != Other.PathTracingData.EnableDirectLighting ||
PathTracingData.EnableEmissive != Other.PathTracingData.EnableEmissive ||
PathTracingData.VisualizeLightGrid != Other.PathTracingData.VisualizeLightGrid ||
PathTracingData.MaxPathIntensity != Other.PathTracingData.MaxPathIntensity ||
PathTracingData.FilterWidth != Other.PathTracingData.FilterWidth ||
PathTracingData.AbsorptionScale != Other.PathTracingData.AbsorptionScale ||
PathTracingData.CameraFocusDistance != Other.PathTracingData.CameraFocusDistance ||
PathTracingData.CameraLensRadius != Other.PathTracingData.CameraLensRadius ||
ViewRect != Other.ViewRect ||
LightShowFlags != Other.LightShowFlags ||
LightGridResolution != Other.LightGridResolution ||
LightGridMaxCount != Other.LightGridMaxCount ||
VisibleLights != Other.VisibleLights ||
UseMISCompensation != Other.UseMISCompensation ||
LockedSamplingPattern != Other.LockedSamplingPattern;
}
};
struct FPathTracingState {
FPathTracingConfig LastConfig;
// Textures holding onto the accumulated frame data
TRefCountPtr<IPooledRenderTarget> RadianceRT;
TRefCountPtr<IPooledRenderTarget> AlbedoRT;
TRefCountPtr<IPooledRenderTarget> NormalRT;
TRefCountPtr<IPooledRenderTarget> RadianceDenoisedRT;
// Current sample index to be rendered by the path tracer - this gets incremented each time the path tracer accumulates a frame of samples
uint32 SampleIndex = 0;
// Path tracer frame index, not reset on invalidation unlike SampleIndex to avoid
// the "screen door" effect and reduce temporal aliasing
uint32_t FrameIndex = 0;
};
// This function prepares the portion of shader arguments that may involve invalidating the path traced state
static void PrepareShaderArgs(const FViewInfo& View, FPathTracingData& PathTracingData)
{
PathTracingData.EnableDirectLighting = true;
int32 MaxBounces = CVarPathTracingMaxBounces.GetValueOnRenderThread();
if (MaxBounces < 0)
{
MaxBounces = View.FinalPostProcessSettings.PathTracingMaxBounces;
}
if (View.Family->EngineShowFlags.DirectLighting)
{
if (!View.Family->EngineShowFlags.GlobalIllumination)
{
// direct lighting, but no GI
MaxBounces = 1;
}
}
else
{
PathTracingData.EnableDirectLighting = false;
if (View.Family->EngineShowFlags.GlobalIllumination)
{
// skip direct lighting, but still do the full bounces
}
else
{
// neither direct, nor GI is on
MaxBounces = 0;
}
}
PathTracingData.MaxBounces = MaxBounces;
PathTracingData.MaxSSSBounces = CVarPathTracingMaxSSSBounces.GetValueOnRenderThread();
PathTracingData.MaxNormalBias = GetRaytracingMaxNormalBias();
PathTracingData.MISMode = CVarPathTracingMISMode.GetValueOnRenderThread();
PathTracingData.MaxPathIntensity = CVarPathTracingMaxPathIntensity.GetValueOnRenderThread();
if (PathTracingData.MaxPathIntensity <= 0)
{
// cvar clamp disabled, use PPV exposure value instad
PathTracingData.MaxPathIntensity = FMath::Pow(2.0f, View.FinalPostProcessSettings.PathTracingMaxPathExposure);
}
PathTracingData.ApproximateCaustics = CVarPathTracingApproximateCaustics.GetValueOnRenderThread();
PathTracingData.EnableCameraBackfaceCulling = CVarPathTracingEnableCameraBackfaceCulling.GetValueOnRenderThread();
PathTracingData.SamplerType = CVarPathTracingSamplerType.GetValueOnRenderThread();
int32 EnableEmissive = CVarPathTracingEnableEmissive.GetValueOnRenderThread();
PathTracingData.EnableEmissive = EnableEmissive < 0 ? View.FinalPostProcessSettings.PathTracingEnableEmissive : EnableEmissive;
PathTracingData.VisualizeLightGrid = CVarPathTracingLightGridVisualize.GetValueOnRenderThread();
float FilterWidth = CVarPathTracingFilterWidth.GetValueOnRenderThread();
if (FilterWidth < 0)
{
FilterWidth = View.FinalPostProcessSettings.PathTracingFilterWidth;
}
PathTracingData.FilterWidth = FilterWidth;
PathTracingData.AbsorptionScale = CVarPathTracingAbsorptionScale.GetValueOnRenderThread();
PathTracingData.CameraFocusDistance = 0;
PathTracingData.CameraLensRadius = 0;
if (View.Family->EngineShowFlags.DepthOfField &&
View.FinalPostProcessSettings.PathTracingEnableReferenceDOF &&
View.FinalPostProcessSettings.DepthOfFieldFocalDistance > 0 &&
View.FinalPostProcessSettings.DepthOfFieldFstop > 0)
{
const float FocalLengthInCM = 0.05f * View.FinalPostProcessSettings.DepthOfFieldSensorWidth * View.ViewMatrices.GetProjectionMatrix().M[0][0];
PathTracingData.CameraFocusDistance = View.FinalPostProcessSettings.DepthOfFieldFocalDistance;
PathTracingData.CameraLensRadius = 0.5f * FocalLengthInCM / View.FinalPostProcessSettings.DepthOfFieldFstop;
}
}
static bool ShouldCompilePathTracingShadersForProject(EShaderPlatform ShaderPlatform)
{
return ShouldCompileRayTracingShadersForProject(ShaderPlatform) &&
FDataDrivenShaderPlatformInfo::GetSupportsPathTracing(ShaderPlatform) &&
CVarPathTracing.GetValueOnAnyThread() != 0;
}
class FPathTracingSkylightPrepareCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FPathTracingSkylightPrepareCS)
SHADER_USE_PARAMETER_STRUCT(FPathTracingSkylightPrepareCS, FGlobalShader)
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
// NOTE: skylight code is shared with RT passes
return ShouldCompileRayTracingShadersForProject(Parameters.Platform);
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
//OutEnvironment.CompilerFlags.Add(CFLAG_WarningsAsErrors);
OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_X"), FComputeShaderUtils::kGolden2DGroupSize);
OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_Y"), FComputeShaderUtils::kGolden2DGroupSize);
}
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_TEXTURE(TextureCube, SkyLightCubemap0)
SHADER_PARAMETER_TEXTURE(TextureCube, SkyLightCubemap1)
SHADER_PARAMETER_SAMPLER(SamplerState, SkyLightCubemapSampler0)
SHADER_PARAMETER_SAMPLER(SamplerState, SkyLightCubemapSampler1)
SHADER_PARAMETER(float, SkylightBlendFactor)
SHADER_PARAMETER(float, SkylightInvResolution)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, SkylightTextureOutput)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, SkylightTexturePdf)
SHADER_PARAMETER(FVector3f, SkyColor)
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_SHADER_TYPE(, FPathTracingSkylightPrepareCS, TEXT("/Engine/Private/PathTracing/PathTracingSkylightPrepare.usf"), TEXT("PathTracingSkylightPrepareCS"), SF_Compute);
class FPathTracingSkylightMISCompensationCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FPathTracingSkylightMISCompensationCS)
SHADER_USE_PARAMETER_STRUCT(FPathTracingSkylightMISCompensationCS, FGlobalShader)
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
// NOTE: skylight code is shared with RT passes
return ShouldCompileRayTracingShadersForProject(Parameters.Platform);
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
//OutEnvironment.CompilerFlags.Add(CFLAG_WarningsAsErrors);
OutEnvironment.CompilerFlags.Add(CFLAG_AllowTypedUAVLoads);
OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_X"), FComputeShaderUtils::kGolden2DGroupSize);
OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_Y"), FComputeShaderUtils::kGolden2DGroupSize);
}
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, SkylightTexturePdfAverage)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, SkylightTextureOutput)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, SkylightTexturePdf)
SHADER_PARAMETER(FVector3f, SkyColor)
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_SHADER_TYPE(, FPathTracingSkylightMISCompensationCS, TEXT("/Engine/Private/PathTracing/PathTracingSkylightMISCompensation.usf"), TEXT("PathTracingSkylightMISCompensationCS"), SF_Compute);
// this struct holds a light grid for both building or rendering
BEGIN_SHADER_PARAMETER_STRUCT(FPathTracingLightGrid, RENDERER_API)
SHADER_PARAMETER(uint32, SceneInfiniteLightCount)
SHADER_PARAMETER(FVector3f, SceneLightsBoundMin)
SHADER_PARAMETER(FVector3f, SceneLightsBoundMax)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, LightGrid)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint>, LightGridData)
SHADER_PARAMETER(unsigned, LightGridResolution)
SHADER_PARAMETER(unsigned, LightGridMaxCount)
SHADER_PARAMETER(int, LightGridAxis)
END_SHADER_PARAMETER_STRUCT()
class FPathTracingBuildLightGridCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FPathTracingBuildLightGridCS)
SHADER_USE_PARAMETER_STRUCT(FPathTracingBuildLightGridCS, FGlobalShader)
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return ShouldCompileRayTracingShadersForProject(Parameters.Platform);
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
//OutEnvironment.CompilerFlags.Add(CFLAG_WarningsAsErrors);
OutEnvironment.CompilerFlags.Add(CFLAG_AllowTypedUAVLoads);
OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_X"), FComputeShaderUtils::kGolden2DGroupSize);
OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_Y"), FComputeShaderUtils::kGolden2DGroupSize);
}
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<FPathTracingLight>, SceneLights)
SHADER_PARAMETER(uint32, SceneLightCount)
SHADER_PARAMETER_STRUCT_INCLUDE(FPathTracingLightGrid, LightGridParameters)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWLightGrid)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, RWLightGridData)
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_SHADER_TYPE(, FPathTracingBuildLightGridCS, TEXT("/Engine/Private/PathTracing/PathTracingBuildLightGrid.usf"), TEXT("PathTracingBuildLightGridCS"), SF_Compute);
class FPathTracingRG : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FPathTracingRG)
SHADER_USE_ROOT_PARAMETER_STRUCT(FPathTracingRG, FGlobalShader)
class FCompactionType : SHADER_PERMUTATION_INT("PATH_TRACER_USE_COMPACTION", 2);
using FPermutationDomain = TShaderPermutationDomain<FCompactionType>;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return ShouldCompilePathTracingShadersForProject(Parameters.Platform);
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("USE_RECT_LIGHT_TEXTURES"), 1);
OutEnvironment.CompilerFlags.Add(CFLAG_WarningsAsErrors);
}
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, RadianceTexture)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, AlbedoTexture)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, NormalTexture)
SHADER_PARAMETER_SRV(RaytracingAccelerationStructure, TLAS)
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, ViewUniformBuffer)
SHADER_PARAMETER_STRUCT_INCLUDE(FPathTracingData, PathTracingData)
// scene lights
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<FPathTracingLight>, SceneLights)
SHADER_PARAMETER(uint32, SceneLightCount)
SHADER_PARAMETER(uint32, SceneVisibleLightCount)
SHADER_PARAMETER_STRUCT_INCLUDE(FPathTracingLightGrid, LightGridParameters)
// Skylight
SHADER_PARAMETER_STRUCT_INCLUDE(FPathTracingSkylight, SkylightParameters)
// IES Profiles
SHADER_PARAMETER_RDG_TEXTURE(Texture2DArray, IESTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, IESTextureSampler) // Shared sampler for all IES profiles
// Rect lights
SHADER_PARAMETER_TEXTURE_ARRAY(Texture2D, RectLightTexture, [PATHTRACER_MAX_RECT_TEXTURES])
SHADER_PARAMETER_SAMPLER(SamplerState, RectLightSampler) // Shared sampler for all rectlights
// Used by multi-GPU rendering
SHADER_PARAMETER(FIntVector, TileOffset)
// extra parameters required for path compacting kernel
SHADER_PARAMETER(int, Bounce)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer<FPathTracingPackedPathState>, PathStateData)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<int>, ActivePaths)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<int>, NextActivePaths)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<int>, NumPathStates)
RDG_BUFFER_ACCESS(PathTracingIndirectArgs, ERHIAccess::IndirectArgs | ERHIAccess::SRVCompute)
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_GLOBAL_SHADER(FPathTracingRG, "/Engine/Private/PathTracing/PathTracing.usf", "PathTracingMainRG", SF_RayGen);
class FPathTracingIESAtlasCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FPathTracingIESAtlasCS)
SHADER_USE_PARAMETER_STRUCT(FPathTracingIESAtlasCS, FGlobalShader)
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return ShouldCompileRayTracingShadersForProject(Parameters.Platform);
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
//OutEnvironment.CompilerFlags.Add(CFLAG_WarningsAsErrors);
OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_X"), FComputeShaderUtils::kGolden2DGroupSize);
OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_Y"), FComputeShaderUtils::kGolden2DGroupSize);
}
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_TEXTURE(Texture2D, IESTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, IESSampler)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2DArray, IESAtlas)
SHADER_PARAMETER(int32, IESAtlasSlice)
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_SHADER_TYPE(, FPathTracingIESAtlasCS, TEXT("/Engine/Private/PathTracing/PathTracingIESAtlas.usf"), TEXT("PathTracingIESAtlasCS"), SF_Compute);
template<bool UseAnyHitShader, bool UseIntersectionShader>
class TPathTracingMaterial : public FMeshMaterialShader
{
DECLARE_SHADER_TYPE(TPathTracingMaterial, MeshMaterial);
public:
TPathTracingMaterial() = default;
TPathTracingMaterial(const FMeshMaterialShaderType::CompiledShaderInitializerType& Initializer)
: FMeshMaterialShader(Initializer)
{}
static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
{
const bool bUseProceduralPrimitive = Parameters.VertexFactoryType->SupportsRayTracingProceduralPrimitive() && FDataDrivenShaderPlatformInfo::GetSupportsRayTracingProceduralPrimitive(Parameters.Platform);
return Parameters.VertexFactoryType->SupportsRayTracing()
&& (UseIntersectionShader == bUseProceduralPrimitive)
&& ((Parameters.MaterialParameters.bIsMasked || Parameters.MaterialParameters.BlendMode != BLEND_Opaque) == UseAnyHitShader)
&& ShouldCompilePathTracingShadersForProject(Parameters.Platform);
}
static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
OutEnvironment.SetDefine(TEXT("USE_MATERIAL_CLOSEST_HIT_SHADER"), 1);
OutEnvironment.SetDefine(TEXT("USE_MATERIAL_ANY_HIT_SHADER"), UseAnyHitShader ? 1 : 0);
OutEnvironment.SetDefine(TEXT("USE_MATERIAL_INTERSECTION_SHADER"), UseIntersectionShader ? 1 : 0);
OutEnvironment.SetDefine(TEXT("USE_RAYTRACED_TEXTURE_RAYCONE_LOD"), 0);
OutEnvironment.SetDefine(TEXT("SCENE_TEXTURES_DISABLED"), 1);
OutEnvironment.SetDefine(TEXT("SIMPLIFIED_MATERIAL_SHADER"), 0); // TODO: expose a permutation so we can unify with GPULightmass?
FMeshMaterialShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
}
static bool ValidateCompiledResult(EShaderPlatform Platform, const FShaderParameterMap& ParameterMap, TArray<FString>& OutError)
{
if (ParameterMap.ContainsParameterAllocation(FSceneTextureUniformParameters::StaticStructMetadata.GetShaderVariableName()))
{
OutError.Add(TEXT("Ray tracing closest hit shaders cannot read from the SceneTexturesStruct."));
return false;
}
for (const auto& It : ParameterMap.GetParameterMap())
{
const FParameterAllocation& ParamAllocation = It.Value;
if (ParamAllocation.Type != EShaderParameterType::UniformBuffer
&& ParamAllocation.Type != EShaderParameterType::LooseData)
{
OutError.Add(FString::Printf(TEXT("Invalid ray tracing shader parameter '%s'. Only uniform buffers and loose data parameters are supported."), *(It.Key)));
return false;
}
}
return true;
}
};
using FPathTracingMaterialCHS = TPathTracingMaterial<false, false>;
using FPathTracingMaterialCHS_AHS = TPathTracingMaterial<true , false>;
using FPathTracingMaterialCHS_IS = TPathTracingMaterial<false, true >;
using FPathTracingMaterialCHS_AHS_IS = TPathTracingMaterial<true , true >;
IMPLEMENT_MATERIAL_SHADER_TYPE(template <>, FPathTracingMaterialCHS , TEXT("/Engine/Private/PathTracing/PathTracingMaterialHitShader.usf"), TEXT("closesthit=PathTracingMaterialCHS"), SF_RayHitGroup);
IMPLEMENT_MATERIAL_SHADER_TYPE(template <>, FPathTracingMaterialCHS_AHS , TEXT("/Engine/Private/PathTracing/PathTracingMaterialHitShader.usf"), TEXT("closesthit=PathTracingMaterialCHS anyhit=PathTracingMaterialAHS"), SF_RayHitGroup);
IMPLEMENT_MATERIAL_SHADER_TYPE(template <>, FPathTracingMaterialCHS_IS , TEXT("/Engine/Private/PathTracing/PathTracingMaterialHitShader.usf"), TEXT("closesthit=PathTracingMaterialCHS intersection=MaterialIS"), SF_RayHitGroup);
IMPLEMENT_MATERIAL_SHADER_TYPE(template <>, FPathTracingMaterialCHS_AHS_IS, TEXT("/Engine/Private/PathTracing/PathTracingMaterialHitShader.usf"), TEXT("closesthit=PathTracingMaterialCHS anyhit=PathTracingMaterialAHS intersection=MaterialIS"), SF_RayHitGroup);
bool FRayTracingMeshProcessor::ProcessPathTracing(
const FMeshBatch& RESTRICT MeshBatch,
uint64 BatchElementMask,
const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
const FMaterialRenderProxy& RESTRICT MaterialRenderProxy,
const FMaterial& RESTRICT MaterialResource)
{
const FVertexFactory* VertexFactory = MeshBatch.VertexFactory;
TMeshProcessorShaders<
FMeshMaterialShader,
FMeshMaterialShader,
FMeshMaterialShader,
FMeshMaterialShader,
FMeshMaterialShader> RayTracingShaders;
FMaterialShaderTypes ShaderTypes;
const bool bUseProceduralPrimitive = VertexFactory->GetType()->SupportsRayTracingProceduralPrimitive() && FDataDrivenShaderPlatformInfo::GetSupportsRayTracingProceduralPrimitive(GMaxRHIShaderPlatform);
if (MaterialResource.IsMasked() || MaterialResource.GetBlendMode() != BLEND_Opaque)
{
if (bUseProceduralPrimitive)
ShaderTypes.AddShaderType<FPathTracingMaterialCHS_AHS_IS>();
else
ShaderTypes.AddShaderType<FPathTracingMaterialCHS_AHS>();
}
else
{
if (bUseProceduralPrimitive)
ShaderTypes.AddShaderType<FPathTracingMaterialCHS_IS>();
else
ShaderTypes.AddShaderType<FPathTracingMaterialCHS>();
}
FMaterialShaders Shaders;
if (!MaterialResource.TryGetShaders(ShaderTypes, VertexFactory->GetType(), Shaders))
{
return false;
}
check(Shaders.TryGetShader(SF_RayHitGroup, RayTracingShaders.RayHitGroupShader));
TBasePassShaderElementData<FUniformLightMapPolicy> ShaderElementData(MeshBatch.LCI);
ShaderElementData.InitializeMeshMaterialData(ViewIfDynamicMeshCommand, PrimitiveSceneProxy, MeshBatch, -1, true);
BuildRayTracingMeshCommands(
MeshBatch,
BatchElementMask,
PrimitiveSceneProxy,
MaterialRenderProxy,
MaterialResource,
PassDrawRenderState,
RayTracingShaders,
ShaderElementData);
return true;
}
RENDERER_API void PrepareSkyTexture_Internal(
FRDGBuilder& GraphBuilder,
FReflectionUniformParameters& Parameters,
uint32 Size,
FLinearColor SkyColor,
bool UseMISCompensation,
// Out
FRDGTextureRef& SkylightTexture,
FRDGTextureRef& SkylightPdf,
float& SkylightInvResolution,
int32& SkylightMipCount
)
{
FRDGTextureDesc SkylightTextureDesc = FRDGTextureDesc::Create2D(
FIntPoint(Size, Size),
PF_A32B32G32R32F, // half precision might be ok?
FClearValueBinding::None,
TexCreate_ShaderResource | TexCreate_UAV);
SkylightTexture = GraphBuilder.CreateTexture(SkylightTextureDesc, TEXT("PathTracer.Skylight"), ERDGTextureFlags::None);
FRDGTextureDesc SkylightPdfDesc = FRDGTextureDesc::Create2D(
FIntPoint(Size, Size),
PF_R32_FLOAT, // half precision might be ok?
FClearValueBinding::None,
TexCreate_ShaderResource | TexCreate_UAV,
FMath::CeilLogTwo(Size) + 1);
SkylightPdf = GraphBuilder.CreateTexture(SkylightPdfDesc, TEXT("PathTracer.SkylightPdf"), ERDGTextureFlags::None);
SkylightInvResolution = 1.0f / Size;
SkylightMipCount = SkylightPdfDesc.NumMips;
// run a simple compute shader to sample the cubemap and prep the top level of the mipmap hierarchy
{
TShaderMapRef<FPathTracingSkylightPrepareCS> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
FPathTracingSkylightPrepareCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FPathTracingSkylightPrepareCS::FParameters>();
PassParameters->SkyColor = FVector(SkyColor.R, SkyColor.G, SkyColor.B);
PassParameters->SkyLightCubemap0 = Parameters.SkyLightCubemap;
PassParameters->SkyLightCubemap1 = Parameters.SkyLightBlendDestinationCubemap;
PassParameters->SkyLightCubemapSampler0 = Parameters.SkyLightCubemapSampler;
PassParameters->SkyLightCubemapSampler1 = Parameters.SkyLightBlendDestinationCubemapSampler;
PassParameters->SkylightBlendFactor = Parameters.SkyLightParameters.W;
PassParameters->SkylightInvResolution = SkylightInvResolution;
PassParameters->SkylightTextureOutput = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(SkylightTexture, 0));
PassParameters->SkylightTexturePdf = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(SkylightPdf, 0));
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("SkylightPrepare"),
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(FIntPoint(Size, Size), FComputeShaderUtils::kGolden2DGroupSize));
}
FGenerateMips::ExecuteCompute(GraphBuilder, SkylightPdf, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
if (UseMISCompensation)
{
TShaderMapRef<FPathTracingSkylightMISCompensationCS> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
FPathTracingSkylightMISCompensationCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FPathTracingSkylightMISCompensationCS::FParameters>();
PassParameters->SkylightTexturePdfAverage = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::CreateForMipLevel(SkylightPdf, SkylightMipCount - 1));
PassParameters->SkylightTextureOutput = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(SkylightTexture, 0));
PassParameters->SkylightTexturePdf = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(SkylightPdf, 0));
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("SkylightMISCompensation"),
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(FIntPoint(Size, Size), FComputeShaderUtils::kGolden2DGroupSize));
FGenerateMips::ExecuteCompute(GraphBuilder, SkylightPdf, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
}
}
RENDERER_API FRDGTexture* PrepareIESAtlas(const TMap<FTexture*, int>& InIESLightProfilesMap, FRDGBuilder& GraphBuilder)
{
// We found some IES profiles to use -- upload them into a single atlas so we can access them easily in HLSL
// TODO: This is redundant because all the IES textures are already on the GPU. Handling IES profiles via Miss shaders
// would be cleaner.
// TODO: This is also redundant with the logic in RayTracingLighting.cpp, but the latter is limitted to 1D profiles and
// does not consider the same set of lights as the path tracer. Longer term we should aim to unify the representation of lights
// across both passes
// TODO: This process is repeated every frame! More motivation to move to a Miss shader based implementation
// This size matches the import resolution of light profiles (see FIESLoader::GetWidth)
const int kIESAtlasSize = 256;
const int NumSlices = InIESLightProfilesMap.Num();
FRDGTextureDesc IESTextureDesc = FRDGTextureDesc::Create2DArray(
FIntPoint(kIESAtlasSize, kIESAtlasSize),
PF_R32_FLOAT,
FClearValueBinding::None,
TexCreate_ShaderResource | TexCreate_UAV,
NumSlices);
FRDGTexture* IESTexture = GraphBuilder.CreateTexture(IESTextureDesc, TEXT("PathTracer.IESAtlas"), ERDGTextureFlags::None);
for (auto&& Entry : InIESLightProfilesMap)
{
FPathTracingIESAtlasCS::FParameters* AtlasPassParameters = GraphBuilder.AllocParameters<FPathTracingIESAtlasCS::FParameters>();
const int Slice = Entry.Value;
AtlasPassParameters->IESTexture = Entry.Key->TextureRHI;
AtlasPassParameters->IESSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
AtlasPassParameters->IESAtlas = GraphBuilder.CreateUAV(IESTexture);
AtlasPassParameters->IESAtlasSlice = Slice;
TShaderMapRef<FPathTracingIESAtlasCS> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("Path Tracing IES Atlas (Slice=%d)", Slice),
ComputeShader,
AtlasPassParameters,
FComputeShaderUtils::GetGroupCount(FIntPoint(kIESAtlasSize, kIESAtlasSize), FComputeShaderUtils::kGolden2DGroupSize));
}
return IESTexture;
}
RDG_REGISTER_BLACKBOARD_STRUCT(FPathTracingSkylight)
bool PrepareSkyTexture(FRDGBuilder& GraphBuilder, FScene* Scene, const FViewInfo& View, bool SkylightEnabled, bool UseMISCompensation, FPathTracingSkylight* SkylightParameters)
{
SkylightParameters->SkylightTextureSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
FReflectionUniformParameters Parameters;
SetupReflectionUniformParameters(View, Parameters);
if (!SkylightEnabled || !(Parameters.SkyLightParameters.Y > 0))
{
// textures not ready, or skylight not active
// just put in a placeholder
SkylightParameters->SkylightTexture = GraphBuilder.RegisterExternalTexture(GSystemTextures.BlackDummy);
SkylightParameters->SkylightPdf = GraphBuilder.RegisterExternalTexture(GSystemTextures.BlackDummy);
SkylightParameters->SkylightInvResolution = 0;
SkylightParameters->SkylightMipCount = 0;
return false;
}
// the sky is actually enabled, lets see if someone already made use of it for this frame
const FPathTracingSkylight* PreviousSkylightParameters = GraphBuilder.Blackboard.Get<FPathTracingSkylight>();
if (PreviousSkylightParameters != nullptr)
{
*SkylightParameters = *PreviousSkylightParameters;
return true;
}
// should we remember the skylight prep for the next frame?
const bool IsSkylightCachingEnabled = CVarPathTracingSkylightCaching.GetValueOnAnyThread() != 0;
FLinearColor SkyColor = Scene->SkyLight->GetEffectiveLightColor();
const bool bSkylightColorChanged = SkyColor != Scene->PathTracingSkylightColor;
if (!IsSkylightCachingEnabled || bSkylightColorChanged)
{
// we don't want any caching (or the light color changed)
// release what we might have been holding onto so we get the right texture for this frame
Scene->PathTracingSkylightTexture.SafeRelease();
Scene->PathTracingSkylightPdf.SafeRelease();
}
if (Scene->PathTracingSkylightTexture.IsValid() &&
Scene->PathTracingSkylightPdf.IsValid())
{
// we already have a valid texture and pdf, just re-use them!
// it is the responsability of code that may invalidate the contents to reset these pointers
SkylightParameters->SkylightTexture = GraphBuilder.RegisterExternalTexture(Scene->PathTracingSkylightTexture, TEXT("PathTracer.Skylight"));
SkylightParameters->SkylightPdf = GraphBuilder.RegisterExternalTexture(Scene->PathTracingSkylightPdf, TEXT("PathTracer.SkylightPdf"));
SkylightParameters->SkylightInvResolution = 1.0f / SkylightParameters->SkylightTexture->Desc.GetSize().X;
SkylightParameters->SkylightMipCount = SkylightParameters->SkylightPdf->Desc.NumMips;
return true;
}
RDG_EVENT_SCOPE(GraphBuilder, "Path Tracing SkylightPrepare");
Scene->PathTracingSkylightColor = SkyColor;
// since we are resampled into an octahedral layout, we multiply the cubemap resolution by 2 to get roughly the same number of texels
uint32 Size = FMath::RoundUpToPowerOfTwo(2 * Scene->SkyLight->CaptureCubeMapResolution);
RDG_GPU_MASK_SCOPE(GraphBuilder,
IsSkylightCachingEnabled ? FRHIGPUMask::All() : GraphBuilder.RHICmdList.GetGPUMask());
PrepareSkyTexture_Internal(
GraphBuilder,
Parameters,
Size,
SkyColor,
UseMISCompensation,
// Out
SkylightParameters->SkylightTexture,
SkylightParameters->SkylightPdf,
SkylightParameters->SkylightInvResolution,
SkylightParameters->SkylightMipCount
);
// hang onto these for next time (if caching is enabled)
if (IsSkylightCachingEnabled)
{
GraphBuilder.QueueTextureExtraction(SkylightParameters->SkylightTexture, &Scene->PathTracingSkylightTexture);
GraphBuilder.QueueTextureExtraction(SkylightParameters->SkylightPdf, &Scene->PathTracingSkylightPdf);
}
// remember the skylight parameters for future passes within this frame
GraphBuilder.Blackboard.Create<FPathTracingSkylight>() = *SkylightParameters;
return true;
}
RENDERER_API void PrepareLightGrid(FRDGBuilder& GraphBuilder, FPathTracingLightGrid* LightGridParameters, const FPathTracingLight* Lights, uint32 NumLights, uint32 NumInfiniteLights, FRDGBufferSRV* LightsSRV)
{
const float Inf = std::numeric_limits<float>::infinity();
LightGridParameters->SceneInfiniteLightCount = NumInfiniteLights;
LightGridParameters->SceneLightsBoundMin = FVector(+Inf, +Inf, +Inf);
LightGridParameters->SceneLightsBoundMax = FVector(-Inf, -Inf, -Inf);
LightGridParameters->LightGrid = nullptr;
LightGridParameters->LightGridData = nullptr;
int NumFiniteLights = NumLights - NumInfiniteLights;
// if we have some finite lights -- build a light grid
if (NumFiniteLights > 0)
{
// get bounding box of all finite lights
const FPathTracingLight* FiniteLights = Lights + NumInfiniteLights;
for (int Index = 0; Index < NumFiniteLights; Index++)
{
const FPathTracingLight& Light = FiniteLights[Index];
LightGridParameters->SceneLightsBoundMin = FVector::Min(LightGridParameters->SceneLightsBoundMin, Light.BoundMin);
LightGridParameters->SceneLightsBoundMax = FVector::Max(LightGridParameters->SceneLightsBoundMax, Light.BoundMax);
}
const uint32 Resolution = FMath::RoundUpToPowerOfTwo(CVarPathTracingLightGridResolution.GetValueOnRenderThread());
const uint32 MaxCount = FMath::Clamp(
CVarPathTracingLightGridMaxCount.GetValueOnRenderThread(),
1,
FMath::Min(NumFiniteLights, RAY_TRACING_LIGHT_COUNT_MAXIMUM)
);
LightGridParameters->LightGridResolution = Resolution;
LightGridParameters->LightGridMaxCount = MaxCount;
// pick the shortest axis
FVector Diag = LightGridParameters->SceneLightsBoundMax - LightGridParameters->SceneLightsBoundMin;
if (Diag.X < Diag.Y && Diag.X < Diag.Z)
{
LightGridParameters->LightGridAxis = 0;
}
else if (Diag.Y < Diag.Z)
{
LightGridParameters->LightGridAxis = 1;
}
else
{
LightGridParameters->LightGridAxis = 2;
}
FPathTracingBuildLightGridCS::FParameters* LightGridPassParameters = GraphBuilder.AllocParameters< FPathTracingBuildLightGridCS::FParameters>();
FRDGTextureDesc LightGridDesc = FRDGTextureDesc::Create2D(
FIntPoint(Resolution, Resolution),
PF_R32_UINT,
FClearValueBinding::None,
TexCreate_ShaderResource | TexCreate_UAV);
FRDGTexture* LightGridTexture = GraphBuilder.CreateTexture(LightGridDesc, TEXT("PathTracer.LightGrid"), ERDGTextureFlags::None);
LightGridPassParameters->RWLightGrid = GraphBuilder.CreateUAV(LightGridTexture);
EPixelFormat LightGridDataFormat = PF_R32_UINT;
size_t LightGridDataNumBytes = sizeof(uint32);
if (NumLights <= (MAX_uint8 + 1))
{
LightGridDataFormat = PF_R8_UINT;
LightGridDataNumBytes = sizeof(uint8);
}
else if (NumLights <= (MAX_uint16 + 1))
{
LightGridDataFormat = PF_R16_UINT;
LightGridDataNumBytes = sizeof(uint16);
}
FRDGBufferDesc LightGridDataDesc = FRDGBufferDesc::CreateBufferDesc(LightGridDataNumBytes, MaxCount * Resolution * Resolution);
FRDGBuffer* LightGridData = GraphBuilder.CreateBuffer(LightGridDataDesc, TEXT("PathTracer.LightGridData"));
LightGridPassParameters->RWLightGridData = GraphBuilder.CreateUAV(LightGridData, LightGridDataFormat);
LightGridPassParameters->LightGridParameters = *LightGridParameters;
LightGridPassParameters->SceneLights = LightsSRV;
LightGridPassParameters->SceneLightCount = NumLights;
TShaderMapRef<FPathTracingBuildLightGridCS> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("Light Grid Create (%u lights)", NumFiniteLights),
ComputeShader,
LightGridPassParameters,
FComputeShaderUtils::GetGroupCount(FIntPoint(Resolution, Resolution), FComputeShaderUtils::kGolden2DGroupSize));
// hookup to the actual rendering pass
LightGridParameters->LightGrid = LightGridTexture;
LightGridParameters->LightGridData = GraphBuilder.CreateSRV(LightGridData, LightGridDataFormat);
}
else
{
// light grid is not needed - just hookup dummy data
LightGridParameters->LightGridResolution = 0;
LightGridParameters->LightGridMaxCount = 0;
LightGridParameters->LightGridAxis = 0;
LightGridParameters->LightGrid = GraphBuilder.RegisterExternalTexture(GSystemTextures.BlackDummy);
FRDGBufferDesc LightGridDataDesc = FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), 1);
FRDGBuffer* LightGridData = GraphBuilder.CreateBuffer(LightGridDataDesc, TEXT("PathTracer.LightGridData"));
AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(LightGridData, PF_R32_UINT), 0);
LightGridParameters->LightGridData = GraphBuilder.CreateSRV(LightGridData, PF_R32_UINT);
}
}
void SetLightParameters(FRDGBuilder& GraphBuilder, FPathTracingRG::FParameters* PassParameters, FScene* Scene, const FViewInfo& View, bool UseMISCompensation)
{
PassParameters->SceneVisibleLightCount = 0;
// Lights
uint32 MaxNumLights = 1 + Scene->Lights.Num(); // upper bound
// Allocate from the graph builder so that we don't need to copy the data again when queuing the upload
FPathTracingLight* Lights = (FPathTracingLight*) GraphBuilder.Alloc(sizeof(FPathTracingLight) * MaxNumLights, 16);
uint32 NumLights = 0;
// Prepend SkyLight to light buffer since it is not part of the regular light list
const float Inf = std::numeric_limits<float>::infinity();
if (PrepareSkyTexture(GraphBuilder, Scene, View, true, UseMISCompensation, &PassParameters->SkylightParameters))
{
check(Scene->SkyLight != nullptr);
FPathTracingLight& DestLight = Lights[NumLights++];
DestLight.Color = FVector(1, 1, 1); // not used (it is folded into the importance table directly)
DestLight.Flags = Scene->SkyLight->bTransmission ? PATHTRACER_FLAG_TRANSMISSION_MASK : 0;
DestLight.Flags |= PATHTRACER_FLAG_LIGHTING_CHANNEL_MASK;
DestLight.Flags |= PATHTRACING_LIGHT_SKY;
DestLight.Flags |= Scene->SkyLight->bCastShadows ? PATHTRACER_FLAG_CAST_SHADOW_MASK : 0;
DestLight.IESTextureSlice = -1;
DestLight.BoundMin = FVector(-Inf, -Inf, -Inf);
DestLight.BoundMax = FVector( Inf, Inf, Inf);
if (Scene->SkyLight->bRealTimeCaptureEnabled || CVarPathTracingVisibleLights.GetValueOnRenderThread() == 2)
{
// When using the realtime capture system, always make the skylight visible
// because this is our only way of "seeing" the atmo/clouds at the moment
// Also allow seeing just the sky via a cvar for debugging purposes
PassParameters->SceneVisibleLightCount = 1;
}
}
// Add directional lights next (all lights with infinite bounds should come first)
if (View.Family->EngineShowFlags.DirectionalLights)
{
for (auto Light : Scene->Lights)
{
ELightComponentType LightComponentType = (ELightComponentType)Light.LightSceneInfo->Proxy->GetLightType();
if (LightComponentType != LightType_Directional)
{
continue;
}
FLightShaderParameters LightParameters;
Light.LightSceneInfo->Proxy->GetLightShaderParameters(LightParameters);
if (LightParameters.Color.IsZero())
{
continue;
}
FPathTracingLight& DestLight = Lights[NumLights++];
uint32 Transmission = Light.LightSceneInfo->Proxy->Transmission();
uint8 LightingChannelMask = Light.LightSceneInfo->Proxy->GetLightingChannelMask();
DestLight.Flags = Transmission ? PATHTRACER_FLAG_TRANSMISSION_MASK : 0;
DestLight.Flags |= LightingChannelMask & PATHTRACER_FLAG_LIGHTING_CHANNEL_MASK;
DestLight.Flags |= Light.LightSceneInfo->Proxy->CastsDynamicShadow() ? PATHTRACER_FLAG_CAST_SHADOW_MASK : 0;
DestLight.IESTextureSlice = -1;
DestLight.RectLightTextureIndex = -1;
// these mean roughly the same thing across all light types
DestLight.Color = LightParameters.Color;
DestLight.Position = LightParameters.Position;
DestLight.Normal = -LightParameters.Direction;
DestLight.dPdu = FVector::CrossProduct(LightParameters.Tangent, LightParameters.Direction);
DestLight.dPdv = LightParameters.Tangent;
DestLight.Attenuation = LightParameters.InvRadius;
DestLight.FalloffExponent = 0;
DestLight.Normal = LightParameters.Direction;
DestLight.Dimensions = FVector(LightParameters.SourceRadius, LightParameters.SoftSourceRadius, 0.0f);
DestLight.Flags |= PATHTRACING_LIGHT_DIRECTIONAL;
DestLight.BoundMin = FVector(-Inf, -Inf, -Inf);
DestLight.BoundMax = FVector( Inf, Inf, Inf);
}
}
uint32 NumInfiniteLights = NumLights;
int32 NextRectTextureIndex = 0;
TMap<FTexture*, int> IESLightProfilesMap;
for (auto Light : Scene->Lights)
{
ELightComponentType LightComponentType = (ELightComponentType)Light.LightSceneInfo->Proxy->GetLightType();
if ( (LightComponentType == LightType_Directional) /* already handled by the loop above */ ||
((LightComponentType == LightType_Rect ) && !View.Family->EngineShowFlags.RectLights ) ||
((LightComponentType == LightType_Spot ) && !View.Family->EngineShowFlags.SpotLights ) ||
((LightComponentType == LightType_Point ) && !View.Family->EngineShowFlags.PointLights ))
{
// This light type is not currently enabled
continue;
}
FLightShaderParameters LightParameters;
Light.LightSceneInfo->Proxy->GetLightShaderParameters(LightParameters);
if (LightParameters.Color.IsZero())
{
continue;
}
FPathTracingLight& DestLight = Lights[NumLights++];
uint32 Transmission = Light.LightSceneInfo->Proxy->Transmission();
uint8 LightingChannelMask = Light.LightSceneInfo->Proxy->GetLightingChannelMask();
DestLight.Flags = Transmission ? PATHTRACER_FLAG_TRANSMISSION_MASK : 0;
DestLight.Flags |= LightingChannelMask & PATHTRACER_FLAG_LIGHTING_CHANNEL_MASK;
DestLight.Flags |= Light.LightSceneInfo->Proxy->CastsDynamicShadow() ? PATHTRACER_FLAG_CAST_SHADOW_MASK : 0;
DestLight.IESTextureSlice = -1;
DestLight.RectLightTextureIndex = -1;
if (View.Family->EngineShowFlags.TexturedLightProfiles)
{
FTexture* IESTexture = Light.LightSceneInfo->Proxy->GetIESTextureResource();
if (IESTexture != nullptr)
{
// Only add a given texture once
DestLight.IESTextureSlice = IESLightProfilesMap.FindOrAdd(IESTexture, IESLightProfilesMap.Num());
}
}
// these mean roughly the same thing across all light types
DestLight.Color = LightParameters.Color;
DestLight.Position = LightParameters.Position;
DestLight.Normal = -LightParameters.Direction;
DestLight.dPdu = FVector::CrossProduct(LightParameters.Tangent, LightParameters.Direction);
DestLight.dPdv = LightParameters.Tangent;
DestLight.Attenuation = LightParameters.InvRadius;
DestLight.FalloffExponent = 0;
switch (LightComponentType)
{
case LightType_Rect:
{
DestLight.Dimensions = FVector(2.0f * LightParameters.SourceRadius, 2.0f * LightParameters.SourceLength, 0.0f);
DestLight.Shaping = FVector2D(LightParameters.RectLightBarnCosAngle, LightParameters.RectLightBarnLength);
DestLight.FalloffExponent = LightParameters.FalloffExponent;
DestLight.Flags |= Light.LightSceneInfo->Proxy->IsInverseSquared() ? 0 : PATHTRACER_FLAG_NON_INVERSE_SQUARE_FALLOFF_MASK;
DestLight.Flags |= PATHTRACING_LIGHT_RECT;
if (Light.LightSceneInfo->Proxy->HasSourceTexture())
{
// there is an actual texture associated with this light, go look for it
FLightShaderParameters ShaderParameters;
Light.LightSceneInfo->Proxy->GetLightShaderParameters(ShaderParameters);
FRHITexture* TextureRHI = ShaderParameters.SourceTexture;
if (TextureRHI != nullptr)
{
// have we already given this texture an index?
// NOTE: linear search is ok since max texture is small
for (int Index = 0; Index < NextRectTextureIndex; Index++)
{
if (PassParameters->RectLightTexture[Index] == TextureRHI)
{
DestLight.RectLightTextureIndex = Index;
break;
}
}
if (DestLight.RectLightTextureIndex == -1 && NextRectTextureIndex < PATHTRACER_MAX_RECT_TEXTURES)
{
// first time we see this texture and we still have free slots available
// assign texture to next slot and store it in the light
DestLight.RectLightTextureIndex = NextRectTextureIndex;
PassParameters->RectLightTexture[NextRectTextureIndex] = TextureRHI;
NextRectTextureIndex++;
}
}
}
float Radius = 1.0f / LightParameters.InvRadius;
FVector Center = DestLight.Position;
FVector Normal = DestLight.Normal;
FVector Disc = FVector(
FMath::Sqrt(FMath::Clamp(1 - Normal.X * Normal.X, 0.0f, 1.0f)),
FMath::Sqrt(FMath::Clamp(1 - Normal.Y * Normal.Y, 0.0f, 1.0f)),
FMath::Sqrt(FMath::Clamp(1 - Normal.Z * Normal.Z, 0.0f, 1.0f))
);
// quad bbox is the bbox of the disc + the tip of the hemisphere
// TODO: is it worth trying to account for barndoors? seems unlikely to cut much empty space since the volume _inside_ the barndoor receives light
FVector Tip = Center + Normal * Radius;
DestLight.BoundMin = FVector::Min(Tip, Center - Radius * Disc);
DestLight.BoundMax = FVector::Max(Tip, Center + Radius * Disc);
break;
}
case LightType_Spot:
{
DestLight.Dimensions = FVector(LightParameters.SourceRadius, LightParameters.SoftSourceRadius, LightParameters.SourceLength);
DestLight.Shaping = LightParameters.SpotAngles;
DestLight.FalloffExponent = LightParameters.FalloffExponent;
DestLight.Flags |= Light.LightSceneInfo->Proxy->IsInverseSquared() ? 0 : PATHTRACER_FLAG_NON_INVERSE_SQUARE_FALLOFF_MASK;
DestLight.Flags |= PATHTRACING_LIGHT_SPOT;
float Radius = 1.0f / LightParameters.InvRadius;
FVector Center = DestLight.Position;
FVector Normal = DestLight.Normal;
FVector Disc = FVector(
FMath::Sqrt(FMath::Clamp(1 - Normal.X * Normal.X, 0.0f, 1.0f)),
FMath::Sqrt(FMath::Clamp(1 - Normal.Y * Normal.Y, 0.0f, 1.0f)),
FMath::Sqrt(FMath::Clamp(1 - Normal.Z * Normal.Z, 0.0f, 1.0f))
);
// box around ray from light center to tip of the cone
FVector Tip = Center + Normal * Radius;
DestLight.BoundMin = FVector::Min(Center, Tip);
DestLight.BoundMax = FVector::Max(Center, Tip);
// expand by disc around the farthest part of the cone
float CosOuter = LightParameters.SpotAngles.X;
float SinOuter = FMath::Sqrt(1.0f - CosOuter * CosOuter);
DestLight.BoundMin = FVector::Min(DestLight.BoundMin, Center + Radius * (Normal * CosOuter - Disc * SinOuter));
DestLight.BoundMax = FVector::Max(DestLight.BoundMax, Center + Radius * (Normal * CosOuter + Disc * SinOuter));
break;
}
case LightType_Point:
{
DestLight.Dimensions = FVector(LightParameters.SourceRadius, LightParameters.SoftSourceRadius, LightParameters.SourceLength);
DestLight.FalloffExponent = LightParameters.FalloffExponent;
DestLight.Flags |= Light.LightSceneInfo->Proxy->IsInverseSquared() ? 0 : PATHTRACER_FLAG_NON_INVERSE_SQUARE_FALLOFF_MASK;
DestLight.Flags |= PATHTRACING_LIGHT_POINT;
float Radius = 1.0f / LightParameters.InvRadius;
FVector Center = DestLight.Position;
// simple sphere of influence
DestLight.BoundMin = Center - FVector(Radius, Radius, Radius);
DestLight.BoundMax = Center + FVector(Radius, Radius, Radius);
break;
}
default:
{
// Just in case someone adds a new light type one day ...
checkNoEntry();
break;
}
}
}
{
// assign dummy textures to the remaining unused slots
for (int32 Index = NextRectTextureIndex; Index < PATHTRACER_MAX_RECT_TEXTURES; Index++)
{
PassParameters->RectLightTexture[Index] = GWhiteTexture->TextureRHI;
}
PassParameters->RectLightSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
}
PassParameters->SceneLightCount = NumLights;
{
// Upload the buffer of lights to the GPU
uint32 NumCopyLights = FMath::Max(1u, NumLights); // need at least one since zero-sized buffers are not allowed
size_t DataSize = sizeof(FPathTracingLight) * NumCopyLights;
PassParameters->SceneLights = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(CreateStructuredBuffer(GraphBuilder, TEXT("PathTracer.LightsBuffer"), sizeof(FPathTracingLight), NumCopyLights, Lights, DataSize, ERDGInitialDataFlags::NoCopy)));
}
if (CVarPathTracingVisibleLights.GetValueOnRenderThread() == 1)
{
// make all lights in the scene visible
PassParameters->SceneVisibleLightCount = PassParameters->SceneLightCount;
}
if (!IESLightProfilesMap.IsEmpty())
{
PassParameters->IESTexture = PrepareIESAtlas(IESLightProfilesMap, GraphBuilder);
}
else
{
PassParameters->IESTexture = GraphBuilder.RegisterExternalTexture(GSystemTextures.WhiteDummy);
}
PrepareLightGrid(GraphBuilder, &PassParameters->LightGridParameters, Lights, NumLights, NumInfiniteLights, PassParameters->SceneLights);
}
class FPathTracingCompositorPS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FPathTracingCompositorPS)
SHADER_USE_PARAMETER_STRUCT(FPathTracingCompositorPS, FGlobalShader)
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return ShouldCompileRayTracingShadersForProject(Parameters.Platform);
}
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D<float4>, RadianceTexture)
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, ViewUniformBuffer)
SHADER_PARAMETER(uint32, Iteration)
SHADER_PARAMETER(uint32, MaxSamples)
SHADER_PARAMETER(int, ProgressDisplayEnabled)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_SHADER_TYPE(, FPathTracingCompositorPS, TEXT("/Engine/Private/PathTracing/PathTracingCompositingPixelShader.usf"), TEXT("CompositeMain"), SF_Pixel);
void FDeferredShadingSceneRenderer::PreparePathTracing(const FSceneViewFamily& ViewFamily, TArray<FRHIRayTracingShader*>& OutRayGenShaders)
{
if (ViewFamily.EngineShowFlags.PathTracing
&& ShouldCompilePathTracingShadersForProject(ViewFamily.GetShaderPlatform()))
{
// Declare all RayGen shaders that require material closest hit shaders to be bound
for (int CompactionType = 0; CompactionType < FPathTracingRG::FCompactionType::PermutationCount; CompactionType++)
{
FPathTracingRG::FPermutationDomain PermutationVector;
PermutationVector.Set<FPathTracingRG::FCompactionType>(CompactionType);
FGlobalShaderPermutationParameters Parameters(FPathTracingRG::StaticGetTypeLayout().Name, ViewFamily.GetShaderPlatform(), PermutationVector.ToDimensionValueId());
if (!FPathTracingRG::ShouldCompilePermutation(Parameters))
{
continue;
}
auto RayGenShader = GetGlobalShaderMap(ViewFamily.GetShaderPlatform())->GetShader<FPathTracingRG>(PermutationVector);
OutRayGenShaders.Add(RayGenShader.GetRayTracingShader());
}
}
}
void FSceneViewState::PathTracingInvalidate()
{
FPathTracingState* State = PathTracingState.Get();
if (State)
{
State->RadianceRT.SafeRelease();
State->AlbedoRT.SafeRelease();
State->NormalRT.SafeRelease();
State->RadianceDenoisedRT.SafeRelease();
State->SampleIndex = 0;
}
}
uint32 FSceneViewState::GetPathTracingSampleIndex() const {
const FPathTracingState* State = PathTracingState.Get();
return State ? State->SampleIndex : 0;
}
uint32 FSceneViewState::GetPathTracingSampleCount() const {
const FPathTracingState* State = PathTracingState.Get();
return State ? State->LastConfig.PathTracingData.MaxSamples : 0;
}
BEGIN_SHADER_PARAMETER_STRUCT(FDenoiseTextureParameters, )
RDG_TEXTURE_ACCESS(InputTexture, ERHIAccess::CopySrc)
RDG_TEXTURE_ACCESS(InputAlbedo, ERHIAccess::CopySrc)
RDG_TEXTURE_ACCESS(InputNormal, ERHIAccess::CopySrc)
RDG_TEXTURE_ACCESS(OutputTexture, ERHIAccess::CopyDest)
END_SHADER_PARAMETER_STRUCT()
DECLARE_GPU_STAT_NAMED(Stat_GPU_PathTracing, TEXT("Path Tracing"));
void FDeferredShadingSceneRenderer::RenderPathTracing(
FRDGBuilder& GraphBuilder,
const FViewInfo& View,
TRDGUniformBufferRef<FSceneTextureUniformParameters> SceneTexturesUniformBuffer,
FRDGTextureRef SceneColorOutputTexture)
{
RDG_GPU_STAT_SCOPE(GraphBuilder, Stat_GPU_PathTracing);
RDG_EVENT_SCOPE(GraphBuilder, "Path Tracing");
if (!ensureMsgf(FDataDrivenShaderPlatformInfo::GetSupportsPathTracing(View.GetShaderPlatform()),
TEXT("Attempting to use path tracing on unsupported platform.")))
{
return;
}
if (CVarPathTracing.GetValueOnRenderThread() == 0)
{
// Path tracing is not enabled on this project (should not be seen by end-users since the menu entry to pick path tracing should be hidden)
// If they reach this code through ShowFlag manipulation, they may observe an incomplete image. Is there a way to inform the user here?
return;
}
FPathTracingConfig Config = {};
// Get current value of MaxSPP and reset render if it has changed
// NOTE: we ignore the CVar when using offline rendering
int32 SamplesPerPixelCVar = View.bIsOfflineRender ? -1 : CVarPathTracingSamplesPerPixel.GetValueOnRenderThread();
uint32 MaxSPP = SamplesPerPixelCVar > -1 ? SamplesPerPixelCVar : View.FinalPostProcessSettings.PathTracingSamplesPerPixel;
MaxSPP = FMath::Max(MaxSPP, 1u);
Config.LockedSamplingPattern = CVarPathTracingFrameIndependentTemporalSeed.GetValueOnRenderThread() == 0;
// compute an integer code of what show flags related to lights are currently enabled so we can detect changes
Config.LightShowFlags = 0;
Config.LightShowFlags |= View.Family->EngineShowFlags.SkyLighting ? 1 << 0 : 0;
Config.LightShowFlags |= View.Family->EngineShowFlags.DirectionalLights ? 1 << 1 : 0;
Config.LightShowFlags |= View.Family->EngineShowFlags.RectLights ? 1 << 2 : 0;
Config.LightShowFlags |= View.Family->EngineShowFlags.SpotLights ? 1 << 3 : 0;
Config.LightShowFlags |= View.Family->EngineShowFlags.PointLights ? 1 << 4 : 0;
Config.LightShowFlags |= View.Family->EngineShowFlags.TexturedLightProfiles ? 1 << 5 : 0;
PrepareShaderArgs(View, Config.PathTracingData);
Config.VisibleLights = CVarPathTracingVisibleLights.GetValueOnRenderThread() != 0;
Config.UseMISCompensation = Config.PathTracingData.MISMode == 2 && CVarPathTracingMISCompensation.GetValueOnRenderThread() != 0;
Config.ViewRect = View.ViewRect;
Config.LightGridResolution = FMath::RoundUpToPowerOfTwo(CVarPathTracingLightGridResolution.GetValueOnRenderThread());
Config.LightGridMaxCount = FMath::Clamp(CVarPathTracingLightGridMaxCount.GetValueOnRenderThread(), 1, RAY_TRACING_LIGHT_COUNT_MAXIMUM);
Config.PathTracingData.MaxSamples = MaxSPP;
bool FirstTime = false;
if (!View.ViewState->PathTracingState.IsValid())
{
View.ViewState->PathTracingState = MakePimpl<FPathTracingState>();
FirstTime = true; // we just initialized the option state for this view -- don't bother comparing in this case
}
check(View.ViewState->PathTracingState.IsValid());
FPathTracingState* PathTracingState = View.ViewState->PathTracingState.Get();
if (FirstTime || Config.UseMISCompensation != PathTracingState->LastConfig.UseMISCompensation)
{
// if the mode changes we need to rebuild the importance table
Scene->PathTracingSkylightTexture.SafeRelease();
Scene->PathTracingSkylightPdf.SafeRelease();
}
// if the skylight has changed colors, reset both the path tracer and the importance tables
if (Scene->SkyLight && Scene->SkyLight->GetEffectiveLightColor() != Scene->PathTracingSkylightColor)
{
Scene->PathTracingSkylightTexture.SafeRelease();
Scene->PathTracingSkylightPdf.SafeRelease();
// reset last color here as well in case we don't reach PrepareSkyLightTexture
Scene->PathTracingSkylightColor = Scene->SkyLight->GetEffectiveLightColor();
View.ViewState->PathTracingInvalidate();
}
// If the scene has changed in some way (camera move, object movement, etc ...)
// we must invalidate the ViewState to start over from scratch
// NOTE: only check things like hair position changes for interactive viewports, for offline renders we don't want any chance of mid-render invalidation
if (FirstTime || Config.IsDifferent(PathTracingState->LastConfig) || (!View.bIsOfflineRender && HairStrands::HasPositionsChanged(GraphBuilder, View)))
{
// remember the options we used for next time
PathTracingState->LastConfig = Config;
View.ViewState->PathTracingInvalidate();
}
// Setup temporal seed _after_ invalidation in case we got reset
if (Config.LockedSamplingPattern)
{
// Count samples from 0 for deterministic results
Config.PathTracingData.TemporalSeed = PathTracingState->SampleIndex;
}
else
{
// Count samples from an ever-increasing counter to avoid screen-door effect
Config.PathTracingData.TemporalSeed = PathTracingState->FrameIndex;
}
Config.PathTracingData.Iteration = PathTracingState->SampleIndex;
Config.PathTracingData.BlendFactor = 1.0f / (Config.PathTracingData.Iteration + 1);
// Prepare radiance buffer (will be shared with display pass)
FRDGTexture* RadianceTexture = nullptr;
FRDGTexture* AlbedoTexture = nullptr;
FRDGTexture* NormalTexture = nullptr;
if (PathTracingState->RadianceRT)
{
// we already have a valid radiance texture, re-use it
RadianceTexture = GraphBuilder.RegisterExternalTexture(PathTracingState->RadianceRT, TEXT("PathTracer.Radiance"));
AlbedoTexture = GraphBuilder.RegisterExternalTexture(PathTracingState->AlbedoRT, TEXT("PathTracer.Albedo"));
NormalTexture = GraphBuilder.RegisterExternalTexture(PathTracingState->NormalRT, TEXT("PathTracer.Normal"));
}
else
{
// First time through, need to make a new texture
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
View.ViewRect.Size(),
PF_A32B32G32R32F,
FClearValueBinding::None,
TexCreate_ShaderResource | TexCreate_UAV);
RadianceTexture = GraphBuilder.CreateTexture(Desc, TEXT("PathTracer.Radiance"), ERDGTextureFlags::MultiFrame);
AlbedoTexture = GraphBuilder.CreateTexture(Desc, TEXT("PathTracer.Albedo") , ERDGTextureFlags::MultiFrame);
NormalTexture = GraphBuilder.CreateTexture(Desc, TEXT("PathTracer.Normal") , ERDGTextureFlags::MultiFrame);
}
const bool bNeedsMoreRays = Config.PathTracingData.Iteration < MaxSPP;
if (bNeedsMoreRays)
{
// Round up to coherent path tracing tile size to simplify pixel shuffling
// TODO: be careful not to write extra pixels past the boundary when using multi-gpu
const int32 ResX = View.ViewRect.Size().X;
const int32 ResY = View.ViewRect.Size().Y;
const int32 DispatchResX = FMath::DivideAndRoundUp(ResX, PATHTRACER_COHERENT_TILE_SIZE) * PATHTRACER_COHERENT_TILE_SIZE;
const int32 DispatchResY = FMath::DivideAndRoundUp(ResY, PATHTRACER_COHERENT_TILE_SIZE) * PATHTRACER_COHERENT_TILE_SIZE;
int32 DispatchSize = FMath::Max(CVarPathTracingDispatchSize.GetValueOnRenderThread(), 64);
DispatchSize = FMath::DivideAndRoundUp(DispatchSize, PATHTRACER_COHERENT_TILE_SIZE) * PATHTRACER_COHERENT_TILE_SIZE;
// should we use path compaction?
const int CompactionType = CVarPathTracingCompaction.GetValueOnRenderThread();
const bool bUseIndirectDispatch = GRHISupportsRayTracingDispatchIndirect && CVarPathTracingIndirectDispatch.GetValueOnRenderThread() != 0;
FRDGBuffer* ActivePaths[2] = {};
FRDGBuffer* NumActivePaths[2] = {};
FRDGBuffer* PathStateData = nullptr;
if (CompactionType == 1)
{
const int32 NumPaths = FMath::Min(DispatchSize * DispatchSize, DispatchResX * DispatchResY);
ActivePaths[0] = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(int32), NumPaths), TEXT("PathTracer.ActivePaths0"));
ActivePaths[1] = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(int32), NumPaths), TEXT("PathTracer.ActivePaths1"));
if (bUseIndirectDispatch)
{
NumActivePaths[0] = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc<int32>(3), TEXT("PathTracer.NumActivePaths0"));
NumActivePaths[1] = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc<int32>(3), TEXT("PathTracer.NumActivePaths1"));
}
else
{
NumActivePaths[0] = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(int32), 3), TEXT("PathTracer.NumActivePaths"));
}
PathStateData = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(FPathTracingPackedPathState), NumPaths), TEXT("PathTracer.PathStateData"));
}
FPathTracingRG::FPermutationDomain PermutationVector;
PermutationVector.Set<FPathTracingRG::FCompactionType>(CompactionType);
TShaderMapRef<FPathTracingRG> RayGenShader(View.ShaderMap, PermutationVector);
FPathTracingRG::FParameters* PreviousPassParameters = nullptr;
for (int32 TileY = 0; TileY < DispatchResY; TileY += DispatchSize)
{
for (int32 TileX = 0; TileX < DispatchResX; TileX += DispatchSize)
{
const int32 DispatchSizeX = FMath::Min(DispatchSize, DispatchResX - TileX);
const int32 DispatchSizeY = FMath::Min(DispatchSize, DispatchResY - TileY);
if (CompactionType == 1)
{
AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(ActivePaths[0], PF_R32_UINT), 0);
}
// When using path compaction, we need to run the path tracer once per bounce
// otherwise, the path tracer is the one doing the bounces
for (int Bounce = 0, MaxBounces = CompactionType == 1 ? Config.PathTracingData.MaxBounces : 0; Bounce <= MaxBounces; Bounce++)
{
FPathTracingRG::FParameters* PassParameters = GraphBuilder.AllocParameters<FPathTracingRG::FParameters>();
PassParameters->TLAS = View.GetRayTracingSceneViewChecked();
PassParameters->ViewUniformBuffer = View.ViewUniformBuffer;
PassParameters->PathTracingData = Config.PathTracingData;
if (PreviousPassParameters == nullptr)
{
// upload sky/lights data
SetLightParameters(GraphBuilder, PassParameters, Scene, View, Config.UseMISCompensation);
PreviousPassParameters = PassParameters;
}
else
{
// re-use from last iteration
PassParameters->IESTexture = PreviousPassParameters->IESTexture;
PassParameters->IESTextureSampler = PreviousPassParameters->IESTextureSampler;
PassParameters->LightGridParameters = PreviousPassParameters->LightGridParameters;
PassParameters->RectLightTexture = PreviousPassParameters->RectLightTexture;
PassParameters->RectLightSampler = PreviousPassParameters->RectLightSampler;
PassParameters->SceneLightCount = PreviousPassParameters->SceneLightCount;
PassParameters->SceneVisibleLightCount = PreviousPassParameters->SceneVisibleLightCount;
PassParameters->SceneLights = PreviousPassParameters->SceneLights;
PassParameters->SkylightParameters = PreviousPassParameters->SkylightParameters;
}
if (Config.PathTracingData.EnableDirectLighting == 0)
{
PassParameters->SceneVisibleLightCount = 0;
}
PassParameters->IESTextureSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->RadianceTexture = GraphBuilder.CreateUAV(RadianceTexture);
PassParameters->AlbedoTexture = GraphBuilder.CreateUAV(AlbedoTexture);
PassParameters->NormalTexture = GraphBuilder.CreateUAV(NormalTexture);
// TODO: in multi-gpu case, assign different tiles to different GPUs
PassParameters->TileOffset.X = TileX;
PassParameters->TileOffset.Y = TileY;
PassParameters->Bounce = Bounce;
if (CompactionType == 1)
{
PassParameters->ActivePaths = GraphBuilder.CreateSRV(ActivePaths[Bounce & 1], PF_R32_SINT);
PassParameters->NextActivePaths = GraphBuilder.CreateUAV(ActivePaths[(Bounce & 1) ^ 1], PF_R32_SINT);
PassParameters->PathStateData = GraphBuilder.CreateUAV(PathStateData);
if (bUseIndirectDispatch)
{
PassParameters->NumPathStates = GraphBuilder.CreateUAV(NumActivePaths[Bounce & 1], PF_R32_UINT);
PassParameters->PathTracingIndirectArgs = NumActivePaths[(Bounce & 1) ^ 1];
}
else
{
PassParameters->NumPathStates = GraphBuilder.CreateUAV(NumActivePaths[0], PF_R32_UINT);
AddClearUAVPass(GraphBuilder, PassParameters->NextActivePaths, -1); // make sure everything is initialized to -1 since paths that go inactive don't write anything
}
AddClearUAVPass(GraphBuilder, PassParameters->NumPathStates, 0);
}
ClearUnusedGraphResources(RayGenShader, PassParameters);
GraphBuilder.AddPass(
CompactionType == 1
? RDG_EVENT_NAME("Path Tracer Compute (%d x %d) Tile=(%d,%d - %dx%d) Sample=%d/%d NumLights=%d (Bounce=%d%s)", ResX, ResY, TileX, TileY, DispatchSizeX, DispatchSizeY, PathTracingState->SampleIndex, MaxSPP, PassParameters->SceneLightCount, Bounce, bUseIndirectDispatch && Bounce > 0 ? TEXT(" indirect") : TEXT(""))
: RDG_EVENT_NAME("Path Tracer Compute (%d x %d) Tile=(%d,%d - %dx%d) Sample=%d/%d NumLights=%d", ResX, ResY, TileX, TileY, DispatchSizeX, DispatchSizeY, PathTracingState->SampleIndex, MaxSPP, PassParameters->SceneLightCount),
PassParameters,
ERDGPassFlags::Compute,
[PassParameters, RayGenShader, DispatchSizeX, DispatchSizeY, bUseIndirectDispatch, &View](FRHIRayTracingCommandList& RHICmdList)
{
FRHIRayTracingScene* RayTracingSceneRHI = View.GetRayTracingSceneChecked();
FRayTracingShaderBindingsWriter GlobalResources;
SetShaderParameters(GlobalResources, RayGenShader, *PassParameters);
if (bUseIndirectDispatch && PassParameters->Bounce > 0)
{
PassParameters->PathTracingIndirectArgs->MarkResourceAsUsed();
RHICmdList.RayTraceDispatchIndirect(
View.RayTracingMaterialPipeline,
RayGenShader.GetRayTracingShader(),
RayTracingSceneRHI, GlobalResources,
PassParameters->PathTracingIndirectArgs->GetIndirectRHICallBuffer(), 0
);
}
else
{
RHICmdList.RayTraceDispatch(
View.RayTracingMaterialPipeline,
RayGenShader.GetRayTracingShader(),
RayTracingSceneRHI, GlobalResources,
DispatchSizeX, DispatchSizeY
);
}
});
}
}
}
// After we are done, make sure we remember our texture for next time so that we can accumulate samples across frames
GraphBuilder.QueueTextureExtraction(RadianceTexture, &PathTracingState->RadianceRT);
GraphBuilder.QueueTextureExtraction(AlbedoTexture , &PathTracingState->AlbedoRT );
GraphBuilder.QueueTextureExtraction(NormalTexture , &PathTracingState->NormalRT );
// Bump counters for next frame
++PathTracingState->SampleIndex;
++PathTracingState->FrameIndex;
}
FRDGTexture* DenoisedRadianceTexture = nullptr;
int DenoiserMode = CVarPathTracingDenoiser.GetValueOnRenderThread();
if (DenoiserMode < 0)
{
DenoiserMode = View.FinalPostProcessSettings.PathTracingEnableDenoiser;
}
const bool IsDenoiserEnabled = DenoiserMode != 0 && GPathTracingDenoiserFunc != nullptr;
if (IsDenoiserEnabled)
{
// request denoise if this is the last sample
bool NeedsDenoise = (Config.PathTracingData.Iteration + 1) == MaxSPP;
// also allow turning on the denoiser after the image has stopped accumulating samples
if (!bNeedsMoreRays)
{
// we aren't currently rendering, run the denoiser if we just turned it on
NeedsDenoise |= DenoiserMode != PathTracingState->LastConfig.DenoiserMode;
}
if (PathTracingState->RadianceDenoisedRT)
{
// we already have a texture for this
DenoisedRadianceTexture = GraphBuilder.RegisterExternalTexture(PathTracingState->RadianceDenoisedRT, TEXT("PathTracer.DenoisedRadiance"));
}
if (NeedsDenoise)
{
if (DenoisedRadianceTexture == nullptr)
{
// First time through, need to make a new texture
FRDGTextureDesc RadianceTextureDesc = FRDGTextureDesc::Create2D(
View.ViewRect.Size(),
PF_A32B32G32R32F,
FClearValueBinding::None,
TexCreate_ShaderResource | TexCreate_UAV);
DenoisedRadianceTexture = GraphBuilder.CreateTexture(RadianceTextureDesc, TEXT("PathTracer.DenoisedRadiance"), ERDGTextureFlags::MultiFrame);
}
FDenoiseTextureParameters* DenoiseParameters = GraphBuilder.AllocParameters<FDenoiseTextureParameters>();
DenoiseParameters->InputTexture = RadianceTexture;
DenoiseParameters->InputAlbedo = AlbedoTexture;
DenoiseParameters->InputNormal = NormalTexture;
DenoiseParameters->OutputTexture = DenoisedRadianceTexture;
GraphBuilder.AddPass(RDG_EVENT_NAME("Path Tracer Denoiser Plugin"), DenoiseParameters, ERDGPassFlags::Readback,
[DenoiseParameters, DenoiserMode](FRHICommandListImmediate& RHICmdList)
{
GPathTracingDenoiserFunc(RHICmdList,
DenoiseParameters->InputTexture->GetRHI()->GetTexture2D(),
DenoiseParameters->InputAlbedo->GetRHI()->GetTexture2D(),
DenoiseParameters->InputNormal->GetRHI()->GetTexture2D(),
DenoiseParameters->OutputTexture->GetRHI()->GetTexture2D());
}
);
GraphBuilder.QueueTextureExtraction(DenoisedRadianceTexture, &PathTracingState->RadianceDenoisedRT);
}
}
PathTracingState->LastConfig.DenoiserMode = DenoiserMode;
// now add a pixel shader pass to display our Radiance buffer
FPathTracingCompositorPS::FParameters* DisplayParameters = GraphBuilder.AllocParameters<FPathTracingCompositorPS::FParameters>();
DisplayParameters->Iteration = Config.PathTracingData.Iteration;
DisplayParameters->MaxSamples = MaxSPP;
DisplayParameters->ProgressDisplayEnabled = CVarPathTracingProgressDisplay.GetValueOnRenderThread();
DisplayParameters->ViewUniformBuffer = View.ViewUniformBuffer;
DisplayParameters->RadianceTexture = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::Create(DenoisedRadianceTexture ? DenoisedRadianceTexture : RadianceTexture));
DisplayParameters->RenderTargets[0] = FRenderTargetBinding(SceneColorOutputTexture, ERenderTargetLoadAction::ELoad);
FScreenPassTextureViewport Viewport(SceneColorOutputTexture, View.ViewRect);
// wiper mode - reveals the render below the path tracing display
// NOTE: we still path trace the full resolution even while wiping the cursor so that rendering does not get out of sync
if (CVarPathTracingWiperMode.GetValueOnRenderThread() != 0)
{
float DPIScale = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(View.CursorPos.X, View.CursorPos.Y);
Viewport.Rect.Min.X = View.CursorPos.X / DPIScale;
}
TShaderMapRef<FPathTracingCompositorPS> PixelShader(View.ShaderMap);
AddDrawScreenPass(
GraphBuilder,
RDG_EVENT_NAME("Path Tracer Display (%d x %d)", View.ViewRect.Size().X, View.ViewRect.Size().Y),
View,
Viewport,
Viewport,
PixelShader,
DisplayParameters
);
}
#endif