skills/shaders/SKILL.md
This skill should be used when the user asks to "write a custom Three.js shader", "create a GLSL shader effect", "build a TSL shader", "debug a WebGL or WebGPU shader issue", or "build a post-processing pipeline". Covers shader workflow, the 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 authoringtools
This skill should be used when a Claude Code session needs to keep working after Anthropic usage runs out, or when the user asks to run the Claude Code harness on GPT-5.6 Sol. Trigger phrases include "my Anthropic usage ran out", "I'm out of Claude usage", "usage limit reached, what now", "keep working on another model", "run Claude Code on GPT-5.6 Sol", "use GPT-5.6 Sol as the model", "set up claudex", "claudex isn't working", "route the harness through CLIProxyAPI", or "bill against my ChatGPT/Codex subscription". It stands up a local proxy so the Claude Code CLI runs on OpenAI's Codex backend as an escape hatch, and diagnoses that setup when it drifts. macOS + Homebrew.
testing
This skill should be used when the user asks to "open Visual Wayfinder", "answer a Wayfinder ticket visually", "turn this decision into a configurator", "show Wayfinder choices as a dashboard", "prototype the Wayfinder questionnaire", or wants interactive choice cards, tradeoff controls, rankings, ranges, toggles, and consequence previews for one active Wayfinder decision. It wraps the Wayfinder skill and JSON Render; it never replaces the tracker or resolves more than the active decision.
development
This skill should be used when the user asks to "make a visual proposal", "write this up so I can share it", "present these options visually", "diagram the trade-offs", "turn this plan into something reviewable", or requests a shareable design pitch, architecture proposal, RFC, options comparison, or visual roadmap for work that has not been built. It produces one self-contained, theme-aware HTML page led by grounded diagrams. Use visual-review instead for completed code changes; do not use this skill for internal task tracking.
tools
This skill should be used when the user asks to "add plugin settings", "make a plugin configurable", "store per-project plugin configuration", "use settings.local.json", "create a plugin state file", "expose skill settings in Agent Master", or "add a skill interface". Distinguishes official Claude Code settings from project-owned configuration and documents bOpen Agent Master skill interface discovery.