Skip to content
glyphcss

Glyph Effects

Loading…

Matrix rain replaces the cube’s surface with animated text. Drag to orbit. Install @glyphcss/effects alongside glyphcss or your framework binding; effects change appearance without changing the mesh.

import { createGlyphScene, createGlyphPerspectiveCamera } from "glyphcss";
import { cubePolygons } from "@glyphcss/core";
import { GlyphEffects } from "@glyphcss/effects";
const host = document.querySelector<HTMLElement>("#scene")!;
const scene = createGlyphScene(host, {
camera: createGlyphPerspectiveCamera({ zoom: 80 }),
mode: "solid",
autoSize: true,
});
scene.add(cubePolygons({ center: [0, 0, 0], size: 1 }));
const rain = scene.addEffectLayer({
effect: GlyphEffects.matrixRain,
blend: "replace",
params: { glyphs: "HOLA", space: "auto", colorMode: "monochrome" },
});
let raf = 0;
function frame(now: number) {
rain.params.time = now / 1000;
raf = requestAnimationFrame(frame);
}
raf = requestAnimationFrame(frame);
// Call when the host unmounts:
function dispose() {
cancelAnimationFrame(raf);
rain.dispose();
scene.destroy();
}

Effects own no clock: update params.time in seconds. Use layer.setParams({...}) for batches. Animation libraries can target the stable layer.params object directly.

const cube = scene.add(cubePolygons({ center: [0, 0, 0], size: 1 }));
const layer = scene.addEffectLayer({
effect: GlyphEffects.scan,
target: cube,
blend: "over",
});
OptionBehavior
blend: "replace"Empty effect cells hide the base surface
blend: "over"Paint active cells over the base texture
target: "surfaces"Visible geometry; default target
target: "viewport"Includes the base output’s background
target: mesh or [meshA, meshB]Only those meshes; solid mode only

Mesh targets are fixed at mount; dispose and remount to retarget. Removed meshes stop matching. Layers compose in order; later scene-wide layers can fill earlier carve holes.

spaceMapping
"auto"Authored UVs, otherwise generated surface mapping, otherwise scene coordinates
"surface"Generated per-face mapping; ignores authored UVs
"scene"Projected 2D coordinates
"object"Mesh-local 3D field; needs solid mode, otherwise falls back to surface/scene

matrixRain defaults to "object"; flowText, scan, and fieldSynth default to "auto". scale changes pattern frequency, not grid density; retune it when switching to object space. Generated flow follows downhill per face; flat caps get a deterministic direction.

Text effects require printable single-cell glyphs. Combining marks, emoji, and double-width characters are rejected.

import { getGlyphEffect, defaultGlyphEffectParams, GlyphEffectCatalog } from "@glyphcss/effects";
const scan = getGlyphEffect("scan")!;
const defaults = defaultGlyphEffectParams(scan);
const schema = scan.parameterSchema;

Every stock effect takes time. Inspect parameterSchema for ranges and defaults, or iterate GlyphEffectCatalog. direction accepts up, down, left, or right.

EffectMain parameters and defaults
matrixRainglyphs: "HOLA", direction: "down", speedMin: 5, speedMax: 12, trail: 14, density: 0.55, seed: 1, colorMode: "original", color: "#00ff66", headColor: "#d8ffe4"
flowTextglyphs: "HOLA", direction: "right", speed: 6 cells/s
scanspeed: 10 cells/s, width: 3, spacing: 28, color: "#ffffff"
wipeprogress: 0.5, softness: 0.04, direction, invert: false
scrambleglyphs: "@#$%&*+=?", amount: 0.35, rate: 10 Hz, seed: 1
glitchglyphs: "#%/=+!?", amount: 0.28, rate: 12 Hz, bandSize: 4, seed: 1, color: "#ff4fd8"
noiseDissolveprogress: 0.5, softness: 0.08, scale: 0.22, seed: 1
rippleglyphs: "*+", speed: 6, frequency: 0.5, width: 0.18, amount: 0.85, color: "#72d9ff"

Rain’s colorMode: "monochrome" uses color and a bright headColor; "original" preserves model colors. wipe and noiseDissolve are progress-driven masks: use blend: "replace" so masked cells disappear.

Loading…
const synth = scene.addEffectLayer({
effect: GlyphEffects.fieldSynth,
blend: "replace",
params: {
field1: "radial", wave1: "sin", freq1: 4, speed1: 0.6, amp1: 1,
field2: "angular", wave2: "saw", freq2: 6, speed2: 0.3, amp2: 1,
combine: "multiply", color: "#ffcf5a", colorB: "#ff4fa3", gradient: 0.6,
},
});

Drive synth.params.time with the same animation loop. Each voice samples a spatial field through a waveform. Try GlyphEffects.fieldSynth.presets ({ name, params } pairs) or design a patch in the synth workbench.

ControlsMeaning
field1..9radial, angular, spiral, linearX, linearY, diagonal, noise; linearZ requires object space
wave1..9sin, triangle, saw, square, step; step is non-periodic
freqN, speedN, ampNCycles/unit, cycles/second, mix weight; ampN: 0 skips a voice
dutyN, phaseNSquare-wave high fraction (0.5), phase in cycles (0)
scale, originU, originVGlobal frequency (2) and center (0.5, 0.5)
angleN, originUN, originVN, originWNPer-voice rotation in degrees and origin offsets; default 0
combineadd, multiply, max, min, difference, argmax
gain, biasContrast and brightness: clamp01(bias + gain * combined * 0.5)
glyphsDark-to-dense ramp, default " .:-=+*#%@"
color, colorB, gradientTwo-color gradient; gradient: 0 uses only color
voiceColors, colorNPer-voice colors instead of the gradient; default off
litSurface lighting amount: 1 by default, 0 unlit

argmax selects the winning voice; amplitudes become comparison weights. Linear fields ignore origins: shift them with phaseN. Object-space radial fields are spherical; angular/spiral remain in XY. noise evolves over time. Object-space scan/flow text use triplanar mapping.

synth.setParams({ layer1: 1, layer2: 2, layerCombine2: "max", layerBlend2: "multiply" });

Assign voices to layer1..9 (default 1), choosing layers 1..3. Each layer has layerCombineL, layerThresholdOnL/layerThresholdL (off/0, range -3..3), layerInvertL, layerBlendL (multiply), and layerAmpL (1). Populated layers cannot use argmax in a multi-layer patch.

synth.setParams({
space: "object", render: "carve",
field1: "menger", wave1: "step", freq1: 3, amp1: 1, iter1: 3,
amp2: 0, combine: "min",
});
OptionBehavior / limitation
gyroid, menger, sierpinskiDistance fields; iterN: 1..4 (3) controls recursive detail; use waveN: "step" for solid membership
render: "paint"Default surface texture
render: "carve"Interior walls/holes; requires object space and preferably full-opacity replace
render: "xray"Integrated density; requires object space and subcellRes: "1x1"; xrayGain: 4 (0..16) controls contrast
marchSteps, marchFadeSampling budget (48, maximum 256), carve depth fade (1)
subcellRes: "1x1"Default ramp glyphs
subcellRes: "2x4"Braille dots; ignores ramp; surface mapping can shear
subcellRes: "ink"ASCII contours; ignores ramp; inkLevels: 4 (1..12) in paint, inkSpacing: 0.25 in carve

Carve contours/dots respect mesh boundaries. Xray ignores voiceColors; thresholded fields show structure more clearly. Nonzero speedN animates volumetrics; reset/wrap time for looping step-wave erosion.

import { buildGlyphFieldProgram } from "@glyphcss/effects";
const program = buildGlyphFieldProgram({
domain: "3d",
layers: [{ voices: [{ field: "menger", wave: "step", freq: 3, iter: 3 }], combine: "min" }],
});
scene.addEffectLayer({
effect: GlyphEffects.fieldSynth,
program,
blend: "replace",
params: { space: "object", render: "carve" },
});

Programs remove the nine-voice cap. program overrides voice/layer params; mapping, rendering, and output still use params. colorProgram independently supplies color voices. Both are immutable after mount, supported in React/Vue/HTML, and unavailable in workbench URLs or static exports.

Use validateGlyphFieldProgram to validate authored data before calling evaluateGlyphFieldProgram yourself.

import { GlyphRamps, calibrateGlyphRamp } from "@glyphcss/effects";
const { ramp } = calibrateGlyphRamp({ font: { family: "Menlo, monospace", size: 32 }, steps: 12 });
synth.setParams({ glyphs: ramp });
// Or use GlyphRamps.Blocks directly.

Named ramps: Fade, Blocks, Shades, Dots, Binary, ASCII, Hatch, Stars, Digital. Calibration measures your font; measureGlyphInkCoverage(glyph, { font }) measures one glyph. Both need browser Canvas 2D, or a supplied canvasFactory outside the DOM. Results are plain strings usable in effects or static compilation.

import { isGlyphFieldSynthStaticExportSupported } from "@glyphcss/effects";
const supported = isGlyphFieldSynthStaticExportSupported(synth.params);

Only field synth supports effect-only static export through buildGlyphFieldSynthStaticExport. Object-space, carve/xray, and program-authored patches require the interactive export. Check support rather than duplicating restrictions; other static compile paths do not evaluate effects.

Scene-image sampling/displacement, scratch graphs, and surface-key/UV-footprint fields are unsupported and reject explicitly.