GLSL Atmospheric Ozone Absorption Formula
This article explains how to compute atmospheric ozone absorption across standard RGB sunlight spectrum channels using GLSL. It covers the underlying Beer-Lambert extinction formula, the Chappuis band absorption coefficients for visual wavelengths, the stratospheric tent function used to approximate ozone density by altitude, and a ready-to-use GLSL shader implementation.
The Physics of Ozone Absorption
Ozone absorbs visible light primarily within the Chappuis band (roughly 400 nm to 750 nm), with peak absorption occurring in the green and yellow spectrum around 600 nm. Because ozone preferentially absorbs yellow and orange wavelengths while letting blue light pass, it is largely responsible for the deep blue appearance of the sky during civil and nautical twilight.
Light attenuation through atmospheric ozone is modeled using the Beer-Lambert Law:
\[T(\lambda) = \exp\left(-\beta_{\text{ozone}}(\lambda) \cdot \tau_{\text{ozone}}\right)\]
Where:
- \(T(\lambda)\) is the spectral transmittance.
- \(\beta_{\text{ozone}}(\lambda)\) is the wavelength-dependent extinction cross-section.
- \(\tau_{\text{ozone}}\) is the integrated optical depth along the light ray.
Optical Coefficients for Sunlight Channels
For real-time physically based rendering in RGB color space (typically sampled around \(650\text{ nm}\) for Red, \(550\text{ nm}\) for Green, and \(440\text{ nm}\) for Blue), the ozone extinction coefficients per meter are approximately:
\[\beta_{\text{ozone}} = (0.650 \times 10^{-6},\; 1.881 \times 10^{-6},\; 0.085 \times 10^{-6})\text{ m}^{-1}\]
In GLSL vector format:
const vec3 OZONE_EXTINCTION = vec3(0.650e-6, 1.881e-6, 0.085e-6); // 1/mOzone Density Altitude Profile
Unlike Rayleigh and Mie scattering, which decay exponentially from the surface, ozone concentrates primarily in the stratosphere between 15 km and 35 km, peaking at approximately 25 km. A standard triangular tent profile models this distribution:
\[\rho_{\text{ozone}}(h) = \max\left(0.0, 1.0 - \frac{|h - h_0|}{w}\right)\]
Where:
- \(h\) is the current altitude above the planet surface (in meters).
- \(h_0\) is the peak layer center altitude (\(25{,}000\text{ m}\)).
- \(w\) is the layer half-width (\(15{,}000\text{ m}\)).
GLSL Implementation
The following GLSL functions calculate the local ozone density and evaluate the integrated ozone transmittance along a ray.
// Extinction coefficients for RGB sunlight channels (1/meters)
const vec3 OZONE_EXTINCTION = vec3(0.650e-6, 1.881e-6, 0.085e-6);
// Parameters for the ozone density profile (meters)
const float OZONE_CENTER_ALTITUDE = 25000.0;
const float OZONE_WIDTH = 15000.0;
// Computes relative ozone density at a given altitude
float getOzoneDensity(float altitude) {
return max(0.0, 1.0 - abs(altitude - OZONE_CENTER_ALTITUDE) / OZONE_WIDTH);
}
// Computes optical depth across a ray segment
float integrateOzoneOpticalDepth(vec3 rayStart, vec3 rayDir, float rayLength, float planetRadius, int sampleCount) {
float stepSize = rayLength / float(sampleCount);
float accumulatedDepth = 0.0;
for (int i = 0; i < sampleCount; ++i) {
vec3 samplePoint = rayStart + rayDir * ((float(i) + 0.5) * stepSize);
float altitude = length(samplePoint) - planetRadius;
accumulatedDepth += getOzoneDensity(altitude) * stepSize;
}
return accumulatedDepth;
}
// Computes ozone spectral transmittance across RGB channels
vec3 calculateOzoneTransmittance(vec3 rayStart, vec3 rayDir, float rayLength, float planetRadius) {
const int SAMPLES = 16;
float opticalDepth = integrateOzoneOpticalDepth(rayStart, rayDir, rayLength, planetRadius, SAMPLES);
// Beer-Lambert law applied per channel
return exp(-OZONE_EXTINCTION * opticalDepth);
}Usage Notes
To evaluate solar attenuation, set rayDir toward the sun
from the view position to the top of the atmosphere. Multiply the
returned RGB transmittance vector by the incoming extraterrestrial
sunlight irradiance before combining with Rayleigh and Mie in-scattering
terms.