JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 28
  • Score
    100M100P100Q95293F
  • License MIT

High-performance WebGPU particle engine with compute shaders, GPU instancing, GLB model support, and skeletal animations

Package Exports

  • hz-particles
  • hz-particles/r3f

Readme

hz-particles

npm version license WebGPU

A high-performance WebGPU particle engine for the web. Use it as a standalone WebGPU renderer or as a plug-and-play React Three Fiber component.

Features

  • WebGPU Compute Shaders — GPU-accelerated particle physics simulation
  • GPU Instancing — Efficient rendering of thousands of particles
  • GLB Model Support — Use 3D models as particle shapes with automatic texture extraction
  • Skeletal Animations — Animated GLB models with full animation control
  • Scene Serialization — Save/load particle configurations as JSON
  • Multiple Emitter Shapes — Sphere, cube, cylinder, circle, square emission patterns
  • Real-time Physics — Gravity, attractors, drag, velocity, and lifetime management
  • React Three Fiber Component — Drop-in HZParticlesFX component with preset support
  • 3D Object Support — Static 3D objects alongside particle systems

Requirements

WebGPU-compatible browser required:

  • Chrome/Edge 113+ (stable)
  • Firefox Nightly (experimental)
  • Safari Technology Preview (experimental)

Check browser support: caniuse.com/webgpu

Installation

WebGPU

npm install hz-particles
import { initWebGPU, ParticleSystemManager } from 'hz-particles';

React Three Fiber

npm install hz-particles
import { HZParticlesFX } from 'hz-particles/r3f';

Important: Your R3F Canvas must use WebGPURenderer. Pass gl={{ powerPreference: 'high-performance' }} and configure Three.js to use its WebGPU backend, or use @react-three/fiber with a WebGPU-capable renderer setup.

Quick Start

WebGPU

import { initWebGPU, ParticleSystemManager } from 'hz-particles';

// 1. Setup WebGPU context
const canvas = document.getElementById('webgpu-canvas');
canvas.width = 800;
canvas.height = 600;
const { device, context, format } = await initWebGPU(canvas);

// 2. Create particle system manager
const manager = new ParticleSystemManager(device);

// 3. Create a particle system
const systemId = manager.createParticleSystem({
  maxParticles: 10000,
  particleCount: 1000,
});

// 4. Initialize compute pipeline
const system = manager.getActiveSystem();
await system.initComputePipeline(device);

// 5. Render loop
let lastTime = performance.now();

function render() {
  const currentTime = performance.now();
  const deltaTime = (currentTime - lastTime) / 1000;
  lastTime = currentTime;

  manager.updateAllSystems(deltaTime);

  const commandEncoder = device.createCommandEncoder();
  const renderPass = commandEncoder.beginRenderPass({
    colorAttachments: [{
      view: context.getCurrentTexture().createView(),
      clearValue: { r: 0, g: 0, b: 0, a: 1 },
      loadOp: 'clear',
      storeOp: 'store',
    }],
  });

  system.render(renderPass);
  renderPass.end();
  device.queue.submit([commandEncoder.finish()]);

  requestAnimationFrame(render);
}

render();

React Three Fiber

Minimal example with a preset file:

import { HZParticlesFX } from 'hz-particles/r3f';
import explosionPreset from './presets/explosion.json';

function Scene() {
  return (
    <HZParticlesFX
      preset={explosionPreset}
      position={[0, 1, 0]}
    />
  );
}

Complete example with all key props:

import { useRef, useState } from 'react';
import { HZParticlesFX } from 'hz-particles/r3f';
import explosionPreset from './presets/explosion.json';

function Scene() {
  const targetRef = useRef();
  const [resetKey, setResetKey] = useState(0);

  return (
    <>
      <mesh ref={targetRef} position={[0, 1, 0]}>
        <boxGeometry />
        <meshStandardMaterial />
      </mesh>

      <HZParticlesFX
        preset={explosionPreset}
        positionRef={targetRef}
        scale={1.5}
        resetKey={resetKey}
        onComplete={() => console.log('effect finished')}
      />

      <button onClick={() => setResetKey(k => k + 1)}>
        Replay
      </button>
    </>
  );
}

Props Reference

The HZParticlesFX component accepts the following props:

Prop Type Default Description
preset SceneData | string Particle preset — pass a JSON object or a URL/path to a JSON file
position [number, number, number] [0, 0, 0] Static world-space position of the effect
positionRef React.RefObject<Object3D> Attach the effect to a scene object; position tracks the ref each frame
autoPlay boolean true Start playing immediately when mounted
visible boolean true Show or hide the effect without unmounting
scale number 1 Uniform scale applied to the entire effect
resetKey number 0 Increment to reset and respawn the particle system
onComplete () => void Callback fired when the effect finishes playing

Preset Configuration

Presets are plain JSON files that describe a particle scene. Key fields:

Field Type Description
particleCount number Number of active particles
lifetime number Particle lifetime in seconds
emissionRate number Particles emitted per second
emissionShape string Emitter shape: sphere, cube, cylinder, circle, square
particleSize number Base size of each particle
colors string[] Array of hex color values interpolated over lifetime
fadeIn number Opacity fade-in duration (0–1, fraction of lifetime)
fadeOut number Opacity fade-out start (0–1, fraction of lifetime)
bloom boolean Enable bloom glow on particles
gravity number Gravity strength applied to particles
damping number Velocity damping factor (0 = no drag, 1 = full stop)
emissionTrailEnabled boolean Enable particle trail rendering
emissionTrailDuration number How long trail segments persist in seconds
emissionTrailWidth number Width of the emission trail

API Reference

initWebGPU(canvas)

Convenience helper to initialize a WebGPU context and device.

async function initWebGPU(canvas: HTMLCanvasElement): Promise<{
  device: GPUDevice,
  context: GPUCanvasContext,
  format: GPUTextureFormat,
  canvas: HTMLCanvasElement
}>

Parameters:

  • canvas (required) — Canvas element. Set canvas.width and canvas.height before calling.

Returns: Object with WebGPU device, canvas context, texture format, and canvas element.

Throws: Error if canvas is missing or WebGPU is not supported.


ParticleSystem

Core particle system class managing simulation and rendering.

class ParticleSystem {
  constructor(device: GPUDevice, config?: object)
}

Config options:

  • maxParticles (number, default 10000) — Maximum particle buffer size
  • particleCount (number, default 100) — Initial active particle count

Key Methods:

Method Description
initComputePipeline(device) Initialize GPU compute and render pipelines. Must be called before rendering.
setTexture(imageBitmap) Set particle texture from an ImageBitmap.
setGLBModel(arrayBuffer) Use GLB model geometry as particle shape. Automatically extracts textures.
updateParticles(deltaTime) Execute a physics simulation step on the GPU.
spawnParticles() Emit new particles according to emitter configuration.
setGravity(value) Set gravity strength (default 9.8).
setAttractor(strength, position) Set an attractor point with strength and 3D position [x, y, z].
render(renderPass) Render particles to the current render pass.

ParticleSystemManager

Manages multiple particle systems within a single scene.

class ParticleSystemManager {
  constructor(device: GPUDevice)
}

Key Methods:

Method Description
createParticleSystem(config?) Create a new particle system. Returns system ID.
getActiveSystem() Get the currently active ParticleSystem instance.
getActiveConfig() Get the active system configuration object.
setActiveSystem(index) Switch active system by index. Returns success status.
removeSystem(index) Remove a system by index. Returns success status.
updateAllSystems(deltaTime) Update physics for all systems.
getSystemsList() Get list of all systems with name, id, index, isActive.
duplicateActiveSystem() Clone the active system. Returns new system ID.
replaceSystems(sceneData) Load a scene from serialized data. Returns success status.

parseGLB(arrayBuffer)

Parse GLB binary format and extract geometry data.

async function parseGLB(arrayBuffer: ArrayBuffer): Promise<{
  positions: Float32Array,
  normals: Float32Array,
  indices: Uint16Array | Uint32Array,
  texCoords: Float32Array | null,
  vertexCount: number,
  indexCount: number,
  animationData: object | null,
  hasBaseColorTexture: boolean
}>

GLBAnimator

Handles skeletal animation playback for animated GLB models.

class GLBAnimator {
  constructor(animationData: object)

  currentTime: number
  playing: boolean
  speed: number   // default 1.0
  loop: boolean   // default true
}

Key Methods:

Method Description
setRestPose(positions, normals) Set the T-pose/bind pose for animation.
update(deltaTime) Advance animation. Returns { positions, normals, changed }.
setAnimation(index) Switch to animation clip by index.
getAnimationNames() Get list of available animation clip names.

GLB Models & Animation

import { parseGLB, GLBAnimator } from 'hz-particles';

// Load GLB file
const response = await fetch('model.glb');
const arrayBuffer = await response.arrayBuffer();
const glbData = await parseGLB(arrayBuffer);

// Use as particle shape
await system.setGLBModel(arrayBuffer);

// Setup animation (if model has animations)
if (glbData.animationData) {
  const animator = new GLBAnimator(glbData.animationData);
  animator.setRestPose(glbData.positions, glbData.normals);
  animator.playing = true;
  animator.loop = true;
  animator.speed = 1.0;

  // In render loop:
  const { positions, normals, changed } = animator.update(deltaTime);
  if (changed) {
    // Update particle system with new geometry
    // (See full API documentation for geometry update methods)
  }
}

Scene Save/Load

import { saveScene, loadScene } from 'hz-particles';

// Save current scene
document.getElementById('save-button').addEventListener('click', () => {
  saveScene(manager);  // Downloads scene.json
});

// Load scene from file input
document.getElementById('file-input').addEventListener('change', async (event) => {
  const success = await loadScene(event, manager);
  if (success) {
    console.log('Scene loaded successfully');
  }
});

TypeScript

Type declarations are not yet included in this package. For TypeScript projects, suppress the import error with:

// @ts-ignore
import { ParticleSystem, initWebGPU } from 'hz-particles';
// @ts-ignore
import { HZParticlesFX } from 'hz-particles/r3f';

Community-contributed type definitions are welcome via PR.

Secondary Exports

The following modules are also exported for advanced usage:

  • ParticleEmitter — Emission shape configuration (sphere, cube, cylinder, circle, square)
  • ParticlePhysics — Physics simulation parameter management
  • ParticleTextureManager — Texture loading utilities
  • Objects3DManager — 3D object scene management
  • saveScene(manager) — Export scene as JSON file download
  • loadScene(event) — Load scene from file input event
  • extractGLBTexture(arrayBuffer) — Extract base color texture from GLB file
  • Shader exports — WGSL shader code for compute and rendering pipelines
  • Geometry helpers — Primitive shape generators (cube, sphere, etc.)
  • Render pipeline creators — Low-level WebGPU pipeline construction

Online Editor

Try the interactive particle editor at http://localhost:8110/editor when running the Docker container (docker-compose up from the repository root).

The editor provides a visual interface for:

  • Real-time particle system configuration
  • Emitter shape selection and tuning
  • GLB model import and animation control
  • Scene preset library
  • Export/import scene JSON

Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT License - see LICENSE file for details.

Copyright (c) 2025 HZ