151 lines
8.0 KiB
GLSL
151 lines
8.0 KiB
GLSL
/**
|
|
* This function computes the colors contributed by Rayliegh and Mie scattering on a given ray, as well as
|
|
* the transmittance value for the ray. This function uses automatic uniforms
|
|
* so the atmosphere settings are always synced with the current scene.
|
|
*
|
|
* @name czm_computeScattering
|
|
* @glslfunction
|
|
*
|
|
* @param {czm_ray} primaryRay The ray from the camera to the position.
|
|
* @param {float} primaryRayLength The length of the primary ray.
|
|
* @param {vec3} lightDirection The direction of the light to calculate the scattering from.
|
|
* @param {vec3} rayleighColor The variable the Rayleigh scattering will be written to.
|
|
* @param {vec3} mieColor The variable the Mie scattering will be written to.
|
|
* @param {float} opacity The variable the transmittance will be written to.
|
|
*/
|
|
void czm_computeScattering(
|
|
czm_ray primaryRay,
|
|
float primaryRayLength,
|
|
vec3 lightDirection,
|
|
float atmosphereInnerRadius,
|
|
out vec3 rayleighColor,
|
|
out vec3 mieColor,
|
|
out float opacity
|
|
) {
|
|
const float ATMOSPHERE_THICKNESS = 111e3; // The thickness of the atmosphere in meters.
|
|
const int PRIMARY_STEPS_MAX = 16; // Maximum number of times the ray from the camera to the world position (primary ray) is sampled.
|
|
const int LIGHT_STEPS_MAX = 4; // Maximum number of times the light is sampled from the light source's intersection with the atmosphere to a sample position on the primary ray.
|
|
|
|
// Initialize the default scattering amounts to 0.
|
|
rayleighColor = vec3(0.0);
|
|
mieColor = vec3(0.0);
|
|
opacity = 0.0;
|
|
|
|
float atmosphereOuterRadius = atmosphereInnerRadius + ATMOSPHERE_THICKNESS;
|
|
|
|
vec3 origin = vec3(0.0);
|
|
|
|
// Calculate intersection from the camera to the outer ring of the atmosphere.
|
|
czm_raySegment primaryRayAtmosphereIntersect = czm_raySphereIntersectionInterval(primaryRay, origin, atmosphereOuterRadius);
|
|
|
|
// Return empty colors if no intersection with the atmosphere geometry.
|
|
if (primaryRayAtmosphereIntersect == czm_emptyRaySegment) {
|
|
rayleighColor = vec3(1.0, 0.0, 1.0);
|
|
return;
|
|
}
|
|
|
|
// To deal with smaller values of PRIMARY_STEPS (e.g. 4)
|
|
// we implement a split strategy: sky or horizon.
|
|
// For performance reasons, instead of a if/else branch
|
|
// a soft choice is implemented through a weight 0.0 <= w_stop_gt_lprl <= 1.0
|
|
float x = 1e-7 * primaryRayAtmosphereIntersect.stop / length(primaryRayLength);
|
|
// Value close to 0.0: close to the horizon
|
|
// Value close to 1.0: above in the sky
|
|
float w_stop_gt_lprl = 0.5 * (1.0 + czm_approximateTanh(x));
|
|
|
|
// The ray should start from the first intersection with the outer atmopshere, or from the camera position, if it is inside the atmosphere.
|
|
float start_0 = primaryRayAtmosphereIntersect.start;
|
|
primaryRayAtmosphereIntersect.start = max(primaryRayAtmosphereIntersect.start, 0.0);
|
|
// The ray should end at the exit from the atmosphere or at the distance to the vertex, whichever is smaller.
|
|
primaryRayAtmosphereIntersect.stop = min(primaryRayAtmosphereIntersect.stop, length(primaryRayLength));
|
|
|
|
// For the number of ray steps, distinguish inside or outside atmosphere (outer space)
|
|
// (1) from outer space we have to use more ray steps to get a realistic rendering
|
|
// (2) within atmosphere we need fewer steps for faster rendering
|
|
float x_o_a = start_0 - ATMOSPHERE_THICKNESS; // ATMOSPHERE_THICKNESS used as an ad-hoc constant, no precise meaning here, only the order of magnitude matters
|
|
float w_inside_atmosphere = 1.0 - 0.5 * (1.0 + czm_approximateTanh(x_o_a));
|
|
int PRIMARY_STEPS = PRIMARY_STEPS_MAX - int(w_inside_atmosphere * 12.0); // Number of times the ray from the camera to the world position (primary ray) is sampled.
|
|
int LIGHT_STEPS = LIGHT_STEPS_MAX - int(w_inside_atmosphere * 2.0); // Number of times the light is sampled from the light source's intersection with the atmosphere to a sample position on the primary ray.
|
|
|
|
// Setup for sampling positions along the ray - starting from the intersection with the outer ring of the atmosphere.
|
|
float rayPositionLength = primaryRayAtmosphereIntersect.start;
|
|
// (1) Outside the atmosphere: constant rayStepLength
|
|
// (2) Inside atmosphere: variable rayStepLength to compensate the rough rendering of the smaller number of ray steps
|
|
float totalRayLength = primaryRayAtmosphereIntersect.stop - rayPositionLength;
|
|
float rayStepLengthIncrease = w_inside_atmosphere * ((1.0 - w_stop_gt_lprl) * totalRayLength / (float(PRIMARY_STEPS * (PRIMARY_STEPS + 1)) / 2.0));
|
|
float rayStepLength = max(1.0 - w_inside_atmosphere, w_stop_gt_lprl) * totalRayLength / max(7.0 * w_inside_atmosphere, float(PRIMARY_STEPS));
|
|
|
|
vec3 rayleighAccumulation = vec3(0.0);
|
|
vec3 mieAccumulation = vec3(0.0);
|
|
vec2 opticalDepth = vec2(0.0);
|
|
vec2 heightScale = vec2(czm_atmosphereRayleighScaleHeight, czm_atmosphereMieScaleHeight);
|
|
|
|
// Sample positions on the primary ray.
|
|
for (int i = 0; i < PRIMARY_STEPS_MAX; ++i) {
|
|
|
|
// The loop should be: for (int i = 0; i < PRIMARY_STEPS; ++i) {...} but WebGL1 cannot
|
|
// loop with non-constant condition, so it has to break early instead
|
|
if (i >= PRIMARY_STEPS) {
|
|
break;
|
|
}
|
|
|
|
// Calculate sample position along viewpoint ray.
|
|
vec3 samplePosition = primaryRay.origin + primaryRay.direction * (rayPositionLength + rayStepLength);
|
|
|
|
// Calculate height of sample position above ellipsoid.
|
|
float sampleHeight = length(samplePosition) - atmosphereInnerRadius;
|
|
|
|
// Calculate and accumulate density of particles at the sample position.
|
|
vec2 sampleDensity = exp(-sampleHeight / heightScale) * rayStepLength;
|
|
opticalDepth += sampleDensity;
|
|
|
|
// Generate ray from the sample position segment to the light source, up to the outer ring of the atmosphere.
|
|
czm_ray lightRay = czm_ray(samplePosition, lightDirection);
|
|
czm_raySegment lightRayAtmosphereIntersect = czm_raySphereIntersectionInterval(lightRay, origin, atmosphereOuterRadius);
|
|
|
|
float lightStepLength = lightRayAtmosphereIntersect.stop / float(LIGHT_STEPS);
|
|
float lightPositionLength = 0.0;
|
|
|
|
vec2 lightOpticalDepth = vec2(0.0);
|
|
|
|
// Sample positions along the light ray, to accumulate incidence of light on the latest sample segment.
|
|
for (int j = 0; j < LIGHT_STEPS_MAX; ++j) {
|
|
|
|
// The loop should be: for (int j = 0; i < LIGHT_STEPS; ++j) {...} but WebGL1 cannot
|
|
// loop with non-constant condition, so it has to break early instead
|
|
if (j >= LIGHT_STEPS) {
|
|
break;
|
|
}
|
|
|
|
// Calculate sample position along light ray.
|
|
vec3 lightPosition = samplePosition + lightDirection * (lightPositionLength + lightStepLength * 0.5);
|
|
|
|
// Calculate height of the light sample position above ellipsoid.
|
|
float lightHeight = length(lightPosition) - atmosphereInnerRadius;
|
|
|
|
// Calculate density of photons at the light sample position.
|
|
lightOpticalDepth += exp(-lightHeight / heightScale) * lightStepLength;
|
|
|
|
// Increment distance on light ray.
|
|
lightPositionLength += lightStepLength;
|
|
}
|
|
|
|
// Compute attenuation via the primary ray and the light ray.
|
|
vec3 attenuation = exp(-((czm_atmosphereMieCoefficient * (opticalDepth.y + lightOpticalDepth.y)) + (czm_atmosphereRayleighCoefficient * (opticalDepth.x + lightOpticalDepth.x))));
|
|
|
|
// Accumulate the scattering.
|
|
rayleighAccumulation += sampleDensity.x * attenuation;
|
|
mieAccumulation += sampleDensity.y * attenuation;
|
|
|
|
// Increment distance on primary ray.
|
|
rayPositionLength += (rayStepLength += rayStepLengthIncrease);
|
|
}
|
|
|
|
// Compute the scattering amount.
|
|
rayleighColor = czm_atmosphereRayleighCoefficient * rayleighAccumulation;
|
|
mieColor = czm_atmosphereMieCoefficient * mieAccumulation;
|
|
|
|
// Compute the transmittance i.e. how much light is passing through the atmosphere.
|
|
opacity = length(exp(-((czm_atmosphereMieCoefficient * opticalDepth.y) + (czm_atmosphereRayleighCoefficient * opticalDepth.x))));
|
|
}
|