291 lines
8.4 KiB
HLSL
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;
|
|
}
|