901 lines
30 KiB
HLSL
901 lines
30 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
// Bump to force a rebuild of this shader (when changing related C++ code). Generate unique value on merge conflict.
|
|
#pragma message("UESHADERMETADATA_VERSION 42D4CD2F-D217-4305-81AB-F113F7114047")
|
|
|
|
// Substrate use view's resources to read & trace shadow ray
|
|
#define SUBSTRATE_MATERIALCONTAINER_IS_VIEWRESOURCE 1
|
|
|
|
#include "../Common.ush"
|
|
#include "../MonteCarlo.ush"
|
|
#include "../DeferredShadingCommon.ush"
|
|
#include "../LightShaderParameters.ush"
|
|
#include "../SceneTextureParameters.ush"
|
|
#include "../ScreenSpaceDenoise/SSDPublic.ush"
|
|
#include "../TransmissionCommon.ush"
|
|
#include "../HairStrands/HairStrandsVisibilityCommon.ush"
|
|
#include "../Substrate/Substrate.ush"
|
|
#include "../Substrate/SubstrateSubsurface.ush"
|
|
#include "RayTracingCommon.ush"
|
|
#include "RayTracingDeferredShadingCommon.ush"
|
|
#include "RayTracingDirectionalLight.ush"
|
|
#include "RayTracingRectLight.ush"
|
|
#include "RayTracingSphereLight.ush"
|
|
#include "RayTracingCapsuleLight.ush"
|
|
#include "/Engine/Shared/LightDefinitions.h"
|
|
|
|
#define USE_TRANSLUCENT_SHADOW !COMPUTESHADER // support translucent shadow path if any-hit shading is available
|
|
|
|
#define ShadowMaskType_Opaque 0
|
|
#define ShadowMaskType_Hair 1
|
|
|
|
RaytracingAccelerationStructure TLAS;
|
|
RWTexture2D<float4> RWOcclusionMaskUAV;
|
|
RWTexture2D<float> RWRayDistanceUAV;
|
|
RWTexture2D<float4> RWSubPixelOcclusionMaskUAV;
|
|
|
|
uint LightingChannelMask;
|
|
uint SamplesPerPixel;
|
|
float NormalBias;
|
|
int4 LightScissor;
|
|
int2 PixelOffset;
|
|
float TraceDistance;
|
|
float LODTransitionStart;
|
|
float LODTransitionEnd;
|
|
float AvoidSelfIntersectionTraceDistance;
|
|
uint bTransmissionSamplingDistanceCulling;
|
|
uint TransmissionSamplingTechnique;
|
|
uint TransmissionMeanFreePathType;
|
|
uint RejectionSamplingTrials;
|
|
uint bAcceptFirstHit;
|
|
uint bTwoSidedGeometry;
|
|
uint bTranslucentShadow;
|
|
uint MaxTranslucencyHitCount;
|
|
|
|
#define TransmissionSampling_ConstantTrackingInfiniteDomain 0
|
|
#define TransmissionSampling_ConstantTrackingFiniteDomain 1
|
|
|
|
#define TransmissionMeanFreePathType_ExtinctionScale 0
|
|
#define TransmissionMeanFreePathType_MaxMeanFreePath 1
|
|
|
|
#if USE_HAIR_LIGHTING
|
|
#include "../HairStrands/HairStrandsRaytracing.ush"
|
|
uint bUseHairVoxel;
|
|
float HairOcclusionThreshold;
|
|
Texture2D<uint> HairLightChannelMaskTexture;
|
|
|
|
bool NeedTraceHair(
|
|
in uint2 PixelCoord,
|
|
inout float HairDeviceZ)
|
|
{
|
|
const float HairSampleDepth = HairStrands.HairOnlyDepthTexture.Load(uint3(PixelCoord, 0)).x;
|
|
bool bTraceHairRay = false;
|
|
HairDeviceZ = 0;
|
|
if (HairSampleDepth > 0)
|
|
{
|
|
const uint HairLightChannel = HairLightChannelMaskTexture.Load(uint3(PixelCoord, 0));
|
|
HairDeviceZ = HairSampleDepth;
|
|
bTraceHairRay = (HairLightChannel & LightingChannelMask) != 0;
|
|
}
|
|
|
|
return bTraceHairRay;
|
|
}
|
|
#endif
|
|
|
|
bool GenerateOcclusionRay(
|
|
FLightShaderParameters LightParameters,
|
|
float3 TranslatedWorldPosition,
|
|
float3 WorldNormal,
|
|
float2 RandSample,
|
|
out float3 RayOrigin,
|
|
out float3 RayDirection,
|
|
out float RayTMin,
|
|
out float RayTMax
|
|
)
|
|
{
|
|
#if LIGHT_TYPE == LIGHT_TYPE_DIRECTIONAL
|
|
{
|
|
GenerateDirectionalLightOcclusionRay(
|
|
LightParameters,
|
|
TranslatedWorldPosition, WorldNormal,
|
|
RandSample,
|
|
/* out */ RayOrigin,
|
|
/* out */ RayDirection,
|
|
/* out */ RayTMin,
|
|
/* out */ RayTMax);
|
|
}
|
|
#elif LIGHT_TYPE == LIGHT_TYPE_POINT || LIGHT_TYPE == LIGHT_TYPE_SPOT
|
|
{
|
|
#if LIGHT_TYPE == LIGHT_TYPE_SPOT
|
|
// before generating a shadow ray, make sure we are inside the cone of the light
|
|
float3 LightDirection = normalize(LightParameters.TranslatedWorldPosition - TranslatedWorldPosition);
|
|
float CosAngle = LightParameters.SpotAngles.x;
|
|
if (!(dot(LightDirection, LightParameters.Direction) >= CosAngle))
|
|
{
|
|
// outside the cone of the light, skip all work
|
|
RayOrigin = (float3)0;
|
|
RayDirection = (float3)0;
|
|
RayTMin = 0.0f;
|
|
RayTMax = 0.0f;
|
|
return false;
|
|
}
|
|
#endif
|
|
float RayPdf;
|
|
if (LightParameters.SourceLength > 0.0)
|
|
{
|
|
return GenerateCapsuleLightOcclusionRayWithSolidAngleSampling(
|
|
LightParameters,
|
|
TranslatedWorldPosition, WorldNormal,
|
|
RandSample,
|
|
/* out */ RayOrigin,
|
|
/* out */ RayDirection,
|
|
/* out */ RayTMin,
|
|
/* out */ RayTMax,
|
|
/* out */ RayPdf);
|
|
}
|
|
return GenerateSphereLightOcclusionRayWithSolidAngleSampling(
|
|
LightParameters,
|
|
TranslatedWorldPosition, WorldNormal,
|
|
RandSample,
|
|
/* out */ RayOrigin,
|
|
/* out */ RayDirection,
|
|
/* out */ RayTMin,
|
|
/* out */ RayTMax,
|
|
/* out */ RayPdf);
|
|
}
|
|
#elif LIGHT_TYPE == LIGHT_TYPE_RECT
|
|
{
|
|
float RayPdf = 0.0;
|
|
return GenerateRectLightOcclusionRay(
|
|
LightParameters,
|
|
TranslatedWorldPosition, WorldNormal,
|
|
RandSample,
|
|
/* out */ RayOrigin,
|
|
/* out */ RayDirection,
|
|
/* out */ RayTMin,
|
|
/* out */ RayTMax,
|
|
/* out */ RayPdf);
|
|
}
|
|
#else
|
|
#error Unknown light type.
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
float ComputeDiffuseTransmission(
|
|
float3 V, float3 P, float3 N,
|
|
float MeanFreePath, float MaxTransmissionDistance, float Eta,
|
|
FLightShaderParameters LightParameters, float2 LightSample,
|
|
inout RandomSequence RandSequence,
|
|
uint RejectionRetryMax, uint2 PixelCoord)
|
|
{
|
|
float TransmissionDistance = MaxTransmissionDistance;
|
|
float RcpMeanFreePath = rcp(MeanFreePath);
|
|
|
|
// Refract inward and force a scattering event
|
|
float3 Tr = refract(V, N, Eta);
|
|
if (all(Tr) == 0.0)
|
|
{
|
|
return TransmissionDistance;
|
|
}
|
|
|
|
// Find bounding sub-surface ray extent
|
|
const uint RayFlags = 0;
|
|
const uint InstanceInclusionMask = RAY_TRACING_MASK_OPAQUE;
|
|
|
|
float TMax = TransmissionDistance;
|
|
if (bTransmissionSamplingDistanceCulling)
|
|
{
|
|
FRayDesc RejectionTestRay;
|
|
RejectionTestRay.Origin = P;
|
|
RejectionTestRay.Direction = Tr;
|
|
RejectionTestRay.TMin = 0.01;
|
|
RejectionTestRay.TMax = TMax;
|
|
|
|
FMinimalPayload RejectionTestPayload = TraceVisibilityRay(
|
|
TLAS,
|
|
RayFlags,
|
|
InstanceInclusionMask,
|
|
RejectionTestRay);
|
|
TMax = (RejectionTestPayload.IsHit() ? RejectionTestPayload.HitT : RejectionTestRay.TMax);
|
|
|
|
// Assume the back facing is parallel to the front face.
|
|
// If the first ray marching into the volume does not hit anything, we should still
|
|
// resume for the scattering such that it has a chance to fly out of the volume
|
|
// based on the incident angle and the angle to light
|
|
/*if (RejectionTestPayload.IsMiss())
|
|
{
|
|
return TransmissionDistance;
|
|
}*/
|
|
}
|
|
|
|
float RandSample = RandomSequence_GenerateSample1D(RandSequence);
|
|
|
|
float ScatterDistance = TransmissionDistance;
|
|
float ScatterDistancePdf = 0.0;
|
|
if (TransmissionSamplingTechnique == TransmissionSampling_ConstantTrackingFiniteDomain)
|
|
{
|
|
// Use constant tracking through homogeneous media, but renormalize to force a scatter event within the interval: [0, TMax]
|
|
float NormalizationConstant = 1.0 - exp(-RcpMeanFreePath * TMax);
|
|
ScatterDistance = -log(1.0 - RandSample * NormalizationConstant) * MeanFreePath;
|
|
ScatterDistancePdf = (TMax * RcpMeanFreePath) * exp(-RcpMeanFreePath * ScatterDistance) * rcp(NormalizationConstant);
|
|
}
|
|
else // TransmissionSamplingTechnique = TransmissionSampling_ConstantTrackingInfiniteDomain
|
|
{
|
|
// Use constant tracking through homogeneous media, with rejection sampling to force a scatter event
|
|
ScatterDistance = -log(RandSample) * MeanFreePath;
|
|
ScatterDistancePdf = RcpMeanFreePath * exp(-ScatterDistance * RcpMeanFreePath);
|
|
|
|
// Reject scatter distances which penetrate through the medium
|
|
uint RetryCount = 0;
|
|
while (ScatterDistance > TMax && RetryCount < RejectionSamplingTrials)
|
|
{
|
|
RandSample = RandomSequence_GenerateSample1D(RandSequence);
|
|
ScatterDistance = -log(RandSample) * MeanFreePath;
|
|
ScatterDistancePdf = RcpMeanFreePath * exp(-ScatterDistance * RcpMeanFreePath);
|
|
RetryCount++;
|
|
}
|
|
if (ScatterDistance > TMax) return 0.0;
|
|
}
|
|
|
|
// After sampling by pdf, the first interaction could go out of the range
|
|
// resulting in no transmission. Need to reconsider adding it in by commenting this out?
|
|
ScatterDistance /= ScatterDistancePdf;
|
|
|
|
// Avoid self intersection.
|
|
ScatterDistance = max(0.01, ScatterDistance);
|
|
|
|
// Build sub-surface scattering ray
|
|
FRayDesc SSSRay;
|
|
SSSRay.Origin = P + Tr * ScatterDistance;
|
|
bool bIsValidRay = GenerateOcclusionRay(LightParameters, SSSRay.Origin, N, LightSample, SSSRay.Origin, SSSRay.Direction, SSSRay.TMin, SSSRay.TMax);
|
|
|
|
if (!bIsValidRay)
|
|
{
|
|
return TransmissionDistance;
|
|
}
|
|
|
|
// Clip the ray length by the maximum transmission distance
|
|
float LightTMax = SSSRay.TMax;
|
|
SSSRay.TMax = TransmissionDistance - ScatterDistance;
|
|
|
|
FMinimalPayload SSSPayload = TraceVisibilityRay(
|
|
TLAS,
|
|
RayFlags,
|
|
InstanceInclusionMask,
|
|
SSSRay);
|
|
|
|
if (SSSPayload.IsHit())
|
|
{
|
|
// Confirm that there is not an actual occluder beyond the maximum transmission distance
|
|
SSSRay.TMin = SSSPayload.HitT + 0.01;
|
|
SSSRay.TMax = LightTMax;
|
|
FMinimalPayload VisibilityPayload = TraceVisibilityRay(
|
|
TLAS,
|
|
RayFlags,
|
|
InstanceInclusionMask,
|
|
SSSRay);
|
|
if (VisibilityPayload.IsMiss())
|
|
{
|
|
TransmissionDistance = ScatterDistance + SSSPayload.HitT;
|
|
}
|
|
}
|
|
|
|
return TransmissionDistance;
|
|
}
|
|
|
|
struct FOcclusionResult
|
|
{
|
|
float Visibility;
|
|
float SumRayDistance;
|
|
float ClosestRayDistance;
|
|
float TransmissionDistance;
|
|
float HitCount;
|
|
float RayCount;
|
|
};
|
|
|
|
FOcclusionResult InitOcclusionResult()
|
|
{
|
|
FOcclusionResult Out;
|
|
|
|
Out.Visibility = 0.0;
|
|
Out.SumRayDistance = 0.0;
|
|
Out.ClosestRayDistance = DENOISER_INVALID_HIT_DISTANCE;
|
|
Out.TransmissionDistance = 0.0;
|
|
Out.HitCount = 0.0;
|
|
Out.RayCount = 0.0;
|
|
|
|
return Out;
|
|
}
|
|
|
|
float OcclusionToShadow(FOcclusionResult In, uint LocalSamplesPerPixel)
|
|
{
|
|
return (LocalSamplesPerPixel > 0) ? In.Visibility / LocalSamplesPerPixel : In.Visibility;
|
|
}
|
|
|
|
FRayDesc Invert(in FRayDesc Ray)
|
|
{
|
|
FRayDesc InvertedRay;
|
|
InvertedRay.TMin = Ray.TMin;
|
|
InvertedRay.TMax = Ray.TMax;
|
|
#if LIGHT_TYPE == LIGHT_TYPE_DIRECTIONAL
|
|
InvertedRay.TMax = min(InvertedRay.TMax, TraceDistance);
|
|
#endif
|
|
InvertedRay.Origin = Ray.Origin + Ray.Direction * InvertedRay.TMax;
|
|
InvertedRay.Direction = -Ray.Direction;
|
|
|
|
return InvertedRay;
|
|
}
|
|
|
|
struct FOcclusionShadingParameters
|
|
{
|
|
bool bIsValid;
|
|
bool bBackfaceCulling;
|
|
bool bIsHair;
|
|
bool bIsHairStrands;
|
|
bool bIsSubsurface;
|
|
bool bHasSubsurfaceProfile;
|
|
};
|
|
|
|
FOcclusionResult ComputeOcclusion(
|
|
const uint2 PixelCoord,
|
|
const uint RaytracingMask,
|
|
const float DeviceZ,
|
|
float3 WorldNormal,
|
|
const FOcclusionShadingParameters ShadingDesc,
|
|
const FLightShaderParameters LightParameters,
|
|
const FTransmissionProfileParams TransmissionProfileParams,
|
|
const float SubsurfaceSamplingMeanFreePath,
|
|
const uint LocalSamplesPerPixel)
|
|
{
|
|
FOcclusionResult Out = InitOcclusionResult();
|
|
|
|
const float3 TranslatedWorldPosition = ReconstructTranslatedWorldPositionFromDeviceZ(PixelCoord, DeviceZ);
|
|
|
|
// For hair shading model, WorldNormal is actually the shading tangent.
|
|
// To generate proper bias, we compute a normal oriented toward the light.
|
|
// Normal clipping is removed from hair since the BSDF is spherical, rather
|
|
// than hemispherical
|
|
//
|
|
// Note:
|
|
// Since we don't have a notion of backfacing here, there is no correct way
|
|
// to avoid self-intersection with hair. Either we push towards the light,
|
|
// (this creates some issue in triangle facing back the light), or we push
|
|
// toward the view point (this create issue for transmitted light)
|
|
// It seems having proper transmission is more important, thus the light
|
|
// version is enabled by default
|
|
if (ShadingDesc.bIsHair || ShadingDesc.bIsHairStrands)
|
|
{
|
|
#if LIGHT_TYPE == LIGHT_TYPE_DIRECTIONAL
|
|
const float3 LightDirection = LightParameters.Direction;
|
|
#else
|
|
const float3 LightDirection = normalize(LightParameters.TranslatedWorldPosition - TranslatedWorldPosition);
|
|
#endif
|
|
WorldNormal = LightDirection;
|
|
}
|
|
|
|
bool bApplyNormalCulling = ShadingDesc.bBackfaceCulling;
|
|
|
|
// Disable normal culling for RectLights
|
|
if (LIGHT_TYPE == LIGHT_TYPE_RECT)
|
|
{
|
|
bApplyNormalCulling = false;
|
|
}
|
|
|
|
uint TimeSeed = View.StateFrameIndex;
|
|
|
|
#if ENABLE_MULTIPLE_SAMPLES_PER_PIXEL
|
|
LOOP for (uint SampleIndex = 0; SampleIndex < LocalSamplesPerPixel; ++SampleIndex)
|
|
#else // ENABLE_MULTIPLE_SAMPLES_PER_PIXEL
|
|
do if (LocalSamplesPerPixel > 0)
|
|
#endif // ENABLE_MULTIPLE_SAMPLES_PER_PIXEL
|
|
{
|
|
RandomSequence RandSequence;
|
|
|
|
#if ENABLE_MULTIPLE_SAMPLES_PER_PIXEL
|
|
RandomSequence_Initialize(RandSequence, PixelCoord, SampleIndex, TimeSeed, LocalSamplesPerPixel);
|
|
#else
|
|
RandomSequence_Initialize(RandSequence, PixelCoord, 0, TimeSeed, 1);
|
|
#endif
|
|
|
|
float2 RandSample = RandomSequence_GenerateSample2D(RandSequence);
|
|
|
|
FRayDesc Ray = (FRayDesc)0;
|
|
bool bIsValidRay = GenerateOcclusionRay(
|
|
LightParameters,
|
|
TranslatedWorldPosition, WorldNormal,
|
|
RandSample,
|
|
/* out */ Ray.Origin,
|
|
/* out */ Ray.Direction,
|
|
/* out */ Ray.TMin,
|
|
/* out */ Ray.TMax);
|
|
|
|
|
|
uint Stencil = SceneStencilTexture.Load(int3(PixelCoord, 0)) STENCIL_COMPONENT_SWIZZLE;
|
|
bool bDitheredLODFadingOut = Stencil & 1;
|
|
|
|
#if ENABLE_TRANSMISSION
|
|
float NoL = dot(WorldNormal, Ray.Direction);
|
|
if (NoL > 0.0)
|
|
{
|
|
ApplyCameraRelativeDepthBias(Ray, PixelCoord, DeviceZ, WorldNormal, NormalBias + (bDitheredLODFadingOut ? 0.8f : 0.0f));
|
|
}
|
|
else
|
|
{
|
|
ApplyPositionBias(Ray, -WorldNormal, NormalBias);
|
|
}
|
|
#else
|
|
ApplyCameraRelativeDepthBias(Ray, PixelCoord, DeviceZ, WorldNormal, NormalBias + (bDitheredLODFadingOut ? 0.8f : 0.0f));
|
|
#endif
|
|
|
|
BRANCH
|
|
if (!bIsValidRay && (DIM_DENOISER_OUTPUT == 0))
|
|
{
|
|
// The denoiser must still trace invalid rays to get the correct closest-hit distance
|
|
continue;
|
|
}
|
|
else if (bApplyNormalCulling && dot(WorldNormal, Ray.Direction) <= 0.0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Attenuation check
|
|
if (Ray.TMax * LightParameters.InvRadius > 1.0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
uint RayFlags = 0;
|
|
|
|
// Enable back face culling if not used two-sided shadow casting mode.
|
|
if (bTwoSidedGeometry != 1)
|
|
{
|
|
RayFlags |= RAY_FLAG_CULL_BACK_FACING_TRIANGLES;
|
|
}
|
|
|
|
#if USE_HAIR_LIGHTING
|
|
if (ShadingDesc.bIsHairStrands)
|
|
{
|
|
// Denoiser mode 0 doesn't use depth, so take first hit
|
|
RayFlags |= RAY_FLAG_SKIP_CLOSEST_HIT_SHADER | RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH;
|
|
}
|
|
#endif
|
|
|
|
uint RayFlagsForOpaque = bAcceptFirstHit != 0 ? RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH : 0;
|
|
|
|
FMinimalPayload MinimalPayload;
|
|
|
|
#if AVOID_SELF_INTERSECTION_TRACE
|
|
{
|
|
FRayDesc EpsilonRay = Ray;
|
|
EpsilonRay.TMax = AvoidSelfIntersectionTraceDistance;
|
|
|
|
if (Ray.TMax > Ray.TMin)
|
|
{
|
|
MinimalPayload = TraceVisibilityRay(
|
|
TLAS,
|
|
RayFlags | RayFlagsForOpaque | RAY_FLAG_CULL_BACK_FACING_TRIANGLES,
|
|
RaytracingMask,
|
|
EpsilonRay);
|
|
}
|
|
}
|
|
|
|
if (!MinimalPayload.IsHit())
|
|
{
|
|
Ray.TMin = max(Ray.TMin, AvoidSelfIntersectionTraceDistance);
|
|
|
|
MinimalPayload = TraceVisibilityRay(
|
|
TLAS,
|
|
RayFlags | RayFlagsForOpaque,
|
|
RaytracingMask,
|
|
Ray);
|
|
}
|
|
#else
|
|
MinimalPayload = TraceVisibilityRay(
|
|
TLAS,
|
|
RayFlags | RayFlagsForOpaque,
|
|
RaytracingMask,
|
|
Ray);
|
|
#endif
|
|
|
|
#if USE_HAIR_LIGHTING
|
|
if (!ShadingDesc.bIsHairStrands && bUseHairVoxel)
|
|
{
|
|
MinimalPayload.HitT = TraverseHair(PixelCoord, RandSequence, Ray.Origin, Ray.Direction, MinimalPayload.HitT, VirtualVoxel.Raytracing_ShadowOcclusionThreshold);
|
|
}
|
|
#endif
|
|
|
|
Out.RayCount += 1.0;
|
|
if (MinimalPayload.IsHit())
|
|
{
|
|
float HitT = MinimalPayload.HitT;
|
|
|
|
Out.ClosestRayDistance =
|
|
(Out.ClosestRayDistance == DENOISER_INVALID_HIT_DISTANCE) ||
|
|
(HitT < Out.ClosestRayDistance) ? HitT : Out.ClosestRayDistance;
|
|
Out.SumRayDistance += HitT;
|
|
Out.HitCount += 1.0;
|
|
|
|
if (ShadingDesc.bIsSubsurface || ShadingDesc.bIsHair)
|
|
{
|
|
// Reverse the ray to support sub-surface casts
|
|
FRayDesc InvertedRay = Invert(Ray);
|
|
FMinimalPayload SubsurfacePayload = TraceVisibilityRay(
|
|
TLAS,
|
|
RayFlags,
|
|
RaytracingMask,
|
|
InvertedRay);
|
|
|
|
if (SubsurfacePayload.IsHit())
|
|
{
|
|
|
|
float3 HitPosition = InvertedRay.Origin + InvertedRay.Direction * SubsurfacePayload.HitT;
|
|
float Opacity = TransmissionProfileParams.ExtinctionScale;
|
|
float Density = -.05f * log(1 - min(Opacity, .999f));
|
|
if (ShadingDesc.bIsHair)
|
|
{
|
|
Opacity = 1;
|
|
Density = 1;
|
|
}
|
|
float ExtinctionCoefficient = Density;
|
|
float Thickness = length(HitPosition - TranslatedWorldPosition);
|
|
float Transmission = saturate(exp(-ExtinctionCoefficient * Thickness));
|
|
|
|
if (Transmission == 1.0)
|
|
{
|
|
Out.ClosestRayDistance = (Out.ClosestRayDistance == DENOISER_INVALID_HIT_DISTANCE) ? DENOISER_MISS_HIT_DISTANCE : Out.ClosestRayDistance;
|
|
Out.SumRayDistance -= HitT;
|
|
Out.HitCount -= 1.0;
|
|
Out.TransmissionDistance += 1.0;
|
|
Out.Visibility += 1.0;
|
|
}
|
|
|
|
Out.TransmissionDistance += Transmission;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if USE_TRANSLUCENT_SHADOW
|
|
bool bHasClosestEffectiveTranslucentHit = false;
|
|
|
|
if (bTranslucentShadow)
|
|
{
|
|
// When there is no intersection, test for translucent material hit if translucent shadow
|
|
// is supported. If there is a hit, record the accumulated opacity.
|
|
FTranslucentMinimalPayload TranslucentMinimalPayload = TraceTranslucentVisibilityRay(
|
|
TLAS,
|
|
RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_SKIP_CLOSEST_HIT_SHADER,
|
|
RAY_TRACING_MASK_TRANSLUCENT_SHADOW,
|
|
Ray);
|
|
Out.ClosestRayDistance = (Out.ClosestRayDistance == DENOISER_INVALID_HIT_DISTANCE) ? DENOISER_MISS_HIT_DISTANCE : Out.ClosestRayDistance;
|
|
Out.TransmissionDistance += 1.0;
|
|
Out.Visibility += Luminance(TranslucentMinimalPayload.ShadowVisibility);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
Out.ClosestRayDistance = (Out.ClosestRayDistance == DENOISER_INVALID_HIT_DISTANCE) ? DENOISER_MISS_HIT_DISTANCE : Out.ClosestRayDistance;
|
|
Out.TransmissionDistance += 1.0;
|
|
Out.Visibility += 1.0;
|
|
}
|
|
}
|
|
}
|
|
#if !ENABLE_MULTIPLE_SAMPLES_PER_PIXEL
|
|
while (0);
|
|
#endif // ENABLE_MULTIPLE_SAMPLES_PER_PIXEL
|
|
|
|
if (ENABLE_TRANSMISSION && LocalSamplesPerPixel > 0 && ShadingDesc.bHasSubsurfaceProfile)
|
|
{
|
|
RandomSequence RandSequence;
|
|
RandomSequence_Initialize(RandSequence, PixelCoord, 0, TimeSeed, 1);
|
|
|
|
// Fix light sampling to occur at the light center for all transmission computations..
|
|
float2 LightSample = 0.5;
|
|
const float3 TranslatedWorldViewOrigin = DFFastToTranslatedWorld(PrimaryView.WorldViewOrigin, PrimaryView.PreViewTranslation);
|
|
const float3 V = normalize(TranslatedWorldPosition - TranslatedWorldViewOrigin);
|
|
|
|
// Apply sqrt to match the behavior of the rasterizer closer visually when ExtinctionScale <=1.
|
|
const float ExtinctionScale = lerp(sqrt(TransmissionProfileParams.ExtinctionScale),
|
|
TransmissionProfileParams.ExtinctionScale, TransmissionProfileParams.ExtinctionScale > 1);
|
|
|
|
// Decrease ExtinctionScale should increase the max transmission distance. Keep rasterizer and raytracing shadow behave similar for transmission.
|
|
const float MaxTransmissionDistance = SSSS_MAX_TRANSMISSION_PROFILE_DISTANCE / ExtinctionScale;
|
|
const float Eta = TransmissionProfileParams.OneOverIOR;
|
|
const uint RejectionRetryMax = LocalSamplesPerPixel - 1;
|
|
Out.TransmissionDistance = ComputeDiffuseTransmission(V, TranslatedWorldPosition, WorldNormal, SubsurfaceSamplingMeanFreePath,
|
|
MaxTransmissionDistance, Eta, LightParameters, LightSample, RandSequence, RejectionRetryMax, PixelCoord);
|
|
|
|
// Overload closest-hit distance to express occlusion distance when facing the light or transmission distance when back-facing
|
|
if (Out.TransmissionDistance < MaxTransmissionDistance)
|
|
{
|
|
Out.ClosestRayDistance = max(Out.ClosestRayDistance, Out.TransmissionDistance);
|
|
}
|
|
|
|
// Respond to the normal scale
|
|
const float NormalScale = TransmissionProfileParams.NormalScale * 0.5;
|
|
Out.TransmissionDistance += NormalScale;
|
|
|
|
// Conversion to raster model: 1.0 - (distance / SSSS_MAX_TRANSMISSION_PROFILE_DISTANCE).
|
|
Out.TransmissionDistance = saturate(EncodeThickness(Out.TransmissionDistance / MaxTransmissionDistance));
|
|
}
|
|
else if (ShadingDesc.bIsSubsurface || ShadingDesc.bIsHair)
|
|
{
|
|
float Depth = GetDistanceToCameraFromViewVector(TranslatedWorldPosition - View.TranslatedWorldCameraOrigin);
|
|
float Range = LODTransitionEnd - LODTransitionStart;
|
|
if (Depth > LODTransitionStart && Range > 0.0)
|
|
{
|
|
float Alpha = saturate((Depth - LODTransitionStart) / Range);
|
|
Out.Visibility = lerp(Out.Visibility, Out.TransmissionDistance, Alpha);
|
|
}
|
|
Out.TransmissionDistance = (LocalSamplesPerPixel > 0) ? Out.TransmissionDistance / LocalSamplesPerPixel : Out.TransmissionDistance;
|
|
}
|
|
else // Eye/Foliage (SHADINGMODELID_EYE, SHADINGMODELID_TWOSIDED_FOLIAGE)
|
|
{
|
|
Out.TransmissionDistance = (LocalSamplesPerPixel > 0) ? Out.Visibility / LocalSamplesPerPixel : Out.Visibility;
|
|
}
|
|
return Out;
|
|
}
|
|
|
|
#if SUBTRATE_GBUFFER_FORMAT==0
|
|
float DecodeSamplingSSSProfileRadius(FGBufferData GBufferData)
|
|
{
|
|
uint SubsurfaceProfileInt = ExtractSubsurfaceProfileInt(GBufferData);
|
|
|
|
// Burley Sampling parameterization
|
|
float3 SurfaceAlbedo = View.SSProfilesTexture.Load(int3(BSSS_SURFACEALBEDO_OFFSET, SubsurfaceProfileInt, 0)).www;
|
|
float3 DiffuseMeanFreePath = DecodeDiffuseMeanFreePath(View.SSProfilesTexture.Load(int3(BSSS_DMFP_OFFSET, SubsurfaceProfileInt, 0))).www;
|
|
float WorldUnitScale = DecodeWorldUnitScale(View.SSProfilesTexture.Load(int3(SSSS_TINT_SCALE_OFFSET, SubsurfaceProfileInt, 0)).a) * BURLEY_CM_2_MM;
|
|
float Opacity = UseSubsurfaceProfile(GBufferData.ShadingModelID) ? GBufferData.CustomData.a : 0.0f;
|
|
|
|
float SSSRadius = GetMFPFromDMFPApprox(SurfaceAlbedo, SurfaceAlbedo, Opacity * WorldUnitScale * DiffuseMeanFreePath).z;
|
|
|
|
return SSSRadius * BURLEY_MM_2_CM;
|
|
}
|
|
#endif
|
|
|
|
RAY_TRACING_ENTRY_RAYGEN_OR_INLINE(Occlusion)
|
|
{
|
|
uint2 PixelCoord = DispatchThreadIndex.xy + View.ViewRectMin.xy + PixelOffset;
|
|
|
|
FOcclusionResult Occlusion = InitOcclusionResult();
|
|
FOcclusionResult HairOcclusion = InitOcclusionResult();
|
|
|
|
const uint RequestedSamplePerPixel = ENABLE_MULTIPLE_SAMPLES_PER_PIXEL ? SamplesPerPixel : 1;
|
|
uint LocalSamplesPerPixel = RequestedSamplePerPixel;
|
|
|
|
if (all(PixelCoord >= LightScissor.xy) && all(PixelCoord <= LightScissor.zw))
|
|
{
|
|
FLightShaderParameters LightParameters = GetRootLightShaderParameters();
|
|
|
|
// Read material data
|
|
float3 WorldNormal = 0;
|
|
FOcclusionShadingParameters ShadingParameters = (FOcclusionShadingParameters)0;
|
|
FTransmissionProfileParams TransmissionParameters = (FTransmissionProfileParams)0;
|
|
float SubsurfaceSamplingMeanFreePath = 0;
|
|
bool bIsFirstPerson = false;
|
|
#if SUBTRATE_GBUFFER_FORMAT==1
|
|
{
|
|
FSubstrateAddressing SubstrateAddressing = GetSubstratePixelDataByteOffset(PixelCoord, uint2(View.BufferSizeAndInvSize.xy), Substrate.MaxBytesPerPixel);
|
|
const FSubstratePixelHeader SubstratePixelHeader = UnpackSubstrateHeaderIn(Substrate.MaterialTextureArray, SubstrateAddressing, Substrate.TopLayerTexture);
|
|
const FSubstrateTopLayerData TopLayerData = SubstrateUnpackTopLayerData(Substrate.TopLayerTexture.Load(uint3(PixelCoord, 0)));
|
|
|
|
const FSubstrateSubsurfaceHeader SSSHeader = SubstrateLoadSubsurfaceHeader(Substrate.MaterialTextureArray, Substrate.FirstSliceStoringSubstrateSSSData, PixelCoord);
|
|
|
|
const uint SSSType = SSSHEADER_TYPE(SSSHeader);
|
|
const bool bHasSSS = SubstratePixelHeader.HasSubsurface();
|
|
const uint BSDFType = SubstratePixelHeader.SubstrateGetBSDFType();
|
|
|
|
ShadingParameters.bIsValid = SubstratePixelHeader.IsSubstrateMaterial();
|
|
ShadingParameters.bIsHair = BSDFType == SUBSTRATE_BSDF_TYPE_HAIR;
|
|
ShadingParameters.bIsSubsurface = bHasSSS ? SSSType == SSS_TYPE_WRAP || SSSType == SSS_TYPE_TWO_SIDED_WRAP : false;
|
|
ShadingParameters.bHasSubsurfaceProfile = bHasSSS ? (SSSType == SSS_TYPE_DIFFUSION_PROFILE || SSSType == SSS_TYPE_DIFFUSION) : false;
|
|
ShadingParameters.bBackfaceCulling = !(BSDFType == SUBSTRATE_BSDF_TYPE_EYE || (bHasSSS && (SSSType == SSS_TYPE_WRAP || SSSType == SSS_TYPE_TWO_SIDED_WRAP)));
|
|
|
|
// The SSDenoiser reads the top layer normal in order to know how to process the data.
|
|
// RTShadow traces using the top layer normal when NoL>0 only. So if normals for slabs below do not match, light leaking will occur.
|
|
// For instance: a flat translucent top layer above a normal mapped bottom layer.
|
|
// That is why, for materials with multiple local bases, we must not do backface culling.
|
|
if (SubstratePixelHeader.GetLocalBasesCount() > 1)
|
|
{
|
|
// Technically, this could only be done when vertical layering is encountered only.
|
|
// But we do not have that data available (or it would be costly to infer from closures)
|
|
ShadingParameters.bBackfaceCulling = false;
|
|
}
|
|
|
|
WorldNormal = TopLayerData.WorldNormal;
|
|
bIsFirstPerson = SubstratePixelHeader.IsFirstPerson();
|
|
|
|
|
|
if (ENABLE_TRANSMISSION)
|
|
{
|
|
if (SSSType == SSS_TYPE_DIFFUSION_PROFILE)
|
|
{
|
|
TransmissionParameters = GetTransmissionProfileParams(SubstrateSubSurfaceHeaderGetProfileId(SSSHeader));
|
|
}
|
|
else if (SSSType == SSS_TYPE_DIFFUSION)
|
|
{
|
|
FSubsurfaceOpacityMFP SubsurfaceOpacityMFP = SubstrateGetSubsurfaceOpacityMFP(SSSHeader);
|
|
|
|
// We know that or SSS_TYPE_DIFFUSION we have access to the mean free path.
|
|
const float MFP = SubsurfaceOpacityMFP.Data;
|
|
const float Extinction = SubstrateShadowMFPToExtinction(MFP);
|
|
|
|
TransmissionParameters = InitTransmissionProfileParams();
|
|
TransmissionParameters.ExtinctionScale = Extinction;
|
|
|
|
SubsurfaceSamplingMeanFreePath = 1.0;
|
|
BRANCH
|
|
if (TransmissionMeanFreePathType == TransmissionMeanFreePathType_MaxMeanFreePath)
|
|
{
|
|
SubsurfaceSamplingMeanFreePath = MFP;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ShadingParameters.bIsSubsurface) // Legacy Subsurface shading model == Substrate Wrap SSS.
|
|
{
|
|
FSubsurfaceOpacityMFP SubsurfaceOpacityMFP = SubstrateGetSubsurfaceOpacityMFP(SSSHeader);
|
|
if (SubsurfaceOpacityMFP.bDataIsOpacity)
|
|
{
|
|
TransmissionParameters.ExtinctionScale = SubsurfaceOpacityMFP.Data;
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
FGBufferData GBufferData = GetGBufferDataFromSceneTexturesLoad(PixelCoord);
|
|
ShadingParameters.bIsValid = GBufferData.ShadingModelID != SHADINGMODELID_UNLIT;
|
|
ShadingParameters.bIsHair = GBufferData.ShadingModelID == SHADINGMODELID_HAIR;
|
|
ShadingParameters.bIsSubsurface = GBufferData.ShadingModelID == SHADINGMODELID_SUBSURFACE;
|
|
ShadingParameters.bHasSubsurfaceProfile = GBufferData.ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE;
|
|
// eye, two sided foliage and subsurface do not need backface culling.
|
|
ShadingParameters.bBackfaceCulling =
|
|
!(GBufferData.ShadingModelID == SHADINGMODELID_EYE ||
|
|
GBufferData.ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE ||
|
|
GBufferData.ShadingModelID == SHADINGMODELID_SUBSURFACE);
|
|
|
|
WorldNormal = GBufferData.WorldNormal;
|
|
bIsFirstPerson = IsFirstPerson(GBufferData);
|
|
|
|
if (ENABLE_TRANSMISSION)
|
|
{
|
|
TransmissionParameters = GetTransmissionProfileParams(ExtractSubsurfaceProfileInt(GBufferData));
|
|
|
|
// If MFP is available, use the actual mean free path, otherwise, fallback to the old behavior using extinction scale in the subsurface profile.
|
|
SubsurfaceSamplingMeanFreePath = TransmissionParameters.ExtinctionScale;
|
|
|
|
BRANCH
|
|
if (TransmissionMeanFreePathType == TransmissionMeanFreePathType_MaxMeanFreePath)
|
|
{
|
|
SubsurfaceSamplingMeanFreePath = DecodeSamplingSSSProfileRadius(GBufferData);
|
|
}
|
|
}
|
|
|
|
if (ShadingParameters.bIsSubsurface)
|
|
{
|
|
TransmissionParameters.ExtinctionScale = GBufferData.CustomData.a;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Mask out depth values that are infinitely far away
|
|
float DeviceZ = SceneDepthTexture.Load(int3(PixelCoord, 0)).r;
|
|
const bool bIsDepthValid = SceneDepthTexture.Load(int3(PixelCoord, 0)).r > 0.0;
|
|
const bool bIsValidPixel = ShadingParameters.bIsValid && bIsDepthValid;
|
|
const uint LightChannel = GetSceneLightingChannel(PixelCoord);
|
|
const bool bTraceRay = bIsValidPixel && (LightChannel & LightingChannelMask) != 0;
|
|
if (!bTraceRay)
|
|
{
|
|
LocalSamplesPerPixel = 0;
|
|
}
|
|
|
|
uint RaytracingMask = RAY_TRACING_MASK_SHADOW | RAY_TRACING_MASK_THIN_SHADOW;
|
|
|
|
// The first person GBuffer bit is not available when static lighting is enabled, so we skip intersections with the first person world space representation to avoid artifacts in that case.
|
|
#if HAS_FIRST_PERSON_GBUFFER_BIT
|
|
// When tracing a ray for a first person pixel, we do not want to intersect the world space representation of that first person primitive.
|
|
// Doing so would lead to "self"-intersection artifacts between the two representations of that mesh.
|
|
if (!bIsFirstPerson)
|
|
{
|
|
RaytracingMask |= RAY_TRACING_MASK_OPAQUE_FP_WORLD_SPACE;
|
|
}
|
|
#endif
|
|
|
|
Occlusion = ComputeOcclusion(
|
|
PixelCoord,
|
|
RaytracingMask,
|
|
DeviceZ,
|
|
WorldNormal,
|
|
ShadingParameters,
|
|
LightParameters,
|
|
TransmissionParameters,
|
|
SubsurfaceSamplingMeanFreePath,
|
|
LocalSamplesPerPixel);
|
|
|
|
#if USE_HAIR_LIGHTING
|
|
float HairDeviceZ = 0;
|
|
if (NeedTraceHair(PixelCoord, HairDeviceZ))
|
|
{
|
|
FTransmissionProfileParams HairTransmissionParameters = (FTransmissionProfileParams)0;
|
|
FOcclusionShadingParameters HairOcclusionShadingParameters = (FOcclusionShadingParameters)0;
|
|
HairOcclusionShadingParameters.bIsValid = true;
|
|
HairOcclusionShadingParameters.bIsHairStrands = true;
|
|
|
|
HairOcclusion = ComputeOcclusion(
|
|
PixelCoord,
|
|
RAY_TRACING_MASK_SHADOW,
|
|
HairDeviceZ,
|
|
float3(1,0,0),
|
|
HairOcclusionShadingParameters,
|
|
LightParameters,
|
|
HairTransmissionParameters,
|
|
0.f/*SubsurfaceSamplingMeanFreePath*/,
|
|
RequestedSamplePerPixel);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Opaque shadow on ray does not need any denoising due to the high frequency nature of hair.
|
|
#if USE_HAIR_LIGHTING
|
|
{
|
|
// the channel assignment is documented in ShadowRendering.cpp (look for Light Attenuation channel assignment)
|
|
const float HairShadow = OcclusionToShadow(HairOcclusion, RequestedSamplePerPixel);
|
|
const float FadedShadow = HairShadow;
|
|
const float4 OutColor = EncodeLightAttenuation(FadedShadow.xxxx);
|
|
RWSubPixelOcclusionMaskUAV[PixelCoord] = OutColor;
|
|
}
|
|
#endif
|
|
|
|
const float Shadow = OcclusionToShadow(Occlusion, LocalSamplesPerPixel);
|
|
|
|
if (DIM_DENOISER_OUTPUT == 2)
|
|
{
|
|
RWOcclusionMaskUAV[PixelCoord] = float4(
|
|
Shadow,
|
|
Occlusion.ClosestRayDistance,
|
|
0,
|
|
Occlusion.TransmissionDistance);
|
|
|
|
}
|
|
else if (DIM_DENOISER_OUTPUT == 1)
|
|
{
|
|
float AvgHitDistance = -1.0;
|
|
if (Occlusion.HitCount > 0.0)
|
|
{
|
|
AvgHitDistance = Occlusion.SumRayDistance / Occlusion.HitCount;
|
|
}
|
|
else if (Occlusion.RayCount > 0.0)
|
|
{
|
|
AvgHitDistance = 1.0e27;
|
|
}
|
|
|
|
// TODO(Denoiser): the denoiser would much prefer a single RG texture.
|
|
RWOcclusionMaskUAV[PixelCoord] = float4(Shadow, Occlusion.TransmissionDistance, Shadow, Occlusion.TransmissionDistance);
|
|
RWRayDistanceUAV[PixelCoord] = AvgHitDistance;
|
|
}
|
|
else
|
|
{
|
|
const float ShadowFadeFraction = 1;
|
|
float SSSTransmission = Occlusion.TransmissionDistance;
|
|
|
|
// 0 is shadowed, 1 is unshadowed
|
|
// RETURN_COLOR not needed unless writing to SceneColor;
|
|
float FadedShadow = lerp(1.0f, Shadow, ShadowFadeFraction);
|
|
float FadedSSSShadow = lerp(1.0f, SSSTransmission, ShadowFadeFraction);
|
|
|
|
// the channel assignment is documented in ShadowRendering.cpp (look for Light Attenuation channel assignment)
|
|
float4 OutColor;
|
|
if (LIGHT_TYPE == LIGHT_TYPE_DIRECTIONAL)
|
|
{
|
|
OutColor = EncodeLightAttenuation(half4(FadedShadow, FadedSSSShadow, 1.0, FadedSSSShadow));
|
|
}
|
|
else
|
|
{
|
|
OutColor = EncodeLightAttenuation(half4(FadedShadow, FadedSSSShadow, FadedShadow, FadedSSSShadow));
|
|
}
|
|
|
|
RWOcclusionMaskUAV[PixelCoord] = OutColor;
|
|
}
|
|
}
|