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

166 lines
5.1 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "./PathTracingAtmosphereCommon.ush"
#include "./PathTracingCloudsCommon.ush"
Texture2D<float4> CloudAccelerationMap;
SamplerState CloudAccelerationMapSampler;
// intersect the cloud layer with high precision
float4 RayCloudIntersectPrecise(float3 Ro, float3 Rd)
{
float4 Result = -1.0;
const FDFVector3 Center = MakeDFVector3(PlanetCenterTranslatedWorldHi, PlanetCenterTranslatedWorldLo);
const FDFScalar CloudTopRadius = DFMultiply(CloudLayerTopKm, SKY_UNIT_TO_CM);
const FDFScalar CloudBotRadius = DFMultiply(CloudLayerBotKm, SKY_UNIT_TO_CM);
const FDFVector3 Co = DFSubtract(Center, Ro);
const FDFScalar b = DFDot(Co, Rd);
const FDFVector3 H = DFSubtract(Co, DFMultiply(Rd, b));
const FDFScalar hd = DFLengthSqr(H);
const FDFScalar TR2 = DFSqr(CloudTopRadius);
FDFScalar ht = DFSubtract(TR2, hd);
if (DFGreater(ht, 0.0))
{
ht = DFSqrt(ht);
FDFScalar q;
if (DFGreater(b, 0.0))
q = DFAdd(b, ht);
else
q = DFSubtract(b, ht);
const float t0 = DFDemote(q);
const float t1 = DFFastDivideDemote(DFSubtract(DFLengthSqr(Co), TR2), q);
Result.x = min(t0, t1);
Result.y = max(t0, t1);
}
const FDFScalar BR2 = DFSqr(CloudBotRadius);
FDFScalar hb = DFSubtract(BR2, hd);
if (DFGreater(hb, 0.0))
{
hb = DFSqrt(hb);
FDFScalar q;
if (DFGreater(b, 0.0))
q = DFAdd(b, hb);
else
q = DFSubtract(b, hb);
const float t0 = DFDemote(q);
const float t1 = DFFastDivideDemote(DFSubtract(DFLengthSqr(Co), BR2), q);
Result.z = min(t0, t1);
Result.w = max(t0, t1);
}
return Result;
}
FVolumeIntersection CloudsIntersect(float3 Origin, float3 Direction, float TMin, float TMax, float PathRoughness)
{
if (PathRoughness > CloudRoughnessCutoff)
{
return CreateEmptyVolumeIntersection();
}
float4 Result = RayCloudIntersectPrecise(Origin, Direction);
float VolumeTMin = max(Result.x, TMin);
float VolumeTMax = min(Result.y, TMax);
if (VolumeTMin < VolumeTMax)
{
// we intersected the top layer of clouds, refine to see if we hit the bottom layer
if (Result.z > 0)
{
// bottom layer start in front - act as the back side of our interval
VolumeTMax = min(Result.z, VolumeTMax);
}
else
{
// we are inside the bottom layer, or missed it entirely - clip the front segment
VolumeTMin = max(VolumeTMin, Result.w);
}
// isect clip slabs, the AABB our clouds are enclosed in
float3 Ro = mul(GetCloudClipBasis(), Origin);
float3 Rd = mul(GetCloudClipBasis(), Direction);
float3 InvRd = rcp(max(abs(Rd), 1e-6)) * select(Rd >= 0, 1.0, -1.0); // protect against division by 0
float2 Slab0 = (-Ro.xy - CloudClipDistKm * SKY_UNIT_TO_CM) * InvRd.xy;
float2 Slab1 = (-Ro.xy + CloudClipDistKm * SKY_UNIT_TO_CM) * InvRd.xy;
VolumeTMin = max(VolumeTMin, max(min(Slab0.x, Slab1.x), min(Slab0.y, Slab1.y)));
VolumeTMax = min(VolumeTMax, min(max(Slab0.x, Slab1.x), max(Slab0.y, Slab1.y)));
if (VolumeTMin >= VolumeTMax)
return CreateEmptyVolumeIntersection();
VolumeTMax = min(VolumeTMax, VolumeTMin + CloudTracingMaxDistance);
#if 1 // use cloud accel map to tighten intersection bounds
// now that we know the ray length, lets march through the grid to see if we can clip away anything
float2 P = Ro.xy + VolumeTMin * Rd.xy;
int2 Idx = PositionToVoxel(P);
float2 NextCrossingT = VolumeTMin + (VoxelToPosition(Idx + int2(Rd.xy >= 0)) - P) * InvRd.xy;
float2 DeltaT = abs(CloudVoxelWidth * InvRd.xy);
int2 Step = select(Rd.xy >= 0, 1, -1);
int2 Stop = select(Rd.xy >= 0, CloudAccelMapResolution, -1);
// start with an empty interval
float ResultTMin = VolumeTMax;
float ResultTMax = VolumeTMin;
float MaxDensity = 0;
for (;;)
{
// visit current voxel
float4 Data = CloudAccelerationMap.Load(uint3(Idx.xy, 0));
bool bHasData = Data.x > 0;
if (bHasData)
{
float CellMinT = VolumeTMin;
float CellMaxT = min3(VolumeTMax, NextCrossingT.x, NextCrossingT.y);
float SlabT0 = (Data.z * SKY_UNIT_TO_CM - Ro.z) * InvRd.z;
float SlabT1 = (Data.w * SKY_UNIT_TO_CM - Ro.z) * InvRd.z;
CellMinT = max(CellMinT, min(SlabT0, SlabT1));
CellMaxT = min(CellMaxT, max(SlabT0, SlabT1));
// do we still have a valid interval?
if (CellMinT < CellMaxT)
{
// expand result bounds
ResultTMin = min(ResultTMin, CellMinT);
ResultTMax = max(ResultTMax, CellMaxT);
MaxDensity = max(Data.x, MaxDensity);
}
}
if (NextCrossingT.x < NextCrossingT.y)
{
// step x first
Idx.x += Step.x;
if (Idx.x == Stop.x || VolumeTMax < NextCrossingT.x)
{
break;
}
VolumeTMin = NextCrossingT.x;
NextCrossingT.x += DeltaT.x;
}
else
{
Idx.y += Step.y;
if (Idx.y == Stop.y || VolumeTMax < NextCrossingT.y)
{
break;
}
VolumeTMin = NextCrossingT.y;
NextCrossingT.y += DeltaT.y;
}
}
return CreateVolumeIntersectionWithDensity(ResultTMin, ResultTMax, MaxDensity);
#else
// if we don't have the cloud accel map, we can't know the upper bound on density...
return CreateVolumeIntersectionWithDensity(VolumeTMin, VolumeTMax, CM_TO_SKY_UNIT);
#endif
}
return CreateEmptyVolumeIntersection();
}