462 lines
19 KiB
HLSL
462 lines
19 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#define PATH_TRACING 1
|
|
#define PATH_TRACER_USE_CLOUD_SHADER 1
|
|
|
|
#include "/Engine/Private/Common.ush"
|
|
#include "/Engine/Private/RayTracing/RayTracingCommon.ush"
|
|
#include "/Engine/Private/PathTracing/PathTracingShaderUtils.ush"
|
|
// Provide aliases for the parameters that now come through a shader buffer
|
|
|
|
#define FogDensity PathTracingCloudParameters.FogDensity
|
|
#define FogHeight PathTracingCloudParameters.FogHeight
|
|
#define FogFalloff PathTracingCloudParameters.FogFalloff
|
|
#define FogAlbedo PathTracingCloudParameters.FogAlbedo
|
|
#define FogPhaseG PathTracingCloudParameters.FogPhaseG
|
|
#define FogCenter PathTracingCloudParameters.FogCenter
|
|
#define FogMinZ PathTracingCloudParameters.FogMinZ
|
|
#define FogMaxZ PathTracingCloudParameters.FogMaxZ
|
|
#define FogRadius PathTracingCloudParameters.FogRadius
|
|
#define FogFalloffClamp PathTracingCloudParameters.FogFalloffClamp
|
|
|
|
#define PlanetCenterTranslatedWorldHi PathTracingCloudParameters.PlanetCenterTranslatedWorldHi
|
|
#define PlanetCenterTranslatedWorldLo PathTracingCloudParameters.PlanetCenterTranslatedWorldLo
|
|
#define Atmosphere PathTracingCloudParameters
|
|
|
|
#define CloudAccelerationMap PathTracingCloudParameters.CloudAccelerationMap
|
|
#define CloudAccelerationMapSampler PathTracingCloudParameters.CloudAccelerationMapSampler
|
|
|
|
#define CloudClipX PathTracingCloudParameters.CloudClipX
|
|
#define CloudClipY PathTracingCloudParameters.CloudClipY
|
|
#define CloudClipZ PathTracingCloudParameters.CloudClipZ
|
|
|
|
#define CloudLayerBotKm PathTracingCloudParameters.CloudLayerBotKm
|
|
#define CloudLayerTopKm PathTracingCloudParameters.CloudLayerTopKm
|
|
#define CloudClipDistKm PathTracingCloudParameters.CloudClipDistKm
|
|
#define CloudClipRadiusKm PathTracingCloudParameters.CloudClipRadiusKm
|
|
|
|
#define CloudClipCenterKm PathTracingCloudParameters.CloudClipCenterKm
|
|
|
|
#define CloudTracingMaxDistance PathTracingCloudParameters.CloudTracingMaxDistance
|
|
#define CloudRoughnessCutoff PathTracingCloudParameters.CloudRoughnessCutoff
|
|
|
|
#define CloudAccelMapResolution PathTracingCloudParameters.CloudAccelMapResolution
|
|
#define CloudVoxelWidth PathTracingCloudParameters.CloudVoxelWidth
|
|
#define CloudInvVoxelWidth PathTracingCloudParameters.CloudInvVoxelWidth
|
|
|
|
|
|
#include "./Volume/PathTracingCloudsMaterialCommon.ush"
|
|
#include "./Volume/PathTracingFog.ush"
|
|
#include "./Volume/PathTracingAtmosphere.ush"
|
|
#include "./Volume/PathTracingVolumeSampling.ush"
|
|
|
|
#include "./PathTracingCommon.ush"
|
|
|
|
FVolumeShadedResult CloudsGetDensity(float3 TranslatedWorldPos)
|
|
{
|
|
|
|
#if CLOUD_VISUALIZATION_SHADER == 1
|
|
float3 P = mul(GetCloudClipBasis(), TranslatedWorldPos); // WorldToTangent
|
|
float2 UV = (P.xy * CM_TO_SKY_UNIT + CloudClipDistKm.xx) / (2 * CloudClipDistKm);
|
|
const int Level = 0;
|
|
float4 Data = CloudAccelerationMap.SampleLevel(CloudAccelerationMapSampler, UV, Level);
|
|
|
|
float Z = P.z * CM_TO_SKY_UNIT;
|
|
float Density = (Z > Data.z && Z < Data.w) ? Data.x : 0.0;
|
|
FVolumeShadedResult Result = (FVolumeShadedResult)0;
|
|
|
|
Result.SigmaT = Density;
|
|
Result.SigmaSDualHG = 0.85 * Density;
|
|
return Result;
|
|
#else
|
|
FDFVector3 AbsoluteWorldPosition = DFFastSubtract(TranslatedWorldPos, ResolvedView.PreViewTranslation);
|
|
|
|
// Measure HeightKm as the distance to the planet center
|
|
const FDFVector3 OC = DFMultiply(DFSubtract(TranslatedWorldPos, MakeDFVector3(PlanetCenterTranslatedWorldHi, PlanetCenterTranslatedWorldLo)), CM_TO_SKY_UNIT);
|
|
const float H = DFLengthDemote(OC); // HeightKm
|
|
float B = CloudLayerBotKm;
|
|
float T = CloudLayerTopKm;
|
|
|
|
FSampleCloudMaterialResult CloudResult = SampleCloudMaterial(AbsoluteWorldPosition, H, B, T);
|
|
FVolumeShadedResult Result = (FVolumeShadedResult)0;
|
|
Result.SigmaT = CloudResult.Extinction;
|
|
Result.SigmaSDualHG = CloudResult.Albedo * Result.SigmaT;
|
|
Result.Emission = CloudResult.Emission;
|
|
#if MATERIAL_VOLUMETRIC_ADVANCED
|
|
Result.DualPhaseData = float3(CloudResult.PhaseG1, CloudResult.PhaseG2, CloudResult.PhaseBlend);
|
|
#endif
|
|
return Result;
|
|
#endif
|
|
}
|
|
|
|
RAY_TRACING_ENTRY_CALLABLE(PathTracingVolumetricCloudMaterialShader, FPackedPathTracingPayload, PackedPayload)
|
|
{
|
|
ResolvedView = ResolveView();
|
|
|
|
// Ray marching happens inside here so that we only have to pay for the cost of callable shading _once_
|
|
// This comes at the cost of some duplication of the logic
|
|
// TODO: Can we use HLSL templates to merge some of the duplicated logic?
|
|
|
|
RandomSequence RandSeq = PackedPayload.GetVolumetricCallableShaderInputRandSequence();
|
|
FRayDesc Ray = PackedPayload.GetVolumetricCallableShaderInputRay();
|
|
float3 PathThroughput = PackedPayload.GetVolumetricCallableShaderInputThroughput();
|
|
float CloudDensity = PackedPayload.GetVolumetricCallableShaderInputCloudDensity();
|
|
const uint Flags = PackedPayload.GetVolumetricCallableShaderInputFlags();
|
|
|
|
const int Bounce = (Flags & PATH_TRACER_VOLUME_CALLABLE_FLAGS_BOUNCE_MASK) >> PATH_TRACER_VOLUME_CALLABLE_FLAGS_BOUNCE_SHIFT;
|
|
const bool bIsCameraRay = Bounce == 0;
|
|
|
|
// TODO: evaluate if it would be better to make a second shader for this (so that we don't get two copies of the shader eval in inner loops)
|
|
// TODO: evaluate if voxel traversal is worth it for transmittance
|
|
if (Flags & PATH_TRACER_VOLUME_CALLABLE_FLAGS_TRANSMITTANCE)
|
|
{
|
|
const float CloudFactor = PackedPayload.GetVolumetricCallableShaderInputCloudFactor();
|
|
// Reset payload
|
|
PackedPayload = (FPackedPathTracingPayload)0;
|
|
|
|
// Transmittance loop for the cloud shader only
|
|
// NOTE: We have specialized this since we know our upper bound is a scalar value, most of the pdf math cancels out and we can take a faster path
|
|
float TMin = Ray.TMin;
|
|
float TMax = Ray.TMax;
|
|
float SigmaBar = CloudDensity * CloudFactor, InvSigmaBar = rcp(SigmaBar);
|
|
float3 Throughput = PathThroughput;
|
|
for (int Step = 0; Step < PathTracingCloudParameters.MaxRaymarchSteps; Step++)
|
|
{
|
|
/* take stochastic steps along the ray to estimate transmittance (null scattering) */
|
|
/* Sample the distance to the next interaction */
|
|
float RandValue = RandomSequence_GenerateSample1D(RandSeq);
|
|
TMin -= log(1 - RandValue) * InvSigmaBar;
|
|
if (TMin >= TMax)
|
|
{
|
|
/* exit the ray marching loop */
|
|
break;
|
|
}
|
|
/* our ray marching step stayed inside the atmo and is still in front of the next hit */
|
|
float3 WorldPos = Ray.Origin + TMin * Ray.Direction;
|
|
/* clamp to make sure we never exceed the majorant (should not be the case, but need to avoid any possible numerical issues) */
|
|
float3 SigmaT = min(CloudFactor * CloudsGetDensity(WorldPos).SigmaT, SigmaBar);
|
|
/* keep tracing through the volume */
|
|
Throughput *= 1.0 - SigmaT * InvSigmaBar;
|
|
/* Encourage early termination of paths with low contribution */
|
|
Throughput *= LowThroughputClampFactor(Throughput);
|
|
if (!any(Throughput > 0))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Do we need to know the sample at the _end_ of the ray interval? capture it first in case it does not scatter any light
|
|
if (Flags & PATH_TRACER_VOLUME_CALLABLE_FLAGS_GET_SAMPLE)
|
|
{
|
|
#if MATERIAL_VOLUMETRIC_ADVANCED_MULTISCATTERING_OCTAVE_COUNT > 0
|
|
FCloudMaterialMSParams MS = (FCloudMaterialMSParams)0;
|
|
if (PathTracingCloudParameters.CloudShaderMultipleScatterApproxEnabled)
|
|
{
|
|
MS = CloudMaterialGetMSParams();
|
|
}
|
|
FRISContext HitSample = InitRISContext(RandomSequence_GenerateSample1D(RandSeq));
|
|
float3 FinalThroughput = 0;
|
|
#endif
|
|
|
|
float3 TranslatedWorldPos = Ray.Origin + Ray.Direction * Ray.TMax;
|
|
FVolumeShadedResult CloudResult = (FVolumeShadedResult)0;
|
|
MergeVolumeShadedResult(CloudResult, CloudsGetDensity(TranslatedWorldPos), bIsCameraRay && (Flags & PATH_TRACER_VOLUME_HOLDOUT_CLOUDS) != 0);
|
|
FVolumeShadedResult Result = CloudResult;
|
|
if (Flags & PATH_TRACER_VOLUME_ENABLE_ATMOSPHERE)
|
|
{
|
|
MergeVolumeShadedResult(Result, AtmosphereGetDensity(TranslatedWorldPos), bIsCameraRay && (Flags & PATH_TRACER_VOLUME_HOLDOUT_ATMOSPHERE) != 0);
|
|
}
|
|
if (Flags & PATH_TRACER_VOLUME_ENABLE_FOG)
|
|
{
|
|
MergeVolumeShadedResult(Result, FogGetDensity(TranslatedWorldPos), bIsCameraRay && (Flags & PATH_TRACER_VOLUME_HOLDOUT_FOG) != 0);
|
|
}
|
|
|
|
float3 SigmaS = min(Result.SigmaSRayleigh + Result.SigmaSHG + Result.SigmaSDualHG, Result.SigmaT);
|
|
#if MATERIAL_VOLUMETRIC_ADVANCED_MULTISCATTERING_OCTAVE_COUNT > 0
|
|
float3 Contrib = Throughput * SigmaS;
|
|
float SelectionWeight = max3(Contrib.x, Contrib.y, Contrib.z);
|
|
if (HitSample.Accept(SelectionWeight))
|
|
#else
|
|
Throughput *= SigmaS;
|
|
#endif
|
|
{
|
|
const float3 RayleighWeight = select(SigmaS > 0.0, Result.SigmaSRayleigh / SigmaS, 0.0);
|
|
const float3 HGWeight = select(SigmaS > 0.0, Result.SigmaSHG / SigmaS, 0.0);
|
|
const float3 DualHGWeight = select(SigmaS > 0.0, Result.SigmaSDualHG / SigmaS, 0.0);
|
|
PackedPayload.SetVolumetricCallableShaderOutputSample(Ray.TMax, RayleighWeight, HGWeight, Result.PhaseG, DualHGWeight, Result.DualPhaseData, 0.0);
|
|
PackedPayload.SetVolumetricCallableShaderOutputCloudDensityFactor(1.0);
|
|
#if MATERIAL_VOLUMETRIC_ADVANCED_MULTISCATTERING_OCTAVE_COUNT > 0
|
|
FinalThroughput = Contrib / SelectionWeight;
|
|
#endif
|
|
}
|
|
#if MATERIAL_VOLUMETRIC_ADVANCED_MULTISCATTERING_OCTAVE_COUNT > 0
|
|
float S = MS.ScattFactor, E = MS.ExtinFactor, P = MS.PhaseFactor;
|
|
for (int ms = 1; ms <= MATERIAL_VOLUMETRIC_ADVANCED_MULTISCATTERING_OCTAVE_COUNT; ms++)
|
|
{
|
|
CloudResult.SigmaSDualHG *= S;
|
|
|
|
// Propose a candidate hit with just the scaled cloud contribution
|
|
Contrib = Throughput * CloudResult.SigmaSDualHG;
|
|
SelectionWeight = max3(Contrib.x, Contrib.y, Contrib.z);
|
|
if (HitSample.Accept(SelectionWeight))
|
|
{
|
|
// stash this hit for next time
|
|
FinalThroughput = Contrib / SelectionWeight;
|
|
// We create a blend of SigmaHG (set to isotropic) and SigmaSDualHG (set to the original phase function) to match what raster does (lerp the eval)
|
|
PackedPayload.SetVolumetricCallableShaderOutputSample(Ray.TMax, 0.0, P, 0.0, 1.0 - P, CloudResult.DualPhaseData, 0.0);
|
|
PackedPayload.SetVolumetricCallableShaderOutputCloudDensityFactor(E); // so that future cloud shadow rays get attenuated
|
|
}
|
|
|
|
// Update scale factors for next iteration
|
|
S *= S;
|
|
P *= P;
|
|
E *= E;
|
|
}
|
|
Throughput = FinalThroughput * HitSample.GetNormalization();
|
|
#endif
|
|
}
|
|
|
|
PackedPayload.SetVolumetricCallableShaderOutput(RandSeq, Throughput);
|
|
}
|
|
else
|
|
{
|
|
// Loop through combined density for sampling
|
|
|
|
FRISContext HitSample = PackedPayload.GetVolumetricCallableShaderRISContext();
|
|
|
|
#if MATERIAL_VOLUMETRIC_ADVANCED_MULTISCATTERING_OCTAVE_COUNT > 0
|
|
FCloudMaterialMSParams MS = (FCloudMaterialMSParams)0;
|
|
if (PathTracingCloudParameters.CloudShaderMultipleScatterApproxEnabled)
|
|
{
|
|
MS = CloudMaterialGetMSParams();
|
|
}
|
|
#endif
|
|
|
|
// zero out the payload so we can write our output to it
|
|
PackedPayload = (FPackedPathTracingPayload) 0;
|
|
PackedPayload.HitT = -1.0; // invalid sample until we are sure we got one
|
|
|
|
#define USE_VOXEL_TRAVERSAL 1
|
|
|
|
#if USE_VOXEL_TRAVERSAL
|
|
// isect clip slabs, the AABB our clouds are enclosed in
|
|
float3 Ro = mul(GetCloudClipBasis(), Ray.Origin);
|
|
float3 Rd = mul(GetCloudClipBasis(), Ray.Direction);
|
|
float3 InvRd = rcp(max(abs(Rd), 1e-6)) * select(Rd >= 0, 1.0, -1.0); // protect against division by 0
|
|
|
|
// now that we know the ray length, lets march through the grid to see if we can clip away anything
|
|
float2 P = Ro.xy + Ray.TMin * Rd.xy;
|
|
int2 Idx = PositionToVoxel(P);
|
|
float2 NextCrossingT = Ray.TMin + (VoxelToPosition(Idx + int2(Rd.xy >= 0)) - P) * InvRd.xy;
|
|
float2 DeltaT = abs(CloudVoxelWidth * InvRd.xy);
|
|
int2 Step = select_internal(Rd.xy >= 0, 1, -1);
|
|
int2 Stop = select_internal(Rd.xy >= 0, CloudAccelMapResolution, -1);
|
|
#endif
|
|
|
|
float3 VolumeRadiance = 0;
|
|
float VolumeAlpha = 0;
|
|
float3 VolumeAlbedo = 0;
|
|
// Limit number of steps to prevent timeouts
|
|
// TODO: This biases the result! Is there a better way to limit the number of steps?
|
|
for (int RaymarchStep = 0; RaymarchStep < PathTracingCloudParameters.MaxRaymarchSteps; RaymarchStep++)
|
|
{
|
|
#if USE_VOXEL_TRAVERSAL
|
|
// visit current voxel
|
|
float4 Data = CloudAccelerationMap.Load(uint3(Idx.xy, 0));
|
|
CloudDensity = Data.x;
|
|
// because each cell has slabs, we can have at most 3 segments (empty, filled, empty) per grid cell
|
|
float CellMinT = Ray.TMin;
|
|
float CellMaxT = min3(Ray.TMax, NextCrossingT.x, NextCrossingT.y);
|
|
float IntegrationMaxT = CellMaxT;
|
|
if (CloudDensity > 0)
|
|
{
|
|
float SlabT0 = (Data.z * SKY_UNIT_TO_CM - Ro.z) * InvRd.z;
|
|
float SlabT1 = (Data.w * SKY_UNIT_TO_CM - Ro.z) * InvRd.z;
|
|
float InsideMinT = max(CellMinT, min(SlabT0, SlabT1));
|
|
float InsideMaxT = min(CellMaxT, max(SlabT0, SlabT1));
|
|
// do we still have a valid interval?
|
|
if (InsideMinT < InsideMaxT)
|
|
{
|
|
// this interval must be going through at least 3 portions (some of which may be empty)
|
|
// Figure out in which we are at the moment
|
|
if (Ray.TMin < InsideMinT)
|
|
{
|
|
// we are outside the slab, take steps up to the slab boundary
|
|
CloudDensity = 0;
|
|
IntegrationMaxT = InsideMinT;
|
|
} else if (Ray.TMin < InsideMaxT)
|
|
{
|
|
// we are inside the slab, take steps up to the end of the slab
|
|
IntegrationMaxT = InsideMaxT;
|
|
} else {
|
|
// we are past the slab, take steps up to the cell boundary
|
|
CloudDensity = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we missed the slabs, so only keep the one segment, going through empty space
|
|
CloudDensity = 0;
|
|
}
|
|
}
|
|
#else
|
|
float IntegrationMaxT = Ray.TMax;
|
|
#endif
|
|
|
|
float3 SigmaBar = CloudDensity;
|
|
if (Flags & PATH_TRACER_VOLUME_ENABLE_ATMOSPHERE)
|
|
{
|
|
SigmaBar += AtmosphereGetDensityBounds(Ray.Origin, Ray.Direction, Ray.TMin, Ray.TMax).SigmaMax;
|
|
}
|
|
if (Flags & PATH_TRACER_VOLUME_ENABLE_FOG)
|
|
{
|
|
SigmaBar += FogGetDensityBounds(Ray.Origin, Ray.Direction, Ray.TMin, Ray.TMax).SigmaMax;
|
|
}
|
|
|
|
// Interval is only crossing atmo/clouds/fog
|
|
float RandValue = RandomSequence_GenerateSample1D(RandSeq);
|
|
float Distance = SampleSpectralTransmittance(RandValue, SigmaBar, PathThroughput);
|
|
if (Distance < 0.0)
|
|
{
|
|
// no energy left
|
|
break;
|
|
}
|
|
if (Ray.TMin + Distance < IntegrationMaxT)
|
|
{
|
|
float4 Evaluation = EvaluateSpectralTransmittanceHit(Distance, SigmaBar, PathThroughput);
|
|
PathThroughput *= Evaluation.xyz;
|
|
Ray.TMin += Distance;
|
|
|
|
// find out how much volume exists at the current point
|
|
float3 Ro = Ray.Origin;
|
|
float3 Rd = Ray.Direction;
|
|
float3 TranslatedWorldPos = Ro + Ray.TMin * Rd;
|
|
FVolumeShadedResult Result = (FVolumeShadedResult)0;
|
|
MergeVolumeShadedResult(Result, CloudsGetDensity(TranslatedWorldPos), bIsCameraRay && (Flags & PATH_TRACER_VOLUME_HOLDOUT_CLOUDS) != 0);
|
|
#if MATERIAL_VOLUMETRIC_ADVANCED_MULTISCATTERING_OCTAVE_COUNT > 0
|
|
FVolumeShadedResult CloudResult = Result; // capture just the Cloud contribution (including holdouts) so we can scale it for the MS approx
|
|
#endif
|
|
if (Flags & PATH_TRACER_VOLUME_ENABLE_ATMOSPHERE)
|
|
{
|
|
MergeVolumeShadedResult(Result, AtmosphereGetDensity(TranslatedWorldPos), bIsCameraRay && (Flags & PATH_TRACER_VOLUME_HOLDOUT_ATMOSPHERE) != 0);
|
|
}
|
|
if (Flags & PATH_TRACER_VOLUME_ENABLE_FOG)
|
|
{
|
|
MergeVolumeShadedResult(Result, FogGetDensity(TranslatedWorldPos), bIsCameraRay && (Flags & PATH_TRACER_VOLUME_HOLDOUT_FOG) != 0);
|
|
}
|
|
|
|
// clamp to make sure we never exceed the majorant (should not be the case, but need to avoid any possible numerical issues)
|
|
float3 SigmaT = min(Result.SigmaT, SigmaBar);
|
|
float3 SigmaN = SigmaBar - SigmaT;
|
|
|
|
float3 SigmaH = min(Result.SigmaH, SigmaT);
|
|
VolumeAlpha += Luminance(PathThroughput * (SigmaT - SigmaH));
|
|
|
|
// If this ray wants to get candidate samples for scatter, process them here
|
|
if (Flags & PATH_TRACER_VOLUME_CALLABLE_FLAGS_GET_SAMPLE)
|
|
{
|
|
float3 SigmaS = min(Result.SigmaSRayleigh + Result.SigmaSHG + Result.SigmaSDualHG, SigmaT);
|
|
|
|
// accumulate a signal for the denoiser
|
|
float3 Contrib = PathThroughput * SigmaS;
|
|
VolumeAlbedo += Contrib;
|
|
float SelectionWeight = max3(Contrib.x, Contrib.y, Contrib.z);
|
|
if (HitSample.Accept(SelectionWeight))
|
|
{
|
|
// stash this hit for next time
|
|
float3 PayloadThroughput = Contrib / SelectionWeight;
|
|
const float3 RayleighWeight = select(SigmaS > 0.0, Result.SigmaSRayleigh / SigmaS, 0.0);
|
|
const float3 HGWeight = select(SigmaS > 0.0, Result.SigmaSHG / SigmaS, 0.0);
|
|
const float3 DualHGWeight = select(SigmaS > 0.0, Result.SigmaSDualHG / SigmaS, 0.0);
|
|
PackedPayload.SetVolumetricCallableShaderOutputSample(Ray.TMin, RayleighWeight, HGWeight, Result.PhaseG, DualHGWeight, Result.DualPhaseData, PayloadThroughput);
|
|
PackedPayload.SetVolumetricCallableShaderOutputCloudDensityFactor(1.0);
|
|
}
|
|
#if MATERIAL_VOLUMETRIC_ADVANCED_MULTISCATTERING_OCTAVE_COUNT > 0
|
|
// The material has enabled fake-multiple scattering. We can take this into account here by stochastically returning additional lobes
|
|
// NOTE: This is not energy conserving! TODO: how to integrate with indirect scattering?
|
|
|
|
float S = MS.ScattFactor, E = MS.ExtinFactor, P = MS.PhaseFactor;
|
|
for (int ms = 1; ms <= MATERIAL_VOLUMETRIC_ADVANCED_MULTISCATTERING_OCTAVE_COUNT; ms++)
|
|
{
|
|
CloudResult.SigmaSDualHG *= S;
|
|
|
|
// Propose a candidate hit with just the scaled cloud contribution
|
|
Contrib = PathThroughput * CloudResult.SigmaSDualHG;
|
|
SelectionWeight = max3(Contrib.x, Contrib.y, Contrib.z);
|
|
if (HitSample.Accept(SelectionWeight))
|
|
{
|
|
// stash this hit for next time
|
|
float3 PayloadThroughput = Contrib / SelectionWeight;
|
|
// We create a blend of SigmaHG (set to isotropic) and SigmaSDualHG (set to the original phase function) to match what raster does (lerp the eval)
|
|
PackedPayload.SetVolumetricCallableShaderOutputSample(Ray.TMin, 0.0, P, 0.0, 1.0 - P, CloudResult.DualPhaseData, PayloadThroughput);
|
|
PackedPayload.SetVolumetricCallableShaderOutputCloudDensityFactor(E); // so that future cloud shadow rays get attenuated
|
|
}
|
|
|
|
// Update scale factors for next iteration
|
|
S *= S;
|
|
P *= P;
|
|
E *= E;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
VolumeRadiance += Result.Emission * PathThroughput;
|
|
// keep tracing through the volume
|
|
PathThroughput *= SigmaN;
|
|
PathThroughput *= LowThroughputClampFactor(PathThroughput);
|
|
if (!any(PathThroughput > 0))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// update the path throughput, knowing that we escaped the medium
|
|
// exit the ray marching loop
|
|
float4 Evaluation = EvaluateSpectralTransmittanceMiss(IntegrationMaxT - Ray.TMin, SigmaBar, PathThroughput);
|
|
PathThroughput *= Evaluation.xyz;
|
|
|
|
#if USE_VOXEL_TRAVERSAL
|
|
Ray.TMin = IntegrationMaxT;
|
|
#else
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
#if USE_VOXEL_TRAVERSAL
|
|
// Advance to next grid cell if we have reached the end of the cell
|
|
if (Ray.TMin == CellMaxT)
|
|
{
|
|
if (NextCrossingT.x < NextCrossingT.y)
|
|
{
|
|
// step x first
|
|
Idx.x += Step.x;
|
|
if (Idx.x == Stop.x || Ray.TMax < NextCrossingT.x)
|
|
{
|
|
break;
|
|
}
|
|
Ray.TMin = NextCrossingT.x;
|
|
NextCrossingT.x += DeltaT.x;
|
|
}
|
|
else
|
|
{
|
|
Idx.y += Step.y;
|
|
if (Idx.y == Stop.y || Ray.TMax < NextCrossingT.y)
|
|
{
|
|
break;
|
|
}
|
|
Ray.TMin = NextCrossingT.y;
|
|
NextCrossingT.y += DeltaT.y;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Write state back here
|
|
PackedPayload.SetVolumetricCallableShaderOutputRadiance(VolumeRadiance, VolumeAlpha);
|
|
PackedPayload.SetVolumetricCallableShaderOutput(RandSeq, PathThroughput, VolumeAlbedo);
|
|
PackedPayload.SetVolumetricCallableShaderRISContext(HitSample);
|
|
}
|
|
}
|