FIRE FOUNTAIN
PARTICLE SYSTEM
Build a realistic fire fountain particle system with Three.js — sphere particles, velocity physics, gravity simulation, and interactive sliders for real-time control.
By Shane Brumback · Published June 30, 2023 · Updated August 25, 2026
LIVE DEMO
The fire fountain is running in the background right now. Use the sliders below to adjust the simulation in real-time.
Orbit: Drag mouse | Zoom: Scroll wheel
Frequently Asked Questions
How do I create a fire particle system in Three.js?
Create a BufferGeometry with many vertices, assign random positions and velocities, use a PointsMaterial with an additive blending fire texture, then animate particles upward with gravity and fade-out in the render loop. Reset particles when they die to create a continuous fountain effect.
What is the best blending mode for fire particles in Three.js?
Use THREE.AdditiveBlending on your PointsMaterial. This makes overlapping particles appear brighter (light adds together), creating a realistic glowing fire effect. Combine with depthWrite: false and transparent: true for proper layering.
How do I make particles fade out as they rise?
Track each particle's age or lifetime in an attribute array. In your animation loop, reduce opacity based on age. Use a custom ShaderMaterial for per-particle opacity, or use size attenuation so particles shrink as they age, giving the appearance of fading.
How many particles can Three.js handle for a fire effect?
Modern GPUs handle 10,000-100,000 point particles easily at 60fps. For a fire fountain, 1,000-5,000 particles gives a good look. Use BufferGeometry (not regular Geometry) and update positions via the position attribute buffer for best performance.
Introduction
Three.js is a powerful JavaScript library for creating stunning 3D visualizations. One exciting application is fire fountain particle systems — realistic, dynamic fire effects that simulate flames erupting upward in a fountain-like manner.
This tutorial creates hundreds of sphere particles with random velocities, simulates gravity pulling them back down, and resets them at the origin to create a continuous flowing fountain. Interactive sliders let you adjust height, velocity, particle count, and radius in real-time.
How It Works
- Particle Creation: 1,000 sphere meshes with random radii and yellow-to-orange gradient colors
- Velocity Physics: Each particle gets random X, Y, Z velocity — Y is upward thrust, X/Z create spread
- Gravity Simulation: Each frame subtracts a random friction value from Y velocity, pulling particles down
- Reset Loop: When particles fall below reset height, they teleport back to origin with fresh velocity
- OrbitControls: Auto-rotating camera with drag/zoom for exploring the effect from any angle
- Interactive Sliders: Real-time adjustment of fountain height, velocity ranges, particle count, and size
DEMO VIDEO
Full Working Code
Copy and paste this into an HTML file to run the fire fountain locally.
<!DOCTYPE html>
<html><head>
<style>body{margin:0;overflow:hidden;background:#000}</style>
<script src="https://cdn.jsdelivr.net/npm/three@latest/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@latest/examples/js/controls/OrbitControls.js"></script>
</head><body>
<script>
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, innerWidth/innerHeight, 0.1, 1000);
camera.position.set(20, 30, 35);
const renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.autoRotate = true;
controls.target.set(0, 5, 0);
scene.add(new THREE.GridHelper(100, 50));
const particles = [];
const colorStart = new THREE.Color(0xffff00);
const colorEnd = new THREE.Color(0xffa500);
for (let i = 0; i < 1000; i++) {
const r = Math.random() * 0.5 + 0.1;
const geo = new THREE.SphereGeometry(r);
const mat = new THREE.MeshBasicMaterial({
color: colorStart.clone().lerp(colorEnd, Math.random())
});
const p = new THREE.Mesh(geo, mat);
p.velocity = new THREE.Vector3(
(Math.random() - 0.5) * 0.5,
Math.random() * 1.9 + 0.1,
(Math.random() - 0.5) * 0.5
);
particles.push(p);
scene.add(p);
}
function animate() {
requestAnimationFrame(animate);
for (const p of particles) {
p.velocity.y -= 0.01 + Math.random() * 0.1;
p.position.add(p.velocity);
if (p.position.y < 0) {
p.position.set(0, 0, 0);
p.velocity.set(
(Math.random()-0.5)*0.5,
Math.random()*2,
(Math.random()-0.5)*0.5
);
}
}
controls.update();
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
</script></body></html>