ether-shaders
Technique reference for the shader + postprocessing + extruded-text stack in the ether engine. Invoke before writing any shader, postprocessing change, or text-as-form work on a site built on it.
When to use
Any of:
- Writing a new
ShaderMaterial, vertex shader, or fragment shader. - Modifying postprocessing (bloom, dither, color grading, new effects).
- Adding or tuning extruded text — hero treatments, chapter heads.
- Diagnosing a banding / glow / type-ghosting / displacement-blur issue.
Invoke alongside ether-threejs (the general slop checklist) — this skill specializes, the other generalizes.
Read first
- The
ether-threejsSKILL.md — the general "no AI-slop three.js" checklist applies first. recipes.mdin this directory — nine numbered recipes.- Your studio's taste doc, if the repo has one — what lands vs. what doesn't.
The engine / site boundary
- Engine owns (ether
src/):postfx/DitherEffect— 8×8 Bayer.postfx/createHeroComposer— restrained bloom + optional dither (LDR composer);createNightComposer— the HDR/ACES variant for emissive-heavy scenes.primitives/ShaderQuad— fullscreen backdrop plane with auto-wireduTime/uAspect.text/extrudedWord— opentype → SVGLoader → ExtrudeGeometry pipeline.
- Your site owns (
src/shaders/<scene>/):- The hero material shaders — the brand's identity. Site-specific until a second consumer needs them.
- Backdrop shaders (caustics, gradients, generative fields).
Don't promote site shaders to the engine without a generalization pass (uniform-driven palettes, no hardcoded brand-token vec3s).
Hard rules
- GLSL is imported via
?raw. Pattern:import frag from './x.frag.glsl?raw'. Even ifvite-plugin-glslis configured, match the codebase's actual pattern. optimizeDeps.exclude: ['ether']is mandatory in yourastro.config.ts(or Vite config). Without it, the engine's?rawconsumers break at build. Never setpreserveSymlinks: truefor afile:layout — it pins the engine at its node_modules path so edits don't hot-reload.- Two composer presets exist — pick one, don't mutate one into the other.
createHeroComposeris LDR by design (noHalfFloatType; values clip at 1.0 deliberately as bloom containment).createNightComposerhas anhdroption usingHalfFloatType+ ACESToneMappingEffect. - Edge AA comes from the composer's
multisampling, never the contextantialiasflag. Post-processing renders into textures that bypass the canvas framebuffer, so context MSAA is visually dead the moment a composer runs (the quality profile sets it false and carriesmsaaSamplesinstead — wire viacreateHeroComposer({ multisampling: quality.msaaSamples })). - Dither runs on every tier. It merges into the SAME fullscreen pass as bloom (a few ALU ops — effectively free) and kills the dark-gradient banding that reads as posterized color on mobile OLED.
- Bloom intensity
0.06is the hero-preset ceiling. Higher = glow-spam. RaiseluminanceThresholdto gate harder if you need more visible bloom. The night preset ships0.38by design — its own ceiling, not a license to raise the hero's. - Custom
ShaderMaterialonly on hero elements. NoMeshBasicMaterial/MeshStandardMaterialfor hero content. Seeether-threejs. - Brand tokens through uniforms. Pull colors from your site's constants module. If tokens are mirrored in CSS, update both when a color changes.
- Wire
uTimethrough the scene tick. Material uniforms updated fromtick()or via a wrapper'stickUniforms. Never via setInterval. - Add
precision highp float;at the top of fragment shaders explicitly — three.js prepends it by default, but stating it inline keeps the intent durable across pipeline changes.
Slop indicators (do not ship)
- Default materials (
MeshBasicMaterial,MeshStandardMaterial) on hero elements. - Bloom
intensity > 0.1on the hero preset, orluminanceThreshold < 0.5(the night preset ships 0.38 by design). - Hardcoded
vec3(...)colors in shaders. - Ambient particle fields with no narrative function (see
ether-threejs). - ShaderMaterial without
uTimewired through the engine's tick. dat.guileft in production builds.console.loginside shader hot paths.- ExtrudedWord with
curveSegments < 6on hero treatments. - Single-color iridescent rim (must be a dual-color mix by normal direction).
- Promoting a site shader to the engine without uniformising the color palette.
- Vertex displacement strong enough to blur bevels on letter-extrusion geometry.
Procedure for a new ShaderMaterial
- Confirm against the
ether-threejsslop checklist first — general rules apply before specifics. - Decide ownership — is the shader reusable across sites (engine) or brand-specific (site)? Default: site, until a second consumer exists.
- Place files —
src/shaders/<scene>/<name>.{vert,frag}.glsl. Use?rawimports. - Wire uniforms through your constants module — brand colors, displacement amplitudes, fresnel exponents.
- Tick uniforms from the scene's
tick()(or a wrapping class'stickUniforms). - Add
precision highp float;to fragment shaders. - Test against the hero composer. If your material needs HDR values, the LDR composer clips them — rework to 0..1 (preferred) or use the night preset.
- Run
premium-reviewbefore reporting complete.
After the skill
- Run the
premium-reviewsubagent on any shader work before reporting complete. - If the shader is scroll-bound, also invoke
ether-scrollfor the bridge pattern. - If creating a new dimensional-type treatment, cite the
extrudedWordoptions you chose in the commit message — future grep will find the lineage.
Files
SKILL.md— this file (the script).recipes.md— nine numbered technique recipes.evals/triggers.json— should/shouldn't-trigger regression set for the description.