atmosphere and volumetric cloud updates (#264)

This commit is contained in:
Silas Oler
2021-05-15 23:36:41 +02:00
committed by GitHub
parent c28476536f
commit 01f9930bb0
3 changed files with 160 additions and 30 deletions
+9
View File
@@ -357,6 +357,15 @@ float3 GetTransmittance(AtmosphereParameters atmosphere, float pHeight, float su
float3 GetAtmosphereTransmittance(float3 worldPosition, float3 worldDirection, AtmosphereParameters atmosphere, Texture2D<float4> transmittanceLutTexture)
{
// If the worldDirection is occluded from this virtual planet, then return.
// We do this due to the low resolution LUT, where the stored zenith to horizon never reaches black, to prevent linear interpolation artefacts.
// At the most shadowed point of the LUT, pure black with earth shadow is never reached.
float2 sol = RaySphereIntersect(worldPosition, worldDirection, float3(0.0f, 0.0f, 0.0f), atmosphere.bottomRadius);
if (sol.x > 0.0f || sol.y > 0.0f)
{
return 0.0f;
}
float pHeight = length(worldPosition);
const float3 UpVector = worldPosition / pHeight;
float SunZenithCosAngle = dot(worldDirection, UpVector);
@@ -105,6 +105,8 @@ static const float g_LODDistance = 25000.0; // After a certain distance, noises
static const float g_LODMin = 0.0; //
static const float g_BigStepMarch = 3.0; // How long inital rays should be until they hit something. Lower values may ives a better image but may be slower.
static const float g_TransmittanceThreshold = 0.005; // Default: 0.005. If the clouds transmittance has reached it's desired opacity, there's no need to keep raymarching for performance.
static const float g_ShadowSampleCount = 5.0f;
static const float g_GroundContributionSampleCount = 2.0f;
float GetHeightFractionForPoint(AtmosphereParameters atmosphere, float3 pos)
@@ -280,19 +282,24 @@ void VolumetricShadow(inout ParticipatingMedia participatingMedia, in Atmosphere
extinctionAccumulation[ms] = 0.0f;
}
const float shadowStepCount = 5.0;
const float invShadowStepCount = 1.0 / shadowStepCount;
float previousNormT = 0.0;
const float sampleCount = g_ShadowSampleCount;
const float sampleSegmentT = 0.5f;
float lodOffset = 0.5;
for (float shadowT = invShadowStepCount; shadowT <= 1.0; shadowT += invShadowStepCount)
for (float s = 0.0f; s < sampleCount; s += 1.0)
{
float currentNormT = shadowT * shadowT;
float deltaNormT = currentNormT - previousNormT; // 5 samples: 0.04, 0.12, 0.2, 0.28, 0.36
float extinctionFactor = deltaNormT;
float shadowSampleDistance = g_ShadowStepLength * (previousNormT + deltaNormT * 0.5); // 5 samples: 0.02, 0.1, 0.26, 0.5, 0.82
// More expensive but artefact free
float t0 = (s) / sampleCount;
float t1 = (s + 1.0) / sampleCount;
// Non linear distribution of sample within the range.
t0 = t0 * t0;
t1 = t1 * t1;
float3 samplePoint = worldPosition + sunDirection * shadowSampleDistance; // Step futher towards the light
float delta = t1 - t0; // 5 samples: 0.04, 0.12, 0.2, 0.28, 0.36
float t = t0 + delta * sampleSegmentT; // 5 samples: 0.02, 0.1, 0.26, 0.5, 0.82
float shadowSampleT = g_ShadowStepLength * t;
float3 samplePoint = worldPosition + sunDirection * shadowSampleT; // Step futher towards the light
float heightFraction = GetHeightFractionForPoint(atmosphere, samplePoint);
if (heightFraction < 0.0 || heightFraction > 1.0)
@@ -314,10 +321,9 @@ void VolumetricShadow(inout ParticipatingMedia participatingMedia, in Atmosphere
[unroll]
for (ms = 0; ms < MS_COUNT; ms++)
{
extinctionAccumulation[ms] += shadowParticipatingMedia.extinctionCoefficients[ms] * extinctionFactor;
extinctionAccumulation[ms] += shadowParticipatingMedia.extinctionCoefficients[ms] * delta;
}
previousNormT = currentNormT;
lodOffset += 0.5;
}
@@ -328,6 +334,74 @@ void VolumetricShadow(inout ParticipatingMedia participatingMedia, in Atmosphere
}
}
void VolumetricGroundContribution(inout float3 environmentLuminance, in AtmosphereParameters atmosphere, float3 worldPosition, float3 sunDirection, float3 sunIlluminance, float3 atmosphereTransmittanceToLight, float3 windOffset, float3 windDirection, float2 coverageWindOffset, float lod)
{
float planetRadius = atmosphere.bottomRadius * SKY_UNIT_TO_M;
float3 planetCenterWorld = atmosphere.planetCenter * SKY_UNIT_TO_M;
float cloudBottomRadius = planetRadius + g_CloudStartHeight;
float cloudSampleAltitudde = length(worldPosition - planetCenterWorld); // Distance from planet center to tracing sample
float cloudSampleHeightToBottom = cloudSampleAltitudde - cloudBottomRadius; // Distance from altitude to bottom of clouds
float3 opticalDepth = 0.0;
const float contributionStepLength = min(4000.0, cloudSampleHeightToBottom);
const float3 groundScatterDirection = float3(0.0, -1.0, 0.0);
const float sampleCount = g_GroundContributionSampleCount;
const float sampleSegmentT = 0.5f;
// Ground Contribution tracing loop, same idea as volumetric shadow
float lodOffset = 0.5;
for (float s = 0.0f; s < sampleCount; s += 1.0)
{
// More expensive but artefact free
float t0 = (s) / sampleCount;
float t1 = (s + 1.0) / sampleCount;
// Non linear distribution of sample within the range.
t0 = t0 * t0;
t1 = t1 * t1;
float delta = t1 - t0; // 5 samples: 0.04, 0.12, 0.2, 0.28, 0.36
float t = t0 + (t1 - t0) * sampleSegmentT; // 5 samples: 0.02, 0.1, 0.26, 0.5, 0.82
float contributionSampleT = contributionStepLength * t;
float3 samplePoint = worldPosition + groundScatterDirection * contributionSampleT; // Step futher towards the scatter direction
float heightFraction = GetHeightFractionForPoint(atmosphere, samplePoint);
/*if (heightFraction < 0.0 || heightFraction > 1.0) // No impact
{
break;
}*/
float3 weatherData = SampleWeather(samplePoint, heightFraction, coverageWindOffset);
if (weatherData.r < 0.4)
{
continue;
}
float contributionCloudDensity = SampleCloudDensity(samplePoint, heightFraction, weatherData, windOffset, windDirection, lod + lodOffset, true);
float3 contributionExtinction = g_ExtinctionCoefficient * contributionCloudDensity;
opticalDepth += contributionExtinction * contributionStepLength * delta;
lodOffset += 0.5;
}
const float3 planetSurfaceNormal = float3(0.0, 1.0, 0.0); // Ambient contribution from the clouds is only done on a plane above the planet
const float3 groundBrdfNdotL = saturate(dot(sunDirection, planetSurfaceNormal)) * (atmosphere.groundAlbedo / PI); // Lambert BRDF diffuse shading
const float uniformPhase = UniformPhase();
const float groundHemisphereLuminanceIsotropic = (2.0f * PI) * uniformPhase; // Assumes the ground is uniform luminance to the cloud and solid angle is bottom hemisphere 2PI
const float3 groundToCloudTransfertIsoScatter = groundBrdfNdotL * groundHemisphereLuminanceIsotropic;
const float3 scatteredLuminance = atmosphereTransmittanceToLight * sunIlluminance * groundToCloudTransfertIsoScatter;
environmentLuminance += scatteredLuminance * exp(-opticalDepth);
}
struct ParticipatingMediaPhase
{
float phase[MS_COUNT];
@@ -398,8 +472,24 @@ void VolumetricCloudLighting(AtmosphereParameters atmosphere, float3 startPositi
ParticipatingMedia participatingMedia = SampleParticipatingMedia(albedo, extinction, g_MultiScatteringScattering, g_MultiScatteringExtinction, atmosphereTransmittanceToLight);
// Calcualte volumetric shadow
VolumetricShadow(participatingMedia, atmosphere, worldPosition, sunDirection, windOffset, windDirection, coverageWindOffset, lod);
// Sample environment lighting
float3 environmentLuminance = SampleAmbientLight(heightFraction);
// Only render if there is any sign of scattering (albedo * extinction)
if (any(participatingMedia.scatteringCoefficients[0] > 0.0))
{
// Calcualte volumetric shadow
VolumetricShadow(participatingMedia, atmosphere, worldPosition, sunDirection, windOffset, windDirection, coverageWindOffset, lod);
// Calculate bounced light from ground onto clouds
const float maxTransmittanceToView = max(max(transmittanceToView.x, transmittanceToView.y), transmittanceToView.z);
if (maxTransmittanceToView > 0.01f)
{
VolumetricGroundContribution(environmentLuminance, atmosphere, worldPosition, sunDirection, sunIlluminance, atmosphereTransmittanceToLight, windOffset, windDirection, coverageWindOffset, lod);
}
}
// Sample dual lob phase with multiple scattering
@@ -407,11 +497,6 @@ void VolumetricCloudLighting(AtmosphereParameters atmosphere, float3 startPositi
ParticipatingMediaPhase participatingMediaPhase = SampleParticipatingMediaPhase(phaseFunction, g_MultiScatteringEccentricity);
// Sample environment lighting
// Todo: Ground contribution?
float3 environmentLuminance = SampleAmbientLight(heightFraction);
// Update depth sampling
float depthWeight = min(transmittanceToView.r, min(transmittanceToView.g, transmittanceToView.b));
depthWeightedSum += depthWeight * length(worldPosition - startPosition);
@@ -25,20 +25,21 @@ inline float HdrWeight4(float3 color, float exposure)
return rcp(Luma4(color) * exposure + 4.0f);
}
float4 clip_aabb(float3 aabb_min, float3 aabb_max, float4 p, float4 q)
// Different aabb clipping method from eg. SSR temporal, suitable for clouds in this case
float4 clip_aabb(float4 aabb_min, float4 aabb_max, float4 prev_sample)
{
float3 p_clip = 0.5 * (aabb_max + aabb_min);
float3 e_clip = 0.5 * (aabb_max - aabb_min) + 0.00000001f;
float4 p_clip = 0.5 * (aabb_max + aabb_min);
float4 e_clip = 0.5 * (aabb_max - aabb_min) + 0.00000001f;
float4 v_clip = q - float4(p_clip, p.w);
float3 v_unit = v_clip.xyz / e_clip;
float3 a_unit = abs(v_unit);
float ma_unit = max(a_unit.x, max(a_unit.y, a_unit.z));
float4 v_clip = prev_sample - p_clip;
float4 v_unit = v_clip / e_clip;
float4 a_unit = abs(v_unit);
float ma_unit = max(max(a_unit.x, max(a_unit.y, a_unit.z)), a_unit.w);
if (ma_unit > 1.0)
return float4(p_clip, p.w) + v_clip / ma_unit;
return p_clip + v_clip / ma_unit;
else
return q; // point inside aabb
return prev_sample; // point inside aabb
}
inline void ResolverAABB(Texture2D<float4> currentColor, SamplerState currentSampler, float sharpness, float exposureScale, float AABBScale, float2 uv, float2 texelSize, inout float4 currentMin, inout float4 currentMax, inout float4 currentAverage, inout float4 currentOutput)
@@ -73,6 +74,8 @@ inline void ResolverAABB(Texture2D<float4> currentColor, SamplerState currentSam
sampleColors[5] * sampleWeights[5] + sampleColors[6] * sampleWeights[6] + sampleColors[7] * sampleWeights[7] + sampleColors[8] * sampleWeights[8]) / totalWeight;
#endif
#if 0 // Standard clipping
// Variance Clipping (AABB)
@@ -87,7 +90,39 @@ inline void ResolverAABB(Texture2D<float4> currentColor, SamplerState currentSam
float4 mean = m1 / 9.0;
float4 stddev = sqrt((m2 / 9.0) - sqr(mean));
#else // Depth check
float originalLinearDepth = getLinearDepth(texture_depth.SampleLevel(sampler_point_clamp, uv, 0).r);
float validSampleCount = 1.0;
float4 m1 = 0.0;
float4 m2 = 0.0;
[unroll]
for (uint x = 0; x < 9; x++)
{
if (x == 4)
{
m1 += sampleColors[x];
m2 += sampleColors[x] * sampleColors[x];
}
else
{
float depth = getLinearDepth(texture_depth.SampleLevel(sampler_point_clamp, uv + (SampleOffset[x] / texelSize), 0).r);
if (abs(originalLinearDepth - depth) < 1.5)
{
m1 += sampleColors[x];
m2 += sampleColors[x] * sampleColors[x];
validSampleCount += 1.0;
}
}
}
float4 mean = m1 / validSampleCount;
float4 stddev = sqrt((m2 / validSampleCount) - sqr(mean));
#endif
currentMin = mean - AABBScale * stddev;
currentMax = mean + AABBScale * stddev;
@@ -169,8 +204,9 @@ void main(uint3 DTid : SV_DispatchThreadID)
float4 currentMin, currentMax, currentAverage;
ResolverAABB(cloud_current, sampler_point_clamp, 0, temporalExposure, temporalScale, uv, xPPResolution, currentMin, currentMax, currentAverage, current);
previous = clip_aabb(currentMin.xyz, currentMax.xyz, clamp(currentAverage, currentMin, currentMax), previous);
//previous = clip_aabb(currentMin.xyz, currentMax.xyz, clamp(currentAverage, currentMin, currentMax), previous);
previous = clip_aabb(currentMin, currentMax, previous);
float4 result = lerp(previous, current, temporalResponse);
result = is_saturated(prevUV) ? result : current;