// 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 RWOcclusionMaskUAV; RWTexture2D RWRayDistanceUAV; RWTexture2D 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 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; } }