mcpField
P
postfx-composer

postfx-composer

github.com/bbeierle12Development962 installsUpdated 1d ago
Repository

About postfx-composer

postfx-composer is a skill for building post-processing pipelines with EffectComposer in React Three Fiber. It covers effect ordering, runtime toggles, custom effects, render targets, and quality adjustments.

Key features

  1. 01EffectComposer pipeline configuration
  2. 02Runtime effect toggles and presets
  3. 03Custom shader-based effects
  4. 04Manual render target management
  5. 05Resolution scaling and adaptive quality

Use cases

  1. 01Composing ordered effects for a 3D scene
  2. 02Toggling visual effects at runtime
  3. 03Creating custom time-dependent effects
  4. 04Adjusting post-processing quality

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/bbeierle12/skill-mcp-claude --skill postfx-composer

After installing, type/postfx-composerin Claude Code to confirm it loaded.

Bundled files_meta.json

Details

From the project README

Post-Processing Composer

EffectComposer architecture, render pipelines, and custom pass creation.

Quick Start

npm install @react-three/postprocessing postprocessing three
import { Canvas } from '@react-three/fiber';
import { EffectComposer, Bloom, Vignette } from '@react-three/postprocessing';

function App() {
  return (
    <Canvas>
      <Scene />
      <EffectComposer>
        <Bloom intensity={1} />
        <Vignette darkness={0.5} />
      </EffectComposer>
    </Canvas>
  );
}

Core Concepts

Render Pipeline Flow
Scene Render → EffectComposer → Effect 1 → Effect 2 → ... → Screen
                    ↓
              [Render Target A]  →  [Render Target B]  →  [Screen]
EffectComposer Configuration
import { EffectComposer } from '@react-three/postprocessing';
import { HalfFloatType } from 'three';

function PostProcessing() {
  return (
    <EffectComposer
      enabled={true}
      depthBuffer={true}
      stencilBuffer={false}
      multisampling={8}
      frameBufferType={HalfFloatType}
    >
      {/* Effects processed in order */}
    </EffectComposer>
  );
}

Effect Ordering

Recommended Order
<EffectComposer>
  {/* 1. Scene modification (needs scene data) */}
  <SSAO />
  <DepthOfField />

  {/* 2. Lighting/color effects */}
  <Bloom />
  <ToneMapping />

  {/* 3. Color grading */}
  <HueSaturation />
  <BrightnessContrast />
  <LUT />

  {/* 4. Screen-space effects */}
  <ChromaticAberration />
  <Vignette />

  {/* 5. Noise/grain (always last) */}
  <Noise />
</EffectComposer>
Why Order Matters
// WRONG: Bloom after color grading reduces glow
<EffectComposer>
  <HueSaturation saturation={-0.5} />
  <Bloom intensity={2} />
</EffectComposer>

// CORRECT: Bloom before color grading
<EffectComposer>
  <Bloom intensity={2} />
  <HueSaturation saturation={-0.5} />
</EffectComposer>

Conditional Effects

Toggle Effects at Runtime
function DynamicEffects({ enableBloom, enableDOF }) {
  return (
    <EffectComposer>
      {enableBloom && <Bloom luminanceThreshold={0.2} intensity={1.5} />}
      {enableDOF && <DepthOfField focusDistance={0.02} focalLength={0.05} />}
      <Vignette darkness={0.4} />
    </EffectComposer>
  );
}
Effect Presets
const PRESETS = {
  cinematic: {
    bloom: { intensity: 0.8, threshold: 0.3 },
    vignette: { darkness: 0.5, offset: 0.3 },
    grain: { opacity: 0.03 }
  },
  scifi: {
    bloom: { intensity: 2.0, threshold: 0.1 },
    chromatic: { offset: [0.003, 0.003] },
    vignette: { darkness: 0.6, offset: 0.2 }
  },
  minimal: { vignette: { darkness: 0.3, offset: 0.4 } }
};

function PresetEffects({ preset = 'cinematic' }) {
  const config = PRESETS[preset];
  return (
    <EffectComposer>
      {config.bloom && <Bloom {...config.bloom} />}
      {config.chromatic && <ChromaticAberration offset={config.chromatic.offset} />}
      {config.vignette && <Vignette {...config.vignette} />}
      {config.grain && <Noise {...config.grain} />}
    </EffectComposer>
  );
}

Custom Effects

Basic Custom Effect
import { Effect } from 'postprocessing';
import { Uniform } from 'three';
import { forwardRef, useMemo } from 'react';

class ColorShiftEffect extends Effect {
  constructor({ shift = 0.0 }) {
    super('ColorShiftEffect', /* glsl */`
      uniform float shift;

      void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {
        vec3 color = inputColor.rgb;
        float hueShift = shift * uv.x;
        color.r = color.r * cos(hueShift) - color.g * sin(hueShift);
        color.g = color.r * sin(hueShift) + color.g * cos(hueShift);
        outputColor = vec4(color, inputColor.a);
      }
    `, {
      uniforms: new Map([['shift', new Uniform(shift)]])
    });
  }

  set shift(value) {
    this.uniforms.get('shift').value = value;
  }
}

const ColorShift = forwardRef(({ shift = 0 }, ref) => {
  const effect = useMemo(() => new ColorShiftEffect({ shift }), []);
  useEffect(() => { effect.shift = shift; }, [shift, effect]);
  return <primitive ref={ref} object={effect} />;
});
Custom Effect with Time
class PulseEffect extends Effect {
  constructor() {
    super('PulseEffect', /* glsl */`
      uniform float uTime;
      uniform float uIntensity;

      void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {
        float pulse = sin(uTime * 2.0) * 0.5 + 0.5;
        float vignette = 1.0 - length(uv - 0.5) * pulse * uIntensity;
        outputColor = vec4(inputColor.rgb * vignette, inputColor.a);
      }
    `, {
      uniforms: new Map([
        ['uTime', new Uniform(0)],
        ['uIntensity', new Uniform(0.5)]
      ])
    });
  }

  update(renderer, inputBuffer, deltaTime) {
    this.uniforms.get('uTime').value += deltaTime;
  }
}

Render Targets

Manual Render Target Management
import { useFBO } from '@react-three/drei';

function CustomRenderPipeline() {
  const { gl, scene, camera } = useThree();
  const targetA = useFBO({ width: 1024, height: 1024 });

  useFrame(() => {
    gl.setRenderTarget(targetA);
    gl.render(scene, camera);
    gl.setRenderTarget(null);
    gl.render(scene, camera);
  });

  return null;
}

Performance Patterns

Resolution Scaling
function ScaledEffects() {
  const { size } = useThree();
  const scale = 0.5;

  return (
    <EffectComposer frameBufferType={HalfFloatType} multisampling={0}>
      <Bloom intensity={1.5} width={size.width * scale} height={size.height * scale} />
    </EffectComposer>
  );
}
Adaptive Quality
import { useDetectGPU } from '@react-three/drei';

function AdaptiveQuality() {
  const { tier } = useDetectGPU();

  const quality = useMemo(() => {
    if (tier >= 3) return { multisampling: 8, bloomLevels: 8 };
    if (tier >= 2) return { multisampling: 4, bloomLevels: 5 };
    return { multisampling: 0, bloomLevels: 3 };
  }, [tier]);

  return (
    <EffectComposer multisampling={quality.multisampling}>
      <Bloom levels={quality.bloomLevels} />
    </EffectComposer>
  );
}

Temporal Collapse Setup

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

function TemporalCollapseComposer({ children }) {
  return (
    <EffectComposer multisampling={4} frameBufferType={HalfFloatType}>
      <ToneMapping mode={ToneMappingMode.ACES_FILMIC} />
      <Bloom luminanceThreshold={0.15} luminanceSmoothing={0.9} intensity={2.0} radius={0.85} mipmapBlur />
      <ChromaticAberration offset={[0.002, 0.001]} radialModulation modulationOffset={0.3} />
      <Vignette darkness={0.6} offset={0.25} />
      <Noise opacity={0.025} blendFunction={BlendFunction.OVERLAY} />
      {children}
    </EffectComposer>
  );
}

Reference

  • See postfx-bloom for bloom-specific techniques
  • See postfx-effects for individual effect configurations
  • See shaders-glsl for writing custom effect shaders

Comments

Markdown supported