// Copyright Epic Games, Inc. All Rights Reserved. #include "/Engine/Shared/RayTracingTypes.h" #include "../Common.ush" #define SUPPORT_CONTACT_SHADOWS 0 #define USE_SOURCE_TEXTURE 1 #define PreIntegratedGF ReflectionStruct.PreIntegratedGF #define PreIntegratedGFSampler GlobalBilinearClampedSampler #include "../DeferredShadingCommon.ush" #include "../DeferredLightingCommon.ush" #include "../ReflectionEnvironmentShared.ush" #include "../Montecarlo.ush" #include "../PathTracing/Utilities/PathTracingRandomSequence.ush" #include "../PathTracing/Material/PathTracingFresnel.ush" #include "../HeightFogCommon.ush" #include "../SobolRandom.ush" #include "../SceneTextureParameters.ush" #include "RayTracingCommon.ush" #include "RayTracingDeferredShadingCommon.ush" #include "RayTracingHitGroupCommon.ush" #define ERayTracingPrimaryRaysFlag_None 0 #define ERayTracingPrimaryRaysFlag_UseGBufferForMaxDistance (1u << 0) #define ERayTracingPrimaryRaysFlag_PrimaryView (1u << 1) #define ERayTracingPrimaryRaysFlag_AllowSkipSkySample (1u << 2) #define SUBSTRATE_MATERIALCONTAINER_IS_VIEWRESOURCE 0 #include "../Substrate/Substrate.ush" #include "../Substrate/SubstrateEvaluation.ush" int SamplesPerPixel; int MaxRefractionRays; int HeightFog; int ForceOpaqueRays; int ShadowsType; int ShadowsTranslucencyType; int ShouldDoDirectLighting; int ShouldDoEmissiveAndIndirectLighting; int UpscaleFactor; int ShouldUsePreExposure; uint PrimaryRayFlags; float TranslucencyMinRayDistance; float TranslucencyMaxRayDistance; float TranslucencyMaxRoughness; int TranslucencyRefraction; float MaxNormalBias; Texture2D SceneColorTexture; RaytracingAccelerationStructure TLAS; RaytracingAccelerationStructure FarFieldTLAS; RWTexture2D ColorOutput; RWTexture2D RayHitDistanceOutput; #include "RayTracingLightingCommon.ush" #define FrontLayerTranslucencyReflectionsStruct LumenGIVolumeStruct #define RadianceCacheInterpolation LumenGIVolumeStruct #include "../Lumen/LumenTranslucencyVolumeShared.ush" float CalcNoT(float CosTheta1, float N1, float N2) { float SinTheta1_Squared = 1.0 - CosTheta1 * CosTheta1; float SinTheta2_Squared = (SinTheta1_Squared * N1 * N1) / (N2 * N2); float CosTheta2_Squared = 1.0 - SinTheta2_Squared; return CosTheta2_Squared > 0.0 ? sqrt(CosTheta2_Squared) : 0.0; } float FresnelDielectric(float Eta, float IoH, float ToH) { float Rs = Square((Eta * IoH - ToH) / (Eta * IoH + ToH)); float Rp = Square((Eta * ToH - IoH) / (Eta * ToH + IoH)); return (Rs + Rp) / 2; } float3 GetSkyRadiance(float3 Direction, float Roughness) { float SkyAverageBrightness = 1.0f; return GetSkyLightReflection(Direction, Roughness, SkyAverageBrightness); } RAY_TRACING_ENTRY_RAYGEN(RayTracingPrimaryRaysRGS) { uint2 DispatchThreadId = DispatchRaysIndex().xy + View.ViewRectMin.xy; uint2 PixelCoord = GetPixelCoord(DispatchThreadId, UpscaleFactor); uint LinearIndex = PixelCoord.y * View.BufferSizeAndInvSize.x + PixelCoord.x; // TODO(Denoiser): PixelCoord or DispatchThreadId float2 InvBufferSize = View.BufferSizeAndInvSize.zw; float2 UV = (float2(PixelCoord) + 0.5) * InvBufferSize; float GBufferDepth = ConvertFromDeviceZ(SampleDeviceZFromSceneTextures(UV)); // CalcSceneDepth SampleDeviceZFromSceneTextures float3 TranslatedWorldPosition = ReconstructTranslatedWorldPositionFromDepth(UV, GBufferDepth); // Trace rays from camera origin to Gbuffer (only translucent objects are considered on the first ray, so a bias is not needed) FRayDesc Ray = CreatePrimaryRay(UV); FRayCone RayCone = (FRayCone)0; RayCone.SpreadAngle = View.EyeToPixelSpreadAngle; if((ERayTracingPrimaryRaysFlag_UseGBufferForMaxDistance & PrimaryRayFlags) != 0) { // NOTE: we need a small epsilon even though we only trace RAY_TRACING_MASK_TRANSLUCENT objects at first // because we might have an object with a mix of materials on it and therefore still need to avoid // hitting the target surface if possible Ray.TMax = dot(TranslatedWorldPosition - Ray.Origin, Ray.Direction) - 0.1; // assumes Ray.Direction is normalized } bool bAllowSkySampling; if((ERayTracingPrimaryRaysFlag_AllowSkipSkySample & PrimaryRayFlags) != 0) { // Sky is only sampled when infinite reflection rays are used. bAllowSkySampling = TranslucencyMaxRayDistance < 0; } else { bAllowSkySampling = true; } // Check if the Sky Light should affect reflection rays within translucency. const bool bSkyLightAffectReflection = ShouldSkyLightAffectReflection(); const bool bPrimaryView = (ERayTracingPrimaryRaysFlag_PrimaryView & PrimaryRayFlags) != 0; bool bHasScattered = false; float BackgroundVisibility = 0.0; // Integrated data by path tracing float3 PathRadiance = 0.0; float3 PathThroughput = 1.0; float LastRoughness = 0.0; float HitDistance = 0.0f; for (uint RefractionRayIndex = 0; RefractionRayIndex < MaxRefractionRays; ++RefractionRayIndex) { const bool bIsCameraRay = !bHasScattered; const uint RefractionRayFlags = (bIsCameraRay ? RAY_FLAG_CULL_BACK_FACING_TRIANGLES : 0) | (ForceOpaqueRays ? RAY_FLAG_FORCE_OPAQUE : 0); const uint RefractionInstanceInclusionMask = (!bPrimaryView && bIsCameraRay) || !TranslucencyRefraction ? RAY_TRACING_MASK_TRANSLUCENT : (RAY_TRACING_MASK_OPAQUE | RAY_TRACING_MASK_TRANSLUCENT | RAY_TRACING_MASK_HAIR_STRANDS); const bool bRefractionRayTraceSkyLightContribution = false; const bool bRefractionDecoupleSampleGeneration = true; #if PROJECT_SUPPORTS_LUMEN const bool bRefractionEnableSkyLightContribution = !IsLumenTranslucencyGIEnabled(); #else const bool bRefractionEnableSkyLightContribution = true; #endif float3 PathVertexRadiance = float3(0, 0, 0); float MaxTraceDistance = 100000.0f; RandomSequence RandSequence; RandomSequence_Initialize(RandSequence, PixelCoord, RefractionRayIndex, View.StateFrameIndex, MaxRefractionRays); FMaterialClosestHitPayload Payload = TraceRayAndAccumulateResults( Ray, TLAS, FarFieldTLAS, RefractionRayFlags, RefractionInstanceInclusionMask, RandSequence, PixelCoord, MaxNormalBias, MaxTraceDistance, ShadowsType, ShadowsTranslucencyType, ShouldDoDirectLighting, ShouldDoEmissiveAndIndirectLighting, bRefractionRayTraceSkyLightContribution, bRefractionDecoupleSampleGeneration, bIsCameraRay, RayCone, bRefractionEnableSkyLightContribution, PathVertexRadiance); float PayloadRoughness = 0; float3 PayloadDiffuseColor = 0; float3 PayloadSpecularColor = 0; float3 PayloadWorldTangent = 0; float PayloadAnisotropy = 0; float3 PayloadLuminanceWeight = 1.0; #if SUBTRATE_GBUFFER_FORMAT==1 float3 SubstrateMaterialThroughput = 1.0; FSubstrateAddressing SubstrateAddressing = GetSubstratePixelDataByteOffset(0, 0, 0); FSubstratePixelHeader SubstratePixelHeader = UnpackSubstrateHeaderIn(Payload.SubstrateData, SubstrateAddressing, Payload.SubstrateData); BRANCH if (SubstratePixelHeader.ClosureCount > 0) // With ray tracing, we only account for the first BSDF since all materials should be simplified to a single Closure. { const bool bSkipSSSMaterialOverride = true; const FSubstrateBSDF BSDF = UnpackSubstrateBSDFIn(Payload.SubstrateData, SubstrateAddressing, SubstratePixelHeader, bSkipSSSMaterialOverride); if (SubstrateIsBSDFVisible(BSDF)) { // Create the BSDF context FSubstrateBSDFContext SubstrateBSDFContext = SubstrateCreateBSDFContext(SubstratePixelHeader, BSDF, SubstrateAddressing, -Ray.Direction); PayloadRoughness = SLAB_ROUGHNESS(BSDF); PayloadDiffuseColor = SubstrateGetBSDFDiffuseColor(BSDF); PayloadSpecularColor = SubstrateGetBSDFSpecularF0(BSDF); PayloadWorldTangent = SubstrateBSDFContext.X; PayloadAnisotropy = SLAB_ANISOTROPY(BSDF); PayloadLuminanceWeight = BSDF.LuminanceWeightV; const float Coverage = saturate(Payload.Opacity); SubstrateMaterialThroughput = (1.0 - Coverage) + Coverage * Payload.TransmittancePreCoverage; } } #else PayloadRoughness = Payload.Roughness; PayloadDiffuseColor = Payload.DiffuseColor; PayloadSpecularColor = Payload.SpecularColor; PayloadWorldTangent = Payload.WorldTangent; PayloadAnisotropy = Payload.Anisotropy; PayloadLuminanceWeight = Payload.Opacity; #endif LastRoughness = PayloadRoughness; // // Handle no hit condition // if (Payload.IsMiss()) { if (bHasScattered && bAllowSkySampling) { // We only sample the sky if the ray has scattered (i.e. been refracted or reflected). Otherwise we are going ot use the regular scene color. PathRadiance += PathThroughput * GetSkyRadiance(Ray.Direction, LastRoughness); } break; } float3 TranslatedHitPoint = Ray.Origin + Ray.Direction * Payload.HitT; float NextMaxRayDistance = Ray.TMax - Payload.HitT; // // Handle surface lighting // const float3 vertexRadianceWeight = PayloadLuminanceWeight; // Opacity as coverage. This works for RAY_TRACING_BLEND_MODE_OPAQUE and RAY_TRACING_BLEND_MODE_TRANSLUCENT. // It is also needed for RAY_TRACING_BLEND_MODE_ADDITIVE and RAY_TRACING_BLEND_MODE_ALPHA_COMPOSITE: radiance contribution is alway weighted by coverage. // #dxr_todo: I have not been able to setup a material using RAY_TRACING_BLEND_MODE_MODULATE. // // Apply fog on primary ray (the first translucent surface encountered) if needed. // We can only handle primary rays because height and volumetric fog are only setup for the camera point of view. // float4 ViewFogInScatteringAndTransmittance = float4(0.0, 0.0, 0.0, 1.0); if (HeightFog > 0) { float3 WorldPositionRelativeToCameraPrimaryRay = PrimaryView.TranslatedWorldCameraOrigin - TranslatedHitPoint; // We always sample the height fog to make sure all the intersected transparent surface get affected by it. // We cannot only do it for the primary ray otherwise refelction on secondary surface will look too bright. // For that sample the fog for the hit position and view assuming no refraction. This seems acceptable. ViewFogInScatteringAndTransmittance = CalculateHeightFog(WorldPositionRelativeToCameraPrimaryRay); if (FogStruct.ApplyVolumetricFog > 0 && !bHasScattered) // We can only sample the volumetric fog contribution (always camera aligned) if the ray is not scattered around { float4 ClipPos = mul(float4(TranslatedHitPoint, 1.0f), PrimaryView.TranslatedWorldToClip); float3 VolumeUV = ComputeVolumeUVFromNDC(ClipPos); ViewFogInScatteringAndTransmittance = CombineVolumetricFog(ViewFogInScatteringAndTransmittance, VolumeUV, 0 /*EyeIndex*/, GBufferDepth); } } PathRadiance += PathThroughput * vertexRadianceWeight * (PathVertexRadiance * ViewFogInScatteringAndTransmittance.a + ViewFogInScatteringAndTransmittance.rgb); const bool bIsHoldout = Payload.IsHoldout(); if (bIsHoldout) { // A holdout object acts as if it was a background pixel so that it influences the alpha channel BackgroundVisibility += Luminance(PathThroughput * vertexRadianceWeight); } // // Handle reflection tracing with a ray per vertex of the refraction path // // Shorten the rays on rougher surfaces between user-provided min and max ray lengths. // When a shortened ray misses the geometry, we fall back to local reflection capture sampling (similar to SSR). const float LocalMaxRayDistance = bAllowSkySampling ? 1e27f : lerp(TranslucencyMaxRayDistance, TranslucencyMinRayDistance, PayloadRoughness); if (PayloadRoughness < TranslucencyMaxRoughness) { // Trace reflection ray float2 RandSample = RandomSequence_GenerateSample2D(RandSequence); FRayDesc ReflectionRay; ReflectionRay.TMin = 0.01; ReflectionRay.TMax = LocalMaxRayDistance; ReflectionRay.Origin = TranslatedHitPoint; ModifyGGXAnisotropicNormalRoughness(PayloadWorldTangent, PayloadAnisotropy, PayloadRoughness, Payload.WorldNormal, Ray.Direction); ReflectionRay.Direction = GenerateReflectedRayDirection(Ray.Direction, Payload.WorldNormal, PayloadRoughness, RandSample); ApplyPositionBias(ReflectionRay, Payload.WorldNormal, MaxNormalBias); const uint ReflectionRayFlags = RAY_FLAG_CULL_BACK_FACING_TRIANGLES | (ForceOpaqueRays ? RAY_FLAG_FORCE_OPAQUE : 0); const uint ReflectionInstanceInclusionMask = (RAY_TRACING_MASK_OPAQUE | RAY_TRACING_MASK_TRANSLUCENT | RAY_TRACING_MASK_HAIR_STRANDS); const bool bReflectionRayTraceSkyLightContribution = false; const bool bReflectionDecoupleSampleGeneration = true; const bool bReflectionEnableSkyLightContribution = bSkyLightAffectReflection; float3 ReflectionRadiance = float3(0, 0, 0); FMaterialClosestHitPayload ReflectionPayload = TraceRayAndAccumulateResults( ReflectionRay, TLAS, FarFieldTLAS, ReflectionRayFlags, ReflectionInstanceInclusionMask, RandSequence, PixelCoord, MaxNormalBias, MaxTraceDistance, ShadowsType, ShadowsTranslucencyType, ShouldDoDirectLighting, ShouldDoEmissiveAndIndirectLighting, bReflectionRayTraceSkyLightContribution, bReflectionDecoupleSampleGeneration, false, RayCone, bReflectionEnableSkyLightContribution, ReflectionRadiance); // If we have not hit anything, sample the distance sky radiance. if (ReflectionPayload.IsMiss()) { ReflectionRadiance = GetSkyRadiance(ReflectionRay.Direction, LastRoughness); } // #dxr_todo: reflection IOR and clear coat also? This only handles default material. float NoV = saturate(dot(-Ray.Direction, Payload.WorldNormal)); const float3 ReflectionThroughput = EnvBRDF(PayloadSpecularColor, PayloadRoughness, NoV); // For reflections, we can only sample the height fog (volumetric fog is only for primary rays) if (HeightFog > 0) { float3 OriginToCollider = ReflectionPayload.TranslatedWorldPos - ReflectionRay.Origin; float4 ReflectionFogInscatteringAndTransmittance = CalculateHeightFog(OriginToCollider); ReflectionRadiance = ReflectionRadiance * ReflectionFogInscatteringAndTransmittance.a + ReflectionFogInscatteringAndTransmittance.rgb; } PathRadiance += PathThroughput * vertexRadianceWeight * ViewFogInScatteringAndTransmittance.a * ReflectionThroughput * ReflectionRadiance; } { float3 VolumeLighting = 0; const float3 TranslucencyEvaluationTranslatedPosition = TranslatedHitPoint; #if PROJECT_SUPPORTS_LUMEN if (IsLumenTranslucencyGIEnabled() && ShouldDoEmissiveAndIndirectLighting) { // Lumen Dynamic GI + shadowed Skylight FDFVector3 TranslucencyEvaluationPosition = DFFastSubtract(TranslucencyEvaluationTranslatedPosition, PrimaryView.PreViewTranslation); FTwoBandSHVectorRGB TranslucencyGISH = GetTranslucencyGIVolumeLighting(TranslucencyEvaluationPosition, PrimaryView.WorldToClip, true); // Diffuse convolution FTwoBandSHVector DiffuseTransferSH = CalcDiffuseTransferSH(Payload.WorldNormal, 1); VolumeLighting.rgb += max(half3(0, 0, 0), DotSH(TranslucencyGISH, DiffuseTransferSH)) / PI; const bool bEvaluateBackface = GetShadingModelRequiresBackfaceLighting(Payload.ShadingModelID); if (bEvaluateBackface) { FTwoBandSHVector SubsurfaceTransferSH = CalcDiffuseTransferSH(-Payload.WorldNormal, 1); VolumeLighting.rgb += max(half3(0, 0, 0), DotSH(TranslucencyGISH, SubsurfaceTransferSH)) / PI; } } else #endif { // noop => if lumen is not enabled: bRefractionEnableSkyLightContribution == true } PathRadiance += PathThroughput * vertexRadianceWeight * VolumeLighting * PayloadDiffuseColor; } float3 RefractedDirection = Ray.Direction; #if SUBTRATE_GBUFFER_FORMAT==1 bHasScattered = false; PathThroughput *= SubstrateMaterialThroughput; if (bIsHoldout) { // In the refraction case, the non-opaque part should also contribute to the holdout // The net result is that the entire object becomes opaque for the alpha channel. BackgroundVisibility += Luminance(PathThroughput) * (1.0 - Payload.Opacity); PathThroughput = 0.0; break; } #else // SUBTRATE_GBUFFER_FORMAT==1 // Update the refraction/transparency path transmittance and check stop condition switch (Payload.BlendingMode) { case RAY_TRACING_BLEND_MODE_ADDITIVE: { break; } case RAY_TRACING_BLEND_MODE_MODULATE: { PathThroughput *= Payload.Radiance; break; } case RAY_TRACING_BLEND_MODE_ALPHA_COMPOSITE: case RAY_TRACING_BLEND_MODE_TRANSLUCENT: case RAY_TRACING_BLEND_MODE_ALPHA_HOLDOUT: { PathThroughput *= 1.0 - Payload.Opacity; break; } default: { PathThroughput *= 0.0; break; } } if (all(PathThroughput <= 0.0)) { break; } // // Handle refraction through the surface. // // Set refraction ray for next iteration if (Payload.ShadingModelID == SHADINGMODELID_THIN_TRANSLUCENT) { const float3 N = Payload.WorldNormal; const float3 V = -Ray.Direction; const float NoV = dot(N, V); const float F0 = F0RGBToF0(Payload.SpecularColor); const float F = FresnelReflectance(NoV, 1.0, F0); const float3 Transmittance = Payload.CustomData.xyz; const float PathLength = rcp(saturate(abs(NoV) + 1e-5)); PathThroughput *= pow(Transmittance, PathLength) * Pow2(1 - F); bHasScattered = false; // need to consider both refraction and translucency through the material by coverage. if (bIsHoldout) { // In the refraction case, the non-opaque part should also contribute to the holdout // The net result is that the entire object becomes opaque for the alpha channel. BackgroundVisibility += Luminance(PathThroughput) * (1.0 - Payload.Opacity); PathThroughput = 0.0; break; } } else if (TranslucencyRefraction && Payload.BlendingMode == RAY_TRACING_BLEND_MODE_TRANSLUCENT && Payload.Ior > 0.0) { if (bIsHoldout) { // In the refraction case, the non-opaque part should also contribute to the holdout // The net result is that the entire object becomes opaque for the alpha channel. BackgroundVisibility += Luminance(PathThroughput) * (1.0 - Payload.Opacity); PathThroughput = 0.0; break; } float Ior = Payload.Ior; bHasScattered |= Ior > 1.0 ? true : false; bool bIsEntering = Payload.IsFrontFace(); float Eta = bIsEntering ? Ior : rcp(Ior); float3 N = Payload.WorldNormal; float3 V = -Ray.Direction; float NoV = dot(N, V); // Hack to allow one-sided materials to be modeled as dielectrics if (NoV < 0.0) { NoV = -NoV; N = -N; bIsEntering = true; } float F0 = F0RGBToF0(Payload.SpecularColor); float F = 0.0; // fresnel SampleRefraction(-V, N, Eta, 1.0 /* always sample refraction or TIR */, RefractedDirection, F); PathThroughput *= F; // ray has bent, so it may need to go arbitrarily far NextMaxRayDistance = LocalMaxRayDistance; } #endif // SUBTRATE_GBUFFER_FORMAT==1 // // Setup refracted ray to be traced // Ray.Origin = TranslatedHitPoint; Ray.TMin = 0.01; Ray.TMax = NextMaxRayDistance; Ray.Direction = RefractedDirection; float SurfaceCurvature = 0.0f; /* #todo_dxr assume no curvature */ RayCone = PropagateRayCone(RayCone, SurfaceCurvature, GBufferDepth); } if (!bHasScattered) { // If the ray did not bend, composite over the SceneColor texture and accumulate alpha if (bPrimaryView) { // our path reached the background, accumulate its contribution for the alpha channel BackgroundVisibility += Luminance(PathThroughput); } else { float4 SceneColorContrib = SceneColorTexture.SampleLevel(GlobalPointClampedSampler, UV, 0); PathRadiance += PathThroughput * SceneColorContrib.xyz / View.PreExposure; BackgroundVisibility += Luminance(PathThroughput) * SceneColorContrib.w; } } // NOTE: UE by convention tracks background visibility which is (1.0-AccumulatedOpacity) (the complement of traditional alpha) const float FinalAlpha = saturate(BackgroundVisibility); PathRadiance *= View.PreExposure; PathRadiance = ClampToHalfFloatRange(PathRadiance); ColorOutput[DispatchThreadId] = float4(PathRadiance, FinalAlpha); RayHitDistanceOutput[DispatchThreadId] = HitDistance; }