---
url: /examples/post-processing-builtin/trails.md
description: >-
  Add persistent motion trails to animated particles with the built-in trails
  post-processing effect.
---

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

```ts
import { glCanvas, linearToneMapping, trails } from "@radiancejs/gl";
import { Pane } from "tweakpane";
import vertex from "./particles.vert?raw";
import "./styles.css";

const count = 100;

const trailsEffect = trails();

const { uniforms } = glCanvas({
  canvas: "#glCanvas",
  fragment: /* glsl */ `
    varying vec4 vColor;
    void main() {
      vec2 uv = gl_PointCoord.xy;
      gl_FragColor.a = vColor.a * smoothstep(0.5, 0.4, length(uv - 0.5));
      gl_FragColor.rgb = vColor.rgb * gl_FragColor.a;
    }
  `,
  vertex,
  attributes: {
    random: {
      data: Array.from({ length: count * 3 }).map(() => Math.random()),
      size: 3,
    },
  },
  uniforms: {
    uParticleSize: 3,
    uTime: ({ time }) => time / 500,
  },
  blending: "normal",
  postEffects: [trailsEffect, linearToneMapping()],
});

const pane = new Pane();

const particlesParams = pane.addFolder({ title: "Particles" });
particlesParams.addBinding(uniforms, "uParticleSize", {
  min: 3,
  max: 20,
  step: 1,
  label: "Size",
});

const trailsParams = pane.addFolder({ title: "Trails Effect" });
trailsParams.addBinding(trailsEffect.uniforms, "uFadeout", { min: 0, max: 1, step: 0.01 });
trailsParams.addBinding(trailsEffect.uniforms, "uErosion", { min: 0, max: 0.5, step: 0.01 });
trailsParams
  .addBinding({ uTailColor: { r: 1, g: 1, b: 1 } }, "uTailColor", {
    color: { type: "float" },
  })
  .on("change", ({ value }) => {
    trailsEffect.uniforms.uTailColor = [value.r, value.g, value.b, 1];
  });
trailsParams.addBinding(trailsEffect.uniforms, "uTailColorFalloff", {
  min: 0,
  max: 1,
  step: 0.01,
});

```

```vert
attribute vec3 random;
uniform float uTime;
uniform float uParticleSize;
varying vec4 vColor;

#define PI 3.141592653589793

void main() {
  float t = uTime * 0.1;

  float rho = pow(random.x, .1) * .8;
  float theta = acos(2. * random.z - 1.) * (1. +  t);
  float phi = random.y * 2. * PI * (1. + t);

  gl_Position = vec4(
    rho * sin(theta) * sin(phi),
    rho * cos(theta),
    rho * sin(theta) * cos(phi),
    1.
  );
  gl_PointSize = (gl_Position.z + 2.) * uParticleSize;

  vColor.rgb = mix(
    vec3(0.1, 0.3, 0.6), // dark blue
    vec3(1.0, 0.7, 0.2), // yellow
    smoothstep(-1.5, 1., dot(gl_Position.xyz, vec3(1., 1., -.5)))
  );
  vColor.rgb *= smoothstep(-2., .8, gl_Position.z);
  vColor.a = 1.;
}

```

```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>

```

:::
