My little water sim in Unity
This one is an old project but honestly one of my favorites. If you're into physics or games, or you just like watching a bunch of spheres pretend to be water, I really recommend building one of these yourself at some point.
It started with a dumb little question: can I make water that actually behaves like water, without just faking the whole thing with shaders?
No splash textures. No scrolling normal maps. I wanted real particles that push each other around, pile up in a box, slosh side to side, and bounce off walls.
Here's the result: Fluid-Simulation
It's a 3D SPH (Smoothed Particle Hydrodynamics) solver written in Unity. C#, Burst, Jobs, a spatial hash grid, and instanced rendering. You drop a column of particles into a tank and watch it collapse into a puddle. Weirdly satisfying.
Why SPH?
Grid-based fluids (the Jos Stam "stable fluids" style) look great, but they live on a fixed lattice. Particles felt more honest for what I wanted. Every particle carries:
- position
- velocity
- forces
- density
- pressure
Then each frame, every particle basically asks its neighbors four questions:
- How packed is it around me? → density
- Am I getting squeezed? → pressure force
- Are my neighbors sliding past me? → viscosity
- Is gravity doing its thing? → body force
That's water's entire personality, boiled down to four numbers.
I used the standard SPH kernels:
- Poly6 for density
- Spiky gradient for pressure
- Viscosity laplacian for damping
If those names sound intimidating, don't worry. They're just weighted ways of asking "how much does this neighbor matter?" based on distance. Close neighbors count a lot, far ones barely exist.
The part that actually made it fast
Naive SPH is $O(n^2)$. Every particle checks every other particle, and that dies instantly once you go past a few hundred.
So I put the world on a spatial hash.
The container gets split into cells, and each particle hashes into one using the classic large-prime trick:
hash = (x * 73856093) ^ (y * 19349663) ^ (z * 83492791);
Now when I compute density or forces, I only look at the particle's own cell plus the 26 cells around it. Suddenly thousands of particles became doable.
Then I threw Unity's Job System + Burst at the density and force loops. Those two are embarrassingly parallel, so they were a perfect fit. Collisions I handled separately — a few iterations of "if two spheres overlap, push them apart" — because SPH pressure alone wasn't enough to stop particles clipping through each other at the timestep I wanted.
Integration is plain Euler with a small timeScale. I clamp velocity so one bad frame can't blow up the whole tank. Walls are literally just "if you cross the box, snap back and flip that axis with some damping." Not pretty, but it works.
How it looks on screen
The solver writes positions into a NativeArray. A second script copies them into a ComputeBuffer and draws everything with Graphics.DrawMeshInstancedProcedural. One mesh, one material, N instances. No GameObject per drop.
The README screenshots are exactly what you get in the editor: a yellow wireframe tank, a stack of white spheres, a few flying off the top when the column collapses. Honestly it looks more like a ball pit than an ocean — because it is a ball pit that happens to obey fluid equations.
I'm fine with that. The point was the motion, not a film-ready ocean.
Things I learned the hard way
Parameters are the real boss. Mass, rest density, gas constant, viscosity, smoothing radius, cell size, timestep. Touch one wrong and the fluid either freezes into concrete or detonates. I spent more time dragging inspector sliders than actually writing kernels.
SPH lives and dies by the radius. Too small and particles ignore each other. Too big and everything turns into soup and the grid stops helping.
Euler with a big timestep lies to you. The sim looks stable right up until it very much isn't. I added velocity clamps and extra collision iterations because the math was technically correct and the integration was just cheap.
Rendering is its own project. Instanced spheres are honest about what they are, but they're not water. A real surface (marching cubes, screen-space fluids, a thickness pass) would make this 10x cooler and 10x more work. I stopped at particles and I'm at peace with that.
Jobs don't magically free the main thread. I still rebuild the hash map and integrate on the main thread. Density and force jobs carry the heavy load. A v2 would keep everything in jobs and maybe move the neighbor search to a compute shader.
Why I still like this project
It sits next to my particle simulator and my 3D engine experiments. Same obsession underneath: how do a bunch of dumb local rules add up to something that feels physical?
AirDelivery is systems. PhotoGPT is product. This was the other side of my brain — the one that wants to watch a column of matter collapse and go "yep, that's just gravity plus a kernel."
I wouldn't ship this as game fluid. I'd hire it as a teacher. You can't hide from bad constants, you can't hide from $O(n^2)$, and you feel every single neighbor.
If you want to run it
Repo: github.com/GochiStuff/Fluid-Simulation
Open it in Unity (URP template), hit play, stare at the tank. Tweak viscosity and the gas constant until it either sloshes or becomes a brick. Both outcomes are educational, trust me.
Resources I leaned on:
- Müller, Charypar, Gross — Particle-Based Fluid Simulation for Interactive Applications (the SPH paper most game devs start with)
- SPH Tutorial
- Unity Jobs / Burst docs, because the math means nothing if your CPU is on fire
It was a fun short project. Hope you build one too.