INDEX/WRITING/BUILDING A WINDSHIELD RAIN SYSTEM THAT MOVES WITH THE CAR

Building a windshield rain system that moves with the car

01Overview

I built this windshield rain system for an unannounced personal project because I wanted the rain to feel attached to the car, not pasted on top of the camera.

The effect is a GPU simulation on the windshield. Drops live in windshield UV space, get pushed by gravity, wind, vehicle motion, and wiper blades, then splat into a heightfield texture. The glass shader reads that heightfield, turns it into normals, refracts the scene behind the windshield, adds wet glints, and uses a wiper mask to cut clean arcs through the water.

That is the whole idea: simulate just enough water movement to make the glass respond to driving.

02the shape of the system

The windshield is treated like a 2D simulation surface. A drop stores UV position, UV velocity, mass, and lifetime:

WindshieldDrops.computehlsl
struct Drop
{
    float2 uv;
    float2 vel;
    float  mass;
    float  life;
};

Those drops do not render directly to the screen. Each live drop is drawn into an R16F height texture attached to the windshield. Small drops make small splats. Heavier drops make larger splats. Dead drops collapse to a degenerate quad.

The visible glass comes later. The shader samples the heightfield, reconstructs a normal from it, and offsets the background scene color. Each drop becomes a tiny lens instead of a flat decal.

03why a heightfield

A particle-only approach is tempting, but it gets awkward as soon as drops need to refract the world, merge visually, leave trails, and be wiped away. The heightfield gives all of those operations one shared surface.

The particles answer "where is the water moving?" The texture answers "what does the water look like at this pixel?"

The splat shader also corrects for windshield aspect ratio. A circle in UV space is rarely a circle on the glass mesh. The driver measures the physical U and V axes from three windshield corner transforms, then scales the splat so drops stay round on the actual surface.

04moving with the car

This is the part that makes the effect feel connected to the vehicle.

The driver computes apparent wind:

WindshieldRainDriver.cscsharp
apparentWind = worldWind - vehicleVelocity;

Then it projects that vector into the windshield's UV frame. Because the windshield is angled in world space, forward motion can push drops upward across the glass instead of simply down.

Vehicle acceleration is handled separately. Acceleration, braking, and turning are projected into the same UV frame and applied as inertial forces. The compute shader combines gravity, apparent wind, inertia, and drag:

drop-forces.hlslhlsl
float2 aGrav = gravityUV * mass;
float2 aIner = -carAccelLocal * inertiaCoupling;
float2 aWind = windUV * windCoupling;
float2 aDrag = -velocity * effectiveDrag;
float2 aTot  = aGrav + aIner + aWind + aDrag;

The goal is not a physically complete fluid solver. The goal is readable driving behavior. Speed up, turn, or brake, and the water should react in a way players understand immediately.

05wipers and clearing

The wipers affect both the particles and the textures.

For particles, the compute shader receives the current and previous blade segment. The swept area between them becomes a capture zone. Drops inside that zone get moved toward the blade's leading edge, inherit some blade velocity, and gain mass. They lag behind the blade enough to form a little bow wave instead of snapping rigidly to the wiper.

For the texture pass, the wipers rasterize capsule shapes into the heightfield and a separate mask. The heightfield subtraction clears refraction. The mask marks freshly wiped glass and decays over time so the clean arc gradually wets over again.

The mask also participates in normal reconstruction. Neighboring height taps are reduced inside the wiped region, which prevents the common artifact where the blade clears the color but old drop normals still bend the image.

06rendering the glass

The windshield shader combines two main views of the scene.

The wet film samples a blurred opaque color pyramid, which softens and smears the world behind the whole windshield. The individual drops sample sharp scene color with a refraction offset from the heightfield normal:

windshield-refraction.hlslhlsl
float3 bgBlur  = SampleOpaqueColorPyramid(screenUV, blurLOD);
float3 bgSharp = SampleSceneColor(screenUV + refractionOffset);

The shader blends between those layers using wetness, drop visibility, and the wiper mask. Glints come from perturbing the glass normal with the drop normal, giving the water sharp highlights against dark roads and bright lights.

The windshield also samples the same fogged scene color as the rest of the transparent rendering path. That keeps the ordering believable: world, fog, rain behind glass, wet film, refracted drops, glints, and wiper clearing.

07cited works

Related
<- All writing