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

446 lines
17 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "../../HeterogeneousVolumes/HeterogeneousVolumesTracingUtils.ush"
#include "../../HeterogeneousVolumes/HeterogeneousVolumesVoxelGridTraversal.ush"
#include "../Utilities/PathTracingRandomSequence.ush"
#include "PathTracingVolumeSampling.ush"
float3 GetTranslatedWorldPos(float3 WorldPos)
{
float3 TranslatedWorldPos = DFFastToTranslatedWorld(WorldPos, PrimaryView.PreViewTranslation);
return TranslatedWorldPos;
}
float GetPhaseG()
{
return 0.0;
}
FVolumeIntersection HeterogeneousVolumesIntersect(float3 Origin, float3 Direction, float TMin, float TMax)
{
float3 WorldBoundsMin = FrustumGridUniformBuffer.TopLevelGridWorldBoundsMin;
float3 WorldBoundsMax = FrustumGridUniformBuffer.TopLevelGridWorldBoundsMax;
if (OrthoGridUniformBuffer.bUseOrthoGrid)
{
WorldBoundsMin = OrthoGridUniformBuffer.TopLevelGridWorldBoundsMin;
WorldBoundsMax = OrthoGridUniformBuffer.TopLevelGridWorldBoundsMax;
}
if (OrthoGridUniformBuffer.bUseOrthoGrid || FrustumGridUniformBuffer.bUseFrustumGrid)
{
float3 TranslatedWorldBoundsMin = GetTranslatedWorldPos(WorldBoundsMin);
float3 TranslatedWorldBoundsMax = GetTranslatedWorldPos(WorldBoundsMax);
float2 RayHitT = IntersectAABB(
Origin,
Direction,
TMin,
TMax,
TranslatedWorldBoundsMin,
TranslatedWorldBoundsMax
);
return CreateVolumeIntersection(RayHitT.x, RayHitT.y);
}
return CreateEmptyVolumeIntersection();
}
FVolumeDensityBounds HeterogeneousVolumesGetDensityBounds(float3 Origin, float3 Direction, float TMin, float TMax)
{
// Density bounds are iteratively discovered when executing the DDA on the majorant grid
return CreateVolumeDensityBound(0, 0);
}
FVolumeShadedResult GetOrthoVoxelGridDensity(float3 TranslatedWorldPos)
{
float3 SigmaT = 0.0;
float3 Albedo = 0.0;
float3 Emission = 0.0;
float3 TranslatedWorldBoundsMin = GetTranslatedWorldPos(OrthoGridUniformBuffer.TopLevelGridWorldBoundsMin);
float3 TranslatedWorldBoundsMax = GetTranslatedWorldPos(OrthoGridUniformBuffer.TopLevelGridWorldBoundsMax);
float3 TranslatedWorldBoundsExtent = TranslatedWorldBoundsMax - TranslatedWorldBoundsMin;
float3 GridUV = (TranslatedWorldPos - TranslatedWorldBoundsMin) / TranslatedWorldBoundsExtent;
if (all(GridUV >= 0.0) && all(GridUV <= 1.0))
{
float3 TopLevelVoxelPos = GridUV * OrthoGridUniformBuffer.TopLevelGridResolution;
uint LinearTopLevelVoxelPos = GetLinearIndex(TopLevelVoxelPos, OrthoGridUniformBuffer.TopLevelGridResolution);
if (OrthoGridUniformBuffer.bEnableIndirectionGrid)
{
FTopLevelGridData TopLevelGridData = OrthoGridUniformBuffer.TopLevelGridBuffer[LinearTopLevelVoxelPos];
if (IsBottomLevelAllocated(TopLevelGridData))
{
uint IndirectionGridIndex = GetBottomLevelIndex(TopLevelGridData);
uint3 IndirectionGridVoxelResolution = GetBottomLevelVoxelResolution(TopLevelGridData);
// Convert to Indirection voxel-space
float3 IndirectionGridVoxelPos = frac(TopLevelVoxelPos) * IndirectionGridVoxelResolution;
int3 IndirectionGridVoxelPosAsInt = clamp(IndirectionGridVoxelPos, 0, IndirectionGridVoxelResolution - 1);
FTopLevelGridData IndirectionData = OrthoGridUniformBuffer.IndirectionGridBuffer[IndirectionGridIndex + MortonEncode3(IndirectionGridVoxelPosAsInt)];
if (IsBottomLevelAllocated(IndirectionData))
{
uint BottomLevelIndex = GetBottomLevelIndex(IndirectionData);
uint3 BottomLevelVoxelResolution = GetBottomLevelVoxelResolution(IndirectionData);
// Constant interpolation
float3 BottomLevelVoxelPos = frac(IndirectionGridVoxelPos) * BottomLevelVoxelResolution;
int3 BottomLevelVoxelPosAsInt = clamp(BottomLevelVoxelPos, 0, BottomLevelVoxelResolution - 1);
uint LinearBottomLevelVoxelPos = BottomLevelIndex + MortonEncode3(BottomLevelVoxelPosAsInt);
SigmaT = GetExtinction(OrthoGridUniformBuffer.ExtinctionGridBuffer[LinearBottomLevelVoxelPos]);
Albedo = GetAlbedo(OrthoGridUniformBuffer.ScatteringGridBuffer[LinearBottomLevelVoxelPos]);
Emission = GetEmission(OrthoGridUniformBuffer.EmissionGridBuffer[LinearBottomLevelVoxelPos]);
}
}
}
else
{
FTopLevelGridData TopLevelData = OrthoGridUniformBuffer.TopLevelGridBuffer[LinearTopLevelVoxelPos];
if (IsBottomLevelAllocated(TopLevelData))
{
uint BottomLevelIndex = GetBottomLevelIndex(OrthoGridUniformBuffer.TopLevelGridBuffer[LinearTopLevelVoxelPos]);
uint3 BottomLevelVoxelResolution = GetBottomLevelVoxelResolution(OrthoGridUniformBuffer.TopLevelGridBuffer[LinearTopLevelVoxelPos]);
// Constant Interpolation
float3 BottomLevelVoxelPos = frac(TopLevelVoxelPos) * BottomLevelVoxelResolution;
int3 BottomLevelVoxelPosAsInt = clamp(BottomLevelVoxelPos, 0, BottomLevelVoxelResolution - 1);
uint LinearBottomLevelVoxelPos = BottomLevelIndex + MortonEncode3(BottomLevelVoxelPosAsInt);
SigmaT = GetExtinction(OrthoGridUniformBuffer.ExtinctionGridBuffer[LinearBottomLevelVoxelPos]);
Albedo = GetAlbedo(OrthoGridUniformBuffer.ScatteringGridBuffer[LinearBottomLevelVoxelPos]);
Emission = GetEmission(OrthoGridUniformBuffer.EmissionGridBuffer[LinearBottomLevelVoxelPos]);
}
}
}
FVolumeShadedResult Result = (FVolumeShadedResult)0;
Result.SigmaT = SigmaT;
Result.SigmaSHG = Albedo;
Result.PhaseG = GetPhaseG();
Result.Emission = Emission;
return Result;
}
FVolumeShadedResult GetFrustumVoxelGridDensity(float3 TranslatedWorldPos, inout bool bInFrustum)
{
float3 SigmaT = 0.0;
float3 Albedo = 0.0;
float3 Emission = 0.0;
// Convert TranslatedWorldPos to voxel space
float3 WorldPos = DFHackToFloat(DFFastSubtract(TranslatedWorldPos, PrimaryView.PreViewTranslation));
float3 ViewPos = mul(float4(WorldPos, 1), FrustumGridUniformBuffer.WorldToView).xyz;
int3 VoxelDimensions = FrustumGridUniformBuffer.VoxelDimensions;
float NearPlaneDepth = FrustumGridUniformBuffer.NearPlaneDepth;
float FarPlaneDepth = FrustumGridUniformBuffer.FarPlaneDepth;
float TanHalfFOV = FrustumGridUniformBuffer.TanHalfFOV;
float3 VoxelPos = ViewToVoxel(ViewPos, VoxelDimensions, NearPlaneDepth, FarPlaneDepth, TanHalfFOV);
bInFrustum = all(VoxelPos > 0) && all(VoxelPos < FrustumGridUniformBuffer.TopLevelFroxelGridResolution);
if (bInFrustum)
{
uint LinearTopLevelVoxelPos = GetLinearIndex(VoxelPos, FrustumGridUniformBuffer.TopLevelFroxelGridResolution);
FTopLevelGridData TopLevelGridData = FrustumGridUniformBuffer.TopLevelFroxelGridBuffer[LinearTopLevelVoxelPos];
if (IsBottomLevelAllocated(TopLevelGridData))
{
uint BottomLevelIndex = GetBottomLevelIndex(TopLevelGridData);
uint3 BottomLevelVoxelResolution = GetBottomLevelVoxelResolution(TopLevelGridData);
float3 BottomLevelVoxelPos = frac(VoxelPos) * BottomLevelVoxelResolution;
uint LinearBottomLevelVoxelPos = BottomLevelIndex + MortonEncode3(uint3(BottomLevelVoxelPos));
SigmaT = GetExtinction(FrustumGridUniformBuffer.ExtinctionFroxelGridBuffer[LinearBottomLevelVoxelPos]);
Albedo = GetAlbedo(FrustumGridUniformBuffer.ScatteringFroxelGridBuffer[LinearBottomLevelVoxelPos]);
Emission = GetEmission(FrustumGridUniformBuffer.EmissionFroxelGridBuffer[LinearBottomLevelVoxelPos]);
}
}
// Return struct
FVolumeShadedResult Result = (FVolumeShadedResult) 0;
Result.SigmaT = SigmaT;
Result.SigmaSHG = Albedo;
Result.PhaseG = GetPhaseG();
Result.Emission = Emission;
return Result;
}
FVolumeShadedResult HeterogeneousVolumesGetDensity(float3 TranslatedWorldPos)
{
FVolumeShadedResult Result = (FVolumeShadedResult) 0;
bool bInFrustum = false;
if (FrustumGridUniformBuffer.bUseFrustumGrid)
{
Result = GetFrustumVoxelGridDensity(TranslatedWorldPos, bInFrustum);
}
if (!bInFrustum && OrthoGridUniformBuffer.bUseOrthoGrid)
{
Result = GetOrthoVoxelGridDensity(TranslatedWorldPos);
}
return Result;
}
struct FVolumeTrackingResult
{
float3 Throughput;
float Pdf;
float Distance;
float3 SigmaBar;
bool bIsCollision;
};
FVolumeTrackingResult CreateVolumeTrackingResult(float Distance)
{
FVolumeTrackingResult Result = (FVolumeTrackingResult)0;
Result.Throughput = 1;
Result.Pdf = 0;
Result.Distance = Distance;
Result.SigmaBar = 0;
Result.bIsCollision = false;
return Result;
}
// Concatenates the result of rhs onto lhs
FVolumeTrackingResult ConcatenateVolumeTrackingResult(FVolumeTrackingResult lhs, FVolumeTrackingResult rhs)
{
FVolumeTrackingResult Result;
Result.Throughput = lhs.Throughput * rhs.Throughput;
Result.Pdf = lhs.Pdf * rhs.Pdf;
Result.Distance = lhs.Distance + rhs.Distance;
Result.SigmaBar = rhs.SigmaBar;
Result.bIsCollision = rhs.bIsCollision;
return Result;
}
FVolumeTrackingResult HeterogeneousVolumesDeltaTracking(float3 PathThroughput, float3 Origin, float3 Direction, float TMax, FMajorantData MajorantData, inout RandomSequence RandSequence)
{
FVolumeTrackingResult Sample = CreateVolumeTrackingResult(0);
Sample.SigmaBar = max(MajorantData.Majorant, 0);
float RandValue = RandomSequence_GenerateSample1D(RandSequence);
#define SAMPLE_ACHROMATIC 0
#if SAMPLE_ACHROMATIC
float DeltaT = -log(1.0 - RandValue) / Sample.SigmaBar.x;
#else
float DeltaT = SampleSpectralTransmittance(RandValue, Sample.SigmaBar, PathThroughput);
if (DeltaT < 0)
{
Sample.Throughput = 0;
Sample.Distance = -1;
return Sample;
}
#endif
Sample.Distance = min(Sample.Distance + DeltaT, TMax);
if (Sample.Distance < TMax)
{
Sample.bIsCollision = true;
float CollisionWeight = 1.0;
#if SAMPLE_ACHROMATIC
Sample.Throughput *= CollisionWeight / Sample.SigmaBar.x;
float Transmittance = exp(-Sample.SigmaBar.x * Sample.Distance);
Sample.Pdf = Transmittance * Sample.SigmaBar.x;
#else
float4 EvalResult = EvaluateSpectralTransmittanceHit(Sample.Distance, Sample.SigmaBar, PathThroughput);
Sample.Throughput = CollisionWeight * EvalResult.xyz;
Sample.Pdf = EvalResult.w;
}
else
{
float4 EvalResult = EvaluateSpectralTransmittanceMiss(Sample.Distance, Sample.SigmaBar, PathThroughput);
Sample.Throughput = EvalResult.xyz;
Sample.Pdf = EvalResult.w;
#endif
}
return Sample;
}
FVolumeTrackingResult HeterogeneousVolumesRatioTracking(float3 PathThroughput, float3 Origin, float3 Direction, float TMax, FMajorantData MajorantData, inout RandomSequence RandSequence)
{
FVolumeTrackingResult Sample = CreateVolumeTrackingResult(0);
Sample.SigmaBar = max(MajorantData.Majorant, 0);
while ((Sample.Distance < TMax))
{
float RandValue = RandomSequence_GenerateSample1D(RandSequence);
#if SAMPLE_ACHROMATIC
float DeltaT = -log(1.0 - RandValue) / Sample.SigmaBar.x;
#else
float3 CombinedThroughput = PathThroughput * Sample.Throughput;
float DeltaT = SampleSpectralTransmittance(RandValue, Sample.SigmaBar, CombinedThroughput);
if (DeltaT < 0)
{
Sample.Throughput = 0;
Sample.Distance = -1;
return Sample;
}
#endif
float CollisionDistance = Sample.Distance + DeltaT;
if (CollisionDistance < TMax)
{
float3 SamplePos = Origin + Direction * CollisionDistance;
float3 SigmaT = HeterogeneousVolumesGetDensity(SamplePos).SigmaT;
float3 SpectralNullCollisionWeight = max(Sample.SigmaBar - SigmaT, 0);
#if SAMPLE_ACHROMATIC
Sample.Throughput *= SpectralNullCollisionWeight / Sample.SigmaBar.x;
float Transmittance = exp(-Sample.SigmaBar.x * DeltaT);
Sample.Pdf *= Transmittance * Sample.SigmaBar.x;
#else
float4 EvalResult = EvaluateSpectralTransmittanceHit(DeltaT, Sample.SigmaBar, CombinedThroughput);
Sample.Throughput *= SpectralNullCollisionWeight * EvalResult.xyz;
Sample.Pdf *= EvalResult.w;
}
else
{
float4 EvalResult = EvaluateSpectralTransmittanceMiss(TMax - Sample.Distance, Sample.SigmaBar, CombinedThroughput);
Sample.Throughput *= EvalResult.xyz;
Sample.Pdf *= EvalResult.w;
#endif
}
Sample.Distance = min(CollisionDistance, TMax);
}
return Sample;
}
FVolumeTrackingResult HeterogeneousVolumesMajorantBasedTracking(float3 PathThroughput, float3 WorldRayOrigin, float3 Direction, float TMin, float TMax, int bTransmittanceOnly, FMajorantData OverlappingMajorant, inout RandomSequence RandSequence)
{
FVolumeTrackingResult Result = CreateVolumeTrackingResult(TMin);
Result.Throughput = PathThroughput;
float3 WorldRayBegin = WorldRayOrigin + Direction * TMin;
float3 WorldRayEnd = WorldRayOrigin + Direction * TMax;
float WorldRayTMax = length(WorldRayEnd - WorldRayBegin);
// Transform to voxel-space
float3 WorldBoundsMin = GetTranslatedWorldPos(OrthoGridUniformBuffer.TopLevelGridWorldBoundsMin);
float3 WorldBoundsMax = GetTranslatedWorldPos(OrthoGridUniformBuffer.TopLevelGridWorldBoundsMax);
float3 TopLevelGridWorldBoundsExtent = WorldBoundsMax - WorldBoundsMin;
float3 VoxelRayBegin = (WorldRayBegin - WorldBoundsMin) / TopLevelGridWorldBoundsExtent * OrthoGridUniformBuffer.TopLevelGridResolution;
float3 VoxelRayEnd = (WorldRayEnd - WorldBoundsMin) / TopLevelGridWorldBoundsExtent * OrthoGridUniformBuffer.TopLevelGridResolution;
float3 VoxelRayDirection = VoxelRayEnd - VoxelRayBegin;
float VoxelRayTMin = 0.0;
float VoxelRayTMax = length(VoxelRayDirection);
if (VoxelRayTMin >= VoxelRayTMax && !bTransmittanceOnly)
{
FVolumeTrackingResult Sample = HeterogeneousVolumesDeltaTracking(Result.Throughput, WorldRayBegin, Direction, TMax - TMin, OverlappingMajorant, RandSequence);
if (Sample.Distance > 0.0)
{
Result = ConcatenateVolumeTrackingResult(Result, Sample);
}
return Result;
}
VoxelRayDirection /= VoxelRayTMax;
float VoxelToWorldScale = WorldRayTMax / VoxelRayTMax;
// March majorant grid via DDA
float3 VoxelRayDirectionInv = 1.0 / VoxelRayDirection;
float VoxelRayMarchT = VoxelRayTMin;
float3 VoxelRayMarchPos = VoxelRayBegin;
float3 VoxelBoundsPos;
VoxelBoundsPos.x = sign(VoxelRayDirection.x) > 0 ? floor(VoxelRayMarchPos.x) + 1 : ceil(VoxelRayMarchPos.x) - 1;
VoxelBoundsPos.y = sign(VoxelRayDirection.y) > 0 ? floor(VoxelRayMarchPos.y) + 1 : ceil(VoxelRayMarchPos.y) - 1;
VoxelBoundsPos.z = sign(VoxelRayDirection.z) > 0 ? floor(VoxelRayMarchPos.z) + 1 : ceil(VoxelRayMarchPos.z) - 1;
float3 VoxelBoundsHitT = (VoxelBoundsPos - VoxelRayMarchPos) * VoxelRayDirectionInv;
// Remove floating-point rounding error
float Epsilon = 1.0e-4;
if (VoxelBoundsHitT.x <= Epsilon) VoxelBoundsHitT.x += abs(VoxelRayDirectionInv.x);
if (VoxelBoundsHitT.y <= Epsilon) VoxelBoundsHitT.y += abs(VoxelRayDirectionInv.y);
if (VoxelBoundsHitT.z <= Epsilon) VoxelBoundsHitT.z += abs(VoxelRayDirectionInv.z);
while ((VoxelRayMarchT < VoxelRayTMax) && any(Result.Throughput > Epsilon) && !Result.bIsCollision)
{
float VoxelRayMarchDeltaT = 0.0;
if (VoxelBoundsHitT.x < VoxelBoundsHitT.y)
{
if (VoxelBoundsHitT.x < VoxelBoundsHitT.z)
{
VoxelRayMarchDeltaT = VoxelBoundsHitT.x - VoxelRayMarchT;
VoxelBoundsHitT.x += abs(VoxelRayDirectionInv.x);
}
else
{
VoxelRayMarchDeltaT = VoxelBoundsHitT.z - VoxelRayMarchT;
VoxelBoundsHitT.z += abs(VoxelRayDirectionInv.z);
}
}
else
{
if (VoxelBoundsHitT.y < VoxelBoundsHitT.z)
{
VoxelRayMarchDeltaT = VoxelBoundsHitT.y - VoxelRayMarchT;
VoxelBoundsHitT.y += abs(VoxelRayDirectionInv.y);
}
else
{
VoxelRayMarchDeltaT = VoxelBoundsHitT.z - VoxelRayMarchT;
VoxelBoundsHitT.z += abs(VoxelRayDirectionInv.z);
}
}
// Clip voxel delta-t by overall voxel ray-length.
if (VoxelRayMarchT + VoxelRayMarchDeltaT > VoxelRayTMax)
{
VoxelRayMarchDeltaT = VoxelRayTMax - VoxelRayMarchT;
}
// Sample at the midpoint of the voxel ray
float3 VoxelEndPos = VoxelRayMarchPos + VoxelRayDirection * VoxelRayMarchDeltaT;
float3 VoxelSamplePos = (VoxelRayMarchPos + VoxelEndPos) * 0.5;
uint VoxelSampleLinearPos = GetLinearIndex(VoxelSamplePos, OrthoGridUniformBuffer.TopLevelGridResolution);
FMajorantData MajorantData = GetMajorantData(OrthoGridUniformBuffer.MajorantGridBuffer[VoxelSampleLinearPos]);
MergeMajorantData(MajorantData, OverlappingMajorant);
float3 TrackingOrigin = WorldRayBegin + Direction * VoxelRayMarchT * VoxelToWorldScale;
float WorldRayMarchDeltaT = VoxelRayMarchDeltaT * VoxelToWorldScale;
FVolumeTrackingResult Sample;
if (bTransmittanceOnly)
{
Sample = HeterogeneousVolumesRatioTracking(Result.Throughput, TrackingOrigin, Direction, WorldRayMarchDeltaT, MajorantData, RandSequence);
}
else
{
Sample = HeterogeneousVolumesDeltaTracking(Result.Throughput, TrackingOrigin, Direction, WorldRayMarchDeltaT, MajorantData, RandSequence);
}
if (Sample.Distance < 0)
{
return Sample;
}
Result = ConcatenateVolumeTrackingResult(Result, Sample);
VoxelRayMarchT += VoxelRayMarchDeltaT;
VoxelRayMarchPos = VoxelEndPos;
}
return Result;
}
float3 HeterogeneousVolumesGetTransmittance(float3 PathThroughput, float3 Origin, float3 Direction, float TMin, float TMax, inout RandomSequence RandSequence)
{
bool bTransmittanceOnly = true;
FMajorantData EmptyOverlappingMajorant = CreateMajorantData(0.0f, 0.0f);
return HeterogeneousVolumesMajorantBasedTracking(PathThroughput, Origin, Direction, TMin, TMax, bTransmittanceOnly, EmptyOverlappingMajorant, RandSequence).Throughput;
}
FVolumeTrackingResult HeterogeneousVolumesSampleDistance(float3 PathThroughput, float3 Origin, float3 Direction, float TMin, float TMax, FMajorantData OverlappingMajorant, inout RandomSequence RandSequence)
{
bool bTransmittanceOnly = false;
return HeterogeneousVolumesMajorantBasedTracking(PathThroughput, Origin, Direction, TMin, TMax, bTransmittanceOnly, OverlappingMajorant, RandSequence);
}