Bringing Fluid Simulation to Life in Unity

Introduction

In this Post, I'm excited to share my journey in implementing a 2D fluid simulation using the Navier-Stokes equations in Unity. This project was heavily inspired by a fantastic tutorial by Mike Ash which you can find here: fluid-simulation-for-dummies, which originally focused on C implementation. I've adapted these concepts into C# to bring this fluid simulation to the Unity engine, showcasing the power of real-time simulation and visualization.

Background

Fluid simulation is a complex yet fascinating area of computer graphics, often used to create realistic animations of liquids in games and visual effects. The foundation of my project is based on the Navier-Stokes equations, which describe the motion of fluid substances and are key to simulating fluid flow.

While implementing this project, I quickly realized that the mathematical components involved in fluid dynamics were quite intricate and, at times, beyond my comprehension. For those who are interested in the deeper mathematical underpinnings and algorithms of fluid simulations, I highly recommend visiting Mike Ash's blog post, which was an invaluable resource for me. A lot of the credit for understanding the mathematical/algorithmic aspects of this simulation goes to the original author's clear and insightful explanation, which was instrumental in bringing this project to life.


Simulation Overview

The fluid simulation in my project, based on the principles outlined in Mike Ash's blog post, involves several key operations and subroutines that work together to simulate realistic fluid behavior:

  • Diffuse: This operation mimics the natural phenomenon where substances like dye spread out in a fluid, even in still conditions. 
  • Project: Since we are simulating incompressible fluids, the volume of fluid in each grid cell must remain constant. The project operation ensures that the inflow and outflow of fluid in each cell are balanced, maintaining equilibrium throughout the simulation. This step corrects any imbalances caused by other operations.
  • Advect: This is the process by which fluid cells move due to their own velocities. Advection affects both the dye in the fluid and the velocities themselves, allowing the simulation to capture the fluid's motion.

In addition to these main operations, there are two crucial subroutines:

  • Set Bounds (set_bnd): This subroutine acts as virtual walls for the fluid container. It prevents the fluid from "leaking" out of the simulation grid by mirroring velocities in the cells adjacent to the boundary. This creates a wall effect, ensuring that any velocity directed towards the boundary is countered by an equal and opposite velocity at the wall.
  • Linear Solver (lin_solve): This function is a bit of a mystery but is essential for both the diffuse and project operations. It solves a linear differential equation, ensuring that the diffusion and projection steps are correctly calculated, even if the exact mathematical intricacies aren't fully clear.

Implementation in Unity

The core of the fluid simulation is written in C# as a Unity MonoBehaviour, FluidCube2D. This script handles the computation of fluid properties such as density and velocity on a 2D grid. Here's a breakdown of the key components:

  • Grid Setup: The simulation operates on a grid, where each cell holds data for fluid properties.
  • Parameters: Configurable parameters such as grid size, time step, diffusion rate, and viscosity allow for fine-tuning of the simulation.
  • Density and Velocity Fields: Two primary arrays store the density and velocity at each grid point.
  • Simulation Steps: The fluid motion is computed through several steps, including diffusion (spreading out of fluid particles), advection (transport of properties by the flow), and projection (ensuring mass conservation).

User Interaction 

 A key feature I wanted to implement is the user's ability to interact with the simulation, I designed a UI panel for changing settings which is designed to give users real-time control over various aspects of the simulation. Accessible via a button in the top left corner, the UI unfolds a panel containing sliders and options that directly influence the behaviour and appearance of the fluid.


Adjustable Parameters: The UI includes sliders for adjusting the viscosity, diffusion rate, fade duration, and simulation speed. These parameters are fundamental to how the fluid behaves and flows. Viscosity controls the 'thickness' of the fluid, diffusion affects how substances like dye spread within the fluid, fade duration determines how quickly the fluid's colour fades over time, and simulation speed allows for the acceleration or deceleration of the entire process.

Colour Customization: Users can also select from various gradient options for fluid colour. These gradients visually represent the density of the fluid, adding an artistic aspect to the simulation. The choice of colour gradient can significantly change the aesthetic and feel of the fluid dynamics.

Visualization with a Shader

To bring the simulation to life, I've developed a custom shader in Unity, "Custom/FluidDensityShader". This shader takes the density data from the simulation and visualizes it in real-time. The shader works by:

  • Sampling Density Data: The shader reads the density values from a texture, which is updated every frame with the latest simulation data.
  • Applying a Gradient: A gradient texture is used to map density values to colours, creating a visually appealing representation of the fluid.
  • Rendering Techniques: To ensure smooth and realistic visualization, the shader utilizes the texture's FilterMode property. By setting this property to Bilinear, the texture sampling within the shader automatically applies bilinear interpolation. This approach smooths out the color transitions and gradients, enhancing the visual quality of the fluid simulation. Additionally, the shader employs alpha blending techniques to achieve realistic transparency and fluid dynamics effects.

Challenges and Solutions

One of the significant challenges in this project was optimizing performance, as the entire simulation runs single-threaded on the CPU. Initially, I aimed to leverage a compute shader or implement multithreading to handle the fluid dynamics calculations, which would have significantly boosted performance. However, I encountered a limitation due to WebGL's lack of support for these advanced features.

To navigate this, I decided to shift as much of the computation burden as possible to the GPU. The logical step was to offload the visualization aspect to a shader. By doing this, I could improve performance within the constraints I was working with. The custom shader "Custom/FluidDensityShader" not only enhanced the visual output but also provided a more efficient way to render the simulation in real time.

Another hurdle was achieving smooth colour transitions in the shader, particularly because we were limited by the grid size due to performance costs. A smaller grid size meant less detailed fluid simulation data, leading to visual artifacts. Initially, the visualization exhibited sharp borders, which was suboptimal for fluid simulation. 

To counteract this and achieve a smoother result I used, TextureFormat.RGBAFloat for more precise density values in the texture and refined the alpha channel calculations in the shader. These adjustments collectively ensured smoother gradients and a more realistic depiction of fluid flow, effectively overcoming the constraints imposed by the grid size.

Future Work

I am eager to implement a 'gravity mode' that would allow users to see how fluid behaves under different gravitational conditions. Additionally, I aim to develop more intricate velocity profiles for mouse interactions, moving beyond the current implementation of adding velocity in the negative direction of mouse movement.

Leave a comment

Log in with itch.io to leave a comment.