Navier-Stokes fluid simulation explained with Godot game engine ·
-->
19 May 2026<br>Navier-Stokes fluid simulation explained with Godot game engine
When I first stumbled upon fluid simulations in game dev I was amazed on how good the effect could be. I really wanted to learn how this works, but learning materials on this topic are suprisingly sparse - and those which I found were pretty difficult to understand. Nonetheless, I decided to give it a try; and - while I’m at it - why not create a blog post out of it to hopefully make it easier for the next guy?
Before we begin, I want to stress a few points:
I’m not a mathematician. If you find errors in my explanations, please DM me on Bluesky or send an email and I’ll correct it
This implementation is for learning purposes only. For that reason it is implemented in a way that is not the best performance-wise. Calculations are made solely on the CPU. We introduce way too many variables. All of that to make it easier to read and learn, not necessarily to squeeze the most FPS.
Learning materials I used:
“Real-Time Fluid Dynamic for Games” by Jos Stam (PDF)
“Fluid Simulation for Dummies” by Mike Ash
You can find all of the code in this repository: github.com/rskupnik/godot-fluid-simulation-demo
I used git commits to mark code checkpoints matching the chapters of this blog post, so if you don’t want to write the code alongside reading, you can make use of the commit view to follow along. For your convenience, I include a “project snapshot” and a “diff” link at each chapter, which lead to, respectively: the codebase at the discussed point and the commit diff view
AI disclosure: Every word of this blog post and every line of this codebase is written by me. All the diagrams and videos were created by me. AI was only used for research.
Finally, if you appreciate such work then consider buying me a coffee :)
Foundations
The algorithms we will use are based on physical equations for fluid flow - the Navier Stokes equations. Our use case is a game dev one - so we want to sacrifice precision of these calculations in favour of speed. The effect needs to be good enough but not overly expensive. We achieve this in three ways. First of all, we use a relatively small grid with large cells. Second - we advance the simulation in arbitrary time steps. Finally, we use approximation equations (such as Gauss-Seidel relaxation) to arrive at good-enough solutions to some equations.
Let me start with the mathematical description of what we will do in this blog post. This description might sound daunting, but don’t worry - we’ll explain everything as we go. Here goes: we will simulate fluid flow by moving a scalar density field through a vector velocity field. We’ll simulate velocity diffusion and advection as well as density diffusion and advection. Then we will add velocity projection with the goal of making the fluid obey the law of mass conservation - which will happen by balancing divergence with a pressure field. We will use bilinear interpolation and Gauss-Seidel relaxation for approximating values where needed.
Alright, with all of that out of the way, let’s begin!
The journey begins with a grid
Project snapshot
Create a new Godot project and add a Node2D. I called mine “FluidGrid”. Attach a script to it. All the code goes into that script.
First, let’s define the grid:
@export var N := 16<br>@export var cell_size := 32
var size := 0
Here, N is the amount of cell in a row and column - basically the size of the grid - and cell_size is the size of a single cell in pixels. We also define size, which we will soon initialize. We will set it to N+2, because we want borders as well.
Next, let’s add arrays for storing the actual data.
# Density means "how much material does this cell contain"<br>var density: PackedFloat32Array<br>var density_prev: PackedFloat32Array
# "u" stores the horizontal velocity (x direction)<br>var u: PackedFloat32Array<br>var u_prev: PackedFloat32Array
# "v" stores the vertical velocity (y direction)<br>var v: PackedFloat32Array<br>var v_prev: PackedFloat32Array
For now, we need three arrays - density will store the density, u will store horizontal velocity and v - vertical velocity.
Density tells us how much material does a given cell contain - it will range between 0.0 and 1.0, where 0 is empty and 1 is full. Technically, it can go above 1.0 but we will only display up to 1.0. The way we display density is simple - with color. A cell full of density will be fully white and without density - fully transparent.
The velocity arrays also store floats and they describe velocity at a given cell. A velocity of 0 means no movement, then it can go positive or negative which corressponds to going right or left (for horizontal) and down or up (for vertical). Combined, they tell us the velocity at a given cell. We could just store the velocities as a single array of Vector2f, but it will be much easier for calculations if we separate them as two float...