paper-shaders
Turns Paper Shaders (30 WebGL shaders: mesh and grain gradients, god rays, liquid metal, smoke ring, dithering, halftone, paper texture and more) into frame-exact backgrounds, textures and logo stings
SKILL.mdpaper-shaders/SKILL.md
GitHubPaper Shaders for video
Paper Shaders draws designer-tuned WebGL shaders into a canvas. Left alone it animates from the wall clock, which flickers and drifts in a video render. This skill mounts every shader at speed 0 and draws each frame from the timeline, so the same frame always gives the same picture.
Gates
- Pin
@paper-design/shaders@0.0.82. Use that exact version everywhere. Never use 0.0.47–0.0.76: they are PolyForm Shield, not open source. Bumping the version followsreferences/licensing.md. - Speed 0, time from the timeline. Mount shaders only through
templates/paper-preset.js. Never setspeedabove 0, and never let scene code readDate.now(),performance.now(),requestAnimationFrameor unseededMath.random(). - No credits here. Shader layers render locally for free. If the video also needs generated footage, voice or music, that is the varg-ai skill: call
estimate_costand wait for the user's approval before any generation. - Rights. Logos and stills fed to image presets must be the user's own or licensed. Do not apply a shader to another company's logo.
- Stills, not footage. Paper's image filters take images only. Grade and add grain to footage with HyperFrames media treatments instead.
Workflow
A person approves at each checkpoint (looks, stills, motion) before the next step. Commands run from the skill folder; HF stands for the pinned CLI, npx --yes hyperframes@0.8.206.
- Pick the look. Match the request to a preset below or to a shader in
references/shaders.md. Say which one and why in one line; that is checkpoint 1. - Scaffold, or add to a project.
node scripts/new-project.mjs ./video # new HyperFrames project from the template node scripts/new-project.mjs ./existing --add # paper-preset.js and presets/ only - Brand it. Get the palette from the user or their brand kit. Never invent colors. Write the branded preset into the project, then import it in
index.html. Image presets take{ image: "assets/logo.png" }; serve images from the project.node scripts/apply-brand.mjs assets/presets/brand-mesh.json palette.json --out ./video/presets/brand-mesh.json node scripts/validate-presets.mjs ./video/presets/brand-mesh.json - Stills (checkpoint 2).
$HF snapshot ./video --at 0.5,2,4writes PNGs and a contact sheet. Show them; change one param at a time until approved. - Motion (checkpoint 3).
(cd ./video && $HF render --quality draft --output draft.mp4). Tunemotion.timeScalefor pace. - Final.
node scripts/check-determinism.mjs ./video # must print "deterministic" (cd ./video && $HF check && $HF render --output final.mp4) # or: npm run check, npm run render
A shader layer in HyperFrames
The host is a timed clip with its own composition id and no GSAP timeline. HyperFrames calls render(seconds) for every frame it captures, in any order.
<div id="bg" class="clip" data-composition-id="bg" data-start="0" data-duration="6"
data-track-index="0" data-no-timeline data-layout-allow-overflow></div>
<script type="module">
import * as PS from "https://cdn.jsdelivr.net/npm/@paper-design/shaders@0.0.82/dist/index.js";
import { paperFrameSource } from "./paper-preset.js";
import preset from "./presets/god-rays-reveal.json" with { type: "json" };
const ramp = (t) => 1 - Math.pow(1 - Math.min(t / 1.2, 1), 3);
window.__hyperframes.registerFrameSource(
paperFrameSource(PS, document.getElementById("bg"), preset, {
paramsAt: (t) => ({ intensity: 0.8 * ramp(t), bloom: 0.4 * ramp(t) }), // a reveal, as a pure function of time
}),
);
</script>
- Register at the top level of a module script, before any
await; HyperFrames boots onDOMContentLoaded. - Each shader scene needs its own host. Keep a composition under about 12 shader hosts; Chrome drops WebGL contexts past 16. For more looks in a row, use
paperSequenceSource: one host, one context, any number of presets (seeexamples/paper-gallery). - Text, logos and other DOM elements go on higher tracks and animate with the GSAP timeline as usual.
templates/hyperframes/shader-background.htmlis a complete composition to start from.
A shader layer in Remotion
npm i @paper-design/shaders@0.0.82
# copy templates/paper-preset.js, templates/paper-preset.d.ts, templates/remotion/ShaderBackground.tsx and the presets
<ShaderBackground preset={brandMesh}>
<h1>Headline</h1>
</ShaderBackground>
<ShaderBackground preset={liquidMetal} image={staticFile("logo.svg")} />
Render with npx remotion render <id> out.mp4 --gl=angle. Do not use Paper's React components for renders; see references/determinism.md.
Presets
Two sets ship. Nine varg presets in assets/presets/, in varg colors with brand roles. And all 124 of Paper's own presets in assets/presets/paper/ (paper-<shader>-<name>, for example paper-mesh-gradient-beach, paper-warp-passion), with the colors and speeds shown on shaders.paper.design. When the user wants "the look from Paper's site", start from those. All are validated against Paper's documented ranges; five of Paper's own go past the editor's slider ranges and stay as Paper ships them.
| Varg preset | Shader | Use |
|---|---|---|
brand-mesh |
MeshGradient | Background behind titles, product cards and lower thirds |
grain-blob |
GrainGradient | Soft grainy blob behind a quote, a product or a speaker |
retro-dither |
Dithering | Two-ink texture for interstitials and end cards |
warp-transition |
Warp | 0.5–1 s full-frame interstitial between scenes |
god-rays-reveal |
GodRays | Rays behind a logo as it appears (ramp with paramsAt) |
liquid-metal-sting |
LiquidMetal | 1–2 s chrome logo sting; needs a transparent logo |
smoke-ring-loader |
SmokeRing | Ring around a countdown, a loader or a mark |
halftone-overlay |
HalftoneDots | Print look for a still (needs the still) |
paper-finish |
PaperTexture | Paper grain and folds over a still or title card |
A preset is JSON: shader, motion (timeScale, frame), optional mount and image notes, roles for brand mapping, and every param. paper-preset.js refuses a preset that leaves a uniform unset rather than render a wrong frame.
Looks: 11 researched backgrounds
Eleven background looks researched from the people who build these shaders, each with 4-5 presets in exact Paper params (checked with validate-presets.mjs) or, where Paper has no shader for it, a custom GLSL skeleton with its numbers. Each look has a reference (how the pros do it, presets, timing, build steps, mistakes, sources) in references/looks/ and its presets in assets/looks/ (50 presets). The look files use their own schema: a preset's params.paper block is what goes into a varg preset JSON, with params.motion as its motion.
| Look | Shader | Presets | Files |
|---|---|---|---|
| Contour lines | custom-glsl | Signature, Survey, Night, Blueprint | contour-lines.md · json |
| Datamosh rain | custom-glsl | Signature, Datamosh, Phosphor, Ember, Ice | datamosh-rain.md · json |
| Dithered gradient | Dithering | Signature, Newsprint, Riso, Phosphor | dithered-gradient.md · json |
| God-ray light field | GodRays | Overhead, Off-center, Streaks, Photograph, Bright core | god-ray-light-field.md · json |
| Gradient flow | Warp | Signature, Chroma, Ember, Aurora, Mono | gradient-flow.md · json |
| Grain gradient | GrainGradient | Signature, Riso, Ember, Sage, Night | grain-gradient-shapes.md · json |
| Hand-drawn radial burst | custom-glsl | Signature, Print, Negative, Riso | hand-drawn-radial-burst.md · json |
| Liquid metal field | LiquidMetal | Signature, Chrome, Noir, Gold | liquid-metal-field.md · json |
| Marbled ink | Warp | Signature, Ink, Ember, Sage | marbled-ink.md · json |
| Mesh gradient with grain | MeshGradient | Signature, Dusk, Lagoon, Studio, Ink | mesh-gradient-grain.md · json |
| Paper texture | PaperTexture | Signature, Kraft, Newsprint, Poster, Print | paper-texture.md · json |
Rules for every look:
- Mount every shader at speed 0 and compute the frame from timeline seconds; custom shaders get
u_t = secondsonly. - Speed is shader-specific (internal clocks differ: Mesh 0.5x, GodRays 0.2x, Grain 0.1x, Warp 0.0625x). Behind text: Mesh 0.15–0.3, GodRays 0.25–0.6, Grain 0.3–0.5, Warp 0.8–1.5, LiquidMetal 0.4–0.7.
- Paper time is not periodic: loop with a 1 s crossfade into a copy offset by L × 1000 × timeScale ms.
- Grain and dither are what platform encoders destroy: render at delivery resolution, ≥3–4 px dither cells and ≥8 px blocks at 1080p, high bitrate.
- Never move or scale a dither grid; animate the source under a screen-fixed grid.
- Keep complementary hues apart in colour order (dark base between); 3–4 colours plus one dark anchor.
- Print looks: two inks, riso or warm CMYK hex (#FF48B0, #0078BF, #FFE800, #231F20 on #FBFAF4), 2–4 px misregistration with multiply.
- Hand-drawn or glitch feel comes from stepped time: boil at 8–12.5 fps, re-roll glitch blocks at 12.5 fps, keep the main motion smooth.
- Constant-width procedural lines need distance estimation (|f|/|∇f| via fwidth), not raw thresholds.
- Domain-warped fBM (gain 0.5, 5 octaves, 0.5 rad octave rotation, IQ offsets) is the base of marble, terrain and organic flow.
Built from: Inigo Quilez; Paper's Ksenia Kondrashova (@uuuuuulala) and Stephen Haney (@stephenhaney); Lucas Pope (@dukope); Surma (Ditherpunk); Patricio Gonzalez Vivo & Jen Lowe (The Book of Shaders); Mirza Beig (@TheMirzaBeig); Rune Skovbo Johansen (@runevision); Xor; Kenny Mitchell. Sources with links: references/looks/sources.md.
Read this when
| When | Read |
|---|---|
| Choosing a shader, writing a new preset, working with footage | references/shaders.md |
| Frames flicker, go black or differ between workers; offline, 4K or Docker renders; Remotion details | references/determinism.md |
| Applying a palette, text contrast over a shader | references/brand-mapping.md |
| A named look (liquid metal field, god rays, marbled ink, contour lines, datamosh rain …) or its expert numbers | references/looks/<look>.md and assets/looks/<look>.json |
| Bumping the Paper Shaders version, vendoring it, credits, client work | references/licensing.md |
Scripts
All run with node or bun, make no network calls of their own, and touch only the paths you give them.
| Script | Does |
|---|---|
new-project.mjs <dir> [--example preset-reel|paper-gallery] [--add] |
Scaffolds a HyperFrames project, or adds paper-preset.js and presets/ to one |
validate-presets.mjs [files] |
Checks presets against the documented params, ranges, enums and color limits |
apply-brand.mjs <preset> <palette> |
Maps a palette onto a preset through its roles |
check-determinism.mjs <project> [--at …] [--software] |
Snapshots forwards and backwards in two runs and compares pixels (uses npx hyperframes) |
build-manifest.mjs |
Rewrites assets/refs.manifest.json after a preset changes |
refresh-params.mjs <llms.txt> <version> |
Rebuilds assets/paper-shaders.params.json when the pinned version changes |
Output contract
- Project:
index.html,paper-preset.js,presets/*.json(the branded presets actually used),package.jsonandhyperframes.jsonwith the HyperFrames version pinned. - Stills: the snapshot folder with its
contact-sheet.jpg, shown at checkpoint 2. - Video:
renders/<name>.mp4or the--outputpath; 1920×1080 at 30 fps unless asked otherwise. - Report: the video path, duration and size; which presets ran with which changed params; and the
check-determinism.mjsresult.
Examples
examples/paper-gallery/ plays all 124 of Paper's presets, 2 s each, full frame, through one host (--example paper-gallery, about 4 minutes of video).
examples/preset-reel/ shows all nine varg presets, 2.5 s each, at 1920×1080 and 30 fps, with a frame counter that is itself a frame source. Posters and the logo are drawn in code, so it needs no outside media. A contact sheet is in assets/previews/preset-reel.webp.
node scripts/new-project.mjs ./preset-reel --example preset-reel
cd preset-reel && npm run render -- --output preset-reel.mp4 # pinned HyperFrames; about 22 s on an M1 Pro
hyperframes check passes; lint's structure warnings come from keeping the reel in one file to read.