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

scenic-forge

v0.1.0

Published

Describe a background and a CSG object tree, get a progressively path-traced image on a WebGPU canvas.

Readme

scenic-forge

scenic-forge renders a scene description on a WebGPU canvas. Describe a background and a tree of solid shapes; the library compiles them into a WGSL compute shader and adds one path-traced sample per pixel per frame.

It is scenic-draft rebuilt on WebGPU. The scene specification is the same — the same builders, materials, patterns, bump fields, backgrounds, camera and fog, producing the same plain data — so a scene written for one renders in the other. What changes is the renderer underneath it, and the four places a spec can carry shader source of its own, which take WGSL here rather than GLSL.

  • Declarative — a scene is plain data: a tree of signed-distance primitives, transforms and boolean operations, plus an environment.
  • No dependencies, no assets — no meshes, no textures, no runtime besides the browser's WebGPU implementation.
  • Path-traced lighting — the background and emissive materials light the scene, producing soft shadows, ambient occlusion, colour bleed, refraction, subsurface scattering and depth of field.
  • One device, any number of canvases — every render on a page shares one GPUDevice, so there is no per-page context limit to ration.
  • Off the main thread where it counts — the shader compiles through createComputePipelineAsync, so a large scene does not freeze the page while its pipeline builds.

Install

npm install scenic-forge   # or: pnpm add scenic-forge / yarn add scenic-forge

The package ships ESM with TypeScript declarations. It needs a browser with WebGPU.

Usage

import {
  gradient,
  materials,
  paint,
  plane,
  render,
  smoothUnion,
  sphere,
  sun,
  torus,
  translate,
  union,
} from 'scenic-forge';

const handle = render(canvas, {
  background: gradient([0.02, 0.03, 0.05], [0.85, 0.9, 1.0], {
    sun: sun([-0.5, 0.8, -0.3], [6, 5, 4]),
  }),
  scene: union(
    plane([0, 1, 0], -1.2),
    smoothUnion(
      0.4,
      sphere(1.2),
      translate(paint(torus(1.6, 0.35), materials.copper), [0, 0.9, 0]),
    ),
  ),
});

// Setup is asynchronous in WebGPU: this rejects if there is no GPU, or if a
// hand-written WGSL function in the scene does not compile.
await handle.ready;

// When the canvas goes away (e.g. React effect cleanup):
handle.stop();

A scene spec has four fields: scene (the CSG tree), background (the environment, and the default light source), an optional camera and an optional fog. Everything else is a render option. The scene vocabulary — every primitive, transform, material field and background — is documented in scenic-draft's README, and applies here unchanged.

What differs from scenic-draft

Setup is asynchronous

render() returns its handle at once. It still throws synchronously for anything it can check without a GPU — a malformed spec, a maxFrames that is not a count — but getting a device and compiling the shader happen afterwards:

  • handle.ready resolves once the first sample is queued, and rejects if WebGPU is unavailable or the shader does not compile. A compile error carries the WGSL compiler's messages and the lines they refer to.
  • handle.done resolves with the final sample count when the render finishes, is stopped or fails. It never rejects.
  • handle.stop() is safe at any point, including before ready settles.

Hand-written functions are WGSL

A custom primitive, patterns.custom, bumps.custom and customBackground take WGSL:

custom({
  name: 'sdBoxFrame',
  code: `fn sdBoxFrame(p0: vec3f, b: vec3f, e: f32) -> f32 {
  let p = abs(p0) - b;
  let q = abs(p + e) - e;
  return min(min(
    length(max(vec3f(p.x, q.y, q.z), vec3f(0.0))) + min(max(p.x, max(q.y, q.z)), 0.0),
    length(max(vec3f(q.x, p.y, q.z), vec3f(0.0))) + min(max(q.x, max(p.y, q.z)), 0.0)),
    length(max(vec3f(q.x, q.y, p.z), vec3f(0.0))) + min(max(q.x, max(q.y, p.z)), 0.0));
}`,
  args: [[1, 0.6, 0.8], 0.05],
});

The signatures are the same shape as scenic-draft's — the point (or ray direction) first, then args — with the return type each one needs: f32 for a distance, a pattern or a height, vec3f for an environment. Three habits of WGSL are worth knowing when translating a GLSL function:

  • Parameters cannot be reassigned. Take the point under another name and declare the one you adjust, as p0 and p are above.
  • There is no implicit splat in builtins: max(v, 0.0) on a vector is max(v, vec3f(0.0)).
  • a < b followed later in the same argument list by a > is read as a template. Wrap comparisons in parentheses when passing them to a function.

Names are checked against WGSL's keywords and reserved words, against the builtins the compiler relies on (a module-scope length would shadow the builtin for the whole shader), and against everything the compiler emits.

Render options

The options are scenic-draft's, with two changes:

  • device — a GPUDevice to render with. Omitted, every render on the page shares one the library requests on first use, with the storage limits raised to whatever the adapter allows.
  • onDeviceLost(info) replaces onContextLost. There is no context limit to exhaust, so a device is lost only when the GPU process restarts or the device is destroyed.

stop({ releaseContext: true }) unconfigures the canvas's WebGPU context, blanking it.

Companion packages

  • scenic-forge-fluent — the same scenes written as a chain: sphere(1).translate([0, 1, 0]).paint(materials.gold). It includes and re-exports this package.
  • scenic-forge-extras — hexagonal and triangular prisms, chain links and quadrilateral faces written in WGSL against custom(), fifty-odd more material presets, and explain(spec), a report of what a scene will cost the shader.

Tips

  • Transmissive and translucent materials send rays inside solids, and each crossing spends a bounce: give them bounces: 10–12.
  • buildShader(spec) returns the WGSL a scene compiles to, which is the quickest way to see what a spec costs.
  • A render's accumulation buffer is 16 bytes a pixel — 16MB at the default 1024 × 1024, 132MB at 4K.

Licence

PolyForm Noncommercial 1.0.0.