Files
UnrealEngine/Engine/Shaders/Private/PathTracing/Volume/PathTracingVolumeCommon.ush
2025-05-18 13:04:45 +08:00

291 lines
8.4 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
struct FVolumeIntersection
{
float VolumeTMin;
float VolumeTMax;
float BlockerHitT;
float MaxDensity;
bool HitVolume()
{
return VolumeTMin < VolumeTMax;
}
bool HitBlocker()
{
return BlockerHitT > 0.0;
}
};
FVolumeIntersection CreateVolumeIntersection(float TMin, float TMax, float BlockerT = 0.0)
{
FVolumeIntersection Result = (FVolumeIntersection)0;
Result.VolumeTMin = TMin;
Result.VolumeTMax = TMax;
Result.BlockerHitT = BlockerT;
return Result;
}
FVolumeIntersection CreateVolumeIntersectionWithDensity(float TMin, float TMax, float MaxDensity)
{
FVolumeIntersection Result = (FVolumeIntersection)0;
Result.VolumeTMin = TMin;
Result.VolumeTMax = TMax;
Result.MaxDensity = MaxDensity;
return Result;
}
FVolumeIntersection CreateEmptyVolumeIntersection()
{
return (FVolumeIntersection)0;
}
// representes an interval along the ray over which the specified volumes are known to exist
struct FVolumeIntersectionInterval
{
float VolumeTMin;
float VolumeTMax;
uint VolumeMask;
float CloudDensity;
};
struct FVolumeIntersectionList
{
float4 VolumeTMin;
float4 VolumeTMax;
float CloudDensity;
uint VolumeMask;
// only need to track one blocker (assume they are opaque)
float BlockerHitT;
bool HitVolume()
{
return VolumeMask != 0;
}
bool HitBlocker()
{
return BlockerHitT > 0;
}
void Add(uint ID, FVolumeIntersection VolumeIntersection)
{
// merge the provided hit into the list
if (VolumeIntersection.HitVolume())
{
VolumeTMin[ID] = VolumeIntersection.VolumeTMin;
VolumeTMax[ID] = VolumeIntersection.VolumeTMax;
if (ID == VOLUMEID_CLOUDS)
{
CloudDensity = VolumeIntersection.MaxDensity;
}
VolumeMask |= PATH_TRACER_VOLUME_ENABLE_BIT << ID;
}
if (VolumeIntersection.HitBlocker())
{
if (!HitBlocker() || BlockerHitT > VolumeIntersection.BlockerHitT)
{
BlockerHitT = VolumeIntersection.BlockerHitT;
}
}
}
// Return the interval of TValues closest to the front of the list
FVolumeIntersectionInterval GetCurrentInterval()
{
FVolumeIntersectionInterval Result = (FVolumeIntersectionInterval)0;
for (int Index = 0; Index < PATH_TRACER_MAX_VOLUMES; Index++)
{
uint Mask = PATH_TRACER_VOLUME_ENABLE_BIT << Index;
if ((VolumeMask & Mask) == 0)
{
continue; // not valid
}
float TMin = VolumeTMin[Index];
float TMax = VolumeTMax[Index];
if (Result.VolumeMask == 0)
{
// we haven't stored the start interval yet
Result.VolumeTMin = TMin;
Result.VolumeTMax = TMax;
Result.VolumeMask = Mask;
}
else
{
if (Result.VolumeTMin == TMin)
{
// both segments start together, figure out which ends first
Result.VolumeTMax = min(Result.VolumeTMax, TMax);
}
else if (Result.VolumeTMin < TMin)
{
// what we have so far _starts_ before the current segment
// so either the two segments are disjoint (Result.VolumeTMax is closer)
// or the two overlap, and we need to stop at the next transition line
Result.VolumeTMax = min(Result.VolumeTMax, TMin);
}
else
{
// segments could be disjoint (TMax is closer) or overlapping (VolumeTMin is closer)
Result.VolumeTMax = min(TMax, Result.VolumeTMin);
Result.VolumeTMin = TMin; // we just established this one is closer
}
}
}
// now that we established the distance, figure out which IDs overlap with the resolved segment
Result.VolumeMask = 0;
for (int Index2 = 0; Index2 < PATH_TRACER_MAX_VOLUMES; Index2++)
{
uint Mask = PATH_TRACER_VOLUME_ENABLE_BIT << Index2;
if ((VolumeMask & Mask) == 0)
{
continue; // not overlapping
}
if (Result.VolumeTMax > VolumeTMin[Index2] &&
Result.VolumeTMin < VolumeTMax[Index2])
{
Result.VolumeMask |= Mask;
}
}
// Transfer CloudDensity to Interval if this interval includes clouds
Result.CloudDensity = Result.VolumeMask & PATH_TRACER_VOLUME_ENABLE_CLOUDS ? CloudDensity : 0;
return Result;
}
// Update all active intervals, knowing we have processed everything up to distance 'T'
FVolumeIntersectionList Update(float T)
{
FVolumeIntersectionList Result = (FVolumeIntersectionList)0;
// clip the current intervals, knowning that we are "T" away
for (int Index = 0; Index < PATH_TRACER_MAX_VOLUMES; Index++)
{
uint Mask = PATH_TRACER_VOLUME_ENABLE_BIT << Index;
if ((VolumeMask & Mask) == 0)
{
continue; // not valid
}
float TMax = VolumeTMax[Index];
if (T < TMax)
{
// still valid? then add it back in
Result.VolumeTMin[Index] = max(T, VolumeTMin[Index]);
Result.VolumeTMax[Index] = TMax;
Result.VolumeMask |= Mask;
}
}
Result.BlockerHitT = BlockerHitT;
Result.CloudDensity = CloudDensity;
return Result;
}
};
FVolumeIntersectionList CreateEmptyVolumeIntersectionList()
{
return (FVolumeIntersectionList)0;
}
// Represent a strict lower and upper bound on the values that can be returned by VolumeGetDensity along the given ray
struct FVolumeDensityBounds
{
float3 SigmaMin; // minorant (control extinction)
float3 SigmaMax; // majorant (upper bound)
};
FVolumeDensityBounds CreateVolumeDensityBound(float3 SigmaMin, float3 SigmaMax)
{
FVolumeDensityBounds Result = (FVolumeDensityBounds)0;
Result.SigmaMin = SigmaMin;
Result.SigmaMax = SigmaMax;
return Result;
}
void MergeVolumeDensityBounds(inout FVolumeDensityBounds Merged, FVolumeDensityBounds Other)
{
Merged.SigmaMin += Other.SigmaMin;
Merged.SigmaMax += Other.SigmaMax;
}
// The result of sampling a volume at a given point
// See PathTracingMedium.usf for the implementation of the scattering model
struct FVolumeShadedResult
{
float3 SigmaT; // extinction
float3 SigmaH; // holdout density
float3 SigmaSRayleigh; // Rayleight scattering coefficient
float3 SigmaSHG; // Henyey-Greenstein scattering coefficient (for atmosphere, fog and heterogeneous volumes)
float3 SigmaSDualHG; // Dual Henyey-Greenstein scattering coefficient (for clouds)
float3 Emission;
float PhaseG; // 'g' term for HG lobe
float3 DualPhaseData; // g1,g2,blend for DualHG lobe
};
FPathTracingPayload CreateMediumHitPayload(float HitT, float3 TranslatedWorldPos, FVolumeShadedResult ShadedResult)
{
FPathTracingPayload Result = (FPathTracingPayload)0; // clear all fields
Result.HitT = HitT;
Result.TranslatedWorldPos = TranslatedWorldPos;
Result.ShadingModelID = SHADINGMODELID_MEDIUM;
Result.BSDFOpacity = 1.0;
Result.PrimitiveLightingChannelMask = 7;
Result.SetFrontFace();
const float3 SigmaS = ShadedResult.SigmaSRayleigh + ShadedResult.SigmaSHG + ShadedResult.SigmaSDualHG;
const float3 RayleighWeight = select(SigmaS > 0.0, ShadedResult.SigmaSRayleigh / SigmaS, 0.0);
const float3 HGWeight = select(SigmaS > 0.0, ShadedResult.SigmaSHG / SigmaS, 0.0);
const float3 DualHGWeight = select(SigmaS > 0.0, ShadedResult.SigmaSDualHG / SigmaS, 0.0);
Result.SetBaseColor(RayleighWeight);
Result.SetHG(HGWeight, ShadedResult.PhaseG);
Result.SetDualHG(DualHGWeight, ShadedResult.DualPhaseData);
Result.SetCloudFactor(1.0);
return Result;
}
// Merge the shaded result "R" into "M"
void MergeVolumeShadedResult(inout FVolumeShadedResult M, FVolumeShadedResult R, bool bAsHoldout)
{
if (bAsHoldout)
{
M.SigmaT += R.SigmaT;
M.SigmaH += R.SigmaT;
}
else
{
M.SigmaT += R.SigmaT;
M.SigmaSRayleigh += R.SigmaSRayleigh;
M.SigmaSHG += R.SigmaSHG;
M.SigmaSDualHG += R.SigmaSDualHG;
M.Emission += R.Emission;
// when combining across volumes, we track dual HG on its own to simplify the logic even though it technicaly create 3 distinct hg lobes
// A possible improvement we could make in the future is reduce this to only 2 HG lobes with improved blending here.
float Q1r = R.SigmaSHG.x + R.SigmaSHG.y + R.SigmaSHG.z;
float Q1m = M.SigmaSHG.x + M.SigmaSHG.y + M.SigmaSHG.z;
float Q2r = R.SigmaSDualHG.x + R.SigmaSDualHG.y + R.SigmaSDualHG.z;
float Q2m = M.SigmaSDualHG.x + M.SigmaSDualHG.y + M.SigmaSDualHG.z;
M.PhaseG = lerp(M.PhaseG , R.PhaseG , Q1m > 0 ? Q1r / Q1m : 0.0);
M.DualPhaseData = lerp(M.DualPhaseData, R.DualPhaseData, Q2m > 0 ? Q2r / Q2m : 0.0);
}
}
// represent a stochastically chosen volume element that is guaranteed to lie between
struct FVolumeSegment
{
float3 Throughput; // path contribution from the start of the volume segment
FVolumeIntersectionInterval Interval;
bool IsValid() { return Interval.VolumeTMin < Interval.VolumeTMax; }
};
FVolumeSegment CreateEmptyVolumeSegment()
{
return (FVolumeSegment)0;
}