Start here
Quick start
A campfire in an ordinary three.js scene. It burns from the surface of your own log mesh, lights the floor with a shadow-casting fire light and reflects in a wet floor.
This is the complete examples/basic/ integration, with commentary. Every step is required except where it says optional.
#Set up the renderer
render() does its own bloom, exposure and ACES tone mapping, so leave renderer.toneMapping at its default. Turn shadows on if you want the fire light to cast them.
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { FireSystem, createTeepeeGeometry, patchReflectiveMaterial } from 'naturegl-fire';
const renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);#Build your scene as usual
World units are metres. A dark scene shows the fire best. The fire light does most of the lighting, and a weak hemisphere light fills the shadows.
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x05060a);
scene.add(new THREE.HemisphereLight(0x223048, 0x0a0806, 0.4));
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 100);
camera.position.set(5, 2.6, 6.5);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 1.4, 0);#Add a floor and something to burn
patchReflectiveMaterial is optional. It makes the floor's smooth texels count as wet, so the fire shows up in them. createTeepeeGeometry() is the campfire's log geometry. Use your own mesh if you have one.
const floorMat = new THREE.MeshStandardMaterial({ color: 0x2a2622, roughness: 0.55 });
patchReflectiveMaterial(floorMat, { strength: 0.6 });
const floor = new THREE.Mesh(new THREE.PlaneGeometry(30, 30), floorMat);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);
const logs = new THREE.Mesh(
createTeepeeGeometry(),
new THREE.MeshStandardMaterial({ color: 0x1a120c, roughness: 0.95, emissive: 0x401000 }),
);
logs.castShadow = true;
scene.add(logs);#Create the fire
The fire only exists inside domain, a box in world space. Here it's 4 × 6 × 4 m standing on the floor. The campfire preset emits from the mesh in its logs slot. create() also adds fire.fireLight and fire.fillLight to your scene.
const fire = await FireSystem.create({
renderer, scene, camera,
quality: 'high', // low | medium | high | ultra
domain: { center: [0, 3, 0], size: [4, 6, 4] },
meshes: { logs },
preset: 'campfire',
});#Update, then render
update(dt) runs every offscreen GPU pass: emitters, combustion, velocity, pressure, the light volume, embers and the fire probe. render() then replaces renderer.render(scene, camera). It draws your scene into its own colour and depth target, composites the fire over it and writes the tone-mapped result to the canvas.
const timer = new THREE.Timer();
renderer.setAnimationLoop(t => {
timer.update(t);
controls.update();
fire.update(timer.getDelta()); // simulation step (offscreen)
fire.render(); // scene + fire + bloom + tone mapping → canvas
});
addEventListener('resize', () => {
renderer.setSize(innerWidth, innerHeight);
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
});render() notices the new drawing-buffer size by itself, so there's no fire.resize() call.
campfire preset up close in the demo: the logs are the emitter, and the fire light lights the stones and the floor around them.#Make it do something
addEventListener('dblclick', () => fire.explode([0, 1.2, 0])); // detonation at 1.2 m
fire.fireball([0, 1, 0], 0.8); // a smaller rolling fireball
fire.setWind(1.5); // m/s along params.windDirection
fire.setParams({ smoke: 1.6, intensity: 1.3 }); // any FireParams field
fire.loadPreset('plume'); // swap the whole fire