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

@nachi-vfx/post

v0.3.1

Published

Composable TSL post effects for Three.js RenderPipeline

Readme

@nachi-vfx/post

Composable screen-space effects for Three.js r185's TSL RenderPipeline. The package provides shockwave and heat-haze distortion, radial blur, bloom presets, and native-WebGPU weighted blended OIT without depending on @nachi-vfx/core.

pnpm add @nachi-vfx/post [email protected]
# TypeScript projects also need Three's separately published declarations:
pnpm add -D @types/[email protected]

The supported peer is exactly [email protected]. Post nodes are sensitive to Three's TSL API, so upgrade Three and this package together.

RenderPipeline integration

import { bloomPreset, createPostPipeline, radialBlur, screenDistortion } from '@nachi-vfx/post';

const post = createPostPipeline(renderer, scene, camera, {
  distortion: screenDistortion({
    shockwaves: [
      {
        center: [0.5, 0.5],
        radius: 0.05,
        ringWidth: 0.04,
        strength: 0.025,
        speed: 0.6,
        duration: 0.8,
      },
    ],
    heatHaze: [
      {
        center: [0.5, 0.35],
        size: [0.45, 0.3],
        strength: 0.008,
      },
    ],
  }),
  radialBlur: radialBlur({ center: [0.5, 0.5], strength: 0.12, samples: 8 }),
  bloom: bloomPreset('soft'),
});

function frame(localTime: number) {
  post.controls.setTime(localTime);
  post.render(); // use this instead of renderer.render(scene, camera)
}

Before starting the animation loop, await post.prepare({ signal, onProgress }) renders one frame to the canvas while the loading UI is still covering it. The final composite pipeline cache key depends on the live output format, sample count, and color space, so an arbitrary offscreen target cannot prepare it reliably. Pass outputTarget when the live loop presents to a custom target. Preparation restores the caller's RenderTarget/MRT and does not advance any effect clock.

The default order is distortion -> radialBlur -> bloom. Supply order, for example ['bloom', 'distortion', 'radialBlur'], to choose another permutation. Every configured pass must appear exactly once.

PostPipelineConfig is revalidated by the public constructor even when direct JavaScript data bypasses screenDistortion(), radialBlur(), or bloomPreset(). Pass tags/configs, numeric ranges, source shapes, samples, presets, order arrays, and outputColorTransform fail synchronously before Three creates a render graph or target.

soft, intense, and cinematic bloom presets wrap Three r185's BloomNode. Overrides can tune strength, radius, threshold, and internal resolutionScale.

Time and effect-driven distortion

Omitting screenDistortion.time creates a package-owned uniform writable through post.controls.setTime(). Supplying a number creates a fixed clock, and supplying a TSL node uses an externally owned clock; the setter rejects both explicit forms. This is the same standalone uniform/external-node split used by fxMaterial.

Every shockwave and heat-haze field also accepts a number/tuple or a TSL node. Numeric fields become package-owned uniforms and can be updated with setShockwave() or setHeatHaze(). A node remains externally owned. Nachi effects should pass their User.* uniform nodes for hit position, radius, width, strength, and enable state. The gameplay hit already knows those values, so this connection does not read particle storage back to the CPU. GPU particle readback is deliberately not a hidden fallback; high-density distortion particles will require a future dedicated distortion buffer.

All coordinates, sizes, radii, widths, strengths, and blur distances are normalized screen UV units. Shockwaves travel as radius + speed * (time - startTime) and fade over duration. Heat-haze regions are axis-aligned rectangles with a feathered edge and deterministic procedural value noise. Its smoothly interpolated lattice produces low-frequency wobble rather than pixel-to-pixel white noise. Distortion and multi-sample radial-blur UVs use an inset [0.001, 0.999] clamp.

Offscreen rendering

The pipeline renders into the renderer's currently selected target. Headless WebGPU must select an offscreen RenderTarget, call post.render(), and use readRenderTargetPixelsAsync(); do not present the WebGPU canvas. Use outputColorTransform: false when measuring linear pixel thresholds.

Weighted blended OIT

Use createWboitPipeline() with a transparent-only scene. Each participating NodeMaterial must assign createWboitOutput() to material.mrtNode:

const material = new THREE.NodeMaterial();
material.transparent = true;
material.depthWrite = false;
material.mrtNode = createWboitOutput(colorNode, alphaNode);

const oit = createWboitPipeline(renderer, transparentScene, camera, { width, height });
oit.render(); // composites over the renderer's current output target

The accum target is RGBA16F and revealage is R8. Native WebGPU attachment-specific blending is required; WebGL2 is rejected explicitly. WBOIT is order-independent but approximate, so particle bitonic sorting is normally disabled for objects routed through this pipeline. The current pipeline owns a separate depth attachment and cannot import the opaque target's depth, so opaque geometry does not occlude WBOIT fragments yet; keep effects that must disappear behind walls on a sorted alpha path until borrowed-depth ownership is implemented.