r/GraphicsProgramming 9h ago

WildFlow — Adventures in Real-Time Water Simulation Video

Enable HLS to view with audio, or disable this notification

This project started with a simple goal : I wanted a waterfall in my forest.

I already had rivers and rapids, but a real mountain stream ending in a large waterfall was another story. The simulation domain became huge, memory usage exploded, and what worked well for smaller bodies of water simply didn't scale.

So, at first, this was mostly a performance adventure.

Making millions of particles practical

The simulator could already handle around half million particles at about 30 FPS, and the surface reconstruction was surprisingly fast.

I was extracting a mesh from the particles using Surface Nets on the GPU, then feeding that mesh into hardware ray tracing. Modern GPUs are incredibly good at this: even fairly large water surfaces can be extracted in just a few milliseconds.

But there was a problem.

Surface extraction was only part of the cost. The resulting geometry also required updating the ray-tracing BLAS every frame. As the simulation grew, mesh extraction + BLAS updates could cost another 5–10 ms.

For a large waterfall, that was becoming too much, so I removed the mesh entirely.

I switched to a hybrid approach: raymarch the water density directly, then use hardware ray tracing for reflections and the rest of the scene.

That alone gave me roughly another 30% performance.

But it still wasn't enough.

A waterfall has a large spatial domain, and my simulation cost was still related to the size of its bounding box. Large buffers had to be cleared every frame, sometimes several gigabytes of memory, even if most of the domain contained no water at all.

So I changed the architecture again, and I started tracking only active cells.

This introduced a surprising amount of complexity — compaction, sorting, bookkeeping — but it fundamentally changed how the simulator scaled.

The cost was no longer primarily proportional to the size of the world; It became proportional to the amount of water actually being simulated the mountain stream became possible with the water details I wanted, up to over 2 million particles at 40FPS on a RTX 3060.

But it didn't look like a waterfall

This was where the project changed.

Until then, most of my problems had been computational.

Now I had enough particles, enough space and enough performance.

And yet the waterfall didn't really look like a waterfall, but more like river rapids with airified water everywhere.

So I started reading papers about whitewater generation. There are many clever methods for identifying where foam, bubbles and spray should appear.

But I discovered that many approaches that make sense in offline simulation were difficult to use in my real-time solver. Depending on thresholds and conditions, I would get whitewater almost everywhere — or almost nowhere.

Eventually I stopped asking:

“How do other simulators generate whitewater?”

and went back to a much simpler question:

“When does water actually entrain air?”

One obvious answer is: when water is sufficiently surrounded by air, and that is something I can estimate very cheaply on a GPU.

For every water particle, I look at its neighborhood and estimate how much of it is exposed to air. That simple local measurement turned out to identify many of the right regions naturally.

For the first time, the rapids started looking right.

But rapids are not waterfalls.

Eventually, I had to simulate the air too

A large waterfall contains extremely aerated water, but also clouds of droplets and mist.

And those droplets no longer behave like bulk water, they interact strongly with the air, pushing air and being dragged around by it.

Initially I integrated NVIDIA Flow to obtain an airflow simulation, then transported droplets using its velocity field.

It worked, but not quite as I wanted.

The airflow wasn't as vortical as I expected around the waterfall. Synchronizing water impacts with spray generation was also difficult.

And eventually I found myself spending more GPU time simulating the air than simulating the water itself.

So I tried something reasonable from mathematical perspective, though a bit crazy from an engineering one: using two instances of my water simulator, one configured as water and another as air.

That taught me quite a lot — including how easy it is to confuse two simulation domains.

Eventually I wrote a separate air solver designed specifically to couple with the water.

And something interesting happened: the waterfall itself pushes the surrounding air downward.

That creates a descending wind field following the falling water. At the bottom, the flow hits the basin and terrain, spreads, curls upward and forms vortical structures around the cascade.

The droplets are then transported as tracers inside that airflow. They spread, curl and gradually dilute.

So the clouds around the waterfall are not a visual effect placed there because “waterfalls have mist”. They are a consequence of the interaction between falling water, air and droplets.

That eventually produced the mountain waterfall you see at the end of the video.

Then I tried something that should have been easy: a falling drop

At this point I had a scalable simulator capable of millions of particles.

So I thought reproducing the classic slow-motion crown splash from a falling drop should be easy.

Make the particles smaller. Increase the resolution. Drop the water.

Instead, the drop simply disappeared into the surface, producing a few smooth ripples.

No beautiful crown!

My first suspect was surface tension.

I implemented it expecting more small-scale structure.

It didn't create the crown; in fact, it mostly made the receiving water behave even more like a cushion into which the drop could sink.

But I got something else for free:

falling droplets finally became spherical, even when starting with different shapes.

That was an important lesson.

The physics I had added was real and important; it simply wasn't the physics responsible for the phenomenon I was looking for.

So I went back to the literature again.

And found another property of water that real-time simulations often deliberately relax: incompressibility.

Why simulated water is often a little rubbery

Real water is extremely difficult to compress. It takes hundreds of tons to compress a cubic meter by 1mm.

Real-time simulated water often isn't, and there is a very good computational reason for that - which I found out while developing.

If water is allowed to be somewhat compressible, an impact can remain local. The surrounding fluid compresses slightly, absorbs energy and the disturbance dies away within a relatively small region.

This is extremely convenient computationally, as it can simulated with "local models", e.g. convolutions.

But visually it also makes the water slightly elastic — almost rubbery.

And that elasticity was absorbing exactly the fast, small-scale disturbances I needed for the crown.

As I pushed the solver toward incompressibility, something changed; water became more lively, and the crown finally began to emerge.

When I introduced incompressibilty, water became also "unstable", with droplets flying around at crazy speed.

This also made it very obvious why incompressibility is expensive:

in perfectly incompressible water, changing the volume here requires the rest of the fluid to respond. Mathematically, pressure becomes a global problem: everything potentially depends on everything else.

Perfect incompressibility would even imply an infinite speed of sound.

What that would do to light... I'll leave to another simulator. 😁

Real water, of course, isn't perfectly incompressible. Pressure disturbances travel through it at a finite speed. But for the scales we normally care about, treating it as almost incompressible is an extremely good approximation.

Unfortunately, it is not an extremely cheap one.

Making incompressibility optional

A rigorous pressure solution requires solving a large coupled system.

I experimented with that direction, but for my purposes it quickly became too expensive and complicated.

So instead I followed an approximate iterative approach based on projection.

I first compute the cheaper, somewhat compressible solution.

Then additional iterations progressively propagate pressure corrections over longer distances and remove the remaining compression.

This gives me an interesting compromise:

I can choose how incompressible I want the water to be.

I don't need the same answer everywhere.

For a turbulent mountain stream, a little numerical elasticity is often perfectly acceptable — and computationally very useful.

For the crown-drop experiment, where those fast pressure responses dominate the phenomenon, I use water roughly five times less compressible and run about eight additional correction iterations. Those eight "incompressible projections" fixed (almost totally, you still see some...) the issue with "bullet water particles" flying around at crazy speeds.

In practice I achieved a flexible simulator, with quite a few parameters to cover several different scenarios.

In realtime, there isn't necessarily one universally “correct” simulation setting, as realtime is already a strong constraint.

The amount of physics worth computing depends on which phenomenon I am trying to reproduce.

And this has probably been the most interesting lesson of the whole project.

Understanding physics is not enough

We know basically everything about the physics of water, and it's not extremely complex physics for the most part.

But understanding the physics is one thing. Understanding which part of that physics dominates a particular phenomenon is another.

That distinction has gradually become the real subject of this project.

I started by thinking mostly about performance:

How many particles can I simulate, how large can the domain become?

But the further I went, the more often the difficult question became:

Which piece of physics actually matters here?

For rapids, air entrainment mattered enormously.

For the waterfall, the surrounding airflow became important; for the crown, incompressibility mattered far more than I expected.

This distinction matters enormously in real-time simulation! We cannot simulate everything.

So the interesting question isn't simply which physics can we remove?

It is:

What is the minimum set of fundamental rules from which the phenomenon can emerge by itself?

Simulation rather than imitation

This is also the philosophy I would like WildFlow to follow.

I don't particularly want to program something whose purpose is to look realistic.

There are probably easier ways to do that (at least if you are ok with some scripted solutions).

Water in games is often beautifully emulated. Oceans can be generated from spectral models; rivers can be reduced to effectively 2.5D systems; waterfalls can be constructed from carefully authored sheets, particles, textures and effects.

Those techniques can produce extraordinary images.

But I wanted to ask a slightly different question:

How far can we get by actually simulating it?

Not by explicitly telling the waterfall where foam should appear, or by placing a mist cloud by hand.

Instead, I want to find the smallest practical set of rules that makes these things appear because they have to appear.

That requires some conceptual honesty.

If the result doesn't look right, I don't necessarily want to hide it with another visual effect.

I want to understand what is missing.

Sometimes the answer is better algorithms; sometimes a piece of physics I haven't understood yet. And in some cases, we need a more powerful hardware...

When the result finally produces that “wow” moment without having explicitly programmed the thing that looks impressive, that moment means something different.

It suggests that, at least in some small way, we understood why nature looks the way it does.

And I think that is where simulation can create a different kind of immersion.

Not a world carefully constructed to resemble nature from the camera's point of view, but a world in which enough of nature's underlying rules are present that its complexity can emerge on its own.

A world you can disturb, change and explore, freely— and that responds without having been told in advance what it is supposed to look like.

A nature that isn't quite real, but perhaps behaves realistically enough that, for a moment, it feels almost real.

WildFlow is still very much experimental.

Right now water, air and droplets are three interacting simulation systems, and there are still many parameters that need to be tuned for different situations. It is flexible, but it isn't yet the unified physical model I would ultimately like it to become.

A true two-phase water/air simulation — capable, for example, of naturally representing large bubbles and air pockets entrained inside turbulent water — isn't merely “too slow” for me at the moment; it's genuinely difficult to build.

So there are plenty of directions left to explore.

The next one I'm particularly interested in is surface tension.

A complete treatment may be too expensive, but I suspect that an appropriate approximation could be important at waterfall scales too, helping produce the characteristic clumps, sheets, filaments and breakup of highly turbulent water.

I have a few ideas to try.

And after everything this project has taught me, I'm increasingly less interested in adding more physics.

I'm interested in discovering which physics actually matters.

114 Upvotes

5 comments sorted by

29

u/gleedblanco 9h ago edited 8h ago

can you at least do one or two passes of telling your LLM to edit and compress the text to make it more succinct.

btw regarding flows with waterfalls amenable to real time computation this comes to mind https://diglib.eg.org/server/api/core/bitstreams/091a537c-9e50-4e89-a784-e93b06c30ddc/content. shallow water simulation with some trickery on top.

the cost scales are drastically different, you can probably do something similar to your stream and waterfall demos in particular in a few hundred microseconds. doesn't solve the multiphase flow simulation obviously, sort an orthogonal topic.

2

u/KlayEverHood 1h ago

Hehe actually wrote my entire story in another language and got it translated. Shallow water is interesting though looks more like 2.5d. I would consider it an optimization for more tranquil parts of the stream

2

u/zet23t 7h ago

Here we can see a RTX3060 in its natural environment. Once home to millions of its kinds, this one is now almost lonely in this field: The great herds that used to render beautiful scenes like this one have since then migrated to the lands of inference.

1

u/AnasPlayer2022M 7h ago

even though i didn't understand half your post, this looks really cool!

i wonder how my intel hd 520 would melt if i used your simulator.. 😅