Sign In

@nachi-vfx/post

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nachi-vfx/post

Composable TSL post effects for Three.js RenderPipeline

latest
Source
npmnpm
Version
0.2.3
Version published
Weekly downloads
20
100%
Maintainers
1
Weekly downloads
 
Created
Source

@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 three@0.185.1
# TypeScript projects also need Three's separately published declarations:
pnpm add -D @types/three@0.185.0

The supported peer is exactly three@0.185.1. 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.

Keywords

three.js

FAQs

Package last updated on 16 Jul 2026

Related posts