Useful Three.js Snippets Cheat Sheet

One file worth of copy-paste snippets for the Three.js patterns that come up in every simulation: scene setup, resize, custom shaders, InstancedMesh and post-processing.

1. Minimal Boilerplate Scene

scene-setup.js
import * as THREE from 'three';

const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2)); // avoid oversampling on Retina
renderer.setSize(innerWidth, innerHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.1;
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000);
camera.position.set(0, 5, 15);

// Resize handler
const onResize = () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
};
window.addEventListener('resize', onResize);

2. rAF Loop with delta time

let prevTime = performance.now();

function animate(time) {
  requestAnimationFrame(animate);
  const dt = Math.min((time - prevTime) / 1000, 0.05);
  prevTime = time;

  update(dt);
  renderer.render(scene, camera);
}
requestAnimationFrame(animate);

3. OrbitControls (lazy import)

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 2;
controls.maxDistance = 200;

// In animate():  controls.update();

4. InstancedMesh for Particles

const N = 10000;
const geo = new THREE.SphereGeometry(0.05, 4, 4);
const mat = new THREE.MeshBasicMaterial({ color: 0x00ffff });
const mesh = new THREE.InstancedMesh(geo, mat, N);
mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
scene.add(mesh);

const dummy = new THREE.Object3D();
// Update each frame:
for (let i = 0; i < N; i++) {
  dummy.position.set(particles[i].x, particles[i].y, particles[i].z);
  dummy.updateMatrix();
  mesh.setMatrixAt(i, dummy.matrix);
}
mesh.instanceMatrix.needsUpdate = true;

5. Custom ShaderMaterial

const mat = new THREE.ShaderMaterial({
  uniforms: {
    uTime:  { value: 0 },
    uColor: { value: new THREE.Color('#00ffcc') },
  },
  vertexShader: /* glsl */`
    uniform float uTime;
    varying vec2 vUv;
    void main() {
      vUv = uv;
      vec3 pos = position;
      pos.y += sin(pos.x * 4.0 + uTime) * 0.2;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
    }
  `,
  fragmentShader: /* glsl */`
    uniform vec3 uColor;
    varying vec2 vUv;
    void main() {
      gl_FragColor = vec4(uColor * (0.5 + 0.5 * vUv.y), 1.0);
    }
  `,
  side: THREE.DoubleSide,
});

// In animate:  mat.uniforms.uTime.value = time * 0.001;

6. Ping-Pong FBO (GPGPU)

const opts = { type: THREE.FloatType, depthBuffer: false };
let rtA = new THREE.WebGLRenderTarget(W, H, opts);
let rtB = new THREE.WebGLRenderTarget(W, H, opts);

function pingPong() {
  computeMat.uniforms.uState.value = rtA.texture;
  renderer.setRenderTarget(rtB);
  renderer.render(computeScene, computeCamera);
  renderer.setRenderTarget(null);
  [rtA, rtB] = [rtB, rtA]; // swap
}

// Read back data (slow — avoid if possible):
// renderer.readRenderTargetPixels(rtA, 0, 0, W, H, buffer);

7. Post-processing Bloom

import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';

renderer.outputColorSpace = THREE.LinearSRGBColorSpace; // composer handles output

const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
composer.addPass(new UnrealBloomPass(
  new THREE.Vector2(innerWidth, innerHeight),
  1.2,   // strength
  0.4,   // radius
  0.85   // threshold
));
composer.addPass(new OutputPass());

// In animate: composer.render() instead of renderer.render()

8. Dispose Properly

// Preventing memory leaks when removing objects
function disposeMesh(mesh) {
  mesh.geometry?.dispose();
  if (Array.isArray(mesh.material)) mesh.material.forEach(m => m.dispose());
  else mesh.material?.dispose();
  scene.remove(mesh);
}

// Renderer cleanup on unmount
renderer.dispose();
renderer.domElement.remove();