RainStorm
  • Workflow
  • Scheduler
  • Immersive
  • Native
Coming soon on the Mac App Store
← Back to home

Behind the scenes

The Science of RainStorm

The physics, mathematics, and philosophy that make the storm feel real.

On this page

  1. The scheduler
  2. Intensity drift
  3. Organic randomness
  4. Atmospheric depth
  5. Droplet sizing
  6. Dynamic wind
  7. Visual artistry

Have you ever looked out a window during a storm and felt a sudden sense of calm? There is a deep, biological reason for that. Natural rain is chaotic, immersive, and constantly shifting. It is never repetitive, and it never moves in perfect sync.

Most screen-saver and background animations feel robotic because they rely on simple, repeating particle loops or uniform speeds. In RainStorm, we took a different path. We built a native Metal GPU-accelerated physics engine designed to mimic the organic complexity of real-world rainfall.

Here is a look under the hood at the mathematics, physics, and creative philosophies that bring RainStorm to life on your desktop.

1

Nature cannot be corralled: the scheduler philosophy

Our guiding philosophy is Aggressive Naturalism. Human brains are incredibly good at spotting patterns. If rain falls at perfectly regular intervals, your brain quickly detects the repetition, breaking the cozy ambient immersion.

Because of this, the automatic storm scheduler is built with non-exact timing:

  • The ±20% timing jitter. If you set a storm interval of one hour, it will not trigger exactly on the hour. The app applies a logarithmic scale and adds a ±20% timing jitter to the center point, so your one-hour interval actually schedules the next storm anywhere between 48 minutes and 1 hour 12 minutes.
  • Intensity is a guideline, not a command. Your storm intensity preference is a guideline for the atmosphere, not a rigid lock. If you set your preference to prefer downpours, a weighted random distribution makes downpours highly likely (~86%), but keeps drizzle at a 5% possibility and medium rain at a 10% wildcard. Even in a storm-heavy cycle, you will still occasionally get a light drizzle.
48m–1h 12m what a “1-hour” interval actually becomes
±20% timing jitter on every scheduled storm
2

Dynamic intensity drift: the Cruise system

Once a storm reaches its target intensity, it does not just sit perfectly static. In the real world, a rainstorm naturally waxes and wanes. The pitter-patter gets heavier, then lighter, then picks up again as the clouds pass. We replicate this through a dynamic background drift system called Cruise:

  • Random Cruise segments. While a storm is active, it constantly transitions through a sequence of random time blocks, each assigned a random duration between 90 seconds and 4 minutes.
  • Squared-step math. At the start of each segment, the engine calculates a small change in intensity. To prevent sudden, jarring shifts, the step magnitude squares a random number and scales it:

stepMagnitude = random² × 0.12Squaring the random value skews the odds heavily toward micro-adjustments, so the vast majority of updates are slow, gentle drifts rather than noticeable transitions.

Continuous swells. The system randomly chooses whether to nudge the intensity up or down by this step, clamping the target between a floor of 0.01 and a ceiling of 1.0. The intensity then interpolates to the new target over the segment, creating an endless, organic swell where the storm naturally breathes.

3

Organic randomness & GPU-driven physics

For the rain overlay itself, we rely on GPU-driven pseudo-randomness to ensure every storm is unique:

  • Hash-based PRNG on the GPU. To avoid taxing the CPU, our Metal shader implements a fast, cryptographic-style hash function operating directly on each GPU thread.
  • Seed decoupling. Every single drop is spawned using a unique seed calculated from its grid index combined with a high-entropy base:

seed = seedBase ⊕ (gridID × 977 + 0x9E3779B9)The constant 0x9E3779B9 is derived from the Golden Ratio, ensuring an even distribution of random values across threads and preventing visual “grid lines” or repeating bands.

Breaking the alignment. In most particle systems, every droplet tilts at the exact same angle when the wind blows. In RainStorm, 60% of the droplets opt in to the baseline wind while the remaining 40% fall on their own custom heading. This simple gate breaks up visual uniformity, making the storm feel layered and organic rather than flat.

0x9E3779B9 golden-ratio constant seeding every drop
60 / 40 drops on the baseline wind vs. their own heading
4

Atmospheric depth & 3D parallax

Rain doesn't happen on a flat pane of glass; it surrounds you. To create a three-dimensional sense of volume, RainStorm calculates a continuous depth value between 0.0 (distant background) and 1.0 (immediate foreground) for every drop.

The depth distribution curve

If we distributed drops evenly across depth, the screen would feel cluttered and unnatural. In nature, you perceive far more distant drops than foreground ones because of your field of view. We model this by skewing the depth distribution with a power curve:

depth = random1.55An exponent of 1.55 skews the curve heavily toward the background, populating a rich, soft curtain of distant rain while keeping the immediate foreground clear enough to focus on your work.

A plot of depth equals random to the 1.55 power, bowing below the even-spread diagonal toward the background. random (0 → 1) depth (0 → 1) depth = random¹·⁵⁵ even spread
The exponent of 1.55 bends the curve below the even-spread diagonal, so a uniformly random value resolves to a low depth far more often — packing the scene with distant background rain while keeping the foreground sparse.

Depth scaling

A droplet's depth dynamically scales all of its physical properties:

  • Foreground drops (depth → 1.0) are rendered wider, longer, faster, more opaque, and highly reactive to gusts.
  • Background drops (depth → 0.0) are rendered thinner, shorter, slower, semi-transparent, and deeply stable.
Three depth bands: background drops are thin, short and faint; foreground drops are thick, long and bright. Background · 0.0 Midground · 0.5 Foreground · 1.0
A single depth value scales each drop's width, length, speed, opacity and wind-reactivity together — building a layered curtain that runs from a faint, near-vertical haze in back to bold, slanted streaks up front.
5

Storm intensity & droplet-size skewing

To keep system resources low while maintaining high visual quality, the simulation caps active drops between roughly 35 and 950 depending on the current stage. Because we operate in this tight particle budget, we cannot rely on “mist” effects. Instead, a drizzle is modeled as a light, gentle pitter-patter of individual falling drops, whereas a downpour contains a highly diverse mix of massive, heavy drops and small ones.

35–950 live drops on screen, scaling from drizzle to downpour

To capture this, RainStorm dynamically biases the drop-size distribution based on the storm's current intensity (0.0 → 1.0):

sizeFactor = randomexponentexponent = mix(2.0, 0.7, intensity)

  • Drizzle (intensity ≈ 0.1): the exponent rises toward 2.0. Squaring the random value heavily skews the distribution toward 0.0, forcing drops to be tiny — a light, scattered pitter-patter.
  • Downpour (intensity ≈ 1.0): the exponent drops to 0.7. Raising a fraction to a power below 1.0 “flattens” the curve, boosting the odds of massive, heavy drops while still allowing smaller ones to co-exist.
Drizzle produces uniformly tiny drops; a downpour mixes large, heavy drops with small ones. Drizzle · exp ≈ 2.0 Downpour · exp ≈ 0.7
Drop size is random^exponent. In drizzle the exponent climbs toward 2.0, crushing nearly every drop toward tiny; in a downpour it drops to 0.7, flattening the odds so big, heavy drops mix in among the small.

This size factor directly determines the drop's terminal velocity. Rather than scaling linearly, the base speed uses a physics-based mapping:

terminalVelocity ∝ √sizeFactorThis mimics real-world fluid dynamics, where larger droplets fall faster because their mass overcomes air resistance.

6

Dynamic wind, spatial gusts & curved arcs

Wind in the real world doesn't instantly tilt the entire sky. It drifts, sweeps in pockets, and affects different drops in different ways.

The multi-sine baseline wind

To create slow, sweeping wind patterns, the CPU calculates a baseline wind using three independent sine-wave oscillators with prime-ish periods (comfortably above a four-minute floor):

drift = 0.5·sin(2πt / 240) + 0.3·sin(2πt / 521) + 0.2·sin(2πt / 743)

These three sines construct an ever-shifting baseline that never repeats. To keep the wind within realistic visual bounds even when the waves constructively peak, we pass the raw value through a hyperbolic tangent (tanh) for a smooth, asymptotic soft-clamp:

baselineWind = maxBaseline × tanh(drift / maxBaseline)

Decoupled wind and gust susceptibility

A droplet's reaction to sustained wind and sudden gusts is decoupled to maximize visual variety. Each drop receives a randomized wind susceptibility and gust susceptibility on spawn. So when a gust front rolls through, some drops lurch and dance immediately while others fall with steady drift — mimicking the complex micro-currents of air within a real storm.

Spatial gust cells & sweeping fronts

Instead of a global acceleration, gusts are calculated as local 2D noise fields in the Metal shader, combining regional cells, sweeping fronts, and micro-turbulence:

cell = sin(x · 0.0034 + t) × cos(y · 0.0042 − t)This creates regional “pockets” of wind, so drops on the left side of your screen might bend while drops on the right fall straight.

Layer-desynced pulses. The foreground and background layers pulse at different rates and phases. This creates depth parallax in the wind itself, letting foreground gusts sweep past while the background remains stable.

The curved fall: vertical recovery

In nature, when raindrops fall from a high-altitude wind layer into a sheltered area, they don't fall in straight diagonal lines. They curve. RainStorm replicates this by attenuating wind speed as a droplet approaches the bottom of your viewport:

windAttenuation = 1.0 − effectiveProgress × verticalRecoveryBecause verticalRecovery and the arc's start point are randomized per particle, some drops fall straight, some curve gradually across the whole screen, and others hold their slant most of the way down before arcing sharply near the bottom — guiding the eye along flowing curves rather than rigid angles.

Rain paths that hold a slant high up then arc toward vertical near the bottom of the screen as wind attenuates. wind eases near the ground no recovery
As a drop nears the bottom, windAttenuation scales its sideways drift toward zero. With the start point and recovery randomized per drop, some fall nearly straight (dashed), some curve across the whole screen, and others hold their slant before arcing sharply into the recovery zone.
7

Visual artistry & sub-pixel shading

To keep resource usage incredibly low, we don't draw individual lines or complex vector curves. Instead, every raindrop is rendered as an instanced quad (two triangles) textured with a custom, procedurally generated alpha streak.

Velocity-based stretch

When a droplet falls faster (or is accelerated by a gust), its quad is stretched in the vertex shader along its vector of motion:

streakLength ∝ baseLength × mix(0.86, 1.72, normalizedSpeed)This velocity-based stretching naturally mimics the motion blur of real rainfall.

The multi-layer shading model

Rather than rendering flat white or blue streaks, our fragment shader applies a multi-layered lighting model so the rain reads clearly against any background — light or dark:

  • The shadow layer. A subtle, dark backing (depth-tinted between #2E343A and #1D2024) is rendered behind the drop, keeping it visible even when passing over bright white windows.
  • The core body. Rendered in a curated soft grey (#ABABAB) rather than harsh white, mimicking the refractive nature of water inheriting the ambient shading of a storm.
  • The rim highlight. A bright, cool-blue edge highlight simulates light refractively bending around the outer boundary of the droplet.
  • The head glint. A tight, warm light cast (#FFFDF5) is injected at the leading tip of the streak, simulating the focal refraction of light at the bottom of a falling droplet and giving the leading edge a subtle, bright definition.

Every storm you see is generated in real time, drop by drop — no two are ever alike. See it in motion →

RainStorm
  • The Physics
  • Support
  • Privacy Policy
  • Terms of Service

© 2026 RainStorm.