About a decade ago, I wrote a desktop app for simulating the atmospheric formation of water clouds and rendering them into fairly realistic images. It was an interesting project, crossing the fields of physics, chemistry, and optics, among others.
In this post, I'll describe a brief overview of the project's implementation – to the extent that I can remember. I'll start with how I simulated the atmosphere, then go over the rendering part.
Water clouds form when moisture in the air condenses into droplets, which is preceded by the air's relative humidity increasing due to a decrease in its temperature, which results from the air expanding as it rises.
Simulating cloud formation is in other words concerned with realistically moving a body of air and modeling the resulting condensation.
Air is a fluid, so a fluid simulator can be used to model its kinetic behavior – how it moves when a force is applied to it.
I'm not particularly mathematical, so I adopted the first fluid simulator that looked reasonable to me. That happened to be the fluid solver described in Stam: Real-time fluid dynamics for games.
You can see a sample 2D JavaScript implementation of Stam's solver below. Its source code is available here (intended for demonstrational purposes, not of production quality).
In my opinion, the solver produces a fairly convincing fluidic effect.
There are various forces that could move air, but a common one is buoyancy, in which a parcel of warm air experiences lift in a cooler environment.
I'm not sure what the exact formula for air buoyancy is, but the following works empirically:
B is the air parcel's buoyancy (upward force) and T is temperature (in this case, °C).
As the air parcel rises, its temperature cools, and the buoyancy subsides.
The amount of water vapor air can hold is proportional to the temperature of the air (see relative humidity).
As an air parcel cools, its relative humidity increases. Once the relative humidity exceeds 100%, the excess moisture begins to condense. Condensation slows the rate of cooling as the air rises further (see moist adiabatic lapse rate).
The following may (or might not) be the correct way to compute an air parcel's relative humidity:
RH is the relative humidity (%), C is the air parcel's maximum capacity (kg) of water vapor, V is the amount (kg) of water vapor in the air parcel, P is pressure (hPa), and M is a mole mass constant (kg).
The sample simulation below applies buoyancy as a force to the Stam fluid solver and computes the resulting changes in the air's relative humidity – darker blue areas correspond to greater relative humidity. The sample's underlying source code is available here (intended for demonstrational purposes, not of production quality).
The simulation runs in a 2D grid in which each grid cell represents an air parcel. Each parcel is associated with atmospheric variables like temperature and moisture, as well as corresponding velocity vectors from the fluid simulation.
Every tick, the grid cells' variables are moved along the velocity field, and changes resulting from the movement are computed (e.g. a reduction in temperature due to the air rising). Basic evaporation is also empirically modeled (see the source code for details).
The actual simulation runs on a 3D grid, producing as its output a 3D image of condensed moisture (= clouds).
Clouds look the way they do due to refractive interaction of sunlight with the cloud droplets. Each time a ray intersects a droplet in a cloud, the direction of the ray may become altered:
The job of the renderer, then, is to take the moisture (droplet) grid produced by the simulator and model the behavior or light inside it to produce images bearing the likeness of real clouds. The diagram below demonstrates the overall concept. A light ray originates from a viewer (black circle), enters a cloud (box), bounces around inside the cloud, then exits and scatters into the sky (blue arc).
For rendering these light paths, I wrote a volumetric path tracer.
The sky gets its blue gradient appearance from sunlight being distributed across it by Rayleigh scattering.
Hosek & Wilkie give a visually convincing model of Rayleigh scattering, as well as an open-source reference implementation. (A newer version of the model is also available, but I'm not familiar with it.)
The two sample images below use the Hosek & Wilkie model to replicate the appearance of the sky at two different times of day (the color of the ground is not part of the model):
As noted above, clouds are made of droplets, and light is scattered by those droplets in specific ways (see Mie scattering for details).
Referring back to me not being mathematically oriented, I didn't actually properly compute the droplet scattering parameters. Instead, I simply used the MiePlot software to get a visual idea of the distributions of scattering for various wavelengths of light, then implemented them in-code as fixed probabilities – a ray would have an X% chance of being scattered in a particular direction, and depending on the scattering angle, its color might be slightly modulated.
Due to the nature of the scattering involved, the number of potential light paths inside a cloud is vast. It's very unlikely that a given ray originating from the viewer's eye finds its way straight into the sun though a cloud. Most scattering will be into the sky dome, with the occasional ray finding the sun. As a result, the rendering will take a very long time to converge.
To speed up the rendering, I added a separate photon mapping phase, which precedes the path tracing phase and in which rays are cast from the sun toward the clouds, depositing the sun's light into any droplets they intersect, producing a 3D cloud lightmap.
Then, during the path tracing phase, the lightmap is used to accumulate sunlight onto the view rays when they intersect droplets that were lit by the sun.
Below are a couple of the renderings I made back then.
Due to hardware/performance limitations, none of the images are fully converged, and their resolution is fairly low.
The second image shows a mushroom cloud with unnaturally high heat and moisture parameters, causing the simulation grid to be partly visible.
This post has outlined the process I used to create a cloud simulator/renderer. For brevity, I've left out some details (like non-convective lift, cloud evaporation, and droplet weight), but what's included is hopefully enough to get an interested person started. You're welcome to
A big problem I had when working on this app was its inhibitive computational cost. The number of potential light paths inside a cloud is massive, the atmospheric simulation wasn't fast to run either, and there were a large number of empirical parameters to adjust in both the simulator and the renderer (I wasn't an expert on the topic, so I did plenty of trial and error). It was hard to appreciate the effects of even smaller changes to the model when they required overnight runs to complete.
So the first thing I'd look to improve is probably the performance. One interesting paper I later came across was one in which the authors used neural nets for a considerable speedup to cloud rendering.