Rendering realistic and dynamic skies is a challenging problem in real-time computer graphics. The math behind light scattering is too complex to solve directly in real-time, but it can be done offline.
This program solves the atmospheric scattering equations for every possible view and sun direction, and stores the results in look-up tables. The tables can then be sampled in a fragment shader to reconstruct an accurate rendering of the sky at any time of day.
This branch contains the code that precomputes the scattering tables. For an example of using the precomputed data in a renderer, please look at this repository's gh-pages
branch.
Before light reaches your eye, there is a probability a portion of it will be absorbed by aerosols (Mie theory) or scattered into a different direction by molecules (Rayleigh theory).
The amount of light reaching your eye after undergoing either Rayleigh or Mie scattering within the atmosphere is described by the following equation:
For a complete explanation of the math behind atmospheric scattering, I recommend Gustav Bodare and Edvard Sandberg's excellent thesis on atmospheric scattering.
This program precomputes the above integral for:
- 2 scattering events (Rayleigh and Mie).
- 3 wavelengths (which roughly correspond to red, green, and blue).
- 64 view-zenith angles (from 0 to π).
- 64 sun-zenith angles (from 0 to π).
To use it:
- Clone this repository.
cd
intoatmosphere/
- Run
make
. - Run
./build/atmosphere
.
You can specify a number of runtime flags to control the pre-computation:
-o <output_dir>
puts the results into the specified directory. (The directory must exist).-n
normalizes the pre-computed results into the [0, 1] range. This helps preserve precision in the double to float conversion process.-exr
outputs to.exr
files rather than binary float arrays. (This requires the OpenEXR library. You might have to set the path correctly in the makefile).
This program outputs three files:
rayleigh
contains the precomputed Rayleigh scattering table as either a binary-encoded float array (.bin
) or an HDR image (.exr
).mie
contains the precomputed Mie scattering table as either a binary-encoded float array (.bin
) or an HDR image (.exr
).results.txt
contains the necessary constants to correctly render the atmosphere in your renderer of choice.
I excluded the spectral intensity Ii(λ)
and phase function F(θ)
terms from the pre-computed results. They are constant anyway, and in deferring them to a fragment shader we can avoid some visual artifacts around the sun.
I also provide the option to normalize the table values in the [0, 1] range to preserve as much precision as possible when converting from doubles to floats. After you sample the tables in a fragment shader, you must un-normalize the values with the following formula: val = val * (max - min) + min
. The max and min values are written into results.txt
as part of the pre-computation process.
For a complete example of using the precomputed data in a renderer, please see this repository's gh-pages
branch. The basic process is outlined below:
- Load each table as a float array.
- Create 64x64 RGB floating point textures from each array.
- Upload the Rayleigh and Mie textures to the GPU.
- In the fragment shader, convert the current view-zenith and sun-zenith angle into texture coordinates with the following formula:
u = 0.5 * (1.0 + sign(cosViewZenith)*pow(abs(cosViewZenith), 1.0/3.0));
v = 0.5 * (1.0 + sign(cosSunZenith)*pow(abs(cosSunZenith), 1.0/3.0));
- Sample the Rayleigh and Mie textures with the texture coordinate
(u, v)
. - Remap the Rayleigh and Mie values with the
min
andmax
constants fromresults.txt
. - Multiply the Rayleigh and Mie values with their respective phase functions.
- Add the Rayleigh and Mie values to get the total scattering.
- Multiply the total scattering by the spectral irradiance constants from
results.txt
to compute the radiance. - Multiply the radiance by the spectral-to-rgb conversion constants from
results.txt
to compute the rgb color. - If you are working in a low dynamic range renderer, you must tone-map the rgb color via
rgb = pow(1.0 - exp(-rgb), 1.0/2.2)
. - If you are working in an HDR environment, you should let your post processing filter handle the HDR color grading and tone-mapping.