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.

Three.js Fire Fountain Particle System

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.

fire-fountain.html
<!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>