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

@kusuromi/ascii-shader-renderer

v0.3.0

Published

Two-pass WebGL renderer for the ASCII shader.

Readme

@kusuromi/ascii-shader-renderer

The single WebGL renderer for the ASCII shader. Computes everything on the GPU in two passes, one pixel per grid cell.

How it renders

  1. Field pass — a fragment shader computes the per-cell luminance field: a float32 value-noise through hash21 → noise2 → 4-octave fbm, blended with two travelling waves and a sweep term, plus an optional pointer glow; final pow(clamp(lum * lightness − 0.09, 0, 1), contrast) is written to an RGBA8 texture (NEAREST). This shader is the single source of truth for the field formula.
  2. Composite pass — a fullscreen quad samples the field, picks a glyph from a 16-column atlas (floor(min(lum * glyphCount, …))), shades it (188–246 of 255), and modulates alpha by glyph coverage (quantized to 0.01 steps).

The glyph atlas is rasterized once per settings change with a Canvas 2D context (the only 2D usage — text cannot be rasterized on the GPU), with vertical ink-centering baked in.

Usage

import { createAsciiShaderRenderer } from "@kusuromi/ascii-shader-renderer";

const gl = canvas.getContext("webgl");
if (gl) {
  const renderer = createAsciiShaderRenderer(gl);

  function frame(time: number) {
    renderer.render({
      width: canvas.clientWidth,
      height: canvas.clientHeight,
      dpr: Math.min(window.devicePixelRatio || 1, 2),
      time,
      settings,
      pointer: { currentX: 0.5, currentY: 0.5 },
    });
    requestAnimationFrame(frame);
  }
  requestAnimationFrame(frame);
}

The renderer pairs well with the animation loop, pointer tracking, and resize handling from @kusuromi/ascii-shader-core's createAsciiEngineHost; for React, use @kusuromi/ascii-shader-react (which depends on @kusuromi/ascii-shader-renderer and @kusuromi/ascii-shader-core).

Settings and pointer

render accepts the same AsciiSettings shape as the rest of the monorepo (see @kusuromi/ascii-shader-core) plus a normalized pointer position in 0..1 space.

Texture scale

The field shader converts cell positions to canvas pixels (worldPx = gl_FragCoord.xy * uCellSize) and calculates uv as worldPx / WORLD_SCALE. WORLD_SCALE is defined in the shader as:

#define WORLD_SCALE 1600.0

It means one texture-space unit represents 1600 canvas pixels. The base periods of the two travelling waves are approximately 1600 / (frequency × 2.8) for waveA and 1600 / (frequency × 5.2) for waveB. At frequency = 4.5, these are about 127 px and 68 px respectively; the FBM octaves add finer detail. Because these distances are in pixels, the details keep the same size on any screen. Resizing the window changes only how many cells and how much of the field are visible; the texture scale and feature proportions do not change.

WORLD_SCALE is a shader constant, not a uniform: it is an intrinsic property of this shader and is not a consumer-configurable setting. The value 1600 was chosen as a fixed design reference because it gives the documented ~127 px and ~68 px base periods at the maximum frequency of 4.5. Those details stay at a useful, consistent pixel size instead of being rescaled to fit each viewport.

Version 0.1.0 settings warning: If you tuned settings against 0.1.0, expect the rendered image to change after upgrading. Previously, uv was normalized to 0…1 across the entire viewport at every screen size, so the texture stretched to fill the canvas. Existing frequency values therefore produce a different texture scale now, and both frequency and lightness may need to be retuned for the desired appearance.

Lifecycle

  • debug() — reads back the luminance field texture ({ columns, rows, sample: number[] }).
  • dispose() — releases all GL objects.
  • On webglcontextlost, dispose the renderer; on webglcontextrestored, create a new one — all GL objects are invalidated by the loss.