302 lines
12 KiB
HLSL
302 lines
12 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "./PathTracingAtmosphereCommon.ush"
|
|
#include "../../DoubleFloat.ush"
|
|
#include "../../SkyAtmosphereCommon.ush"
|
|
|
|
|
|
#define PLANET_ISECT_USES_DOUBLE_WORD_MATH 1
|
|
|
|
// Shader parameters
|
|
// NOTE: Atmosphere is a global struct (not directly defined here)
|
|
#ifndef PlanetCenterTranslatedWorldHi
|
|
float3 PlanetCenterTranslatedWorldHi;
|
|
float3 PlanetCenterTranslatedWorldLo;
|
|
Texture2D<float3> AtmosphereOpticalDepthLUT;
|
|
SamplerState AtmosphereOpticalDepthLUTSampler;
|
|
#endif
|
|
|
|
#if PLANET_ISECT_USES_DOUBLE_WORD_MATH
|
|
|
|
// intersect the planet using high precision
|
|
// returns: planet hit T, atmo hit min/max (or -1.0 for any if missed)
|
|
float3 RayPlanetIntersectPrecise(float3 Ro, float3 Rd)
|
|
{
|
|
float3 Result = -1.0;
|
|
|
|
const FDFVector3 Center = MakeDFVector3(PlanetCenterTranslatedWorldHi, PlanetCenterTranslatedWorldLo);
|
|
const FDFScalar PlanetRadius = DFMultiply(View.SkyAtmosphereBottomRadiusKm, SKY_UNIT_TO_CM);
|
|
const FDFScalar AtmoRadius = DFMultiply(View.SkyAtmosphereTopRadiusKm , 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 AR2 = DFSqr(AtmoRadius);
|
|
FDFScalar ha = DFSubtract(AR2, hd);
|
|
if (DFGreater(ha, 0.0))
|
|
{
|
|
ha = DFSqrt(ha);
|
|
FDFScalar q;
|
|
if (DFGreater(b, 0.0))
|
|
q = DFAdd(b, ha);
|
|
else
|
|
q = DFSubtract(b, ha);
|
|
const float t0 = DFDemote(q);
|
|
const float t1 = DFFastDivideDemote(DFSubtract(DFLengthSqr(Co), AR2), q);
|
|
Result.y = min(t0, t1);
|
|
Result.z = max(t0, t1);
|
|
}
|
|
const FDFScalar PR2 = DFSqr(PlanetRadius);
|
|
FDFScalar hp = DFSubtract(PR2, hd);
|
|
if (DFGreater(hp, 0.0))
|
|
{
|
|
hp = DFSqrt(hp);
|
|
FDFScalar q;
|
|
if (DFGreater(b, 0.0))
|
|
q = DFAdd(b, hp);
|
|
else
|
|
q = DFSubtract(b, hp);
|
|
const float t0 = DFDemote(q);
|
|
const float t1 = DFFastDivideDemote(DFSubtract(DFLengthSqr(Co), PR2), q);
|
|
Result.x = min(t0, t1);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
#else
|
|
|
|
// robust intersector with plain floats, still runs into trouble beyond 1000km or so
|
|
float3 RayPlanetIntersectPrecise(float3 Ro, float3 Rd)
|
|
{
|
|
float3 Result = -1.0;
|
|
float3 Center = PlanetCenterTranslatedWorldHi + PlanetCenterTranslatedWorldLo;
|
|
float PlanetRadius = View.SkyAtmosphereBottomRadiusKm * SKY_UNIT_TO_CM;
|
|
float AtmoRadius = View.SkyAtmosphereTopRadiusKm * SKY_UNIT_TO_CM;
|
|
|
|
float3 co = Center - Ro;
|
|
float b = dot(co, Rd);
|
|
// "Precision Improvements for Ray / Sphere Intersection" - Ray Tracing Gems (2019)
|
|
// https://link.springer.com/content/pdf/10.1007%2F978-1-4842-4427-2_7.pdf
|
|
// NOTE: we assume the incoming ray direction is normalized
|
|
float hd = length2(co - b * Rd);
|
|
float ha = AtmoRadius * AtmoRadius - hd;
|
|
if (ha > 0)
|
|
{
|
|
ha = sqrt(ha);
|
|
float q = b > 0.0 ? b + ha : b - ha;
|
|
float t0 = q;
|
|
float t1 = (length2(co) - AtmoRadius * AtmoRadius) / q;
|
|
Result.y = min(t0, t1);
|
|
Result.z = max(t0, t1);
|
|
}
|
|
float hp = PlanetRadius * PlanetRadius - hd;
|
|
if (hp > 0)
|
|
{
|
|
hp = sqrt(hp);
|
|
float q = b > 0.0 ? b + hp : b - hp;
|
|
float t0 = q;
|
|
float t1 = (length2(co) - PlanetRadius * PlanetRadius) / q;
|
|
Result.x = min(t0, t1);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
#endif
|
|
|
|
#if CLOUD_LAYER_PIXEL_SHADER == 0
|
|
|
|
FVolumeIntersection AtmosphereIntersect(float3 Origin, float3 Direction, float TMin, float TMax)
|
|
{
|
|
float3 Result = RayPlanetIntersectPrecise(Origin, Direction);
|
|
// clip to provided ray interval
|
|
return CreateVolumeIntersection(
|
|
max(TMin, Result.y),
|
|
min(TMax, Result.z),
|
|
Result.x > TMin && Result.x < TMax ? Result.x : -1.0);
|
|
}
|
|
|
|
FPackedPathTracingPayload AtmosphereGetBlockerHit(float3 Origin, float3 Direction, float HitT, bool bIsCameraRay)
|
|
{
|
|
float3 Center = PlanetCenterTranslatedWorldHi + PlanetCenterTranslatedWorldLo;
|
|
float3 WorldP = Origin + HitT * Direction;
|
|
float3 WorldN = normalize(WorldP - Center);
|
|
|
|
FPathTracingPayload Payload = (FPathTracingPayload)0;
|
|
Payload.SetBaseColor(Atmosphere.GroundAlbedo.rgb);
|
|
Payload.BSDFOpacity = 1.0;
|
|
Payload.WorldNormal = Payload.WorldGeoNormal = Payload.WorldSmoothNormal = WorldN;
|
|
Payload.ShadingModelID = SHADINGMODELID_NUM; // defaults to diffuse
|
|
Payload.HitT = HitT;
|
|
Payload.PrimitiveLightingChannelMask = 7; // visible to all lights
|
|
Payload.SetFrontFace();
|
|
if (bIsCameraRay && (VolumeFlags & PATH_TRACER_VOLUME_HOLDOUT_ATMOSPHERE) != 0)
|
|
{
|
|
Payload.SetHoldout();
|
|
Payload.ShadingModelID = SHADINGMODELID_UNLIT;
|
|
}
|
|
return PackPathTracingPayload(Payload);
|
|
}
|
|
|
|
#endif
|
|
|
|
struct FSphericalPlanetParameters {
|
|
float HeightKm; // Distance from planet center (in Km)
|
|
float ZenithCosAngle; // angle between the ray and the planet normal (+1 goes up towards space, -1 points down toward the ground)
|
|
|
|
FSphericalPlanetParameters GetAt(float TKm)
|
|
{
|
|
// work out the ViewHeight / CosAngle of the specified point along the ray (in SkyUnits)
|
|
const float TOverH = TKm / HeightKm;
|
|
const float RelativeHeight = sqrt(mad(mad(2.0f, ZenithCosAngle, TOverH), TOverH, 1.0f));
|
|
FSphericalPlanetParameters Result;
|
|
Result.HeightKm = HeightKm * RelativeHeight;
|
|
Result.ZenithCosAngle = (TOverH + ZenithCosAngle) / RelativeHeight;
|
|
return Result;
|
|
}
|
|
|
|
float GetHorizonCosine()
|
|
{
|
|
return ComputeHorizonCosine(HeightKm, Atmosphere.BottomRadiusKm);
|
|
}
|
|
};
|
|
|
|
FSphericalPlanetParameters GetSphericalPlanetParameters(float3 Origin, float3 Direction)
|
|
{
|
|
FSphericalPlanetParameters Result = (FSphericalPlanetParameters)0;
|
|
#if PLANET_ISECT_USES_DOUBLE_WORD_MATH
|
|
// Compute the origin's height/angle with high precision since it involves distances to the planet center
|
|
const FDFVector3 Center = MakeDFVector3(PlanetCenterTranslatedWorldHi, PlanetCenterTranslatedWorldLo);
|
|
|
|
const FDFVector3 OC = DFMultiply(DFSubtract(Origin, Center), CM_TO_SKY_UNIT);
|
|
|
|
const FDFScalar HeightKm = DFLength(OC);
|
|
Result.HeightKm = DFDemote(HeightKm);
|
|
Result.ZenithCosAngle = DFFastDivideDemote(DFDot(OC, Direction), HeightKm);
|
|
#else
|
|
// Get the ViewHeight / CosAngle of the origin
|
|
const float3 PosFromCenter = Origin - (PlanetCenterTranslatedWorldHi + PlanetCenterTranslatedWorldLo);
|
|
Result.HeightKm = length(PosFromCenter * CM_TO_SKY_UNIT);
|
|
Result.ZenithCosAngle = dot(normalize(PosFromCenter), Direction);
|
|
#endif
|
|
return Result;
|
|
}
|
|
|
|
FVolumeDensityBounds AtmosphereGetDensityBounds(float3 Origin, float3 Direction, float TMin, float TMax)
|
|
{
|
|
#if 0
|
|
// conservative global bound for validation
|
|
const float ta = Atmosphere.AbsorptionDensity0LayerWidth;
|
|
const float tv = Atmosphere.AbsorptionDensity0LinearTerm * ta + Atmosphere.AbsorptionDensity0ConstantTerm;
|
|
float3 SigmaMax = (Atmosphere.MieExtinction.rgb + Atmosphere.RayleighScattering.rgb + tv * Atmosphere.AbsorptionExtinction.rgb) * CM_TO_SKY_UNIT;
|
|
return CreateVolumeDensityBound(0.0, SigmaMax);
|
|
#else
|
|
// optimized bounds for the current ray
|
|
FSphericalPlanetParameters Params = GetSphericalPlanetParameters(Origin, Direction);
|
|
|
|
const float Ha = Params.GetAt(TMin * CM_TO_SKY_UNIT).HeightKm;
|
|
const float Hb = Params.GetAt(TMax * CM_TO_SKY_UNIT).HeightKm;
|
|
// one of the two end-points has to be the closest
|
|
float HLo = min(Ha, Hb);
|
|
float HHi = max(Ha, Hb);
|
|
// compute the closest point to the center along the ray
|
|
float TClosest = -Params.HeightKm * Params.ZenithCosAngle * SKY_UNIT_TO_CM;
|
|
// see if it falls within our ray segment
|
|
if (TClosest > TMin && TClosest < TMax)
|
|
{
|
|
// if so, we can reduce our lower bound a bit more
|
|
HLo = min(HLo, Params.HeightKm * sqrt(saturate(1 - Params.ZenithCosAngle * Params.ZenithCosAngle)));
|
|
}
|
|
|
|
// now compute density bounds for each component of the atmosphere
|
|
// exponential portion is easy since this is a stricly decreasing function of altitude
|
|
const float2 SampleHeight = max(0.0, float2(HLo, HHi) - Atmosphere.BottomRadiusKm);
|
|
const float2 DensityMie = exp(Atmosphere.MieDensityExpScale * SampleHeight.yx); // flip height lo/hi because function is decreasing
|
|
const float2 DensityRay = exp(Atmosphere.RayleighDensityExpScale * SampleHeight.yx);
|
|
|
|
// Ozone is a bit trickier -- first eval both ends
|
|
float2 DensityOzo = float2(
|
|
SampleHeight.x < Atmosphere.AbsorptionDensity0LayerWidth ?
|
|
saturate(Atmosphere.AbsorptionDensity0LinearTerm * SampleHeight.x + Atmosphere.AbsorptionDensity0ConstantTerm) :
|
|
saturate(Atmosphere.AbsorptionDensity1LinearTerm * SampleHeight.x + Atmosphere.AbsorptionDensity1ConstantTerm),
|
|
SampleHeight.y < Atmosphere.AbsorptionDensity0LayerWidth ?
|
|
saturate(Atmosphere.AbsorptionDensity0LinearTerm * SampleHeight.y + Atmosphere.AbsorptionDensity0ConstantTerm) :
|
|
saturate(Atmosphere.AbsorptionDensity1LinearTerm * SampleHeight.y + Atmosphere.AbsorptionDensity1ConstantTerm)
|
|
);
|
|
if (DensityOzo.x > DensityOzo.y)
|
|
DensityOzo = DensityOzo.yx; // flip if wrong way around
|
|
if (SampleHeight.x < Atmosphere.AbsorptionDensity0LayerWidth && Atmosphere.AbsorptionDensity0LayerWidth < SampleHeight.y)
|
|
{
|
|
// height range includes the peak -- bump max to peak
|
|
const float ta = Atmosphere.AbsorptionDensity0LayerWidth;
|
|
const float tv = Atmosphere.AbsorptionDensity0LinearTerm * ta + Atmosphere.AbsorptionDensity0ConstantTerm;
|
|
DensityOzo.y = tv;
|
|
}
|
|
return CreateVolumeDensityBound(
|
|
(DensityMie.x * Atmosphere.MieExtinction.rgb + DensityRay.x * Atmosphere.RayleighScattering.rgb + DensityOzo.x * Atmosphere.AbsorptionExtinction.rgb) * CM_TO_SKY_UNIT,
|
|
(DensityMie.y * Atmosphere.MieExtinction.rgb + DensityRay.y * Atmosphere.RayleighScattering.rgb + DensityOzo.y * Atmosphere.AbsorptionExtinction.rgb) * CM_TO_SKY_UNIT
|
|
);
|
|
#endif
|
|
}
|
|
|
|
FVolumeShadedResult AtmosphereGetDensity(float3 TranslatedWorldPos)
|
|
{
|
|
#if 0 //PLANET_ISECT_USES_DOUBLE_WORD_MATH
|
|
// Extra precision here doesn't seem to matter too much -- probably because density function is very smooth
|
|
const float H = GetSphericalPlanetParameters(TranslatedWorldPos, float3(0, 0, 1)).HeightKm;
|
|
MediumSampleRGB Medium = SampleAtmosphereMediumRGB(float3(0.0, 0.0, H));
|
|
#else
|
|
float3 PosFromCenter = TranslatedWorldPos - (PlanetCenterTranslatedWorldHi + PlanetCenterTranslatedWorldLo);
|
|
MediumSampleRGB Medium = SampleAtmosphereMediumRGB(PosFromCenter * CM_TO_SKY_UNIT);
|
|
#endif
|
|
FVolumeShadedResult Result = (FVolumeShadedResult)0;
|
|
Result.SigmaT = Medium.Extinction * CM_TO_SKY_UNIT;
|
|
Result.SigmaSRayleigh = Medium.ScatteringRay * CM_TO_SKY_UNIT;
|
|
Result.SigmaSHG = Medium.ScatteringMie * CM_TO_SKY_UNIT;
|
|
Result.PhaseG = Atmosphere.MiePhaseG;
|
|
return Result;
|
|
}
|
|
|
|
#if CLOUD_LAYER_PIXEL_SHADER == 0
|
|
|
|
float3 AtmosphereGetOpticalDensity(FSphericalPlanetParameters Params)
|
|
{
|
|
float3 Tau = 0;
|
|
if (Params.HeightKm < Atmosphere.BottomRadiusKm)
|
|
{
|
|
// Starting below ground level, need to compute transmittance through homogeneous region first
|
|
// TODO: merge this into the LUT somehow (requires a new parameterization)
|
|
|
|
// Ground level extinction, applies to entire planet core
|
|
// We keep this value in reciprocal sky units because we measure the distance in sky units as well (canceling out to unitless optical depth)
|
|
float3 SigmaT = SampleAtmosphereMediumRGB(float3(0, 0, 0)).Extinction;
|
|
|
|
const float C = Params.ZenithCosAngle;
|
|
const float H = Params.HeightKm;
|
|
const float B = Atmosphere.BottomRadiusKm;
|
|
const float DistanceToGround = -C * H + sqrt(max(B * B + H * H * (C * C - 1.0f), 0.0f));
|
|
Tau = DistanceToGround * SigmaT;
|
|
|
|
// Get remaining density from the point on the ground
|
|
Params = Params.GetAt(DistanceToGround);
|
|
}
|
|
float2 UV = ToAtmosphereOpticalDepthLUTUV(Params.HeightKm, Params.ZenithCosAngle, Atmosphere.BottomRadiusKm, Atmosphere.TopRadiusKm);
|
|
return Tau + AtmosphereOpticalDepthLUT.SampleLevel(AtmosphereOpticalDepthLUTSampler, UV, 0);
|
|
}
|
|
|
|
float3 AtmosphereGetTransmittance(float3 Origin, float3 Direction, float TMin, float TMax)
|
|
{
|
|
FSphericalPlanetParameters Params = GetSphericalPlanetParameters(Origin, Direction);
|
|
// now lookup the optical density LUT from each point (integral up to the farthest possible point)
|
|
float3 DensityA = AtmosphereGetOpticalDensity(Params.GetAt(TMin * CM_TO_SKY_UNIT));
|
|
float3 DensityB = AtmosphereGetOpticalDensity(Params.GetAt(TMax * CM_TO_SKY_UNIT));
|
|
// get density for just this segment by removing double-counted density
|
|
float3 Tau = max(DensityA - DensityB, 0.0);
|
|
// compute transmittance
|
|
return exp(-Tau);
|
|
}
|
|
|
|
#endif |