Skip to content

Repository files navigation

nachi

Language: English (this page) / 日本語

Warning

HEAVY EXPERIMENTAL — v0.2.2 is not production-ready. APIs, behavior, performance, compatibility, and package boundaries may change significantly between releases.

Code-first, TSL/WebGPU-native VFX for Three.js, designed around Niagara's staged simulation model. Nachi v0.2.2 is an experimental preview; M12 includes JSON assets, advanced simulation, React Three Fiber bindings, release automation, and a buildable documentation gallery.

Packages

Package Purpose
@nachi-vfx/core Definitions, particle modules, compiler, GPU scheduler, scalability, sim cache, debugger, Grid2D/3D, and neighbor grids
@nachi-vfx/three Three.js WebGPU kernel/runtime adapter, resource resolvers, and particle draw materializers
@nachi-vfx/format Strict nachi-effect v1 JSON schema, serializer/loader, migrations, and asset inheritance
@nachi-vfx/react Thin R3F provider, hook, component lifecycle, and Object3D attachment
@nachi-vfx/timeline Effect-local sequencing, camera shake, hit stop, markers, and mesh-fx lifecycle
@nachi-vfx/trails GPU ribbons and trails
@nachi-vfx/mesh-fx Procedural effect geometry, fxMaterial, and Blender VAT playback
@nachi-vfx/post RenderPipeline distortion, radial blur, bloom presets, and WebGPU WBOIT
@nachi-vfx/tsl-kit Standalone Three.js TSL shader building blocks

The repository also contains the Vite playground and static documentation site.

Contributions

Bug reports, feature requests, and detailed use-case feedback are welcome through GitHub Issues. External pull requests are not accepted; accepted changes are implemented and reviewed by the maintainers, with opt-in co-author credit for substantive issue contributions. See CONTRIBUTING.md. Report vulnerabilities privately according to SECURITY.md.

Install

Core Three.js usage:

pnpm add @nachi-vfx/core @nachi-vfx/three three@0.185.1

React Three Fiber usage keeps React, R3F, and Three as peers:

pnpm add @nachi-vfx/core @nachi-vfx/three @nachi-vfx/react react@^19 @react-three/fiber@^9 three@0.185.1

Packages that expose Three.js types (@nachi-vfx/three, @nachi-vfx/tsl-kit, @nachi-vfx/mesh-fx, @nachi-vfx/trails, @nachi-vfx/timeline, @nachi-vfx/post, and @nachi-vfx/react) also expect the separately published matching declarations in TypeScript projects:

pnpm add -D @types/three@0.185.0

three@0.185.1 is the supported and tested runtime. Do not deduplicate these integrations onto a different Three minor version.

VFXSystemProvider synchronizes the active R3F camera and pixel viewport with core before each update by default. Pass syncCamera={false} only when the application calls system.setCamera() itself.

Quick start

import {
  VFXSystem,
  billboard,
  burst,
  defineEffect,
  defineEmitter,
  drag,
  gravity,
  lifetime,
  positionSphere,
} from '@nachi-vfx/core';
import {
  createThreeKernelAdapter,
  createThreeRuntimeRenderer,
  materializeThreeSpriteDraw,
} from '@nachi-vfx/three';
import * as THREE from 'three/webgpu';

const renderer = new THREE.WebGPURenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.append(renderer.domElement);
await renderer.init();

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 1, 5);

const kernelAdapter = createThreeKernelAdapter({ backend: 'webgpu' });
const runtimeRenderer = createThreeRuntimeRenderer(renderer, kernelAdapter);

const sparks = defineEmitter({
  capacity: 512,
  spawn: burst({ count: 120 }),
  init: [positionSphere({ radius: 0.2 }), lifetime(0.8)],
  update: [gravity(-9.8), drag(0.35)],
  render: billboard({ blending: 'additive' }),
});

const effect = defineEffect({ elements: { sparks } });
const system = new VFXSystem(runtimeRenderer, scene);
const instance = system.spawn(effect, { position: [0, 1, 0], seed: 42 });
const emitter = instance.getEmitter('sparks');
if (!emitter) throw new Error('The sparks emitter was not created.');

const draw = materializeThreeSpriteDraw(emitter.program, emitter.kernels);
scene.add(draw);

let previousTime = performance.now();
async function frame(time: number) {
  const deltaSeconds = Math.min((time - previousTime) / 1000, 0.1);
  previousTime = time;
  await system.update(deltaSeconds);
  renderer.render(scene, camera);
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

// When the effect is no longer needed:
// scene.remove(draw); instance.release(); draw.geometry.dispose(); draw.material.dispose();

In R3F, create the same adapter once and let the binding own instance cleanup. The complete materialization example is in @nachi-vfx/react.

useEffectInstance() is the hook form. Live parameters, transform, time scale, and attachment are forwarded to core; changing seed or priority creates a fresh instance. Keep definition at module scope (or otherwise referentially stable), because changing its reference respawns the instance. attachTo owns the complete v1 live position/rotation and overwrites spawn/prop values on each scheduled step. EffectWorldTransform has no scale; Three attachment world scale is not forwarded.

Assets and advanced simulation

import { loadEffect, serializeEffect } from '@nachi-vfx/format';

const document = serializeEffect(effect);
const loaded = loadEffect(JSON.stringify(document));

Only the declarative subset is serializable. Inline callbacks, functions, live Three.js resources, class instances, and cycles fail with path-specific NACHI_ASSET_* diagnostics. Grid2D/3D stages, neighbor-grid declarations, built-in fluid stages, boids, and PBD constraints are part of the v1 declarative model; inline custom grid/neighbor TSL remains code-only.

Simulation caches use bakeSimulation() and replaySimulation(). Runtime debugging uses instance.debug.captureAttributes() and system.debug.captureProfile(). Attribute capture keeps backend compaction order by default; use { order: 'physical-slot', offset, limit } for stable physical-identity pagination when comparing snapshots with the same slot membership. It removes compaction-order differences, not backend allocation differences; aliveIndex remains the original compact index. order defaults only when omitted or undefined; invalid values and out-of-capacity membership are structured diagnostics rather than rows containing fabricated zero attributes.

Three draw objects are lifetime-bound to their emitter kernels. Reuse an existing materialized mesh or dispose/unmaterialize it before replacement; releasing an instance cleans registered draws before its kernels enter the pool, and a respawn must attach the newly materialized draw.

Effect authoring guide and agent skill

A practical authoring guide lives in skills/nachi-effect-authoring/: the library usage catalog (library-usage.md) and scale-based skill-effect recipes (effect-recipes.md). It is packaged in the Agent Skills format, so coding agents (Claude Code, Codex, Cursor, GitHub Copilot, …) can install it into a consuming project with the skills CLI — no extra tooling beyond Node.js:

npx skills add reifier-k/nachi --skill nachi-effect-authoring

Use -a <agent> to target specific agents and -g for a user-wide install. The skills/*/SKILL.md layout is also compatible with gh skill install (GitHub CLI v2.90.0+).

Development and release checks

pnpm typecheck
pnpm lint
pnpm test
pnpm format:check
pnpm build
pnpm docs:build
pnpm esm-all
pnpm release:dry  # build + every package ESM import gate + publish-shaped pnpm pack checks
pnpm golden:regress  # with the playground dev server running; headless SwiftShader golden suite
node tools/bundle-size.mjs
node tools/license-report.mjs

Biome is the workspace linter and formatter for JavaScript, TypeScript, JSON, CSS, and HTML. Markdown and YAML are not automatically formatted.

Changesets are independently versioned: run pnpm changeset, then pnpm version-packages when a release versioning pass is intended. release:dry never publishes. The maintainer release procedure, including the one-time npm bootstrap and token-free trusted publishing migration, is documented in docs/releasing.md.

Design and status: PLAN.md, ROADMAP.md, normative API RFC, and the Effekseer compatibility study. Release compatibility is defined by RFC 003; FA evidence is summarized by the parity, bundle, and license reports.

About

Code-first, TSL/WebGPU-native VFX for Three.js, designed around Niagara-style staged simulation.

Topics

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages