Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
enzed avatar

R3f Postprocessing

  • 1k installs
  • 105 repo stars
  • Updated January 20, 2026
  • enzed/r3f-skills

r3f-postprocessing is an agent skill that adds bloom, depth-of-field, vignette, and color grading to React Three Fiber 3D scenes for developers who need polished WebGL screen-space effects.

About

r3f-postprocessing is a React Three Fiber agent skill from enzed/r3f-skills that teaches developers to add high-quality screen-space visual effects to 3D WebGL scenes using @react-three/postprocessing. The skill covers EffectComposer setup with effects such as Bloom, depth-of-field, Vignette, blur, glow, and custom color-grading shaders inside @react-three/fiber Canvas components. Developers reach for r3f-postprocessing when a Three.js scene renders correctly but lacks cinematic polish—emissive highlights need bloom, backgrounds need depth blur, or the frame needs vignette and grading without rewriting the core scene graph. The quick-start pattern imports EffectComposer and individual effect components, then nests them inside Canvas alongside meshes and lights. Guidance also covers luminance thresholds, emissive intensity tuning, and when to compose multiple passes for glow and screen-space blur.

  • Wraps @react-three/postprocessing and postprocessing library
  • Provides EffectComposer as the required container for all effects
  • Includes ready-to-use effects: Bloom, Vignette, and support for DOF plus custom screen-space shaders
  • Works directly inside any React Three Fiber Canvas after scene content
  • Supports runtime toggling via enabled, depthBuffer, stencilBuffer and multisampling props

R3f Postprocessing by the numbers

  • 1,002 all-time installs (skills.sh)
  • +43 installs in the week ending Jul 28, 2026 (Skillselion tracking)
  • Ranked #364 of 2,277 Frontend Development skills by installs in the Skillselion catalog
  • Security screen: MEDIUM risk (skills.sh audit)
  • Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/enzed/r3f-skills --skill r3f-postprocessing

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs1k
repo stars105
Security audit3 / 3 scanners passed
Last updatedJanuary 20, 2026
Repositoryenzed/r3f-skills

How do you add post-processing effects in React Three Fiber?

Add high-quality screen-space visual effects such as bloom, depth-of-field, vignette, and color grading to React Three Fiber 3D scenes.

Who is it for?

Frontend developers polishing React Three Fiber or WebGL scenes that need bloom, blur, vignette, or color grading without custom shader boilerplate.

Skip if: Developers building non-React Three.js apps, 2D CSS-only UIs, or server-side rendering pipelines without a WebGL Canvas.

When should I use this skill?

A developer asks to add bloom, depth-of-field, vignette, glow, blur, or color grading to a React Three Fiber 3D scene.

What you get

EffectComposer JSX with Bloom, DOF, Vignette, and color-grading passes wired into a React Three Fiber Canvas.

  • EffectComposer TSX
  • Configured screen-space effect passes

Files

SKILL.mdMarkdownGitHub ↗

React Three Fiber Post-Processing

Quick Start

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

function Scene() {
  return (
    <Canvas>
      <ambientLight />
      <mesh>
        <boxGeometry />
        <meshStandardMaterial color="hotpink" emissive="hotpink" emissiveIntensity={2} />
      </mesh>

      <EffectComposer>
        <Bloom luminanceThreshold={0.5} luminanceSmoothing={0.9} intensity={1.5} />
        <Vignette offset={0.5} darkness={0.5} />
      </EffectComposer>
    </Canvas>
  )
}

Installation

npm install @react-three/postprocessing postprocessing

EffectComposer

The container for all post-processing effects.

import { EffectComposer } from '@react-three/postprocessing'

function Scene() {
  return (
    <Canvas>
      {/* Scene content */}
      <mesh>...</mesh>

      {/* Post-processing - must be inside Canvas, after scene content */}
      <EffectComposer
        enabled={true}           // Toggle all effects
        depthBuffer={true}       // Enable depth buffer
        stencilBuffer={false}    // Enable stencil buffer
        autoClear={true}         // Auto clear before render
        multisampling={8}        // MSAA samples (0 to disable)
      >
        {/* Effects go here */}
      </EffectComposer>
    </Canvas>
  )
}

Common Effects

Bloom (Glow)

import { EffectComposer, Bloom } from '@react-three/postprocessing'
import { BlendFunction } from 'postprocessing'

<EffectComposer>
  <Bloom
    intensity={1.0}              // Bloom intensity
    luminanceThreshold={0.9}     // Brightness threshold
    luminanceSmoothing={0.025}   // Smoothness of threshold
    mipmapBlur={true}            // Enable mipmap blur
    radius={0.8}                 // Bloom radius
    levels={8}                   // Mipmap levels
    blendFunction={BlendFunction.ADD}
  />
</EffectComposer>

// For objects to glow, use emissive materials
<mesh>
  <boxGeometry />
  <meshStandardMaterial
    color="black"
    emissive="#ff00ff"
    emissiveIntensity={2}
    toneMapped={false}  // Important for values > 1
  />
</mesh>

Selective Bloom

import { EffectComposer, Bloom, Selection, Select } from '@react-three/postprocessing'

function Scene() {
  return (
    <Canvas>
      <Selection>
        <EffectComposer>
          <Bloom
            luminanceThreshold={0}
            intensity={2}
            mipmapBlur
          />
        </EffectComposer>

        {/* This mesh will bloom */}
        <Select enabled>
          <mesh>
            <sphereGeometry />
            <meshStandardMaterial emissive="red" emissiveIntensity={2} toneMapped={false} />
          </mesh>
        </Select>

        {/* This mesh will NOT bloom */}
        <mesh position={[2, 0, 0]}>
          <boxGeometry />
          <meshStandardMaterial color="blue" />
        </mesh>
      </Selection>
    </Canvas>
  )
}

Depth of Field

import { EffectComposer, DepthOfField } from '@react-three/postprocessing'

<EffectComposer>
  <DepthOfField
    focusDistance={0}     // Focus distance (0 = auto)
    focalLength={0.02}    // Camera focal length
    bokehScale={2}        // Bokeh size
    height={480}          // Resolution height
  />
</EffectComposer>

// With target object
import { useRef } from 'react'

function Scene() {
  const targetRef = useRef()

  return (
    <>
      <mesh ref={targetRef} position={[0, 0, -5]}>
        <boxGeometry />
        <meshStandardMaterial color="red" />
      </mesh>

      <EffectComposer>
        <DepthOfField
          target={targetRef}
          focalLength={0.02}
          bokehScale={2}
        />
      </EffectComposer>
    </>
  )
}

Vignette

import { EffectComposer, Vignette } from '@react-three/postprocessing'

<EffectComposer>
  <Vignette
    offset={0.5}          // Vignette size
    darkness={0.5}        // Vignette intensity
    eskil={false}         // Use Eskil's vignette technique
  />
</EffectComposer>

Noise

import { EffectComposer, Noise } from '@react-three/postprocessing'
import { BlendFunction } from 'postprocessing'

<EffectComposer>
  <Noise
    premultiply            // Multiply noise with input
    blendFunction={BlendFunction.ADD}
    opacity={0.5}
  />
</EffectComposer>

Chromatic Aberration

import { EffectComposer, ChromaticAberration } from '@react-three/postprocessing'
import { BlendFunction } from 'postprocessing'

<EffectComposer>
  <ChromaticAberration
    offset={[0.002, 0.002]}    // Color offset
    radialModulation={true}    // Apply radially
    modulationOffset={0.5}
    blendFunction={BlendFunction.NORMAL}
  />
</EffectComposer>

SSAO (Screen Space Ambient Occlusion)

import { EffectComposer, SSAO } from '@react-three/postprocessing'
import { BlendFunction } from 'postprocessing'

<EffectComposer>
  <SSAO
    blendFunction={BlendFunction.MULTIPLY}
    samples={30}              // Amount of samples
    radius={5}                // Occlusion radius
    intensity={30}            // Occlusion intensity
    luminanceInfluence={0.6}
    color="black"
    worldDistanceThreshold={100}
    worldDistanceFalloff={5}
    worldProximityThreshold={0.01}
    worldProximityFalloff={0.01}
  />
</EffectComposer>

Outline

import { EffectComposer, Outline, Selection, Select } from '@react-three/postprocessing'

function Scene() {
  return (
    <Canvas>
      <Selection>
        <EffectComposer autoClear={false}>
          <Outline
            visibleEdgeColor={0xffffff}    // Visible edge color
            hiddenEdgeColor={0x22090a}     // Hidden edge color
            edgeStrength={2.5}             // Edge strength
            pulseSpeed={0}                 // Pulse animation speed
            blur                           // Enable blur
            xRay                           // Show behind objects
          />
        </EffectComposer>

        {/* Outlined object */}
        <Select enabled>
          <mesh>
            <boxGeometry />
            <meshStandardMaterial color="orange" />
          </mesh>
        </Select>

        {/* Non-outlined object */}
        <mesh position={[2, 0, 0]}>
          <sphereGeometry />
          <meshStandardMaterial color="blue" />
        </mesh>
      </Selection>
    </Canvas>
  )
}

Color Grading

import { EffectComposer, BrightnessContrast, HueSaturation } from '@react-three/postprocessing'

<EffectComposer>
  <BrightnessContrast
    brightness={0}     // -1 to 1
    contrast={0}       // -1 to 1
  />
  <HueSaturation
    hue={0}           // Hue rotation in radians
    saturation={0}    // -1 to 1
  />
</EffectComposer>

Tone Mapping

import { EffectComposer, ToneMapping } from '@react-three/postprocessing'
import { ToneMappingMode } from 'postprocessing'

<EffectComposer>
  <ToneMapping
    mode={ToneMappingMode.ACES_FILMIC}
    // Modes: LINEAR, REINHARD, REINHARD2, OPTIMIZED_CINEON, CINEON, ACES_FILMIC, AGX, NEUTRAL
    resolution={256}
    whitePoint={4.0}
    middleGrey={0.6}
    minLuminance={0.01}
    averageLuminance={1.0}
    adaptationRate={1.0}
  />
</EffectComposer>

Glitch

import { EffectComposer, Glitch } from '@react-three/postprocessing'
import { GlitchMode } from 'postprocessing'

<EffectComposer>
  <Glitch
    delay={[1.5, 3.5]}           // Min/max delay between glitches
    duration={[0.6, 1.0]}        // Min/max duration
    strength={[0.3, 1.0]}        // Min/max strength
    mode={GlitchMode.SPORADIC}   // DISABLED, SPORADIC, CONSTANT_MILD, CONSTANT_WILD
    active                       // Enable/disable
    ratio={0.85}                 // Glitch ratio (0 = none, 1 = always)
  />
</EffectComposer>

Pixelation

import { EffectComposer, Pixelation } from '@react-three/postprocessing'

<EffectComposer>
  <Pixelation
    granularity={5}    // Pixel size
  />
</EffectComposer>

Scanline

import { EffectComposer, Scanline } from '@react-three/postprocessing'
import { BlendFunction } from 'postprocessing'

<EffectComposer>
  <Scanline
    blendFunction={BlendFunction.OVERLAY}
    density={1.25}     // Line density
    opacity={0.5}      // Effect opacity
  />
</EffectComposer>

Grid

import { EffectComposer, Grid } from '@react-three/postprocessing'
import { BlendFunction } from 'postprocessing'

<EffectComposer>
  <Grid
    blendFunction={BlendFunction.OVERLAY}
    scale={1.0}        // Grid scale
    lineWidth={0.0}    // Line width
  />
</EffectComposer>

DotScreen

import { EffectComposer, DotScreen } from '@react-three/postprocessing'
import { BlendFunction } from 'postprocessing'

<EffectComposer>
  <DotScreen
    blendFunction={BlendFunction.NORMAL}
    angle={Math.PI * 0.5}    // Pattern angle
    scale={1.0}              // Pattern scale
  />
</EffectComposer>

SMAA (Anti-Aliasing)

import { EffectComposer, SMAA } from '@react-three/postprocessing'

<EffectComposer multisampling={0}>  {/* Disable MSAA when using SMAA */}
  <SMAA />
</EffectComposer>

FXAA (Anti-Aliasing)

import { EffectComposer, FXAA } from '@react-three/postprocessing'

<EffectComposer multisampling={0}>
  <FXAA />
</EffectComposer>

Combining Multiple Effects

import { EffectComposer, Bloom, Vignette, ChromaticAberration, Noise, SMAA } from '@react-three/postprocessing'
import { BlendFunction } from 'postprocessing'

function PostProcessing() {
  return (
    <EffectComposer multisampling={0}>
      {/* Glow effect */}
      <Bloom
        intensity={1.5}
        luminanceThreshold={0.9}
        luminanceSmoothing={0.025}
        mipmapBlur
      />

      {/* Color aberration */}
      <ChromaticAberration
        offset={[0.001, 0.001]}
        radialModulation
        modulationOffset={0.5}
      />

      {/* Film grain */}
      <Noise
        premultiply
        blendFunction={BlendFunction.ADD}
        opacity={0.2}
      />

      {/* Vignette */}
      <Vignette
        offset={0.3}
        darkness={0.5}
      />

      {/* Anti-aliasing (should be last) */}
      <SMAA />
    </EffectComposer>
  )
}

Custom Effects

Using postprocessing Effect Class

import { forwardRef, useMemo } from 'react'
import { Effect, BlendFunction } from 'postprocessing'
import { Uniform } from 'three'

// Fragment shader
const fragmentShader = `
  uniform float time;
  uniform float intensity;

  void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {
    vec2 distortedUv = uv;
    distortedUv.x += sin(uv.y * 10.0 + time) * 0.01 * intensity;

    vec4 color = texture2D(inputBuffer, distortedUv);
    outputColor = color;
  }
`

// Effect class
class WaveDistortionEffect extends Effect {
  constructor({ intensity = 1.0, blendFunction = BlendFunction.NORMAL } = {}) {
    super('WaveDistortionEffect', fragmentShader, {
      blendFunction,
      uniforms: new Map([
        ['time', new Uniform(0)],
        ['intensity', new Uniform(intensity)],
      ]),
    })
  }

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

// React component wrapper
export const WaveDistortion = forwardRef(({ intensity = 1.0, blendFunction }, ref) => {
  const effect = useMemo(() => new WaveDistortionEffect({ intensity, blendFunction }), [intensity, blendFunction])
  return <primitive ref={ref} object={effect} dispose={null} />
})

// Usage
<EffectComposer>
  <WaveDistortion intensity={0.5} />
</EffectComposer>

Shader with wrapEffect

import { wrapEffect } from '@react-three/postprocessing'
import { Effect, BlendFunction } from 'postprocessing'
import { Uniform } from 'three'

class InvertEffect extends Effect {
  constructor({ blendFunction = BlendFunction.NORMAL } = {}) {
    super('InvertEffect', `
      void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {
        outputColor = vec4(1.0 - inputColor.rgb, inputColor.a);
      }
    `, {
      blendFunction,
    })
  }
}

export const Invert = wrapEffect(InvertEffect)

// Usage
<EffectComposer>
  <Invert />
</EffectComposer>

Conditional Effects

import { useState } from 'react'
import { EffectComposer, Bloom, Vignette, Glitch } from '@react-three/postprocessing'

function ConditionalPostProcessing() {
  const [effects, setEffects] = useState({
    bloom: true,
    vignette: true,
    glitch: false,
  })

  return (
    <>
      <EffectComposer>
        {effects.bloom && (
          <Bloom intensity={1.5} luminanceThreshold={0.9} />
        )}
        {effects.vignette && (
          <Vignette offset={0.5} darkness={0.5} />
        )}
        {effects.glitch && (
          <Glitch delay={[1, 3]} duration={[0.5, 1]} strength={[0.3, 1]} />
        )}
      </EffectComposer>

      {/* UI to toggle effects */}
      <div className="controls">
        <button onClick={() => setEffects(e => ({ ...e, bloom: !e.bloom }))}>
          Toggle Bloom
        </button>
        <button onClick={() => setEffects(e => ({ ...e, vignette: !e.vignette }))}>
          Toggle Vignette
        </button>
        <button onClick={() => setEffects(e => ({ ...e, glitch: !e.glitch }))}>
          Toggle Glitch
        </button>
      </div>
    </>
  )
}

Animated Effects

import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
import { EffectComposer, Bloom, ChromaticAberration } from '@react-three/postprocessing'

function AnimatedEffects() {
  const bloomRef = useRef()
  const chromaticRef = useRef()

  useFrame(({ clock }) => {
    const t = clock.elapsedTime

    // Animate bloom intensity
    if (bloomRef.current) {
      bloomRef.current.intensity = 1 + Math.sin(t) * 0.5
    }

    // Animate chromatic aberration
    if (chromaticRef.current) {
      const offset = Math.sin(t * 2) * 0.002
      chromaticRef.current.offset.set(offset, offset)
    }
  })

  return (
    <EffectComposer>
      <Bloom ref={bloomRef} luminanceThreshold={0.9} />
      <ChromaticAberration ref={chromaticRef} />
    </EffectComposer>
  )
}

N8AO (High Quality AO)

import { EffectComposer } from '@react-three/postprocessing'
import { N8AO } from '@react-three/postprocessing'

<EffectComposer>
  <N8AO
    aoRadius={0.5}
    intensity={1}
    aoSamples={6}
    denoiseSamples={4}
    denoiseRadius={12}
    distanceFalloff={1}
    color="black"
    quality="low"   // low, medium, high, ultra
    halfRes={false}
  />
</EffectComposer>

God Rays

import { EffectComposer, GodRays } from '@react-three/postprocessing'
import { useRef } from 'react'

function Scene() {
  const sunRef = useRef()

  return (
    <Canvas>
      {/* Sun mesh (light source for god rays) */}
      <mesh ref={sunRef} position={[0, 5, -10]}>
        <sphereGeometry args={[1]} />
        <meshBasicMaterial color="#ffddaa" />
      </mesh>

      <EffectComposer>
        {sunRef.current && (
          <GodRays
            sun={sunRef}
            blendFunction={BlendFunction.SCREEN}
            samples={60}
            density={0.96}
            decay={0.9}
            weight={0.4}
            exposure={0.6}
            clampMax={1}
            blur
          />
        )}
      </EffectComposer>
    </Canvas>
  )
}

LUT (Color Lookup Table)

import { EffectComposer, LUT } from '@react-three/postprocessing'
import { LUTCubeLoader } from 'postprocessing'
import { useLoader } from '@react-three/fiber'

function ColorGradedScene() {
  const texture = useLoader(LUTCubeLoader, '/luts/cinematic.cube')

  return (
    <EffectComposer>
      <LUT lut={texture} />
    </EffectComposer>
  )
}

Blend Functions

import { BlendFunction } from 'postprocessing'

// Available blend functions:
BlendFunction.SKIP           // Skip blending
BlendFunction.ADD            // Additive
BlendFunction.ALPHA          // Alpha
BlendFunction.AVERAGE        // Average
BlendFunction.COLOR          // Color
BlendFunction.COLOR_BURN     // Color Burn
BlendFunction.COLOR_DODGE    // Color Dodge
BlendFunction.DARKEN         // Darken
BlendFunction.DIFFERENCE     // Difference
BlendFunction.DIVIDE         // Divide
BlendFunction.DST            // Destination
BlendFunction.EXCLUSION      // Exclusion
BlendFunction.HARD_LIGHT     // Hard Light
BlendFunction.HARD_MIX       // Hard Mix
BlendFunction.HUE            // Hue
BlendFunction.INVERT         // Invert
BlendFunction.INVERT_RGB     // Invert RGB
BlendFunction.LIGHTEN        // Lighten
BlendFunction.LINEAR_BURN    // Linear Burn
BlendFunction.LINEAR_DODGE   // Linear Dodge
BlendFunction.LINEAR_LIGHT   // Linear Light
BlendFunction.LUMINOSITY     // Luminosity
BlendFunction.MULTIPLY       // Multiply
BlendFunction.NEGATION       // Negation
BlendFunction.NORMAL         // Normal
BlendFunction.OVERLAY        // Overlay
BlendFunction.PIN_LIGHT      // Pin Light
BlendFunction.REFLECT        // Reflect
BlendFunction.SATURATION     // Saturation
BlendFunction.SCREEN         // Screen
BlendFunction.SET            // Set
BlendFunction.SOFT_LIGHT     // Soft Light
BlendFunction.SRC            // Source
BlendFunction.SUBTRACT       // Subtract
BlendFunction.VIVID_LIGHT    // Vivid Light

Performance Tips

1. Limit effect count: Each effect adds rendering overhead 2. Use multisampling wisely: Higher values = slower performance 3. Disable when not needed: Toggle enabled prop 4. Lower resolution: Some effects have resolution props 5. Profile with DevTools: Monitor GPU usage

// Disable all effects
<EffectComposer enabled={performanceMode}>
  ...
</EffectComposer>

// Reduce effect quality on mobile
const isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent)

<EffectComposer multisampling={isMobile ? 0 : 8}>
  <Bloom mipmapBlur={!isMobile} radius={isMobile ? 0.4 : 0.8} />
  {!isMobile && <SSAO samples={16} />}
</EffectComposer>

See Also

  • r3f-shaders - Custom shader development
  • r3f-materials - Emissive materials for bloom
  • r3f-fundamentals - Canvas and renderer setup

Related skills

How it compares

Pick r3f-postprocessing for declarative React Three Fiber effect passes instead of hand-rolling raw Three.js EffectComposer wiring in imperative JavaScript.

FAQ

Which library does r3f-postprocessing use?

r3f-postprocessing centers on @react-three/postprocessing composed inside @react-three/fiber Canvas components. Developers import EffectComposer and effect primitives like Bloom, Vignette, and depth-of-field directly into TSX scene trees.

What effects does r3f-postprocessing cover?

r3f-postprocessing covers bloom, depth-of-field, vignette, blur, glow, color grading, and custom screen-space shaders. The skill shows luminanceThreshold tuning and emissive material setup for convincing highlight bloom.

Is R3f Postprocessing safe to install?

skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.

Frontend Developmentfrontendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.