skills/shaders/SKILL.md
This skill should be used when writing custom shaders for Three.js, creating visual effects with GLSL or TSL (Three Shader Language) for WebGL and WebGPU, debugging shader issues, building post-processing pipelines, implementing noise functions, procedural textures, or custom materials. Covers shader workflow, TSL node system, GLSL patterns, debugging, performance optimization, and post-processing with pmndrs/postprocessing.
npx skillsauth add b-open-io/prompts shadersInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Custom shaders for Three.js — from simple material overrides to full WebGPU node graphs. Covers the complete workflow: choosing an approach, writing code, debugging, and shipping.
| | TSL (Three Shader Language) | GLSL (ShaderMaterial) | |---|---|---| | WebGPU | Yes (compiles to WGSL automatically) | No | | WebGL fallback | Yes (automatic) | Yes | | Syntax | JavaScript / node graph | Raw GLSL strings | | Built-in uniforms | Auto-inferred | Manual declarations | | Best for | New projects, r163+, R3F | Legacy code, full manual control |
Default choice: TSL. Production-ready since r163. Use GLSL only when targeting legacy environments or when RawShaderMaterial manual control is specifically required.
Read references/tsl-guide.md for the full TSL API including all node types, built-ins, control flow, and material setup.
import { extend, useFrame } from '@react-three/fiber';
import * as THREE from 'three/webgpu';
import { color, float, time, mix, mx_noise_float, positionWorld, uniform } from 'three/tsl';
function NoiseSphere() {
const noiseUniform = uniform(0);
useFrame(({ clock }) => {
noiseUniform.value = clock.elapsedTime;
});
const noise = mx_noise_float(positionWorld.mul(1.5).add(time.mul(0.3)));
const dynamicColor = mix(color(0x1a0533), color(0x00eaff), noise);
return (
<mesh>
<sphereGeometry args={[1, 64, 64]} />
<meshStandardNodeMaterial
colorNode={dynamicColor}
roughnessNode={float(0.4)}
/>
</mesh>
);
}
Renderer requirement: WebGPURenderer from three/webgpu — it automatically falls back to WebGL2 when WebGPU is unavailable. Import materials from three/webgpu, nodes from three/tsl.
import { useRef, useMemo } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
const vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = `
uniform float uTime;
uniform vec3 uColor;
varying vec2 vUv;
void main() {
float n = sin(vUv.x * 10.0 + uTime) * 0.5 + 0.5;
gl_FragColor = vec4(uColor * n, 1.0);
}
`;
function ShaderMesh() {
const matRef = useRef();
const uniforms = useMemo(() => ({
uTime: { value: 0 },
uColor: { value: new THREE.Color(0x00aaff) }
}), []);
useFrame(({ clock }) => {
if (matRef.current) matRef.current.uniforms.uTime.value = clock.elapsedTime;
});
return (
<mesh>
<planeGeometry args={[2, 2]} />
<shaderMaterial ref={matRef} vertexShader={vertexShader} fragmentShader={fragmentShader} uniforms={uniforms} />
</mesh>
);
}
Memoize uniforms with useMemo — without it, a new object is created every render and the material recompiles. Update .uniforms.uTime.value inside useFrame, never the object reference.
Read references/glsl-patterns.md for complete recipes: noise, Fresnel, dissolve, hologram, water, matcap, toon shading, particles, and more.
Shader errors are silent: the mesh renders black, pink, or nothing. Debug systematically:
Output intermediate values as colors to inspect them directly:
// Is my UV correct?
gl_FragColor = vec4(vUv, 0.0, 1.0);
// Is my normal correct?
gl_FragColor = vec4(normalize(vNormal) * 0.5 + 0.5, 1.0);
// Is my noise in range?
gl_FragColor = vec4(vec3(noiseValue), 1.0);
In TSL, assign any node directly to colorNode:
material.colorNode = normalWorld.mul(0.5).add(0.5); // visualize normals
material.colorNode = positionLocal; // visualize position
| Symptom | Likely cause |
|---|---|
| Solid black | Missing lights, normals inverted, or NaN from division by zero |
| Pink / magenta | Texture not loaded, sampler2D binding missing |
| Flickering | Uniform not updated every frame, or uniformsNeedUpdate needed |
| No visible change | Wrong material reference, or effect not connected to colorNode |
| NaN propagation | Division by zero, sqrt of negative, atan of zero vector — check all math |
chrome://tracing): GPU timeline and overdraw analysisrenderer.info: draw calls, triangles, textures in flightApply these unconditionally on every shader:
if/else on GPU creates divergent execution across warps. Prefer step(), mix(), or select() (TSL).texture2D / texture() call is a memory fetch. Cache results in a variable; never sample the same texture twice.mediump where possible — in fragment shaders, declare precision mediump float; unless high precision is required. Saves bandwidth on mobile GPUs.depthWrite: true where possible.Install pmndrs/postprocessing:
bun add postprocessing # vanilla
bun add @react-three/postprocessing # R3F wrapper
Effect ordering (wrong order produces incorrect results):
1. SSAO → needs depth buffer, must come first
2. DepthOfField → needs depth, before color grading
3. Bloom → operates on HDR scene before tone mapping
4. ToneMapping → converts HDR → LDR; must follow Bloom
5. ChromaticAberration → full-screen warp, near end
6. Vignette → full-screen overlay
7. Noise → full-screen overlay, last
Performance cost at a glance:
| Effect | Cost | |---|---| | ChromaticAberration, Vignette, Noise, ToneMapping | Very Low | | Bloom (no mipmapBlur), SMAA, GodRays | Low–Medium | | SSAO, DepthOfField, Bloom (mipmapBlur) | Medium–High |
Mobile: Skip SSAO and DepthOfField entirely. Use Bloom without mipmapBlur. Keep Vignette + Noise for polish at near-zero cost.
One EffectPass merges multiple effects into one draw call — always prefer <EffectPass camera={...} effects={[bloom, smaa, toneMap]} /> over separate passes.
Read references/postprocessing.md for full R3F setup, all effect parameters, custom effect authoring, and the selective bloom pattern.
references/tsl-guide.md — Complete TSL API: node types, uniforms, built-ins, control flow, varyings, NodeMaterial setup, GLSL→TSL migrationreferences/glsl-patterns.md — Common GLSL shader recipes with complete vertex + fragment codereferences/postprocessing.md — Full pmndrs/postprocessing guide: R3F setup, all effects, custom effect authoringdevelopment
This skill should be used when the user asks to "design a business card", "make a printable PDF", "render HTML to PDF", "generate a postcard", "build print collateral", "set up an HTML print pipeline", or needs help with bleed, safe areas, font embedding, or QR generation for print. Provides a Playwright-based pipeline with multiple bundled templates and theme variants for business cards (minimal, watercolor light, watercolor dark) and instructions for adding new templates.
tools
Get recent tweets from an X/Twitter user. Use when user asks "what has @username posted", "recent tweets from", "user's X posts", "show timeline for", "what is @user saying". Requires X_BEARER_TOKEN.
data-ai
Get X/Twitter user profile by username. Use when user asks "who is @username", "get X profile", "lookup Twitter user", "find X account", "user details", "follower count for". Requires X_BEARER_TOKEN.
data-ai
Search recent X/Twitter posts by query. Returns RAW TWEETS (last 7 days). Use when user asks "search X for", "find tweets about", "what are people saying about", "Twitter search", "raw tweets about". For AI summaries/sentiment, use x-research instead. Requires X_BEARER_TOKEN.