Overview

FluXY is a grid-based, GPU, 2.5D fluid simulator for Unity. Let's see what this means:

Grid-based

Any fluid simulation must store the physical state of the fluid at every point in space. This state is mainly:

Density
A scalar value that determines how much fluid is there at that point.
Velocity
A vector value that determines the direction and magnitude of fluid movement at that point.
This state is updated over time, according to some physics rules that dictate how fluid behaves. There's many different approaches to storing and updating fluid state. Fluxy chooses to divide the space into square cells, and to store the state of the fluid (both density and velocity) at the center of each cell. This results in a grid-like structure:

density grid (8x8)
velocity grid (8x8)

GPU

To perform the simulation (that is, to update the state of the fluid over time) your computer's graphics processing unit or GPU is used. Since GPUs are good at handling textures, and a texture is basically a grid, Fluxy uses textures to store fluid data: each texel in the texture corresponds to a cell in the grid.

density texture (256x256)
velocity texture (256x256)

Fluxy uses two textures to store fluid state:

Density texture
R,G and B channels store 3 independent density values (they're often interpreted together as a color, and used for rendering) and the A channel stores temperature.
Velocity texture
R and G channels respectively store the X and Y components of a velocity vector, B stores divergence, A stores pressure.

2.5D

Fluid simulation is performed entirely in 2D, but a variety of techniques (billboarding, camera projection, vector projection, inertial forces, etc) are used by Fluxy to endow 2D fluid simulation with a pseudo-3D look. This way you get fluid that looks 3D, but performs orders of magnitude faster.

billboarding and camera projection used to splat an object into a fluid grid.

Architecture

Fluxy's core is composed of 3 components and 1 asset. Let's take a quick look at them and the role they play:

FluxyContainer
Containers are rectangular, 2D boxes that store fluid. They are Fluxy's main component.
FluxySolver
Each solver advances the simulation of up to 16 FluxyContainers in parallel.
FluxyTarget
Adding a FluxyTarget component to an object enabled it to “splat” density and velocity onto a FluxyContainer.
FluxyStorage
Storage assets manages texture memory.

Fluid state is stored on textures, as previously mentioned. The actual allocation and management of these textures is performed by the FluidStorage asset. Each solver has a reference to one storage asset, multiple solvers can share the same storage. The storage asset will make sure that the combined memory consumption of all solvers using it won’t exceed a user-defined threshold. It does this by re-creating the texture buffers when necessary, according to the desired resolution of each solver, its level of detail (LOD), and other factors.

Each FluxySolver will request a couple of textures from its storage asset: one to store densities, one to store velocities. These textures can be shared by multiple (up to 16) FluxyContainers, this is achieved by using automatic texture atlasing: each container is assigned a rectangular region within the solver’s textures, then the fluid in that rectangle is updated by the solver using the parameters specified by the container (turbulence, buoyancy, viscosity, etc).

Atlasing allows all containers sharing the same solver to be updated in parallel and be drawn in a single drawcall. However, since the containers must share the same texture space, the more containers you assign to the same solver the lower resolution they will have.

A single 512x512 solver holding 16 containers of different sizes.

FluxyTargets are splatted onto containers, and they “draw” density and velocity values into it. Which container(s) will a target be splatted onto? You can either specifiy this on a per-container basis, or use a FluxyTargetProvider. Target providers are components that live alongside a FluxyContainer, and dynamically determine which targets to splat onto it. There’s a built-in target provider component (FluxyTargetDetector) and you can easily write your own if required.