# 5. VFX Graph Comparison and Performance
Part of [[Particle Systems and Layered Effects]]. Requires Unity 6.3 LTS+ and URP. Previous: [[4.Controlling Particles from Script]]. Next: [[6.Particle Pitfalls]].
## 5.1 Particle System vs VFX Graph
| | Particle System (Shuriken) | VFX Graph |
|---|---|---|
| Runs on | CPU | GPU (compute shaders) |
| Practical particle counts | Hundreds to low thousands | Hundreds of thousands to millions |
| Physics collisions | Real colliders (World collision), `OnParticleCollision` callbacks | Primitives, signed distance fields, **depth buffer** collision. No callbacks into C# physics |
| Reading particles from C# | `GetParticles` / `SetParticles` | Not practical (data stays on the GPU) |
| Spawning other particles | Sub emitters | **GPU Events** (Trigger Event On Die / On Collide / Rate / Always) feeding another system in the same graph |
| Authoring | Inspector modules | Node graph, custom attributes, Shader Graph output |
| Platforms | Everything, including Quest and WebGL | Needs compute shader support. Quest supports compute but VFX Graph on Quest is costly and less tested *(unverified for 6.3)* |
| Lights per particle | Lights module | No |
**Stay with the Particle System when** you need physics collisions with C# callbacks, gameplay that reads particles, small counts, or you target Quest/mobile.
**Switch to VFX Graph when** you need very large counts (rain over a whole city, swarms, magic with 100k sparks), effects that sample meshes, textures, or signed distance fields, or GPU-side chaining. In VFX Graph the rain splash from part 3 becomes: rain system with a **Collide with Depth Buffer** block → **Trigger Event On Collide** → a GPU Event context → a splash system. It's entirely on the GPU, so it can't call `OnParticleCollision`; to tell gameplay about hits, use **Output Event** handlers on the CPU side (which only fire for events you send yourself, not per-particle collisions).
Install VFX Graph through **Window > Package Manager > Visual Effect Graph**, then **Create > Visual Effects > Visual Effect Graph**.
## 5.2 Overdraw: the main cost of particles
Transparent particles don't write depth, so every overlapping layer gets shaded again. A screen full of large smoke puffs can shade every pixel 20+ times. That's usually your bottleneck, not particle count.
- Use the **Scene view draw mode: Overdraw** (URP: **Rendering Debugger > Rendering > Overdraw**, via Window > Analysis > Rendering Debugger) to see hot spots.
- **Fewer, smaller, more opaque** particles beat many large faint ones. Smoke with 8 dense puffs looks as good as 40 faint ones.
- **Max Particle Size** in the Renderer caps on-screen size (fraction of the screen). Set `0.3–0.5` for smoke.
- **Camera Fading** on the material, so particles right in front of the camera fade out.
- Trim the transparent area of your textures. For large textures with lots of empty space, Unity's Sprite-mesh-style trimming isn't available on particles, but you can use **Mesh** render mode with a tight custom mesh (an octagon instead of a quad).
## 5.3 Particle count and simulation cost
- Set **Max Particles** deliberately in every system.
- **Noise**: Quality 1D/2D, one octave. 3D noise with multiple octaves is expensive.
- **Collision**: prefer **Planes**, then World **Low/Medium** with a small **Collides With** mask. High quality is a raycast per particle per frame.
- **Lights module**: a handful of lights at most.
- **Trails**: small Ratio, short lifetime.
- Use the **Profiler** (CPU > `ParticleSystem.Update` and `ParticleSystem.Draw`) to measure. Shuriken multi-threads simulation, so the cost often shows up on worker threads.
## 5.4 Culling
- A system can only be **procedurally culled** (simulation paused off-screen and fast-forwarded on return) when it uses no modules that make its motion unpredictable. If a system can't be culled, the Inspector shows a small warning icon at the top of the system listing the reason (for example World collision, Noise, or Local→World changes).
- **Culling Mode** (Main module) for non-predictable systems: **Automatic** (pause if looping, keep simulating if not), **Pause and Catch-up**, **Pause**, or **Always Simulate**.
- Bounds: make sure the renderer bounds cover the full effect (Show Bounds in the overlay), or the effect will cull while part of it is still visible.
- Beyond that, disable distant effect GameObjects yourself with a distance check or `CullingGroup` API, or swap to a cheaper LOD effect.
## 5.5 Quest and mobile notes
See [[Meta Quest 2]] for the platform setup. For particles:
- VR renders twice (once per eye, or Single Pass Instanced). Particle shaders from URP support Single Pass Instanced; custom Shader Graphs do too when made in URP.
- **Overdraw is the killer** on the Quest 2's tile-based GPU. Budget a handful of overlapping layers near the player. Large smoke right in front of the face is the worst case, so use Camera Fading and Max Particle Size.
- Prefer **Particles/Unlit** and **Simple Lit** over Lit. Skip soft particles if you don't need them (they need the depth texture, which costs a resolve on tile GPUs).
- Skip **Opaque Texture / Scene Color** distortion entirely; it forces an extra copy of the screen.
- Skip the **Lights module**. Use one flickering point light per fire, or bake light into the environment and fake glow with an additive sprite.
- Keep total live particles in the low hundreds per effect; prefer one shared World-space emitter with `EmitParams` (part 4) over many small systems.
- Billboards in VR can look "stuck to your face" when you turn your head. Use **Render Alignment: Facing** (faces the camera position instead of the view plane) for close-up particles.