mirror of
https://github.com/barkeser2002/flower.git
synced 2026-09-25 12:05:57 +03:00
1255 lines
40 KiB
GLSL
1255 lines
40 KiB
GLSL
#version 460
|
|
|
|
#extension GL_GOOGLE_include_directive : enable
|
|
#extension GL_EXT_samplerless_texture_functions : enable
|
|
|
|
#define SHARED_SAMPLER_SET 1
|
|
#include "common_shader.glsl"
|
|
|
|
// Atmosphere transmittance lut compute, base on
|
|
// https://github.com/sebh/UnrealEngineSkyAtmosphere
|
|
// https://ebruneton.github.io/precomputed_atmospheric_scattering/
|
|
|
|
layout (set = 0, binding = 0) uniform UniformFrameData { PerFrameData frameData; };
|
|
|
|
layout (set = 0, binding = 1, rgba16f) uniform image2D imageTransmittanceLut;
|
|
layout (set = 0, binding = 2) uniform texture2D inTransmittanceLut;
|
|
|
|
layout (set = 0, binding = 3, rgba16f) uniform image2D imageSkyViewLut;
|
|
layout (set = 0, binding = 4) uniform texture2D inSkyViewLut;
|
|
|
|
layout (set = 0, binding = 5, rgba16f) uniform image2D imageMultiScatterLut;
|
|
layout (set = 0, binding = 6) uniform texture2D inMultiScatterLut;
|
|
|
|
layout (set = 0, binding = 7) uniform texture2D inDepth;
|
|
|
|
layout (set = 0, binding = 8, rgba16f) uniform image3D imageFroxelScatter;
|
|
layout (set = 0, binding = 9) uniform texture3D inFroxelScatter;
|
|
|
|
layout (set = 0, binding = 10, rgba16f) uniform image2D imageHdrSceneColor;
|
|
layout (set = 0, binding = 11, rgba16f) writeonly uniform imageCube imageCubeEnv;
|
|
|
|
layout (set = 0, binding = 12, r16f) uniform image2D imageGbufferId;
|
|
layout (set = 0, binding = 13, rgba16f) uniform image2D imageCloudDistantLit; // 1x1
|
|
|
|
|
|
layout (set = 0, binding = 14, rgba16f) uniform image3D imageDistantGrid;
|
|
|
|
|
|
AtmosphereParameters getAtmosphereParameters()
|
|
{
|
|
return getAtmosphereParameters(frameData);
|
|
}
|
|
|
|
vec3 convertToAtmosphereUnit(vec3 o)
|
|
{
|
|
return convertToAtmosphereUnit(o, frameData);
|
|
}
|
|
|
|
vec3 convertToCameraUnit(vec3 o)
|
|
{
|
|
return convertToCameraUnit(o, frameData);
|
|
}
|
|
|
|
float getShadow(in const AtmosphereParameters atmospehre, vec3 p)
|
|
{
|
|
// Use shadow can get godray like effect here.
|
|
// But we can't use because air perspective's resolution too low.
|
|
return 1.0f;
|
|
}
|
|
|
|
// Generate a sample (using importance sampling) along an infinitely long path with a given constant extinction.
|
|
// Zeta is a random number in [0,1]
|
|
float infiniteTransmittanceIS(float extinction, float zeta)
|
|
{
|
|
return -log(1.0f - zeta) / extinction;
|
|
}
|
|
|
|
// Normalized PDF from a sample on an infinitely long path according to transmittance and extinction.
|
|
float infiniteTransmittancePDF(float extinction, float transmittance)
|
|
{
|
|
return extinction * transmittance;
|
|
}
|
|
|
|
// Same as above but a sample is generated constrained within a range t,
|
|
// where transmittance = exp(-extinction*t) over that range.
|
|
float rangedTransmittanceIS(float extinction, float transmittance, float zeta)
|
|
{
|
|
return -log(1.0f - zeta * (1.0f - transmittance)) / extinction;
|
|
}
|
|
|
|
// https://www.youtube.com/watch?v=y-oBGzDCZKI at 9:20
|
|
void uvToSkyViewLutParams(
|
|
in const AtmosphereParameters atmosphere,
|
|
out float viewZenithCosAngle,
|
|
out float lightViewCosAngle,
|
|
in float viewHeight,
|
|
in vec2 uv)
|
|
{
|
|
// Constrain uvs to valid sub texel range (avoid zenith derivative issue making LUT usage visible)
|
|
vec2 lutSize = vec2(imageSize(imageSkyViewLut));
|
|
uv = vec2(fromSubUvsToUnit(uv.x, lutSize.x), fromSubUvsToUnit(uv.y, lutSize.y));
|
|
|
|
float vHorizon = sqrt(viewHeight * viewHeight - atmosphere.bottomRadius * atmosphere.bottomRadius);
|
|
|
|
// Ground to horizon cos
|
|
float cosBeta = vHorizon / viewHeight;
|
|
|
|
float beta = acos(cosBeta);
|
|
float zenithHorizonAngle = kPI - beta;
|
|
|
|
if (uv.y < 0.5f)
|
|
{
|
|
float coord = 2.0 * uv.y;
|
|
coord = 1.0 - coord;
|
|
coord *= coord; // Non-linear sunLightInfo view lut.
|
|
|
|
coord = 1.0 - coord;
|
|
viewZenithCosAngle = cos(zenithHorizonAngle * coord);
|
|
}
|
|
else
|
|
{
|
|
float coord = uv.y * 2.0 - 1.0;
|
|
coord *= coord; // Non-linear sunLightInfo view lut.
|
|
viewZenithCosAngle = cos(zenithHorizonAngle + beta * coord);
|
|
}
|
|
|
|
float coord = uv.x;
|
|
coord *= coord;
|
|
|
|
lightViewCosAngle = -(coord * 2.0 - 1.0);
|
|
}
|
|
|
|
vec3 getMultipleScattering(
|
|
in const AtmosphereParameters atmosphere,
|
|
vec3 scattering,
|
|
vec3 extinction,
|
|
vec3 worldPos,
|
|
float viewZenithCosAngle)
|
|
{
|
|
const float viewHeight = getViewHeight(worldPos, atmosphere);
|
|
vec2 lutSize = vec2(textureSize(inMultiScatterLut, 0));
|
|
|
|
vec2 uv = saturate(vec2(viewZenithCosAngle * 0.5f + 0.5f, viewHeight / (atmosphere.topRadius - atmosphere.bottomRadius)));
|
|
uv = vec2(fromUnitToSubUvs(uv.x, lutSize.x), fromUnitToSubUvs(uv.y, lutSize.y));
|
|
|
|
vec3 multiScatteredLuminance = texture(sampler2D(inMultiScatterLut, linearClampEdgeSampler), uv).rgb;
|
|
return multiScatteredLuminance;
|
|
}
|
|
|
|
struct SingleScatteringResult
|
|
{
|
|
vec3 scatteredLight; // Scattered light (luminance)
|
|
vec3 opticalDepth; // Optical depth (1/m)
|
|
vec3 transmittance; // Transmittance in [0,1] (unitless)
|
|
|
|
vec3 multiScatAs1;
|
|
vec3 newMultiScatStep0Out;
|
|
vec3 newMultiScatStep1Out;
|
|
};
|
|
|
|
SingleScatteringResult buildSingleScatteringResultDefault()
|
|
{
|
|
SingleScatteringResult result;
|
|
|
|
result.scatteredLight = vec3(0.0);
|
|
result.opticalDepth = vec3(0.0);
|
|
result.transmittance = vec3(0.0);
|
|
|
|
result.multiScatAs1 = vec3(0.0);
|
|
result.newMultiScatStep0Out = vec3(0.0);
|
|
result.newMultiScatStep0Out = vec3(0.0);
|
|
|
|
return result;
|
|
}
|
|
|
|
const float kDefaultMaxT = 9000000.0f;
|
|
|
|
SingleScatteringResult integrateScatteredLuminance(
|
|
in vec2 pixPos,
|
|
in vec3 worldPos,
|
|
in vec3 worldDir,
|
|
in vec3 sunDir,
|
|
in const AtmosphereParameters atmosphere,
|
|
in bool bGround,
|
|
in float sampleCountIni,
|
|
in float depthBufferValue,
|
|
in bool bMieRayPhase,
|
|
in float tMaxMax,
|
|
in bool bVariableSampleCount)
|
|
{
|
|
SingleScatteringResult result = buildSingleScatteringResultDefault();
|
|
|
|
const vec3 kEarthOrigin = vec3(0.0);
|
|
|
|
// Compute next intersection with atmosphere or ground
|
|
float tBottom = raySphereIntersectNearest(worldPos, worldDir, kEarthOrigin, atmosphere.bottomRadius);
|
|
float tTop = raySphereIntersectNearest(worldPos, worldDir, kEarthOrigin, atmosphere.topRadius);
|
|
|
|
// Evaluate valid intersect t.
|
|
float tMax = 0.0f;
|
|
if (tBottom < 0.0f) // No intersect with bottom.
|
|
{
|
|
if (tTop < 0.0f) // No intersect with atmosphere.
|
|
{
|
|
// No intersection with earth nor atmosphere: stop right away
|
|
return result;
|
|
}
|
|
else // Intersect with atmosphere.
|
|
{
|
|
tMax = tTop;
|
|
}
|
|
}
|
|
else // Intersect with bottom.
|
|
{
|
|
if (tTop > 0.0f) // Also intersect with atmosphere.
|
|
{
|
|
// Use nearest one.
|
|
tMax = min(tTop, tBottom);
|
|
}
|
|
}
|
|
|
|
// Correct t select with background depth and shading model id.
|
|
// Theses code used when composite sunLightInfo on the scene color texture.
|
|
if (depthBufferValue >= 0.0 && depthBufferValue <= 1.0)
|
|
{
|
|
// World space depth.
|
|
vec2 sampleUv = (pixPos + vec2(0.5)) / vec2(textureSize(inDepth, 0));
|
|
vec3 depthBufferWorldPos = getWorldPos(sampleUv, depthBufferValue, frameData);
|
|
|
|
// Apply earth offset to go back to origin as top of earth mode.
|
|
float tDepth = length(depthBufferWorldPos * 0.001 + vec3(0.0, atmosphere.bottomRadius, 0.0) - worldPos); // Meter -> kilometers
|
|
if (tDepth < tMax)
|
|
{
|
|
tMax = tDepth;
|
|
}
|
|
}
|
|
tMax = min(tMax, tMaxMax);
|
|
|
|
// Sample count
|
|
float sampleCount = sampleCountIni;
|
|
float sampleCountFloor = sampleCountIni;
|
|
float tMaxFloor = tMax;
|
|
if (bVariableSampleCount)
|
|
{
|
|
sampleCount = mix(float(atmosphere.viewRayMarchMinSPP), float(atmosphere.viewRayMarchMaxSPP), saturate(tMax * 0.01));
|
|
sampleCountFloor = floor(sampleCount);
|
|
tMaxFloor = tMax * sampleCountFloor / sampleCount; // rescale tMax to map to the last entire step segment.
|
|
}
|
|
|
|
float dt = tMax / sampleCount;
|
|
|
|
// Phase functions
|
|
const float uniformPhase = getUniformPhase();
|
|
const vec3 wi = sunDir;
|
|
const vec3 wo = worldDir;
|
|
float cosTheta = dot(wi, wo);
|
|
|
|
// mnegate cosTheta because due to WorldDir being a "in" direction.
|
|
float miePhaseValue = hgPhase(atmosphere.miePhaseG, -cosTheta);
|
|
float rayleighPhaseValue = rayleighPhase(cosTheta);
|
|
|
|
vec3 globalL = frameData.sunLightInfo.color * frameData.sunLightInfo.intensity;
|
|
|
|
vec3 L = vec3(0.0);
|
|
vec3 throughput = vec3(1.0);
|
|
vec3 opticalDepth = vec3(0.0);
|
|
float t = 0.0f;
|
|
float tPrev = 0.0;
|
|
const float sampleSegmentT = 0.3f;
|
|
|
|
for (float s = 0.0; s < sampleCount; s += 1.0)
|
|
{
|
|
if (bVariableSampleCount)
|
|
{
|
|
// More expenssive but artefact free
|
|
float t0 = (s) / sampleCountFloor;
|
|
float t1 = (s + 1.0f) / sampleCountFloor;
|
|
|
|
// Non linear distribution of sample within the range.
|
|
t0 = t0 * t0;
|
|
t1 = t1 * t1;
|
|
|
|
// Make t0 and t1 world space distances.
|
|
t0 = tMaxFloor * t0;
|
|
|
|
if (t1 > 1.0)
|
|
{
|
|
// this reveal depth slices
|
|
// t1 = tMaxFloor;
|
|
t1 = tMax;
|
|
}
|
|
else
|
|
{
|
|
t1 = tMaxFloor * t1;
|
|
}
|
|
// With dithering required to hide some sampling artefact relying on TAA later? This may even allow volumetric shadow?
|
|
// t = t0 + (t1 - t0) * (whangHashNoise(pixPos.x, pixPos.y, gFrameId * 1920 * 1080));
|
|
t = t0 + (t1 - t0) * sampleSegmentT;
|
|
dt = t1 - t0;
|
|
}
|
|
else
|
|
{
|
|
float newT = tMax * (s + sampleSegmentT) / sampleCount;
|
|
dt = newT - t;
|
|
t = newT;
|
|
}
|
|
|
|
|
|
// Get current step position.
|
|
vec3 P = worldPos + t * worldDir;
|
|
|
|
// Sample medium color.
|
|
MediumSampleRGB medium = sampleMediumRGB(P, atmosphere);
|
|
|
|
// Get optical depth
|
|
const vec3 sampleOpticalDepth = medium.extinction * dt;
|
|
|
|
// transmittance is exp(-opticalDepth).
|
|
const vec3 sampleTransmittance = exp(-sampleOpticalDepth);
|
|
|
|
// Accumulate sample optical depth.
|
|
opticalDepth += sampleOpticalDepth;
|
|
|
|
// Get sample height.
|
|
float pHeight = length(P);
|
|
|
|
// Get normalize direction.
|
|
const vec3 upVector = P / pHeight;
|
|
|
|
// Get pre-compute transmittance.
|
|
float sunZenithCosAngle = dot(sunDir, upVector);
|
|
vec2 uv;
|
|
lutTransmittanceParamsToUv(atmosphere, pHeight, sunZenithCosAngle, uv);
|
|
vec3 transmittanceToSun = texture(sampler2D(inTransmittanceLut, linearClampEdgeSampler), uv).rgb;
|
|
|
|
vec3 phaseTimesScattering;
|
|
if (bMieRayPhase)
|
|
{
|
|
phaseTimesScattering = medium.scatteringMie * miePhaseValue + medium.scatteringRay * rayleighPhaseValue;
|
|
}
|
|
else
|
|
{
|
|
phaseTimesScattering = medium.scattering * uniformPhase;
|
|
}
|
|
|
|
// Earth shadow
|
|
float tEarth = raySphereIntersectNearest(P, sunDir, kEarthOrigin + kAtmospherePlanetRadiusOffset * upVector, atmosphere.bottomRadius);
|
|
float earthShadow = tEarth >= 0.0f ? 0.0f : 1.0f;
|
|
|
|
// Dual scattering for multi scattering
|
|
vec3 multiScatteredLuminance = vec3(0.0);
|
|
|
|
#ifndef NO_MULTISCATAPPROX_ENABLED
|
|
multiScatteredLuminance = getMultipleScattering(atmosphere, medium.scattering, medium.extinction, P, sunZenithCosAngle);
|
|
#endif
|
|
float shadow = getShadow(atmosphere, P);
|
|
|
|
vec3 S = globalL * (earthShadow * shadow * transmittanceToSun * phaseTimesScattering + (multiScatteredLuminance * medium.scattering));
|
|
|
|
// When using the power serie to accumulate all sattering order, serie r must be <1 for a serie to converge.
|
|
// Under extreme coefficient, multiScatAs1 can grow larger and thus result in broken visuals.
|
|
// The way to fix that is to use a proper analytical integration as proposed in slide 28 of http://www.frostbite.com/2015/08/physically-based-unified-volumetric-rendering-in-frostbite/
|
|
// However, it is possible to disable as it can also work using simple power serie sum unroll up to 5th order. The rest of the orders has a really low contribution.
|
|
vec3 ms = medium.scattering * 1;
|
|
vec3 msint = (ms - ms * sampleTransmittance) / medium.extinction;
|
|
result.multiScatAs1 += throughput * msint;
|
|
|
|
// Evaluate input to multi scattering
|
|
{
|
|
vec3 newMS;
|
|
|
|
newMS = earthShadow * transmittanceToSun * medium.scattering * uniformPhase * 1;
|
|
result.newMultiScatStep0Out += throughput * (newMS - newMS * sampleTransmittance) / medium.extinction;
|
|
|
|
newMS = medium.scattering * uniformPhase * multiScatteredLuminance;
|
|
result.newMultiScatStep1Out += throughput * (newMS - newMS * sampleTransmittance) / medium.extinction;
|
|
}
|
|
|
|
// See slide 28 at http://www.frostbite.com/2015/08/physically-based-unified-volumetric-rendering-in-frostbite/
|
|
vec3 sint = (S - S * sampleTransmittance) / medium.extinction; // integrate along the current step segment
|
|
L += throughput * sint; // accumulate and also take into account the transmittance from previous steps
|
|
throughput *= sampleTransmittance;
|
|
|
|
tPrev = t;
|
|
}
|
|
|
|
if (bGround && (tMax == tBottom) && (tBottom > 0.0))
|
|
{
|
|
// Account for bounced light off the earth
|
|
vec3 P = worldPos + tBottom * worldDir;
|
|
float pHeight = length(P);
|
|
|
|
const vec3 upVector = P / pHeight;
|
|
float sunZenithCosAngle = dot(sunDir, upVector);
|
|
vec2 uv;
|
|
lutTransmittanceParamsToUv(atmosphere, pHeight, sunZenithCosAngle, uv);
|
|
vec3 transmittanceToSun = texture(sampler2D(inTransmittanceLut, linearClampEdgeSampler), uv).rgb;
|
|
|
|
const float NdotL = saturate(dot(normalize(upVector), normalize(sunDir)));
|
|
L += globalL * transmittanceToSun * throughput * NdotL * atmosphere.groundAlbedo / kPI;
|
|
}
|
|
|
|
result.scatteredLight = L;
|
|
result.opticalDepth = opticalDepth;
|
|
result.transmittance = throughput;
|
|
return result;
|
|
}
|
|
|
|
// Input world position and compute it's scatter lighting.
|
|
vec3 getPosScatterLight(
|
|
in const AtmosphereParameters atmosphere,
|
|
in const vec3 inWorldPos,
|
|
in const vec2 uv,
|
|
in const bool bGround,
|
|
in const vec2 pixPos)
|
|
{
|
|
float viewHeight = length(inWorldPos);
|
|
float viewZenithCosAngle;
|
|
float lightViewCosAngle;
|
|
uvToSkyViewLutParams(atmosphere, viewZenithCosAngle, lightViewCosAngle, viewHeight, uv);
|
|
|
|
vec3 sunDir;
|
|
{
|
|
vec3 upVector = inWorldPos / viewHeight;
|
|
float sunZenithCosAngle = dot(upVector, -normalize(frameData.sunLightInfo.direction));
|
|
sunDir = normalize(vec3(sqrt(1.0 - sunZenithCosAngle * sunZenithCosAngle), sunZenithCosAngle, 0.0));
|
|
}
|
|
|
|
// Use view height as world pos here.
|
|
vec3 worldPos = vec3(0.0, viewHeight, 0.0);
|
|
float viewZenithSinAngle = sqrt(1 - viewZenithCosAngle * viewZenithCosAngle);
|
|
vec3 worldDir = vec3(
|
|
viewZenithSinAngle * lightViewCosAngle,
|
|
viewZenithCosAngle,
|
|
viewZenithSinAngle * sqrt(1.0 - lightViewCosAngle * lightViewCosAngle)
|
|
);
|
|
|
|
// Move to top atmospehre
|
|
if (!moveToTopAtmosphere(worldPos, worldDir, atmosphere.topRadius))
|
|
{
|
|
// Ray is not intersecting the atmosphere
|
|
return vec3(0.0, 0.0, 0.0);
|
|
}
|
|
|
|
const float sampleCountIni = 30;
|
|
const float depthBufferValue = -1.0;
|
|
const bool bMieRayPhase = true;
|
|
const float tMaxMax = kDefaultMaxT;
|
|
const bool bVariableSampleCount = true;
|
|
|
|
SingleScatteringResult ss = integrateScatteredLuminance(
|
|
pixPos,
|
|
worldPos,
|
|
worldDir,
|
|
sunDir,
|
|
atmosphere,
|
|
bGround,
|
|
sampleCountIni,
|
|
depthBufferValue,
|
|
bMieRayPhase,
|
|
tMaxMax,
|
|
bVariableSampleCount
|
|
);
|
|
ss.scatteredLight = min(ss.scatteredLight, vec3(kMaxHalfFloat));
|
|
|
|
return ss.scatteredLight;
|
|
}
|
|
|
|
#ifdef TRANSMITTANCE_LUT_PASS
|
|
|
|
#ifndef NO_MULTISCATAPPROX_ENABLED
|
|
#error "Transmittance lut pass must disable multi scatter approx."
|
|
#endif
|
|
|
|
layout (local_size_x = 8, local_size_y = 8) in;
|
|
void main()
|
|
{
|
|
ivec2 lutSize = imageSize(imageTransmittanceLut);
|
|
ivec2 workPos = ivec2(gl_GlobalInvocationID.xy);
|
|
|
|
AtmosphereParameters atmosphere = getAtmosphereParameters();
|
|
|
|
const vec2 pixPos = vec2(workPos) + vec2(0.5f);
|
|
const vec2 uv = pixPos / vec2(lutSize);
|
|
|
|
float viewHeight;
|
|
float viewZenithCosAngle;
|
|
uvToLutTransmittanceParams(atmosphere, viewHeight, viewZenithCosAngle, uv);
|
|
|
|
const vec3 worldPos = vec3(0.0f, viewHeight, 0.0f);
|
|
const vec3 worldDir = vec3(0.0f, viewZenithCosAngle, sqrt(1.0 - viewZenithCosAngle * viewZenithCosAngle));
|
|
const vec3 sunDir = -normalize(frameData.sunLightInfo.direction);
|
|
const bool bGround = false;
|
|
const float sampleCountIni = 40.0;
|
|
const float depthBufferValue = -1.0;
|
|
const bool bMieRayPhase = false;
|
|
const float tMaxMax = kDefaultMaxT;
|
|
const bool bVariableSampleCount = false;
|
|
vec3 opticalDepth = integrateScatteredLuminance(
|
|
pixPos,
|
|
worldPos,
|
|
worldDir,
|
|
sunDir,
|
|
atmosphere,
|
|
bGround,
|
|
sampleCountIni,
|
|
depthBufferValue,
|
|
bMieRayPhase,
|
|
tMaxMax,
|
|
bVariableSampleCount
|
|
).opticalDepth;
|
|
|
|
vec3 transmittance = exp(-opticalDepth);
|
|
transmittance = min(transmittance, vec3(kMaxHalfFloat));
|
|
imageStore(imageTransmittanceLut, workPos, vec4(transmittance, 1.0f));
|
|
}
|
|
|
|
#endif // TRANSMITTANCE_LUT_PASS
|
|
|
|
#ifdef MULTI_SCATTER_PASS
|
|
|
|
#extension GL_GOOGLE_include_directive : enable
|
|
#extension GL_KHR_shader_subgroup_arithmetic : require
|
|
#extension GL_KHR_shader_subgroup_basic : require
|
|
|
|
#ifndef NO_MULTISCATAPPROX_ENABLED
|
|
#error "Multi scatter lut pass must disable multi scatter approx."
|
|
#endif
|
|
|
|
const uint kSqrtSampleCount = 8;
|
|
const uint kSampleCount = kSqrtSampleCount * kSqrtSampleCount;
|
|
|
|
shared vec3 sharedMultiScatAs1[kSampleCount];
|
|
shared vec3 sharedScatterLight[kSampleCount];
|
|
|
|
layout (local_size_x = 1, local_size_y = 1, local_size_z = kSampleCount) in;
|
|
void main()
|
|
{
|
|
ivec2 lutSize = imageSize(imageMultiScatterLut);
|
|
ivec2 workPos = ivec2(gl_GlobalInvocationID.xy);
|
|
|
|
const uint flattenId = gl_GlobalInvocationID.z;
|
|
|
|
AtmosphereParameters atmosphere = getAtmosphereParameters();
|
|
|
|
const vec2 pixPos = vec2(workPos) + vec2(0.5f);
|
|
|
|
vec2 uv = pixPos / vec2(lutSize);
|
|
uv = vec2(fromSubUvsToUnit(uv.x, lutSize.x), fromSubUvsToUnit(uv.y, lutSize.y));
|
|
|
|
float cosSunZenithAngle = uv.x * 2.0 - 1.0;
|
|
vec3 sunDir = vec3(0.0, cosSunZenithAngle, sqrt(saturate(1.0 - cosSunZenithAngle * cosSunZenithAngle)));
|
|
|
|
// We adjust again viewHeight according to kAtmospherePlanetRadiusOffset to be in a valid range.
|
|
float viewHeight = atmosphere.bottomRadius + saturate(uv.y + kAtmospherePlanetRadiusOffset) * (atmosphere.topRadius - atmosphere.bottomRadius - kAtmospherePlanetRadiusOffset);
|
|
|
|
// Config world pos and world direction.
|
|
vec3 worldPos = vec3(0.0f, viewHeight, 0.0f);
|
|
vec3 worldDir = vec3(0.0f, 1.0f, 0.0f);
|
|
|
|
const bool bGround = true;
|
|
const float sampleCountIni = 20; // A minimum set of step is required for accuracy unfortunately.
|
|
const float depthBufferValue = -1.0;
|
|
const bool bMieRayPhase = false;
|
|
const float tMaxMax = kDefaultMaxT;
|
|
const bool bVariableSampleCount = false;
|
|
const float sphereSolidAngle = 4.0 * kPI;
|
|
const float isotropicPhase = 1.0 / sphereSolidAngle;
|
|
|
|
const float sqrtSample = float(kSqrtSampleCount);
|
|
|
|
float i = 0.5 + float(flattenId / kSqrtSampleCount);
|
|
float j = 0.5 + float(flattenId - float((flattenId / kSqrtSampleCount) * kSqrtSampleCount));
|
|
|
|
vec3 multiScatAs1 = vec3(0.0);
|
|
vec3 scatteredLight = vec3(0.0);
|
|
{
|
|
float randA = i / sqrtSample;
|
|
float randB = j / sqrtSample;
|
|
float theta = 2.0f * kPI * randA;
|
|
|
|
// Uniform distribution https://mathworld.wolfram.com/SpherePointPicking.html
|
|
float phi = acos(1.0f - 2.0f * randB);
|
|
|
|
float cosPhi = cos(phi);
|
|
float sinPhi = sin(phi);
|
|
float cosTheta = cos(theta);
|
|
float sinTheta = sin(theta);
|
|
|
|
// Get direction, y up.
|
|
worldDir.x = cosTheta * sinPhi;
|
|
worldDir.y = cosPhi;
|
|
worldDir.z = sinTheta * sinPhi;
|
|
|
|
SingleScatteringResult result = integrateScatteredLuminance(
|
|
pixPos,
|
|
worldPos,
|
|
worldDir,
|
|
sunDir,
|
|
atmosphere,
|
|
bGround,
|
|
sampleCountIni,
|
|
depthBufferValue,
|
|
bMieRayPhase,
|
|
tMaxMax,
|
|
bVariableSampleCount
|
|
);
|
|
|
|
sharedMultiScatAs1[flattenId] = result.multiScatAs1 * sphereSolidAngle / (sqrtSample * sqrtSample);
|
|
sharedScatterLight[flattenId] = result.scatteredLight * sphereSolidAngle / (sqrtSample * sqrtSample);
|
|
}
|
|
|
|
groupMemoryBarrier();
|
|
barrier();
|
|
|
|
// Reduce.
|
|
uint loopIndex = kSampleCount / 2;
|
|
while(loopIndex > 0)
|
|
{
|
|
if(flattenId < loopIndex)
|
|
{
|
|
sharedMultiScatAs1[flattenId] += sharedMultiScatAs1[flattenId + loopIndex];
|
|
sharedScatterLight[flattenId] += sharedScatterLight[flattenId + loopIndex];
|
|
}
|
|
|
|
loopIndex /= 2;
|
|
|
|
groupMemoryBarrier();
|
|
barrier();
|
|
}
|
|
|
|
if (flattenId > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// MultiScatAs1 represents the amount of luminance scattered as if the integral of scattered luminance over the sphere would be 1.
|
|
// - 1st order of scattering: one can ray-march a straight path as usual over the sphere. That is InScatteredLuminance.
|
|
// - 2nd order of scattering: the inscattered luminance is InScatteredLuminance at each of samples of fist order integration. Assuming a uniform phase function that is represented by MultiScatAs1,
|
|
// - 3nd order of scattering: the inscattered luminance is (InScatteredLuminance * MultiScatAs1 * MultiScatAs1)
|
|
// - etc.
|
|
// For a serie, sum_{n=0}^{n=+inf} = 1 + r + r^2 + r^3 + ... + r^n = 1 / (1.0 - r), see https://en.wikipedia.org/wiki/Geometric_series
|
|
|
|
const vec3 r = sharedMultiScatAs1[flattenId] * isotropicPhase; // Equation 7 f_ms
|
|
vec3 inScatteredLuminance = sharedScatterLight[flattenId] * isotropicPhase; // Equation 5 L_2ndOrder
|
|
const vec3 sumOfAllMultiScatteringEventsContribution = 1.0f / (1.0 - r);
|
|
vec3 L = inScatteredLuminance * sumOfAllMultiScatteringEventsContribution; // Equation 10 Psi_ms
|
|
|
|
vec3 result = L * atmosphere.multipleScatteringFactor;
|
|
result = min(result, vec3(kMaxHalfFloat));
|
|
imageStore(imageMultiScatterLut, workPos, vec4(result, 1.0f));
|
|
}
|
|
|
|
#endif // MULTI_SCATTER_PASS
|
|
|
|
#ifdef SKY_LUT_PASS
|
|
|
|
#ifdef NO_MULTISCATAPPROX_ENABLED
|
|
#error "Skylut pass need multi scatter approx."
|
|
#endif
|
|
|
|
layout (local_size_x = 8, local_size_y = 8) in;
|
|
void main()
|
|
{
|
|
ivec2 lutSize = imageSize(imageSkyViewLut);
|
|
ivec2 workPos = ivec2(gl_GlobalInvocationID.xy);
|
|
|
|
if(workPos.x >= lutSize.x || workPos.y >= lutSize.y)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const vec2 pixPos = vec2(workPos) + vec2(0.5f);
|
|
const vec2 uv = pixPos / vec2(lutSize);
|
|
|
|
AtmosphereParameters atmosphere = getAtmosphereParameters();
|
|
vec3 worldPos = convertToAtmosphereUnit(frameData.camWorldPos.xyz) + vec3(0.0, atmosphere.bottomRadius, 0.0);
|
|
vec3 scatteredLight = getPosScatterLight(atmosphere, worldPos, uv, true, pixPos);
|
|
imageStore(imageSkyViewLut, workPos, vec4(scatteredLight, 1.0f));
|
|
}
|
|
|
|
#endif // SKY_LUT_PASS
|
|
|
|
#ifdef AIR_PERSPECTIVE_PASS
|
|
|
|
#ifdef NO_MULTISCATAPPROX_ENABLED
|
|
#error "Air perspective pass need multi scatter approx."
|
|
#endif
|
|
|
|
// 32 x 32 x 32 Dimension.
|
|
layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
|
|
void main()
|
|
{
|
|
ivec3 lutSize = imageSize(imageFroxelScatter);
|
|
ivec3 workPos = ivec3(gl_GlobalInvocationID.xyz);
|
|
|
|
AtmosphereParameters atmosphere = getAtmosphereParameters();
|
|
|
|
const vec2 pixPos = vec2(workPos.xy) + vec2(0.5f);
|
|
const vec2 uv = pixPos / vec2(lutSize.xy);
|
|
|
|
vec4 clipSpace = vec4(uv.x * 2.0f - 1.0f, 1.0f - uv.y * 2.0f, 0.0, 1.0);
|
|
vec4 viewPosH = frameData.camInvertProj * clipSpace;
|
|
vec3 viewDir = viewPosH.xyz / viewPosH.w;
|
|
|
|
vec3 worldDir = normalize((frameData.camInvertView * vec4(viewDir, 0.0)).xyz);
|
|
|
|
vec3 camPos = convertToAtmosphereUnit(frameData.camWorldPos.xyz) + vec3(0, atmosphere.bottomRadius, 0);
|
|
vec3 sunDir = -normalize(frameData.sunLightInfo.direction);
|
|
vec3 sunLuminance = vec3(0.0);
|
|
|
|
// [0, 1)
|
|
float slice = ((float(workPos.z) + 0.5f) / float(lutSize.z));
|
|
slice *= slice; // Squared distribution
|
|
slice *= float(lutSize.z);
|
|
|
|
vec3 worldPos = camPos;
|
|
float viewHeight;
|
|
|
|
// Compute position from froxel information
|
|
float tMax = aerialPerspectiveSliceToDepth(slice);
|
|
vec3 newWorldPos = worldPos + tMax * worldDir;
|
|
|
|
// If the voxel is under the ground, make sure to offset it out on the ground.
|
|
viewHeight = length(newWorldPos);
|
|
if (viewHeight <= (atmosphere.bottomRadius + kAtmospherePlanetRadiusOffset))
|
|
{
|
|
// Apply a position offset to make sure no artefact are visible close to the earth boundaries for large voxel.
|
|
newWorldPos = normalize(newWorldPos) * (atmosphere.bottomRadius + kAtmospherePlanetRadiusOffset + 0.001f);
|
|
worldDir = normalize(newWorldPos - camPos);
|
|
tMax = length(newWorldPos - camPos);
|
|
}
|
|
float tMaxMax = tMax;
|
|
|
|
// Move ray marching start up to top atmosphere.
|
|
viewHeight = length(worldPos);
|
|
if (viewHeight >= atmosphere.topRadius)
|
|
{
|
|
vec3 prevWorlPos = worldPos;
|
|
if (!moveToTopAtmosphere(worldPos, worldDir, atmosphere.topRadius))
|
|
{
|
|
// Ray is not intersecting the atmosphere
|
|
imageStore(imageFroxelScatter, workPos, vec4(0.0, 0.0, 0.0, 1.0));
|
|
return;
|
|
}
|
|
|
|
float lengthToAtmosphere = length(prevWorlPos - worldPos);
|
|
if (tMaxMax < lengthToAtmosphere)
|
|
{
|
|
// tMaxMax for this voxel is not within earth atmosphere
|
|
imageStore(imageFroxelScatter, workPos, vec4(0.0, 0.0, 0.0, 1.0));
|
|
return;
|
|
}
|
|
|
|
// Now world position has been moved to the atmosphere boundary: we need to reduce tMaxMax accordingly.
|
|
tMaxMax = max(0.0, tMaxMax - lengthToAtmosphere);
|
|
}
|
|
|
|
const bool bGround = false;
|
|
const float sampleCountIni = max(1.0, float(workPos.z + 1.0) * 2.0f);
|
|
const float depthBufferValue = -1.0;
|
|
const bool bVariableSampleCount = false;
|
|
const bool bMieRayPhase = true;
|
|
|
|
SingleScatteringResult ss = integrateScatteredLuminance(
|
|
pixPos,
|
|
worldPos,
|
|
worldDir,
|
|
sunDir,
|
|
atmosphere,
|
|
bGround,
|
|
sampleCountIni,
|
|
depthBufferValue,
|
|
bMieRayPhase,
|
|
tMaxMax,
|
|
bVariableSampleCount
|
|
);
|
|
|
|
ss.scatteredLight = min(ss.scatteredLight, vec3(kMaxHalfFloat));
|
|
|
|
imageStore(imageFroxelScatter, workPos, vec4(ss.scatteredLight, 1.0 - mean(ss.transmittance)));
|
|
}
|
|
|
|
#endif // AIR_PERSPECTIVE_PASS
|
|
|
|
#ifdef COMPOSITE_SKY_PASS
|
|
|
|
#ifdef NO_MULTISCATAPPROX_ENABLED
|
|
#error "Sky composite pass need multi scatter approx."
|
|
#endif
|
|
|
|
layout (local_size_x = 8, local_size_y = 8) in;
|
|
void main()
|
|
{
|
|
ivec2 colorSize = imageSize(imageHdrSceneColor);
|
|
|
|
uvec2 groupThreadId = remap8x8(gl_LocalInvocationIndex);
|
|
uvec2 dispatchId = groupThreadId + gl_WorkGroupID.xy * 8;
|
|
|
|
ivec2 workPos = ivec2(dispatchId);
|
|
|
|
if(workPos.x >= colorSize.x || workPos.y >= colorSize.y)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const vec2 pixPos = vec2(workPos) + vec2(0.5f);
|
|
const vec2 uv = pixPos / vec2(colorSize);
|
|
|
|
vec4 srcColor = imageLoad(imageHdrSceneColor, workPos);
|
|
|
|
AtmosphereParameters atmosphere = getAtmosphereParameters();
|
|
|
|
// We are revert z.
|
|
vec4 clipSpace = vec4(uv.x * 2.0f - 1.0f, 1.0f - uv.y * 2.0f, 0.0, 1.0);
|
|
vec4 viewPosH = frameData.camInvertProj * clipSpace;
|
|
vec3 viewDir = viewPosH.xyz / viewPosH.w;
|
|
vec3 worldDir = normalize((frameData.camInvertView * vec4(viewDir, 0.0)).xyz);
|
|
vec3 worldPos = convertToAtmosphereUnit(frameData.camWorldPos.xyz) + vec3(0.0, atmosphere.bottomRadius, 0.0);
|
|
float viewHeight = length(worldPos);
|
|
|
|
vec3 L = vec3(0);
|
|
float depthBufferValue = texture(sampler2D(inDepth, pointClampEdgeSampler), uv).r;
|
|
|
|
const bool bSky = (depthBufferValue == 0.0f);
|
|
|
|
const vec3 sunDirection = -normalize(frameData.sunLightInfo.direction);
|
|
|
|
const bool bUnderAtmosphere = viewHeight < atmosphere.topRadius;
|
|
|
|
vec3 upVector = normalize(worldPos);
|
|
|
|
// Back ground and under atmosphere pixel, sample sunLightInfo view lut.
|
|
if (bUnderAtmosphere && bSky)
|
|
{
|
|
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;
|
|
|
|
bool bIntersectGround = raySphereIntersectNearest(worldPos, worldDir, vec3(0.0), atmosphere.bottomRadius) >= 0.0f;
|
|
|
|
vec2 sampleUv;
|
|
skyViewLutParamsToUv(atmosphere, bIntersectGround, viewZenithCosAngle, lightViewCosAngle, viewHeight, vec2(textureSize(inSkyViewLut, 0)), sampleUv);
|
|
|
|
vec3 luminance = texture(sampler2D(inSkyViewLut, linearClampEdgeSampler), sampleUv).rgb;
|
|
|
|
imageStore(imageHdrSceneColor, workPos, vec4(luminance, srcColor.a));
|
|
imageStore(imageGbufferId, workPos, vec4(packObjectId(kSkyObjectId)));
|
|
|
|
return;
|
|
}
|
|
|
|
float opacity = 0.0;
|
|
if(bUnderAtmosphere) // Composite air perspective.
|
|
{
|
|
// Exist pre-compute data, sample it.
|
|
|
|
// Build world position.
|
|
clipSpace.z = depthBufferValue;
|
|
vec4 depthBufferWorldPos = frameData.camInvertViewProj * clipSpace;
|
|
depthBufferWorldPos.xyz /= depthBufferWorldPos.w;
|
|
|
|
float tDepth = length((depthBufferWorldPos.xyz * 0.001) - (worldPos + vec3(0.0, -atmosphere.bottomRadius, 0.0))); // meter -> kilometers.
|
|
float slice = aerialPerspectiveDepthToSlice(tDepth);
|
|
|
|
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
|
|
|
|
const vec4 airPerspective = weight * texture(sampler3D(inFroxelScatter, linearClampEdgeSampler), vec3(uv, w));
|
|
L.rgb += airPerspective.rgb;
|
|
opacity = airPerspective.a;
|
|
}
|
|
else if(bSky)
|
|
{
|
|
// No precompute data can use. compute new data.
|
|
|
|
// Move to top atmosphere as the starting point for ray marching.
|
|
// This is critical to be after the above to not disrupt above atmosphere tests and voxel selection.
|
|
if (!moveToTopAtmosphere(worldPos, worldDir, atmosphere.topRadius))
|
|
{
|
|
// Ray is not intersecting the atmosphere, return.
|
|
return;
|
|
}
|
|
|
|
const bool bGround = false;
|
|
const float sampleCountIni = 0.0;
|
|
const bool bVariableSampleCount = true;
|
|
const bool bMieRayPhase = true;
|
|
const float tMaxMax = kDefaultMaxT;
|
|
depthBufferValue = -1.0;
|
|
SingleScatteringResult ss = integrateScatteredLuminance(
|
|
pixPos,
|
|
worldPos,
|
|
worldDir,
|
|
sunDirection,
|
|
atmosphere,
|
|
bGround,
|
|
sampleCountIni,
|
|
depthBufferValue,
|
|
bMieRayPhase,
|
|
tMaxMax,
|
|
bVariableSampleCount
|
|
);
|
|
|
|
L += ss.scatteredLight;
|
|
vec3 throughput = ss.transmittance;
|
|
|
|
const float transmittance = mean(throughput);
|
|
opacity = 1.0 - transmittance;
|
|
}
|
|
|
|
vec3 outColor = L.rgb + (1.0 - opacity) * srcColor.xyz;
|
|
|
|
|
|
imageStore(imageHdrSceneColor, workPos, vec4(outColor, srcColor.w));
|
|
}
|
|
|
|
#endif // COMPOSITE_SKY_PASS
|
|
|
|
#ifdef SKY_CAPTURE_PASS
|
|
|
|
#ifdef NO_MULTISCATAPPROX_ENABLED
|
|
#error "Sky capture pass need multi scatter approx."
|
|
#endif
|
|
|
|
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
|
|
void main()
|
|
{
|
|
ivec3 cubeCoord = ivec3(gl_GlobalInvocationID);
|
|
ivec2 cubeSize = imageSize(imageCubeEnv);
|
|
|
|
if(cubeCoord.x >= cubeSize.x || cubeCoord.y >= cubeSize.y || cubeCoord.z >= 6)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const vec2 pixPos = vec2(cubeCoord) + vec2(0.5f);
|
|
const vec2 uv = pixPos / vec2(cubeSize);
|
|
|
|
AtmosphereParameters atmosphere = getAtmosphereParameters();
|
|
|
|
vec3 worldDir = getSamplingVector(cubeCoord.z, uv);
|
|
const vec3 sunDirection = -normalize(frameData.sunLightInfo.direction);
|
|
|
|
// Sample skyview lut and store in cubemap capture.
|
|
vec3 worldPos = convertToAtmosphereUnit(frameData.camWorldPos.xyz) + vec3(0.0, atmosphere.bottomRadius, 0.0);
|
|
float viewHeight = length(worldPos);
|
|
|
|
// If camera out of atmosphere, no capture.
|
|
const bool bCanUseSkyViewLut = viewHeight < atmosphere.topRadius;
|
|
vec3 result = vec3(0.0);
|
|
if (bCanUseSkyViewLut)
|
|
{
|
|
vec3 upVector = normalize(worldPos);
|
|
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;
|
|
|
|
bool bIntersectGround = raySphereIntersectNearest(worldPos, worldDir, vec3(0.0), atmosphere.bottomRadius) >= 0.0f;
|
|
|
|
vec2 sampleUv;
|
|
skyViewLutParamsToUv(atmosphere, bIntersectGround, viewZenithCosAngle, lightViewCosAngle, viewHeight, vec2(textureSize(inSkyViewLut, 0)), sampleUv);
|
|
|
|
result = texture(sampler2D(inSkyViewLut, linearClampEdgeSampler), sampleUv).rgb;
|
|
}
|
|
else
|
|
{
|
|
// Move to top atmosphere as the starting point for ray marching.
|
|
// This is critical to be after the above to not disrupt above atmosphere tests and voxel selection.
|
|
if (moveToTopAtmosphere(worldPos, worldDir, atmosphere.topRadius))
|
|
{
|
|
// Ray intersecting the atmosphere.
|
|
const bool bGround = false;
|
|
const float sampleCountIni = 0.0;
|
|
const bool bVariableSampleCount = true;
|
|
const bool bMieRayPhase = true;
|
|
const float tMaxMax = kDefaultMaxT;
|
|
const float depthBufferValue = -1.0;
|
|
result = integrateScatteredLuminance(
|
|
pixPos,
|
|
worldPos,
|
|
worldDir,
|
|
sunDirection,
|
|
atmosphere,
|
|
bGround,
|
|
sampleCountIni,
|
|
depthBufferValue,
|
|
bMieRayPhase,
|
|
tMaxMax,
|
|
bVariableSampleCount
|
|
).scatteredLight;
|
|
}
|
|
}
|
|
|
|
imageStore(imageCubeEnv, cubeCoord, vec4(result, 1.0));
|
|
return;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef SKY_DISTANCE_LIT_CLOUD_PASS
|
|
|
|
#ifdef NO_MULTISCATAPPROX_ENABLED
|
|
#error "Distant lit need multi scatter approx."
|
|
#endif
|
|
|
|
// NOTE: Non visibility term in this distant lut.
|
|
shared vec3 shareSkyLit[64];
|
|
|
|
// Average distant lit sample for cloud SH approx.
|
|
layout(local_size_x = 64) in;
|
|
void main()
|
|
{
|
|
AtmosphereParameters atmosphere = getAtmosphereParameters();
|
|
|
|
// Total 8192 km.
|
|
const float kSkyDistantOffset = kDistantSkyLitMax * (gl_WorkGroupID.z + 0.5) / float(imageSize(imageCloudDistantLit).x) * 0.001 + atmosphere.bottomRadius;
|
|
|
|
// Get sample pos in km.
|
|
uint localId = gl_LocalInvocationIndex;
|
|
uvec2 dispatchId = remap8x8(gl_LocalInvocationIndex);
|
|
|
|
ivec2 workPos = ivec2(dispatchId);
|
|
vec3 worldDir;
|
|
{
|
|
float randA = (workPos.x + 0.5) / 8.0;
|
|
float randB = (workPos.y + 0.5) / 8.0;
|
|
float theta = 2.0f * kPI * randA;
|
|
|
|
// Uniform distribution https://mathworld.wolfram.com/SpherePointPicking.html
|
|
float phi = acos(1.0f - 2.0f * randB);
|
|
|
|
float cosPhi = cos(phi);
|
|
float sinPhi = sin(phi);
|
|
float cosTheta = cos(theta);
|
|
float sinTheta = sin(theta);
|
|
|
|
// Get direction, y up.
|
|
worldDir.x = cosTheta * sinPhi;
|
|
worldDir.y = cosPhi;
|
|
worldDir.z = sinTheta * sinPhi;
|
|
}
|
|
|
|
const vec3 sunDirection = -normalize(frameData.sunLightInfo.direction);
|
|
|
|
// Ray intersecting the atmosphere.
|
|
const bool bGround = false;
|
|
const float sampleCountIni = 10.0;
|
|
const bool bVariableSampleCount = false;
|
|
const bool bMieRayPhase = false;
|
|
const float tMaxMax = kDefaultMaxT;
|
|
const float depthBufferValue = -1.0;
|
|
|
|
shareSkyLit[localId] = integrateScatteredLuminance(
|
|
vec2(0.0),
|
|
vec3(0.0, kSkyDistantOffset, 0.0),
|
|
worldDir,
|
|
sunDirection,
|
|
atmosphere,
|
|
bGround,
|
|
sampleCountIni,
|
|
depthBufferValue,
|
|
bMieRayPhase,
|
|
tMaxMax,
|
|
bVariableSampleCount
|
|
).scatteredLight;
|
|
|
|
groupMemoryBarrier();
|
|
barrier();
|
|
|
|
uint loopIndex = 32;
|
|
while(loopIndex > 0)
|
|
{
|
|
if(localId < loopIndex)
|
|
{
|
|
shareSkyLit[localId] += shareSkyLit[localId + loopIndex];
|
|
}
|
|
|
|
loopIndex /= 2;
|
|
|
|
groupMemoryBarrier();
|
|
barrier();
|
|
}
|
|
|
|
if (localId < 1)
|
|
{
|
|
vec3 avgColor = shareSkyLit[localId] / 64.0;
|
|
imageStore(imageCloudDistantLit, ivec2(gl_WorkGroupID.z, 0), vec4(avgColor, 1.0));
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef SKY_DISTANCE_GRID_LIT_PASS
|
|
|
|
#ifdef NO_MULTISCATAPPROX_ENABLED
|
|
#error "Distant grid need multi scatter approx."
|
|
#endif
|
|
|
|
// We sample cloud shadow as an approximate of visibility.
|
|
|
|
shared vec3 shareSkyLit[64];
|
|
shared vec3 shareTransmittance[64];
|
|
|
|
// Average distant lit sample for cloud SH approx.
|
|
layout(local_size_x = 64) in;
|
|
void main()
|
|
{
|
|
ivec3 lutSize = imageSize(imageDistantGrid);
|
|
ivec3 workPos = ivec3(gl_WorkGroupID.xyz);
|
|
|
|
AtmosphereParameters atmosphere = getAtmosphereParameters();
|
|
|
|
const vec2 pixPos = vec2(workPos.xy) + vec2(0.5f);
|
|
const vec2 uv = pixPos / vec2(lutSize.xy);
|
|
|
|
vec4 clipSpace = vec4(uv.x * 2.0f - 1.0f, 1.0f - uv.y * 2.0f, 0.0, 1.0);
|
|
vec4 viewPosH = frameData.camInvertProj * clipSpace;
|
|
vec3 viewDir = viewPosH.xyz / viewPosH.w;
|
|
|
|
vec3 worldDir = normalize((frameData.camInvertView * vec4(viewDir, 0.0)).xyz);
|
|
|
|
vec3 camPos = convertToAtmosphereUnit(frameData.camWorldPos.xyz) + vec3(0, atmosphere.bottomRadius, 0);
|
|
vec3 sunDir = -normalize(frameData.sunLightInfo.direction);
|
|
|
|
float slice = ((float(workPos.z) + 0.5f) / float(lutSize.z));
|
|
slice *= slice; // Squared distribution
|
|
slice *= float(lutSize.z);
|
|
|
|
vec3 worldPos = camPos;
|
|
float viewHeight;
|
|
|
|
// Compute position from froxel information
|
|
float tMax = distantGridSliceToDepth(slice);
|
|
vec3 newWorldPos = worldPos + tMax * worldDir;
|
|
|
|
// If the voxel is under the ground, make sure to offset it out on the ground.
|
|
viewHeight = length(newWorldPos);
|
|
if (viewHeight <= (atmosphere.bottomRadius + kAtmospherePlanetRadiusOffset))
|
|
{
|
|
// Apply a position offset to make sure no artefact are visible close to the earth boundaries for large voxel.
|
|
newWorldPos = normalize(newWorldPos) * (atmosphere.bottomRadius + kAtmospherePlanetRadiusOffset + 0.001f);
|
|
worldDir = normalize(newWorldPos - camPos);
|
|
tMax = length(newWorldPos - camPos);
|
|
}
|
|
float tMaxMax = tMax;
|
|
|
|
// Move ray marching start up to top atmosphere.
|
|
viewHeight = length(worldPos);
|
|
if (viewHeight >= atmosphere.topRadius)
|
|
{
|
|
vec3 prevWorlPos = worldPos;
|
|
if (!moveToTopAtmosphere(worldPos, worldDir, atmosphere.topRadius))
|
|
{
|
|
// Ray is not intersecting the atmosphere
|
|
imageStore(imageDistantGrid, workPos, vec4(0.0, 0.0, 0.0, 1.0));
|
|
return;
|
|
}
|
|
|
|
float lengthToAtmosphere = length(prevWorlPos - worldPos);
|
|
if (tMaxMax < lengthToAtmosphere)
|
|
{
|
|
// tMaxMax for this voxel is not within earth atmosphere
|
|
imageStore(imageDistantGrid, workPos, vec4(0.0, 0.0, 0.0, 1.0));
|
|
return;
|
|
}
|
|
|
|
// Now world position has been moved to the atmosphere boundary: we need to reduce tMaxMax accordingly.
|
|
tMaxMax = max(0.0, tMaxMax - lengthToAtmosphere);
|
|
}
|
|
|
|
const bool bGround = false;
|
|
const float sampleCountIni = max(1.0, float(workPos.z + 1.0) * 2.0f);
|
|
const float depthBufferValue = -1.0;
|
|
const bool bVariableSampleCount = false;
|
|
const bool bMieRayPhase = false;
|
|
|
|
|
|
uint localId = gl_LocalInvocationIndex;
|
|
{
|
|
uvec2 dispatchId = remap8x8(gl_LocalInvocationIndex);
|
|
float randA = (dispatchId.x + 0.5) / 8.0;
|
|
float randB = (dispatchId.y + 0.5) / 8.0;
|
|
float theta = 2.0f * kPI * randA;
|
|
|
|
float phi = acos(1.0f - 2.0f * randB);
|
|
|
|
float cosPhi = cos(phi);
|
|
float sinPhi = sin(phi);
|
|
float cosTheta = cos(theta);
|
|
float sinTheta = sin(theta);
|
|
|
|
worldDir.x = cosTheta * sinPhi;
|
|
worldDir.y = cosPhi;
|
|
worldDir.z = sinTheta * sinPhi;
|
|
}
|
|
|
|
SingleScatteringResult ss = integrateScatteredLuminance(
|
|
pixPos,
|
|
worldPos,
|
|
worldDir,
|
|
sunDir,
|
|
atmosphere,
|
|
bGround,
|
|
sampleCountIni,
|
|
depthBufferValue,
|
|
bMieRayPhase,
|
|
tMaxMax,
|
|
bVariableSampleCount
|
|
);
|
|
|
|
shareSkyLit[localId] = ss.scatteredLight;
|
|
shareTransmittance[localId] = ss.transmittance;
|
|
|
|
groupMemoryBarrier();
|
|
barrier();
|
|
|
|
uint loopIndex = 32;
|
|
while(loopIndex > 0)
|
|
{
|
|
if(localId < loopIndex)
|
|
{
|
|
shareSkyLit[localId] += shareSkyLit[localId + loopIndex];
|
|
shareTransmittance[localId] += shareTransmittance[localId + loopIndex];
|
|
}
|
|
|
|
loopIndex /= 2;
|
|
|
|
groupMemoryBarrier();
|
|
barrier();
|
|
}
|
|
|
|
if (localId < 1)
|
|
{
|
|
vec3 avgColor = shareSkyLit[localId] / 64.0;
|
|
vec3 avgTransmittance = shareTransmittance[localId] / 64.0;
|
|
|
|
imageStore(imageDistantGrid, workPos, vec4(avgColor, 1.0 - mean(avgTransmittance)));
|
|
}
|
|
}
|
|
|
|
#endif |