---
url: /examples/post-processing/multi-pass.md
description: >-
  Chain multiple effect passes together to build a configurable bloom-style
  post-processing pipeline.
---

::: example-editor {deps=tweakpane@^4.0.5}

```ts
import { compositeEffectPass, effectPass, glCanvas } from "@radiancejs/gl";
import directionalBlurFragment from "./blur.frag?raw";
import combineFragment from "./combine.frag?raw";
import dotsFragment from "./dots.frag?raw";
import { Pane } from "tweakpane";
import "./styles.css";

const horizontalBlur = effectPass({
  fragment: directionalBlurFragment,
  uniforms: {
    uResolution: ({ passResolution }) => passResolution,
    uTexture: ({ inputPass }) => inputPass.target!.texture,
    uDirection: [1, 0],
    uRadius: 30,
  },
});

const verticalBlur = effectPass({
  fragment: directionalBlurFragment,
  uniforms: {
    uResolution: ({ passResolution }) => passResolution,
    uTexture: () => horizontalBlur.target!.texture,
    uDirection: [0, 1],
    uRadius: 30,
  },
});

const combine = effectPass({
  fragment: combineFragment,
  uniforms: {
    uResolution: ({ passResolution }) => passResolution,
    uBaseImage: ({ inputPass }) => inputPass.target!.texture,
    uBloomTexture: ({ previousPass }) => previousPass.target!.texture, // same as () => verticalBlur.target!.texture
    uMix: 1,
  },
});

const bloomPasses = [horizontalBlur, verticalBlur, combine];

const bloomUniforms = {
  get uRadius() {
    return verticalBlur.uniforms.uRadius;
  },
  set uRadius(value: number) {
    verticalBlur.uniforms.uRadius = value;
    horizontalBlur.uniforms.uRadius = value;
  },
  get uMix() {
    return combine.uniforms.uMix;
  },
  set uMix(value: number) {
    combine.uniforms.uMix = value;
  },
};

const bloom = compositeEffectPass({ passes: bloomPasses, uniforms: bloomUniforms });

glCanvas({
  canvas: "#glCanvas",
  dpr: 1,
  fragment: dotsFragment,
  postEffects: [bloom],
});

const pane = new Pane({ title: "Uniforms" });

// Updating the uniforms of the composite pass will update those of the individual passes,
// which will trigger a re-render
const bloomFolder = pane.addFolder({ title: "Bloom" });
bloomFolder.addBinding(bloom.uniforms, "uMix", { min: 0, max: 1 });
bloomFolder.addBinding(bloom.uniforms, "uRadius", { min: 0, max: 50 });

```

```frag
in vec2 vUv;
out vec4 outColor;

vec3 drawCircle(vec2 pos, float radius, vec3 color) {
  return smoothstep(radius * 1.03, radius * .97, (length(pos) - radius)) * color;
}

void main() {
  vec2 c = vec2(0.5, 0.5);
  float R = 0.15;
  float s60 = 0.86602540378; // sqrt(3)/2

  vec2 pTop = c + vec2(0.0, R);
  vec2 pBL  = c + vec2(-R * s60, -R * 0.5);
  vec2 pBR  = c + vec2( R * s60, -R * 0.5);

  vec3 color = vec3(0.0);
  float r = 0.10;

  color += drawCircle(vUv - pTop, r, vec3(1.0, 0.0, 0.0));
  color += drawCircle(vUv - pBL,  r, vec3(0.0, 1.0, 0.0));
  color += drawCircle(vUv - pBR,  r, vec3(0.0, 0.0, 1.0));

  outColor = vec4(color, 1.0);
}

```

```frag
uniform sampler2D uTexture;
uniform vec2 uResolution;
uniform vec2 uDirection;
uniform float uRadius;

in vec2 vUv;
out vec4 outColor;

/*
* Directional Gaussian Blur
* /!\ Absolutely not optimized, for demonstration purposes only
*/

void main() {
  float sigma = float(uRadius / 2.);
  vec2 sampleOffset = uDirection / uResolution;

  // Build normalized Gaussian weights
  float weights[51];
  weights[0] = 1.0;
  float sum = weights[0];
  for (int i = 1; i <= int(uRadius); ++i) {
    float x = float(i);
    float w = exp(-0.5 * (x * x) / (sigma * sigma));
    weights[i] = w;
    sum += 2.0 * w;
  }
  for (int i = 0; i <= int(uRadius); ++i) {
    weights[i] /= sum;
  }

  vec3 color = texture(uTexture, vUv).rgb * weights[0];
  for (int i = 1; i <= int(uRadius); ++i) {
    float fi = float(i);
    color += texture(uTexture, vUv + sampleOffset * fi).rgb * weights[i];
    color += texture(uTexture, vUv - sampleOffset * fi).rgb * weights[i];
  }

  outColor = vec4(color, 1.0);
}

```

```frag
uniform sampler2D uBaseImage;
uniform sampler2D uBloomTexture;
uniform vec2 uResolution;
uniform float uMix;

in vec2 vUv;
out vec4 outColor;

void main() {
  vec4 baseColor = texture(uBaseImage, vUv);
  vec4 bloomColor = texture(uBloomTexture, vUv);

  outColor = max(baseColor, mix(baseColor, bloomColor, uMix));
}

```

```css
html {
  color-scheme: light dark;
}

body {
  margin: 0;
  height: 100svh;
  display: grid;
  place-items: center;
}

canvas {
  width: min(90svmin, 900px);
  aspect-ratio: 1;
  display: block;
  border-radius: 8px;
  border: 1px solid rgb(128 128 128 / 0.4);
}

```

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>RadianceJS Example</title>
  </head>
  <body>
    <canvas id="glCanvas"></canvas>
    <script src="/index.ts"></script>
  </body>
</html>

```

:::
