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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@interverse/three-particles

v1.3.1

Published

AAA-quality GPU particle system for Three.js with Niagara/Unity VFX Graph-like features

Readme

@interverse/three-particles

AAA-quality GPU particle system for Three.js with Niagara/Unity VFX Graph-like features, powered by TSL (Three Shader Language).

Features

  • GPU Compute Shaders - Physics simulation via TSL compute
  • High Performance - 100K+ particles at 60fps
  • Indirect Rendering - Efficient instanced mesh rendering
  • Ribbon Trails - GPU-accelerated smooth trails with position history
  • Advanced Curves - Non-linear control over size, opacity, and color
  • Texture Sheet Animation - Sprite sheet support with configurable FPS
  • Soft Particles - Depth-aware edge fading
  • Depth Buffer Collisions - Particles bounce off scene geometry
  • Provider System - Extensible behaviors (physics, vortex, boids, etc.)
  • VFX Graph Foundation - Node-based effect composition
  • TypeScript Support - Full type definitions included

Requirements

  • Three.js ≥ 0.182.0
  • WebGPU-enabled browser (Chrome 113+, Edge 113+, or Firefox Nightly)

Installation

yarn add @interverse/three-particles
# or
npm install @interverse/three-particles

Quick Start

import * as THREE from 'three';
import { WebGPURenderer } from 'three/webgpu';
import { GPUParticleSystem } from '@interverse/three-particles';

// Create WebGPU renderer
const renderer = new WebGPURenderer();
await renderer.init();
document.body.appendChild(renderer.domElement);

// Create scene and camera
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight);
camera.position.z = 5;

// Create particle system
const particles = new GPUParticleSystem({
  maxParticles: 10000,
  emissionRate: 500,
  lifetime: 2.0,
  
  // Visual properties
  sizeStart: 0.1,
  sizeEnd: 0.02,
  colorStart: new THREE.Color(1, 0.5, 0),
  colorEnd: new THREE.Color(1, 0, 0),
  opacityStart: 1.0,
  opacityEnd: 0.0,
  
  // Physics
  velocity: new THREE.Vector3(0, 2, 0),
  velocityVariation: new THREE.Vector3(0.5, 0.5, 0.5),
  gravity: new THREE.Vector3(0, -9.8, 0),
  drag: 0.1,
});

scene.add(particles);

// Emit particles
particles.burst(1000);

// Animation loop
function animate() {
  requestAnimationFrame(animate);
  particles.update(renderer, 0.016, camera);
  renderer.render(scene, camera);
}
animate();

Advanced Features

Ribbon Trails

Create smooth, flowing trails behind particles using GPU position history.

const particles = new GPUParticleSystem({
  // ... basic config
  trail: {
    enabled: true,
    segments: 16,        // Number of history points
    width: 0.2,          // Width relative to particle size
    updateInterval: 0.02, // Sampling rate (seconds)
    fadeAlpha: true      // Fade opacity from head to tail
  }
});

Curves & Gradients

Control properties over particle lifetime using non-linear curves.

import { LifetimeCurve, GradientCurve } from '@interverse/three-particles';

const particles = new GPUParticleSystem({
  // Ease-out size
  sizeCurve: new LifetimeCurve([
    { p: 0, v: 0.1 }, 
    { p: 0.2, v: 1.0 }, 
    { p: 1, v: 0.0 }
  ]),
  
  // Color gradient: red -> yellow -> smoke
  colorGradient: new GradientCurve([
    { t: 0, c: new THREE.Color(1, 0, 0) },
    { t: 0.5, c: new THREE.Color(1, 1, 0) },
    { t: 1, c: new THREE.Color(0.2, 0.2, 0.2) }
  ])
});

Advanced Physics

Enable complex interactions like depth collisions and vector fields.

const particles = new GPUParticleSystem({
  // Bounce off scene geometry (requires depth texture)
  depthCollisions: true,
  bounciness: 0.6,
  
  // Simple floor collision
  floorY: 0, 
  
  // 3D Vector field for flow effects
  vectorField: myVectorFieldTexture3D,
  turbulence: 0.5,
});

// Important: Pass depth texture for depth collisions inside render loop
particles.setDepthTexture(depthTexture);

Custom Materials

Inject your own TSL-based material for full shader control. Two approaches available:

Option A: Material Injection

Pass a pre-created material and access particleNodes after construction.

import { SpriteNodeMaterial } from 'three/webgpu';
import { mix, color, instanceIndex } from 'three/tsl';

const customMaterial = new SpriteNodeMaterial();

const particles = new GPUParticleSystem({
  material: customMaterial,
  // ... other config
});

// Build shader using particle nodes
const { velocities, progress, speed } = particles.particleNodes;

// Use helper functions for cleaner code
customMaterial.colorNode = mix(color(0xff0000), color(0xffff00), speed().div(10));
customMaterial.opacityNode = progress().oneMinus();
customMaterial.scaleNode = progress().oneMinus().mul(0.5);

Option B: Material Factory (Recommended)

Use materialFactory callback for cleaner access to particle context.

import { SpriteNodeMaterial } from 'three/webgpu';
import { mix, color } from 'three/tsl';

const particles = new GPUParticleSystem({
  materialFactory: (ctx) => {
    const mat = new SpriteNodeMaterial();
    
    // ctx provides storage nodes + helper functions
    mat.colorNode = mix(color(0xff0000), color(0xffff00), ctx.speed().div(10));
    mat.opacityNode = ctx.progress().oneMinus();
    mat.scaleNode = ctx.progress().oneMinus().mul(0.5);
    
    return mat;
  },
  // ... other config
});

ParticleMaterialContext properties:

  • positions, velocities, ages, lifetimes, rotations, colors, styles - Storage nodes
  • time, delta - Uniforms
  • index - Instance index node
  • progress() - Returns lifetime progress (0-1)
  • speed() - Returns velocity magnitude
  • direction() - Returns normalized velocity
  • styleIndex() - Returns style index for current particle
  • isStyle(n) - Returns true if particle matches style n
  • styleCount - Number of defined styles

Multi-Style Particles

Create particles with multiple visual styles (e.g., fire + smoke) in a single emitter.

import { SpriteNodeMaterial } from 'three/webgpu';
import { mix, color } from 'three/tsl';

const particles = new GPUParticleSystem({
  styles: [
    { name: 'fire', weight: 3, color: new THREE.Color(0xff3300) },
    { name: 'smoke', weight: 1, color: new THREE.Color(0x333333) }
  ],
  materialFactory: (ctx) => {
    const mat = new SpriteNodeMaterial();
    
    // ctx.isStyle(0) returns 1 for fire, 0 for smoke
    const isFire = ctx.isStyle(0);
    mat.colorNode = mix(color(0x333333), color(0xff3300), isFire);
    mat.opacityNode = ctx.progress().oneMinus();
    
    return mat;
  }
});

Style weights determine spawn distribution: fire (weight 3) spawns 75%, smoke (weight 1) spawns 25%.

Providers

Providers extend particle behavior with modular forces and effects. Add multiple providers to create complex simulations.

Using Providers

import { 
  GPUParticleSystem, 
  AttractorProvider, 
  TurbulenceProvider,
  BoidsProvider 
} from '@interverse/three-particles';

const particles = new GPUParticleSystem({ /* config */ });

// Add attractor force field
const attractor = new AttractorProvider(4); // max 4 attractors
attractor.addAttractor({ 
  position: new THREE.Vector3(0, 2, 0), 
  strength: 5 
});
particles.addProvider(attractor);

// Add turbulence for organic motion
particles.addProvider(new TurbulenceProvider({ 
  frequency: 0.5, 
  amplitude: 1.0 
}));

Available Providers

| Provider | Description | |----------|-------------| | AttractorProvider | Point/area attraction with optional spin | | BoidsProvider | Flocking behavior (Reynolds' Boids) | | TurbulenceProvider | Noise-based turbulent motion | | VortexProvider | Spiral/vortex swirling forces | | WindProvider | Directional wind with gusts | | PathProvider | Guide particles along curves | | MouseInteractionProvider | Mouse/touch push/pull interaction | | DepthCollisionProvider | Bounce off scene geometry |


AttractorProvider

Creates gravitational/magnetic point attractors that pull particles.

import { AttractorProvider } from '@interverse/three-particles';

const attractor = new AttractorProvider(8); // max 8 attractors

// Simple gravity point
attractor.addAttractor({
  position: new THREE.Vector3(0, 5, 0),
  strength: 10
});

// Spinning attractor (vortex-like)
attractor.addAttractor({
  position: new THREE.Vector3(3, 0, 0),
  strength: 5,
  spinAxis: new THREE.Vector3(0, 1, 0),
  spinStrength: 2.0
});

// Update attractor position at runtime
attractor.updateAttractor(0, { position: new THREE.Vector3(0, 8, 0) });

particles.addProvider(attractor);

AttractorConfig:

  • position: Vector3 - Attractor world position
  • strength: number - Pull force (higher = stronger)
  • spinAxis?: Vector3 - Axis for orbital spin
  • spinStrength?: number - Spin force multiplier
  • falloff?: 'linear' | 'inverse' | 'inverseSq' - Distance falloff (default: inverseSq)

BoidsProvider

Implements Craig Reynolds' flocking algorithm with three core rules: separation, alignment, and cohesion. Enhanced with goal seeking and boundary avoidance.

import { BoidsProvider } from '@interverse/three-particles';

const boids = new BoidsProvider({
  // Core Reynolds' rules
  separationWeight: 1.5,  // Avoid crowding
  alignmentWeight: 1.0,   // Match neighbor velocity
  cohesionWeight: 1.2,    // Move toward flock center
  
  // Perception
  neighborRadius: 2.5,    // How far to look for neighbors
  separationRadius: 1.0,  // Personal space radius
  
  // Movement limits
  maxSpeed: 5.0,
  maxForce: 1.0,          // Max steering force
  
  // Containment
  boundSize: 10,          // Soft boundary size
  boundaryForce: 2.0,     // Edge avoidance strength
  
  // Optional goal seeking (for scripted paths)
  goalPosition: new THREE.Vector3(5, 0, 0),
  goalWeight: 0.5,
  
  // Organic randomness
  wanderStrength: 0.3
});

// Adjust at runtime
boids.setSeparationWeight(2.0);
boids.setGoal(new THREE.Vector3(10, 5, 0), 0.8);

particles.addProvider(boids);

BoidsConfig:

  • separationWeight?: number - Avoid crowding neighbors
  • alignmentWeight?: number - Steer toward average heading
  • cohesionWeight?: number - Steer toward average position
  • neighborRadius?: number - Perception distance
  • separationRadius?: number - Personal space distance
  • maxSpeed?: number - Maximum speed limit
  • maxForce?: number - Maximum steering force
  • boundSize?: number - Soft boundary size
  • boundaryForce?: number - Edge avoidance strength
  • goalPosition?: Vector3 - Optional goal to steer toward
  • goalWeight?: number - Goal seeking strength
  • wanderStrength?: number - Random organic motion

TurbulenceProvider

Physics-based turbulent forces following fluid dynamics principles. Implements Kolmogorov's energy cascade and Reynolds number effects.

import { TurbulenceProvider } from '@interverse/three-particles';

const turbulence = new TurbulenceProvider({
  // Base turbulence
  frequency: 0.5,
  amplitude: 1.0,
  octaves: 3,
  friction: 0.02,
  
  // Physics enhancements
  velocitySensitivity: 0.3,   // More turbulence at higher speeds
  intermittency: 0.4,         // Puff-like variation over time
  intermittencyFrequency: 0.5,
  kolmogorovScaling: 0.7,     // Follow -5/3 energy law
  
  // Wake effect
  flowDirection: new THREE.Vector3(1, 0, 0),
  wakeIntensity: 0.5          // Stronger behind flow
});

// Adjust at runtime
turbulence.setVelocitySensitivity(0.5);
turbulence.setKolmogorovScaling(1.0);

particles.addProvider(turbulence);

TurbulenceConfig:

  • frequency?: number - Noise spatial frequency
  • amplitude?: number - Force strength
  • octaves?: number - Noise layers (1-4)
  • friction?: number - Velocity damping (viscosity)
  • velocitySensitivity?: number - Reynolds effect (faster = more turbulent)
  • intermittency?: number - Puff behavior intensity
  • intermittencyFrequency?: number - Puff frequency
  • kolmogorovScaling?: number - 0=flat, 1=physically realistic -5/3 law
  • flowDirection?: Vector3 - Direction for wake calculation
  • wakeIntensity?: number - Turbulence boost in wake regions

VortexProvider

Creates a swirling vortex that pulls particles while spinning them around an axis.

import { VortexProvider } from '@interverse/three-particles';

const vortex = new VortexProvider({
  center: new THREE.Vector3(0, 0, 0),
  axis: new THREE.Vector3(0, 1, 0),  // Spin around Y
  strength: 2.0,      // Spin force
  pullStrength: 0.5,  // Pull toward center
  radius: 8.0         // Effect radius
});

// Move vortex at runtime
vortex.setCenter(new THREE.Vector3(5, 0, 0));
vortex.setStrength(3.0);

particles.addProvider(vortex);

VortexConfig:

  • center?: Vector3 - Vortex center position
  • axis?: Vector3 - Rotation axis (normalized)
  • strength?: number - Spinning force
  • pullStrength?: number - Inward pull force
  • radius?: number - Effect radius (falloff)

MouseInteractionProvider

Enables mouse/touch interaction to push or pull particles.

import { MouseInteractionProvider } from '@interverse/three-particles';

const mouse = new MouseInteractionProvider({
  strength: 5.0,
  radius: 3.0,
  push: true  // false = pull particles
});

particles.addProvider(mouse);

// Update mouse position in animation loop
mouse.setMousePosition(mouseWorldPos);

WindProvider

Applies directional wind forces with gusts and turbulence for natural outdoor effects.

import { WindProvider } from '@interverse/three-particles';

const wind = new WindProvider({
  direction: new THREE.Vector3(1, 0, 0.2),  // Wind direction
  strength: 2.0,         // Base force
  gustStrength: 1.5,     // Extra burst force
  gustFrequency: 0.3,    // Gusts per second
  turbulence: 0.3,       // Random variation
  heightFactor: 0.1      // Stronger wind at higher Y
});

// Change wind direction at runtime
wind.setDirection(new THREE.Vector3(-1, 0.2, 0));
wind.setStrength(4.0);

particles.addProvider(wind);

WindConfig:

  • direction?: Vector3 - Wind direction (normalized)
  • strength?: number - Base wind force
  • gustStrength?: number - Additional burst force
  • gustFrequency?: number - Gust frequency (per second)
  • turbulence?: number - Random position-based variation (0-1)
  • heightFactor?: number - Wind strength increase per unit Y

PathProvider

Guides particles along a predefined path with attraction and alignment forces.

import { PathProvider } from '@interverse/three-particles';

const path = new PathProvider({
  pathPoints: [
    new THREE.Vector3(-5, 0, 0),
    new THREE.Vector3(0, 4, 2),
    new THREE.Vector3(5, 1, -1),
    new THREE.Vector3(8, 0, 0)
  ],
  attraction: 2.0,   // Pull toward path
  alignment: 1.5,    // Push along path direction
  spread: 1.0,       // Max distance from path
  speed: 1.0
});

// Update path at runtime
path.setPath([...newPoints]);

particles.addProvider(path);

PathConfig:

  • pathPoints?: Vector3[] - Path control points (min 2)
  • attraction?: number - Force pulling toward path
  • alignment?: number - Force pushing along path
  • spread?: number - Allowed deviation distance
  • speed?: number - Movement speed along path
  • loop?: boolean - Loop back to start

Creating Custom Providers

Extend BaseProvider to create custom force behaviors:

import { BaseProvider, ProviderContext } from '@interverse/three-particles';
import { vec3, Fn } from 'three/tsl';

class WindProvider extends BaseProvider {
  name = 'WindProvider';
  priority = 30;

  getForceNode(ctx: ProviderContext) {
    return Fn(() => {
      // Simple wind force
      return vec3(1.0, 0, 0.5).mul(2.0);
    })();
  }
}

API Reference

GPUParticleSystemConfig

interface GPUParticleSystemConfig {
  // Core
  maxParticles?: number;      // Default: 100000
  emissionRate?: number;      // Particles per second
  lifetime?: number;          // Particle lifetime in seconds
  loop?: boolean;             // Continuous emission
  
  // Geometry
  particleGeometry?: THREE.BufferGeometry;  // Custom geometry
  billboard?: boolean;        // Face camera (default: true)
  
  // Emitter Shape
  emitterShape?: 'point' | 'box' | 'sphere' | 'mesh' | 'line';
  emitterSize?: THREE.Vector3;
  emitterMesh?: THREE.Mesh;   // Emit from surface of mesh
  
  // Visual
  texture?: THREE.Texture;
  textureSheet?: TextureSheetConfig; // Sprite sheet config
  colorStart?: THREE.Color;
  colorEnd?: THREE.Color;
  sizeStart?: number;
  sizeEnd?: number;
  opacityStart?: number;
  opacityEnd?: number;
  
  // Curves
  sizeCurve?: LifetimeCurve | CurvePreset;
  opacityCurve?: LifetimeCurve | CurvePreset;
  colorGradient?: GradientCurve;
  
  // Physics
  velocity?: THREE.Vector3;
  velocityVariation?: THREE.Vector3;
  gravity?: THREE.Vector3;
  drag?: number;
  turbulence?: number;
  vectorField?: THREE.Data3DTexture;
  
  // Trails
  trail?: TrailConfig;
  
  // Collisions
  depthCollisions?: boolean;
  bounciness?: number;
  floorY?: number;
  
  // Quality
  sorted?: boolean;           // Back-to-front sorting
  softParticles?: boolean;    // Depth-aware fading
  softness?: number;          // Soft edge distance
  frustumCulled?: boolean;    // GPU frustum culling
  
  // LOD
  lod?: LODConfig[];
}

Performance Tips

  1. Set maxParticles appropriately - Allocates GPU memory upfront.
  2. Use sorted: false unless alpha blending requires strict ordering.
  3. Control Trail Segments: Higher segments = more memory and vertices. default(8) is usually good.
  4. Disable softParticles if not needed, as it adds a depth read overhead.
  5. Pool particle systems - Reuse systems for ephemeral effects instead of creating/destroying.

Browser Support

| Browser | Status | |---------|--------| | Chrome 113+ | ✅ Full support | | Edge 113+ | ✅ Full support | | Firefox Nightly | ⚠️ Experimental (enable dom.webgpu.enabled) | | Safari 18+ | ⚠️ Experimental |

License

MIT © Interverse Engine