A real-time 3D fluid simulation built in Unity 6 using HLSL compute shaders, implementing Smoothed Particle Hydrodynamics (SPH). This project improves upon an existing CPU-based SPH baseline by porting the full physics pipeline to the GPU with additional stability enhancements.
Course: Computer Animation (2110512) — Chulalongkorn University, Semester 2/2568
Author: NoneTP | GitHub: NoneTP
Baseline Reference: wendtpiotr/sph-fluid-simulation
Simulating fluids in real time using SPH requires computing forces between every pair of nearby particles — an O(N²) operation. At 10,000 particles this means 100 million pairwise interactions per frame. Running this sequentially on the CPU limits the baseline to ~13.7 FPS at 10k particles, with visible numerical instability at higher counts.
- Full SPH pipeline in C# on a single CPU thread
- Dictionary-based 3D spatial hash for neighbour queries
- Rendering via MaterialPropertyBlock batching
- Uses
Time.deltaTime(variable timestep) — source of instability - No predicted positions, no substeps
- All SPH math ported to four HLSL compute shader kernels
- Particles live permanently in
ComputeBufferon the GPU — no CPU readback - Rendering via
DrawMeshInstancedIndirectreading positions directly from GPU buffer
Stability improvements over baseline:
| Improvement | What it fixes |
|---|---|
| Predicted positions | Computes forces from pos + vel * dt instead of stale current positions |
| Fixed timestep + substeps | Replaces variable Time.deltaTime with fixed dt = 1/60, 3 substeps/frame |
| Poly6 viscosity weighting | Wider damping radius catches isolated fast-moving particles |
| XSPH velocity correction | Blends each particle's velocity with neighbours for coherent flow |
| Contact spring repulsion | Short-range force prevents particle interpenetration |
Tested on Intel Core Ultra 5 225H (integrated GPU, shared DDR5 memory):
| Particles | Baseline FPS | Baseline ms | GPU FPS | GPU ms | Speedup |
|---|---|---|---|---|---|
| 500 | 181.5 | 5.92 | 100.4 | 10.21 | 0.58x |
| 2,500 | 55.0 | 18.54 | 75.8 | 13.48 | 1.38x |
| 5,000 | 29.0 | 34.55 | 56.2 | 17.85 | 1.94x |
| 10,000 | 13.7 | 73.08 | 26.8 | 37.50 | 1.95x |
| 20,000 | 3.0 | 333.33 | 10.4 | 96.65 | 3.45x |
Note: The GPU version underperforms at 500 particles due to kernel dispatch overhead. The crossover point is ~1,500–2,000 particles. On dedicated GPU hardware (RTX 3060) the GPU version exceeds 60 FPS at 20,000 particles.
- Unity 6 LTS (6000.2.8f1 or newer)
- Universal Render Pipeline package
- GPU with DirectX 11 / Shader Model 4.5 support (any GPU from ~2012+)
-
Clone this repository:
git clone https://github.com/NoneTP/SPH_fluid_simulation_HLSL_implementation.git
-
Open the project in Unity Hub → select
FluidSim3Dfolder -
Open
Assets/Scenes/SampleScene.unity -
Hit Play
| Parameter | Default | Description |
|---|---|---|
| Particle Count | 12,000 | Total particles (restart required to change) |
| Rest Density | 5.5 | Target fluid density |
| Gas Constant | 50 | Pressure stiffness — lower = softer fluid |
| Viscosity | 0.3 | Fluid thickness |
| Smoothing Radius | 0.3 | SPH kernel radius (world units) |
| Fixed Dt | 1/120 | Integration timestep |
| Substeps | 3 | Physics passes per frame |
| Particle Size | 0.1 | Visual size of each sphere |
A BenchmarkLogger component is attached to the main GameObject. It auto-starts recording after 1 second of play and saves results to:
experiments/results/<RunLabel>_<timestamp>.csv
Change the Run Label field in the Inspector before each test.
Assets/
├── Scenes/
│ └── SampleScene.unity
├── Scripts/
│ ├── HydrodynamicsManagerGPU.cs ← GPU simulation driver
│ ├── IcosphereGenerator.cs ← Sphere mesh generation
│ └── BenchmarkLogger.cs ← Performance recording
├── Shaders/
│ ├── FluidSimGPU.compute ← All 4 SPH compute kernels
│ └── ParticleGPU.shader ← Instance rendering shader
└── Materials/
└── ParticleGPU.mat ← Material using ParticleGPU shader
experiments/
└── results/ ← Benchmark CSV output
final_report/
└── final_report.docx ← Project report
Each simulation frame dispatches four kernels in sequence:
1. PredictPositions → pos_pred = pos + vel * dt
2. CalculateDensities → ρᵢ = Σⱼ m · W_poly6(|r_pred_ij|², h)
Pᵢ = k · ((ρᵢ/ρ₀)⁷ - 1)
3. CalculateForces → F_pressure + F_viscosity + F_spring
4. Integrate → vel += accel * dt | XSPH correction | AABB boundary
All kernels use [numthreads(256, 1, 1)]. Thread id.x handles particle id.x — no thread communication required.
- O(N²) neighbour search — no GPU spatial hash yet. Adding bitonic sort + hash grid would enable 100k+ particles in real time.
- Near-pressure force — implemented but caused instability on iGPU; removed pending further tuning.
- iGPU shared memory — baseline and GPU version share the same DDR5 bus, limiting the speedup advantage vs. dedicated GPU.
- No surface rendering — particles rendered as spheres; marching cubes surface reconstruction would improve visual quality.
- Müller, Charypar, Gross — Particle-Based Fluid Simulation for Interactive Applications, SCA 2003
- Becker, Teschner — Weakly Compressible SPH for Free Surface Flows, SCA 2007
- Schechter, Bridson — Ghost SPH for Animating Water, ACM TOG 2012
- Wendt (2025) — sph-fluid-simulation (baseline)
- Lague (2023) — Coding Adventure: Simulating Fluids