agentleFS
alton47alton47/threejs-skills/skills/threejs-postprocessing/SKILL.md

threejs-postprocessing

Skill13 starsChanged 7 months agoMITView on GitHub

Adds post-processing effects to Three.js scenes using EffectComposer — bloom, SSAO, SMAA, depth of field, motion blur, and custom passes. Also covers pmndrs/postprocessing for React Three Fiber. Use when the user asks about post-processing, bloom, glow, SSAO, depth of field, vignette, chromatic aberration, or visual effects pipeline. Trigger keywords: post-processing, bloom, glow, EffectComposer, SSAO, depth of field, vignette, chromatic aberration, UnrealBloom.

We split this skill into the rules it gives an agent

1 rule. 0 are common, 0 are distinctive, and 1 is very specific to this project.

  • 0

    Common

    Most of the 17 edited versions we read keep these.

  • 0

    Distinctive

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    If scene looks too dark after adding composer: check OutputPass is present and renderer.toneMapping is set

    line 169

“Common” is measured: kept by at least 6 in 10 of the 17 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Three.js Post-Processing

EffectComposer Setup

import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";

const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera)); // always first

// Add effect passes
composer.addPass(
  new UnrealBloomPass(
    new THREE.Vector2(window.innerWidth, window.innerHeight),
    1.5, // strength
    0.4, // radius
    0.85, // threshold — only fragments above this luminance bloom
  ),
);

composer.addPass(new OutputPass()); // always last — handles tone mapping + color space

// Render loop — use composer instead of renderer.render()
function animate() {
  requestAnimationFrame(animate);
  composer.render();
}

// Resize — update composer too
window.addEventListener("resize", () => {
  renderer.setSize(window.innerWidth, window.innerHeight);
  composer.setSize(window.innerWidth, window.innerHeight);
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
});

Available Passes

import { SSAOPass } from "three/addons/postprocessing/SSAOPass.js";
import { SAOPass } from "three/addons/postprocessing/SAOPass.js";
import { SSRPass } from "three/addons/postprocessing/SSRPass.js";
import { BokehPass } from "three/addons/postprocessing/BokehPass.js";
import { FilmPass } from "three/addons/postprocessing/FilmPass.js";
import { GlitchPass } from "three/addons/postprocessing/GlitchPass.js";
import { DotScreenPass } from "three/addons/postprocessing/DotScreenPass.js";
import { HalftonePass } from "three/addons/postprocessing/HalftonePass.js";
import { OutlinePass } from "three/addons/postprocessing/OutlinePass.js";
import { SMAAPass } from "three/addons/postprocessing/SMAAPass.js";
import { FXAAShader } from "three/addons/shaders/FXAAShader.js";
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";

SSAO

const ssaoPass = new SSAOPass(
  scene,
  camera,
  window.innerWidth,
  window.innerHeight,
);
ssaoPass.kernelRadius = 16;
ssaoPass.minDistance = 0.005;
ssaoPass.maxDistance = 0.1;
composer.addPass(ssaoPass);

Selective Bloom (bloom without overexposing everything)

// Strategy: render scene twice — bloom layer separately, then merge
const BLOOM_LAYER = 1;
const bloomLayer = new THREE.Layers();
bloomLayer.set(BLOOM_LAYER);

// Mark objects to bloom
glowMesh.layers.enable(BLOOM_LAYER);

// 1. Render bloom layer with DarkenNonBloomed pass
// 2. Render full scene
// 3. Merge with MixPass / ShaderPass

// Simpler approach — use pmndrs/postprocessing (below) which handles this natively

Custom ShaderPass

import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";

const vignetteShader = {
  uniforms: {
    tDiffuse: { value: null }, // auto-set by ShaderPass
    uStrength: { value: 1.5 },
    uOffset: { value: 1.0 },
  },
  vertexShader: `
    varying vec2 vUv;
    void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
  `,
  fragmentShader: `
    uniform sampler2D tDiffuse;
    uniform float uStrength;
    uniform float uOffset;
    varying vec2 vUv;
    void main() {
      vec4 color = texture2D(tDiffuse, vUv);
      vec2 uv = (vUv - 0.5) * 2.0;
      float vignette = 1.0 - smoothstep(uOffset - 0.5, uOffset + 0.5, length(uv) * uStrength);
      gl_FragColor = vec4(color.rgb * vignette, color.a);
    }
  `,
};

const vignettePass = new ShaderPass(vignetteShader);
composer.addPass(vignettePass);
// Update uniform:
vignettePass.uniforms.uStrength.value = 2.0;

pmndrs/postprocessing (React Three Fiber)

import {
  EffectComposer,
  Bloom,
  SSAO,
  ChromaticAberration,
  Vignette,
  DepthOfField,
  Noise,
  ToneMapping,
} from "@react-three/postprocessing";
import { BlendFunction, ToneMappingMode } from "postprocessing";

<EffectComposer>
  <Bloom
    intensity={1.5}
    luminanceThreshold={0.9}
    luminanceSmoothing={0.025}
    mipmapBlur
  />
  <SSAO radius={0.1} intensity={30} luminanceInfluence={0.6} color="black" />
  <ChromaticAberration offset={[0.002, 0.002]} />
  <Vignette offset={0.5} darkness={0.5} />
  <DepthOfField focusDistance={0} focalLength={0.02} bokehScale={2} />
  <Noise opacity={0.02} />
  <ToneMapping mode={ToneMappingMode.ACES_FILMIC} />
</EffectComposer>;

Common Gotchas

  • OutputPass must be the last pass — it applies tone mapping and color space conversion
  • Without OutputPass, colors look washed out or incorrect after composer
  • composer.render() replaces renderer.render() — don't call both
  • tDiffuse in custom ShaderPass is auto-injected — don't set it manually
  • SSAO and SSR are expensive on mobile — gate behind capability check
  • If scene looks too dark after adding composer: check OutputPass is present and renderer.toneMapping is set
  • Resize: always call composer.setSize() alongside renderer.setSize()

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.