mirror of
https://github.com/barkeser2002/flower.git
synced 2026-09-25 16:36:15 +03:00
624 lines
24 KiB
GLSL
624 lines
24 KiB
GLSL
#ifndef VOLUMETRIC_CLOUD_COMMON_GLSL
|
|
#define VOLUMETRIC_CLOUD_COMMON_GLSL
|
|
|
|
/*
|
|
** Physical based render code, develop by engineer: qiutanguu.
|
|
*/
|
|
|
|
// My personal volumetric cloud implement.
|
|
// Reference implement from https://www.slideshare.net/guerrillagames/the-realtime-volumetric-cloudscapes-of-horizon-zero-dawn.
|
|
|
|
#include "Common.glsl"
|
|
#include "Bayer.glsl"
|
|
|
|
layout (set = 0, binding = 0, rgba16f) uniform image2D imageHdrSceneColor;
|
|
layout (set = 0, binding = 1) uniform texture2D inHdrSceneColor;
|
|
|
|
layout (set = 0, binding = 2, rgba16f) uniform image2D imageCloudRenderTexture; // quater resolution.
|
|
layout (set = 0, binding = 3) uniform texture2D inCloudRenderTexture; // quater resolution.
|
|
|
|
layout (set = 0, binding = 4) uniform texture2D inDepth;
|
|
layout (set = 0, binding = 5) uniform texture2D inGBufferA;
|
|
|
|
layout (set = 0, binding = 6) uniform texture3D inBasicNoise;
|
|
layout (set = 0, binding = 7) uniform texture3D inWorleyNoise;
|
|
|
|
layout (set = 0, binding = 8) uniform texture2D inWeatherTexture;
|
|
layout (set = 0, binding = 9) uniform texture2D inCloudCurlNoise;
|
|
|
|
layout (set = 0, binding = 10) uniform texture2D inTransmittanceLut;
|
|
layout (set = 0, binding = 11) uniform texture3D inFroxelScatter;
|
|
|
|
layout (set = 0, binding = 12, r32f) uniform image2D imageCloudShadowDepth;
|
|
layout (set = 0, binding = 13) uniform texture2D inCloudShadowDepth;
|
|
|
|
layout (set = 0, binding = 14, rgba16f) uniform image2D imageCloudReconstructionTexture; // full resolution.
|
|
layout (set = 0, binding = 15) uniform texture2D inCloudReconstructionTexture; // full resolution.
|
|
|
|
layout (set = 0, binding = 16, r32f) uniform image2D imageCloudDepthTexture; // quater resolution.
|
|
layout (set = 0, binding = 17) uniform texture2D inCloudDepthTexture; // quater resolution.
|
|
|
|
layout (set = 0, binding = 18, r32f) uniform image2D imageCloudDepthReconstructionTexture; // full resolution.
|
|
layout (set = 0, binding = 19) uniform texture2D inCloudDepthReconstructionTexture; // full resolution.
|
|
|
|
layout (set = 0, binding = 20) uniform texture2D inCloudReconstructionTextureHistory;
|
|
layout (set = 0, binding = 21) uniform texture2D inCloudDepthReconstructionTextureHistory;
|
|
|
|
|
|
layout (set = 0, binding = 22, rgba16f) uniform imageCube imageCubeEnv; // Cube capture.
|
|
|
|
layout (set = 0, binding = 23) uniform texture2D inCloudGradientLut;
|
|
|
|
layout (set = 0, binding = 24) uniform texture2D inCloudSkyViewLutBottom;
|
|
layout (set = 0, binding = 25) uniform texture2D inCloudSkyViewLutTop;
|
|
layout (set = 0, binding = 26) uniform texture2D inSkyViewLut;
|
|
|
|
layout (set = 0, binding = 27) uniform texture2D inSDSMShadowDepth;
|
|
layout (set = 0, binding = 28) buffer SSBOCascadeInfoBuffer{ CascadeInfo cascadeInfos[]; };
|
|
|
|
// Other common set.
|
|
layout (set = 1, binding = 0) uniform UniformView { ViewData viewData; };
|
|
layout (set = 2, binding = 0) uniform UniformFrame { FrameData frameData; };
|
|
|
|
// Common sampler set.
|
|
#define COMMON_SAMPLER_SET 3
|
|
#include "CommonSamplerSet.glsl"
|
|
|
|
#define BLUE_NOISE_TEXTURE_SET 4
|
|
#define BLUE_NOISE_BUFFER_SET 5
|
|
#include "BlueNoiseCommon.glsl"
|
|
|
|
// Helper header.
|
|
#include "RayCommon.glsl"
|
|
#include "Sample.glsl"
|
|
#include "Phase.glsl"
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////
|
|
//////////////// Paramters ///////////////////////////
|
|
|
|
// Min max sample count define.
|
|
#define kSampleCountMin 2
|
|
#define kSampleCountMax 96
|
|
|
|
// Max sample count per distance. 16 tap/km
|
|
#define kCloudDistanceToSampleMaxCount (1.0 / 16.0)
|
|
|
|
#define ENABLE_GROUND_CONTRIBUTION 0
|
|
#define kGroundContributionSampleCount 2
|
|
|
|
#define kVolumetricLightSteps 6
|
|
|
|
#define kMsCount 2
|
|
|
|
struct ParticipatingMedia
|
|
{
|
|
float extinctionCoefficients[kMsCount];
|
|
float transmittanceToLight[kMsCount];
|
|
};
|
|
|
|
/////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////
|
|
// Cloud shape.
|
|
|
|
float remap(float value, float orignalMin, float orignalMax, float newMin, float newMax)
|
|
{
|
|
return newMin + (saturate((value - orignalMin) / (orignalMax - orignalMin)) * (newMax - newMin));
|
|
}
|
|
|
|
// TODO: change shape function in the future. This should be artist bias.
|
|
float cloudMap(vec3 posMeter, float normalizeHeight) // Meter
|
|
{
|
|
// If normalize height out of range, pre-return.
|
|
// May evaluate error value on shadow light.
|
|
if(normalizeHeight < 1e-4f || normalizeHeight > 0.9999f)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
const float kCoverage = frameData.earthAtmosphere.cloudCoverage;
|
|
const float kDensity = frameData.earthAtmosphere.cloudDensity;
|
|
|
|
const vec3 windDirection = frameData.earthAtmosphere.cloudDirection;
|
|
const float cloudSpeed = frameData.earthAtmosphere.cloudSpeed;
|
|
|
|
posMeter += windDirection * normalizeHeight * 500.0f;
|
|
vec3 posKm = posMeter * 0.001;
|
|
|
|
vec3 windOffset = (windDirection + vec3(0.0, 0.1, 0.0)) * frameData.appTime.x * cloudSpeed;
|
|
|
|
vec2 sampleUv = posKm.xz * frameData.earthAtmosphere.cloudWeatherUVScale;
|
|
vec4 weatherValue = texture(sampler2D(inWeatherTexture, linearRepeatSampler), sampleUv);
|
|
|
|
float coverage = saturate(kCoverage * weatherValue.x);
|
|
float gradienShape = remap(normalizeHeight, 0.00, 0.10, 0.1, 1.0) * remap(normalizeHeight, 0.10, 0.80, 1.0, 0.2);
|
|
|
|
float basicNoise = texture(sampler3D(inBasicNoise, linearRepeatSampler), (posKm + windOffset) * vec3(frameData.earthAtmosphere.cloudBasicNoiseScale)).r;
|
|
float basicCloudNoise = gradienShape * basicNoise;
|
|
|
|
float basicCloudWithCoverage = coverage * remap(basicCloudNoise, 1.0 - coverage, 1, 0, 1);
|
|
|
|
vec3 sampleDetailNoise = posKm - windOffset * 0.15 + vec3(basicNoise.x, 0.0, basicCloudNoise) * normalizeHeight;
|
|
float detailNoiseComposite = texture(sampler3D(inWorleyNoise, linearRepeatSampler), sampleDetailNoise * frameData.earthAtmosphere.cloudDetailNoiseScale).r;
|
|
float detailNoiseMixByHeight = 0.2 * mix(detailNoiseComposite, 1 - detailNoiseComposite, saturate(normalizeHeight * 10.0));
|
|
|
|
float densityShape = saturate(0.01 + normalizeHeight * 1.15) * kDensity *
|
|
remap(normalizeHeight, 0.0, 0.1, 0.0, 1.0) *
|
|
remap(normalizeHeight, 0.8, 1.0, 1.0, 0.0);
|
|
|
|
float cloudDensity = remap(basicCloudWithCoverage, detailNoiseMixByHeight, 1.0, 0.0, 1.0);
|
|
return cloudDensity * densityShape;
|
|
|
|
}
|
|
|
|
// Cloud shape end.
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct ParticipatingMediaPhase
|
|
{
|
|
float phase[kMsCount];
|
|
};
|
|
|
|
ParticipatingMediaPhase getParticipatingMediaPhase(float basePhase, float baseMsPhaseFactor)
|
|
{
|
|
ParticipatingMediaPhase participatingMediaPhase;
|
|
participatingMediaPhase.phase[0] = basePhase;
|
|
|
|
const float uniformPhase = getUniformPhase();
|
|
float MsPhaseFactor = baseMsPhaseFactor;
|
|
|
|
for (int ms = 1; ms < kMsCount; ms++)
|
|
{
|
|
participatingMediaPhase.phase[ms] = mix(uniformPhase, participatingMediaPhase.phase[0], MsPhaseFactor);
|
|
MsPhaseFactor *= MsPhaseFactor;
|
|
}
|
|
|
|
return participatingMediaPhase;
|
|
}
|
|
|
|
float powder(float opticalDepth)
|
|
{
|
|
return 1.0 - exp2(-opticalDepth * 2.0);
|
|
}
|
|
|
|
vec3 lookupSkylight(vec3 worldDir, vec3 worldPos, float viewHeight, vec3 upVector, ivec2 workPos, in const AtmosphereParameters atmosphere, texture2D lutImage)
|
|
{
|
|
const vec3 sunDirection = -normalize(frameData.directionalLight.direction);
|
|
|
|
float viewZenithCosAngle = dot(worldDir, upVector);
|
|
// Assumes non parallel vectors
|
|
vec3 sideVector = normalize(cross(upVector, worldDir));
|
|
|
|
// aligns toward the sun light but perpendicular to up vector
|
|
vec3 forwardVector = normalize(cross(sideVector, upVector));
|
|
|
|
vec2 lightOnPlane = vec2(dot(sunDirection, forwardVector), dot(sunDirection, sideVector));
|
|
lightOnPlane = normalize(lightOnPlane);
|
|
float lightViewCosAngle = lightOnPlane.x;
|
|
|
|
vec2 sampleUv;
|
|
vec3 luminance;
|
|
|
|
skyViewLutParamsToUv(atmosphere, false, viewZenithCosAngle, lightViewCosAngle, viewHeight, vec2(textureSize(lutImage, 0)), sampleUv);
|
|
luminance = texture(sampler2D(lutImage, linearClampEdgeSampler), sampleUv).rgb;
|
|
|
|
return skyPrepareOut(luminance, atmosphere, frameData, vec2(workPos));
|
|
}
|
|
|
|
ParticipatingMedia volumetricShadow(vec3 posKm, float cosTheta, vec3 sunDirection, in const AtmosphereParameters atmosphere)
|
|
{
|
|
ParticipatingMedia participatingMedia;
|
|
|
|
int ms = 0;
|
|
|
|
float extinctionAccumulation[kMsCount];
|
|
float extinctionCoefficients[kMsCount];
|
|
|
|
for (ms = 0; ms < kMsCount; ms++)
|
|
{
|
|
extinctionAccumulation[ms] = 0.0f;
|
|
extinctionCoefficients[ms] = 0.0f;
|
|
}
|
|
|
|
const float kStepLMul = frameData.earthAtmosphere.cloudLightStepMul;
|
|
const uint kStepLight = frameData.earthAtmosphere.cloudLightStepNum;
|
|
float stepL = frameData.earthAtmosphere.cloudLightBasicStep; // km
|
|
|
|
float d = stepL * 0.5;
|
|
|
|
// Collect total density along light ray.
|
|
for(uint j = 0; j < kStepLight; j++)
|
|
{
|
|
vec3 samplePosKm = posKm + sunDirection * d; // km
|
|
|
|
float sampleHeightKm = length(samplePosKm);
|
|
float sampleDt = sampleHeightKm - atmosphere.cloudAreaStartHeight;
|
|
|
|
float normalizeHeight = sampleDt / atmosphere.cloudAreaThickness;
|
|
vec3 samplePosMeter = samplePosKm * 1000.0f;
|
|
|
|
extinctionCoefficients[0] = cloudMap(samplePosMeter, normalizeHeight);
|
|
extinctionAccumulation[0] += extinctionCoefficients[0] * stepL;
|
|
|
|
float MsExtinctionFactor = frameData.earthAtmosphere.cloudMultiScatterExtinction;
|
|
for (ms = 1; ms < kMsCount; ms++)
|
|
{
|
|
extinctionCoefficients[ms] = extinctionCoefficients[ms - 1] * MsExtinctionFactor;
|
|
MsExtinctionFactor *= MsExtinctionFactor;
|
|
|
|
extinctionAccumulation[ms] += extinctionCoefficients[ms] * stepL;
|
|
}
|
|
|
|
d += stepL;
|
|
stepL *= kStepLMul;
|
|
}
|
|
|
|
for (ms = 0; ms < kMsCount; ms++)
|
|
{
|
|
participatingMedia.transmittanceToLight[ms] = exp(-extinctionAccumulation[ms] * 1000.0); // to meter.
|
|
}
|
|
|
|
return participatingMedia;
|
|
}
|
|
|
|
|
|
|
|
vec3 getVolumetricGroundContribution(
|
|
in AtmosphereParameters atmosphere,
|
|
vec3 posKm,
|
|
vec3 sunDirection,
|
|
vec3 sunIlluminance,
|
|
vec3 atmosphereTransmittanceToLight,
|
|
float posNormalizeHeight)
|
|
{
|
|
const vec3 groundScatterDirection = vec3(0.0, -1.0, 0.0); // Y down.
|
|
const vec3 planetSurfaceNormal = vec3(0.0, 1.0, 0.0); // Ambient contribution from the clouds is only done on a plane above the planet
|
|
|
|
const vec3 groundBrdfNdotL = saturate(dot(sunDirection, planetSurfaceNormal)) * (atmosphere.groundAlbedo / kPI); // Lambert BRDF diffuse shading
|
|
const float uniformPhase = getUniformPhase();
|
|
const float groundHemisphereLuminanceIsotropic = (2.0f * kPI) * uniformPhase; // Assumes the ground is uniform luminance to the cloud and solid angle is bottom hemisphere 2PI
|
|
const vec3 groundToCloudTransfertIsoScatter = groundBrdfNdotL * groundHemisphereLuminanceIsotropic;
|
|
|
|
float cloudSampleHeightToBottom = posNormalizeHeight * atmosphere.cloudAreaThickness; // Distance from altitude to bottom of clouds
|
|
|
|
|
|
vec3 opticalDepth = vec3(0.0); // km.
|
|
|
|
const float contributionStepLength = min(4.0, cloudSampleHeightToBottom); // km
|
|
|
|
// Ground Contribution tracing loop, same idea as volumetric shadow
|
|
const uint sampleCount = kGroundContributionSampleCount;
|
|
const float sampleSegmentT = 0.5f;
|
|
for (uint s = 0; s < sampleCount; s ++)
|
|
{
|
|
// More expensive but artefact free
|
|
float t0 = float(s) / float(sampleCount);
|
|
float t1 = float(s + 1.0) / float(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; // km
|
|
vec3 samplePosKm = posKm + groundScatterDirection * contributionSampleT; // Km
|
|
|
|
float sampleHeightKm = length(samplePosKm);
|
|
float sampleDt = sampleHeightKm - atmosphere.cloudAreaStartHeight;
|
|
|
|
float normalizeHeight = sampleDt / atmosphere.cloudAreaThickness;
|
|
|
|
vec3 samplePosMeter = samplePosKm * 1000.0f;
|
|
float stepCloudDensity = cloudMap(samplePosMeter, normalizeHeight);
|
|
|
|
opticalDepth += stepCloudDensity * delta;
|
|
}
|
|
|
|
const vec3 scatteredLuminance = atmosphereTransmittanceToLight * sunIlluminance * groundToCloudTransfertIsoScatter;
|
|
return scatteredLuminance * exp(-opticalDepth * contributionStepLength * 1000.0); // to meter.
|
|
}
|
|
|
|
vec4 cloudColorCompute(in const AtmosphereParameters atmosphere, vec2 uv, float blueNoise, inout float cloudZ, ivec2 workPos, vec3 worldDir)
|
|
{
|
|
// Get camera in atmosphere unit position, it will treat as ray start position.
|
|
vec3 worldPos = convertToAtmosphereUnit(viewData.camWorldPos.xyz, viewData) + vec3(0.0, atmosphere.bottomRadius, 0.0);
|
|
|
|
float earthRadius = atmosphere.bottomRadius;
|
|
float radiusCloudStart = atmosphere.cloudAreaStartHeight;
|
|
float radiusCloudEnd = radiusCloudStart + atmosphere.cloudAreaThickness;
|
|
|
|
// Unit is atmosphere unit. km.
|
|
float viewHeight = length(worldPos);
|
|
|
|
// Find intersect position so we can do some ray marching.
|
|
float tMin;
|
|
float tMax;
|
|
if(viewHeight < radiusCloudStart)
|
|
{
|
|
float tEarth = raySphereIntersectNearest(worldPos, worldDir, vec3(0.0), earthRadius);
|
|
if(tEarth > 0.0)
|
|
{
|
|
// Intersect with earth, pre-return.
|
|
return vec4(0.0, 0.0, 0.0, 1.0);
|
|
}
|
|
|
|
tMin = raySphereIntersectInside(worldPos, worldDir, vec3(0.0), radiusCloudStart);
|
|
tMax = raySphereIntersectInside(worldPos, worldDir, vec3(0.0), radiusCloudEnd);
|
|
}
|
|
else if(viewHeight > radiusCloudEnd)
|
|
{
|
|
// Eye out of cloud area.
|
|
|
|
vec2 t0t1 = vec2(0.0);
|
|
const bool bIntersectionEnd = raySphereIntersectOutSide(worldPos, worldDir, vec3(0.0), radiusCloudEnd, t0t1);
|
|
if(!bIntersectionEnd)
|
|
{
|
|
// No intersection.
|
|
return vec4(0.0, 0.0, 0.0, 1.0);
|
|
}
|
|
|
|
vec2 t2t3 = vec2(0.0);
|
|
const bool bIntersectionStart = raySphereIntersectOutSide(worldPos, worldDir, vec3(0.0), radiusCloudStart, t2t3);
|
|
if(bIntersectionStart)
|
|
{
|
|
tMin = t0t1.x;
|
|
tMax = t2t3.x;
|
|
}
|
|
else
|
|
{
|
|
tMin = t0t1.x;
|
|
tMax = t0t1.y;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Eye inside cloud area.
|
|
float tStart = raySphereIntersectNearest(worldPos, worldDir, vec3(0.0), radiusCloudStart);
|
|
if(tStart > 0.0)
|
|
{
|
|
tMax = tStart;
|
|
}
|
|
else
|
|
{
|
|
tMax = raySphereIntersectInside(worldPos, worldDir, vec3(0.0), radiusCloudEnd);
|
|
}
|
|
|
|
tMin = 0.0f; // From camera.
|
|
}
|
|
|
|
tMin = max(tMin, 0.0);
|
|
tMax = max(tMax, 0.0);
|
|
|
|
// Pre-return if too far.
|
|
if(tMax <= tMin || tMin > frameData.earthAtmosphere.cloudTracingStartMaxDistance)
|
|
{
|
|
return vec4(0.0, 0.0, 0.0, 1.0);
|
|
}
|
|
|
|
// Clamp marching distance by setting.
|
|
const float marchingDistance = min(frameData.earthAtmosphere.cloudMaxTraceingDistance, tMax - tMin);
|
|
tMax = tMin + marchingDistance;
|
|
|
|
const uint stepCountUnit = uint(max(kSampleCountMin, kSampleCountMax * saturate((tMax - tMin) * kCloudDistanceToSampleMaxCount)));
|
|
const float stepCount = float(stepCountUnit);
|
|
const float stepT = (tMax - tMin) / stepCount; // Per step lenght.
|
|
|
|
float sampleT = tMin + 0.001 * stepT; // Slightly delta avoid self intersect.
|
|
|
|
// Jitter by blue noise.
|
|
sampleT += stepT * blueNoise;
|
|
|
|
vec3 sunColor = frameData.directionalLight.color * frameData.directionalLight.intensity;
|
|
vec3 sunDirection = -normalize(frameData.directionalLight.direction);
|
|
|
|
float VoL = dot(worldDir, sunDirection);
|
|
|
|
// Combine backward and forward scattering to have details in all directions.
|
|
const float cosTheta = -VoL;
|
|
float phase = dualLobPhase(frameData.earthAtmosphere.cloudPhaseForward, frameData.earthAtmosphere.cloudPhaseBackward, frameData.earthAtmosphere.cloudPhaseMixFactor, cosTheta);
|
|
|
|
ParticipatingMediaPhase participatingMediaPhase = getParticipatingMediaPhase(phase, frameData.earthAtmosphere.cloudPhaseMixFactor);
|
|
|
|
float transmittance = 1.0;
|
|
vec3 scatteredLight = vec3(0.0, 0.0, 0.0);
|
|
|
|
// Average ray hit pos to evaluate air perspective and height fog.
|
|
vec3 rayHitPos = vec3(0.0);
|
|
float rayHitPosWeight = 0.0;
|
|
|
|
// Skylight lookup between top and bottom sky. mix by height.
|
|
//
|
|
vec3 cloudTopSkyLight = lookupSkylight(worldDir, vec3(0.0, atmosphere.cloudAreaStartHeight + atmosphere.cloudAreaThickness, 0.0), atmosphere.cloudAreaStartHeight + atmosphere.cloudAreaThickness, vec3(0.0, 1.0, 0.0), workPos, atmosphere, inCloudSkyViewLutTop);
|
|
vec3 cloudBottomSkyLight = lookupSkylight(worldDir, vec3(0.0, atmosphere.cloudAreaStartHeight, 0.0), atmosphere.cloudAreaStartHeight, vec3(0.0, 1.0, 0.0), workPos, atmosphere, inCloudSkyViewLutBottom);
|
|
|
|
// Cloud background sky color.
|
|
vec3 skyBackgroundColor = lookupSkylight(worldDir, worldPos, viewHeight, normalize(worldPos), workPos, atmosphere, inSkyViewLut);
|
|
|
|
for(uint i = 0; i < stepCountUnit; i ++)
|
|
{
|
|
// World space sample pos, in km unit.
|
|
vec3 samplePos = sampleT * worldDir + worldPos;
|
|
|
|
float sampleHeight = length(samplePos);
|
|
|
|
// Get sample normalize height [0.0, 1.0]
|
|
float normalizeHeight = (sampleHeight - atmosphere.cloudAreaStartHeight) / atmosphere.cloudAreaThickness;
|
|
|
|
// Convert to meter.
|
|
vec3 samplePosMeter = samplePos * 1000.0f;
|
|
float stepCloudDensity = cloudMap(samplePosMeter, normalizeHeight);
|
|
|
|
// Add ray march pos, so we can do some average fading or atmosphere sample effect.
|
|
rayHitPos += samplePos * transmittance;
|
|
rayHitPosWeight += transmittance;
|
|
|
|
if(stepCloudDensity > 0.)
|
|
{
|
|
float opticalDepth = stepCloudDensity * stepT * 1000.0; // to meter unit.
|
|
|
|
// Second evaluate transmittance due to participating media
|
|
vec3 atmosphereTransmittance;
|
|
{
|
|
const vec3 upVector = samplePos / sampleHeight;
|
|
float viewZenithCosAngle = dot(sunDirection, upVector);
|
|
vec2 sampleUv;
|
|
lutTransmittanceParamsToUv(atmosphere, viewHeight, viewZenithCosAngle, sampleUv);
|
|
atmosphereTransmittance = texture(sampler2D(inTransmittanceLut, linearClampEdgeSampler), sampleUv).rgb;
|
|
}
|
|
|
|
// beer's lambert.
|
|
#if 1
|
|
float stepTransmittance = exp(-opticalDepth);
|
|
#else
|
|
// Siggraph 2017's new step transmittance formula.
|
|
float stepTransmittance = max(exp(-opticalDepth), exp(-opticalDepth * 0.25) * 0.7);
|
|
#endif
|
|
|
|
// Compute powder term.
|
|
float powderEffectTerm;
|
|
{
|
|
#if 0
|
|
powderEffectTerm = powder(opticalDepth);
|
|
#elif 0
|
|
// Siggraph 2017's new powder formula.
|
|
float depthProbability = 0.05 + pow(opticalDepth, remap(normalizeHeight, 0.3, 0.85, 0.5, 2.0));
|
|
float verticalProbability = pow(remap(normalizeHeight, 0.07, 0.14, 0.1, 1.0), 0.8);
|
|
powderEffectTerm = depthProbability * verticalProbability; //powder(opticalDepth * 2.0);
|
|
#else
|
|
// Unreal engine 5's implement powder formula.
|
|
powderEffectTerm = pow(saturate(opticalDepth * frameData.earthAtmosphere.cloudPowderScale), frameData.earthAtmosphere.cloudPowderPow);
|
|
#endif
|
|
}
|
|
|
|
#if 0
|
|
vec3 ambientLight = skyBackgroundColor;
|
|
#else
|
|
vec3 ambientLight = mix(cloudBottomSkyLight, cloudTopSkyLight, normalizeHeight);
|
|
#endif
|
|
|
|
if(frameData.earthAtmosphere.cloudEnableGroundContribution != 0)
|
|
{
|
|
ambientLight += getVolumetricGroundContribution(
|
|
atmosphere,
|
|
samplePos,
|
|
sunDirection,
|
|
sunColor,
|
|
atmosphereTransmittance,
|
|
normalizeHeight
|
|
);
|
|
}
|
|
|
|
// Amount of sunlight that reaches the sample point through the cloud
|
|
// is the combination of ambient light and attenuated direct light.
|
|
vec3 sunlightTerm = frameData.earthAtmosphere.cloudShadingSunLightScale * sunColor;
|
|
|
|
ParticipatingMedia participatingMedia = volumetricShadow(samplePos, cosTheta, sunDirection, atmosphere);
|
|
|
|
float sigmaS = stepCloudDensity;
|
|
float sigmaE = sigmaS + 1e-4f;
|
|
|
|
vec3 scatteringCoefficients[kMsCount];
|
|
float extinctionCoefficients[kMsCount];
|
|
|
|
vec3 albedo = frameData.earthAtmosphere.cloudAlbedo * vec3(powderEffectTerm);
|
|
|
|
scatteringCoefficients[0] = sigmaS * albedo;
|
|
extinctionCoefficients[0] = sigmaE;
|
|
|
|
float MsExtinctionFactor = frameData.earthAtmosphere.cloudMultiScatterExtinction;
|
|
float MsScatterFactor = frameData.earthAtmosphere.cloudMultiScatterScatter;
|
|
int ms;
|
|
for (ms = 1; ms < kMsCount; ms++)
|
|
{
|
|
extinctionCoefficients[ms] = extinctionCoefficients[ms - 1] * MsExtinctionFactor;
|
|
scatteringCoefficients[ms] = scatteringCoefficients[ms - 1] * MsScatterFactor;
|
|
|
|
MsExtinctionFactor *= MsExtinctionFactor;
|
|
MsScatterFactor *= MsScatterFactor;
|
|
}
|
|
|
|
for (ms = kMsCount - 1; ms >= 0; ms--) // Should terminate at 0
|
|
{
|
|
float sunVisibilityTerm = participatingMedia.transmittanceToLight[ms];
|
|
|
|
vec3 sunSkyLuminance = sunVisibilityTerm * sunlightTerm * participatingMediaPhase.phase[ms];
|
|
sunSkyLuminance += (ms == 0 ? ambientLight : vec3(0.0, 0.0, 0.0));
|
|
|
|
vec3 sactterLitStep = sunSkyLuminance * scatteringCoefficients[ms];
|
|
|
|
// See slide 28 at http://www.frostbite.com/2015/08/physically-based-unified-volumetric-rendering-in-frostbite/
|
|
scatteredLight += atmosphereTransmittance * transmittance * (sactterLitStep - sactterLitStep * stepTransmittance) / max(1e-4f, extinctionCoefficients[ms]);
|
|
|
|
if(ms == 0)
|
|
{
|
|
// Beer's law.
|
|
transmittance *= stepTransmittance;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(transmittance <= 0.001)
|
|
{
|
|
break;
|
|
}
|
|
|
|
sampleT += stepT;
|
|
}
|
|
|
|
|
|
|
|
// Apply cloud transmittance.
|
|
vec3 finalColor = skyBackgroundColor;
|
|
|
|
// Apply some additional effect.
|
|
if(transmittance <= 0.99999)
|
|
{
|
|
// Get average hit pos.
|
|
rayHitPos /= rayHitPosWeight;
|
|
|
|
vec3 rayHitInRender = convertToCameraUnit(rayHitPos - vec3(0.0, atmosphere.bottomRadius, 0.0), viewData);
|
|
vec4 rayInH = viewData.camViewProj * vec4(rayHitInRender, 1.0);
|
|
cloudZ = rayInH.z / rayInH.w;
|
|
|
|
rayHitPos -= worldPos;
|
|
float rayHitHeight = length(rayHitPos);
|
|
|
|
// Fade effect apply.
|
|
float fading = exp(-rayHitHeight * frameData.earthAtmosphere.cloudFogFade);
|
|
float cloudTransmittanceFaded = mix(1.0, transmittance, fading);
|
|
|
|
// Apply air perspective.
|
|
float slice = aerialPerspectiveDepthToSlice(rayHitHeight);
|
|
float weight = 1.0;
|
|
if (slice < 0.5)
|
|
{
|
|
// We multiply by weight to fade to 0 at depth 0. That works for luminance and opacity.
|
|
weight = saturate(slice * 2.0);
|
|
slice = 0.5;
|
|
}
|
|
ivec3 sliceLutSize = textureSize(inFroxelScatter, 0);
|
|
float w = sqrt(slice / float(sliceLutSize.z)); // squared distribution
|
|
|
|
vec4 airPerspective = weight * texture(sampler3D(inFroxelScatter, linearClampEdgeSampler), vec3(uv, w));
|
|
|
|
// Dual mix alpha.
|
|
cloudTransmittanceFaded = mix(1.0, cloudTransmittanceFaded, 1.0 - airPerspective.a);
|
|
|
|
finalColor *= cloudTransmittanceFaded;
|
|
|
|
// Apply air perspective color.
|
|
finalColor += airPerspective.rgb * (1.0 - cloudTransmittanceFaded);
|
|
|
|
// Apply scatter color.
|
|
finalColor += (1.0 - cloudTransmittanceFaded) * scatteredLight;
|
|
|
|
// Update transmittance.
|
|
transmittance = cloudTransmittanceFaded;
|
|
}
|
|
|
|
// Dual mix transmittance.
|
|
return vec4(finalColor, transmittance);
|
|
}
|
|
|
|
#endif |