npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cyberluke/three-particles

v4.1.2

Published

Three.js-based high-performance particle system library designed for creating visually stunning particle effects with ease. Perfect for game developers and 3D applications.

Readme

THREE Particles

Best-in-class particle framework for Three.js. Unity-style effects with a GPU-compute backbone: Three.js TSL kernels run gravity, orbital motion, Bézier over-lifetime curves, force fields and 3D simplex noise for 350K+ particles per system at full framerate.

Author: CyberLuke — the single maintained line since v4.

Features

  • Easy integration with Three.js.
  • Visual editor for fine-tuning and exporting effect configs (@cyberluke/three-particles-editor).
  • Highly customizable particle properties (position, velocity, size, color, alpha, rotation, etc.).
  • Support for various emitter shapes and parameters.
  • Force fields and attractors for dynamic particle behavior (point attraction/repulsion, directional wind) — up to 16 per system.
  • Collision planes — kill, clamp, or bounce particles off infinite planes (e.g., water surfaces, floors, walls). Part of the compute pass.
  • Sub-emitters triggered on particle birth or death events (GPU ping-pong event buffers).
  • Baked Bézier over-lifetime curves — 256-sample lookup arrays (curve-bake.ts), <0.4% max interpolation error.
  • Five renderer types (RendererType): POINTS (billboard quads), INSTANCED (GPU instancing, no gl_PointSize limit), TRAIL (ribbon trails with width/opacity/color tapering), MESH (instanced 3D meshes with full rotation and lighting), FLUID (screen-space fluid: volumetric metaball spheres with spherical-cap normals, Beer-Lambert absorption and Fresnel-mixed environment reflections — see renderer.fluid).
  • Soft particles — depth-based alpha fade near opaque geometry.
  • WebGPU compute — all per-particle physics runs in TSL compute kernels (SimulationBackend.GPU); AUTO uses the registered WebGPU path, CPU maps to the identical GPU path in this GPU-only build.
  • TypeScript definitions shipped (dist/index.d.ts, webgpu.d.ts).

Installation

npm install @cyberluke/three-particles

Both entry points are plain ESM (no separate CDN files; the package ships dist/index.js, dist/webgpu.js and a minified dist/three-particles.min.js).

Usage

The engine is GPU-only (v4): a WebGPU backend is required before creating any system.

import * as THREE from "three/webgpu";
import {
  createParticleSystem,
  updateParticleSystems,
  Shape,
} from "@cyberluke/three-particles";
import { enableWebGPU } from "@cyberluke/three-particles/webgpu";

// 1. WebGPU renderer, then register the library with it (once)
const renderer = new THREE.WebGPURenderer({ antialias: true });
await renderer.init();
enableWebGPU(renderer); // false + warning if the backend lacks compute

// 2. Create a system (same config shape the editor exports)
const system = createParticleSystem({
  maxParticles: 100000,
  gravity: -9.8,
  emission: { rateOverTime: 50 },
  shape: { shape: Shape.CONE, cone: { angle: 0.2, radius: 0.3 } },
  forceFields: [
    { type: "DIRECTIONAL", direction: { x: 1, y: 0, z: 0 }, strength: 5 },
  ],
  collisionPlanes: [
    { position: { x: 0, y: 5, z: 0 }, normal: { x: 0, y: -1, z: 0 }, mode: "KILL" },
  ],
});
scene.add(system.instance);

// 3. Render loop — dispatch compute, then render
renderer.setAnimationLoop(() => {
  const delta = clock.getDelta();
  system.update({ now: performance.now(), delta, elapsed: clock.elapsedTime });
  if (system.computeNode) renderer.compute(system.computeNode);
  renderer.render(scene, camera);
});

// or drive every created system at once:
updateParticleSystems({ now: performance.now(), delta, elapsed });

updateParticleSystems(cycleData) and per-system system.update(cycleData) take the same CycleData object: { now, delta, elapsed } (see types.ts). system.updateConfig(...) applies on the next compute dispatch; system.dispose() releases buffers.

Note on rendererType: POINTS is the billboard-quad path (a unit quad per particle sampled with a computed point UV — there is no point-sprite mode on the GPU backend because WGSL has no gl_PointCoord), INSTANCED/MESH use InstancedBufferGeometry, TRAIL fills a GPU history ring (StorageBufferAttribute) that the ribbon material reads.

Note on FLUID: the fluid family is selected through renderer.fluid.solver, which picks the compute kernels while every solver writes the same instanceOffset / instanceVelocity storage the material reads:

  • 'MLS-MPM' (default) — grid-based Material Point Method: clearGrid -> p2g_1 -> p2g_2 -> updateGrid -> g2p, 2 sub-steps per frame on a 64^3 lattice with a fixed-point u32 atomic scatter (upstream 1e7 multiplier, two's-complement decode). Params in renderer.mlsMpm (MLSMPMConfig).
  • 'SPH' — fixed-radius neighbour search with double-density relaxation: gridClear -> gridBuild -> 3-pass exclusive prefix scan -> reorder -> density -> reorder -> force -> integrate. Params in renderer.sph (SPHConfig).

Both are fed by the dambreak initialisation in fluid-mpm.ts / fluid-sph.ts and rendered through the pass chain in tsl-fluid-screen-space-material.ts (depth map → four bilateral up-samples → additive thickness map → separable Gaussian blur → shading), or directly as spheres with renderer.fluid.sphereRender = true. renderer.fluid.boxWidthRatio reproduces the upstream changeBoxSize() z squeeze. Modules: webgpu/fluid-mpm.ts, webgpu/fluid-sph.ts, webgpu/tsl-fluid-metaball-material.ts, webgpu/tsl-fluid-screen-space-material.ts (+ createFluidSimPipeline in webgpu/tsl-materials.ts).

Usage with Three.js

  • Three.js r186+ ("three": "^0.186.0" — the exact peer version pinned in package.json) with the WebGPU build (three/webgpu).
  • A browser with WebGPU support (Chrome 113+, Edge 113+, Firefox 186+ / Nightly).
  • The v4 engine ships a single WebGPU code path (SimulationBackend.GPU); config objects are otherwise identical to older 3.x configs.

Usage with React Three Fiber

React Three Fiber is supported and working with this engine. No wrapper package is needed — use createParticleSystem directly with R3F hooks:

import * as THREE from "three/webgpu";
import { createParticleSystem } from "@cyberluke/three-particles";

const renderer = new THREE.WebGPURenderer({ antialias: true });
await renderer.init();

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, 1, 0.1, 100);
// ... system = createParticleSystem(config); scene.add(system.instance);

WebGPU Compute Support

Optional GPU-accelerated particle simulation via Three.js WebGPU renderer and TSL (Three Shading Language). Offloads all per-particle physics and modifiers to GPU compute shaders, enabling 50K-350K+ particles at interactive frame rates.

The CPU path used to be the bottleneck for real 350K+ particle counts, so the whole pipeline was reworked around TSL compute kernels. Current split (grounded in src/js/effects/three-particles/webgpu/):

What Runs on GPU (TSL compute kernels)

  • Core physics (compute-particle-update.ts): gravity, velocity integration, position update, lifetime tracking
  • All 7 modifiers (compute-modifiers.ts): size/opacity/color over lifetime, rotation, linear velocity, orbital velocity, noise (3D simplex FBM)
  • Force fields (compute-force-fields.ts): point attractors/repulsors and directional forces with falloff — MAX_FORCE_FIELDS = 16
  • Collision planes (compute-collision-planes.ts): kill/clamp/bounce evaluated inside the compute pass
  • Curves (curve-bake.ts): baked into CURVE_RESOLUTION = 256-sample lookup arrays for fast GPU evaluation (<0.4% error)

The same kernel also fills the TRAIL history ring, ping-pong sub-emitter birth/death buffers and the free-list allocator — all as StorageBufferAttributes read directly by the materials.

What Stays on CPU

  • Emission — particle activation, burst scheduling, rate-over-distance
  • Sub-emitters — birth/death trigger spawning
  • Configuration changes — updateConfig() applies on the next frame
  • Trail renderer — TRAIL type always uses CPU simulation (other renderer types work with GPU)

Important Notes

Color Conventions

All RGB values in particle configs (startColor, backgroundColor) are sRGB — the same convention used everywhere else in three.js. Pass the value a color picker gives you (e.g. { r: 1, g: 0, b: 0 } for pure red) and the renderer will display it correctly.

Internally the library decodes these to linear for shader math and relies on the renderer's standard output pass to convert back to sRGB on the way to the framebuffer. No special outputColorSpace setup is required; the three.js default (SRGBColorSpace) works.

User-supplied color map textures should also be tagged as sRGB (texture.colorSpace = THREE.SRGBColorSpace) — this is also the three.js default for color textures loaded via TextureLoader.

Color Over Lifetime

The colorOverLifetime feature uses a multiplier-based approach (similar to Unity's particle system), where each RGB channel curve acts as a multiplier applied to the particle's startColor.

Formula: finalColor = startColor * colorOverLifetime

[!IMPORTANT] To achieve full color transitions, set startColor to white { r: 1, g: 1, b: 1 }. If any channel in startColor is set to 0, that channel cannot be modified by colorOverLifetime.

Example - Rainbow effect:

{
  startColor: {
    min: { r: 1, g: 1, b: 1 },  // White - allows full color range
    max: { r: 1, g: 1, b: 1 }
  },
  colorOverLifetime: {
    isActive: true,
    r: {  // Red: full → half → off
      type: 'BEZIER',
      scale: 1,
      bezierPoints: [
        { x: 0, y: 1, percentage: 0 },
        { x: 0.5, y: 0.5, percentage: 0.5 },
        { x: 1, y: 0, percentage: 1 }
      ]
    },
    g: {  // Green: off → full → off
      type: 'BEZIER',
      scale: 1,
      bezierPoints: [
        { x: 0, y: 0, percentage: 0 },
        { x: 0.5, y: 1, percentage: 0.5 },
        { x: 1, y: 0, percentage: 1 }
      ]
    },
    b: {  // Blue: off → half → full
      type: 'BEZIER',
      scale: 1,
      bezierPoints: [
        { x: 0, y: 0, percentage: 0 },
        { x: 0.5, y: 0.5, percentage: 0.5 },
        { x: 1, y: 1, percentage: 1 }
      ]
    }
  }
}

Documentation

Full API types ship with the package: dist/index.d.ts and webgpu.d.ts. A machine-readable overview is included as llms.txt / llms-full.txt in the package.