Skip to content

Commit 24da26d

Browse files
committed
improve the trails effect
1 parent 4c81351 commit 24da26d

File tree

9 files changed

+95
-155
lines changed

9 files changed

+95
-155
lines changed

lib/playground/src/pages/post-processing/trails.astro

Lines changed: 61 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -3,118 +3,23 @@ import Layout from "../../layouts/Layout.astro";
33
---
44

55
<script>
6-
import { useWebGLCanvas, linearToneMapping, trails } from "usegl";
7-
import { Pane } from "tweakpane";
6+
import { useWebGLCanvas, linearToneMapping, trails, bloom, useLoop } from "usegl";
7+
import { incrementRenderCount } from "../../components/renderCount";
88

9-
// const trailsEffect = trails();
10-
11-
// const pane = new Pane();
12-
// pane.addBinding(trailsEffect.uniforms, "uErosion", { min: 0, max: 1 });
13-
// pane.addBinding(trailsEffect.uniforms, "uFadeout", { min: 0, max: 0.5 });
14-
// pane
15-
// .addBinding(
16-
// {
17-
// color: {
18-
// r: trailsEffect.uniforms.uTailColor[0],
19-
// g: trailsEffect.uniforms.uTailColor[1],
20-
// b: trailsEffect.uniforms.uTailColor[2],
21-
// a: trailsEffect.uniforms.uTailColor[3],
22-
// },
23-
// },
24-
// "color",
25-
// {
26-
// label: "uTrailColor",
27-
// color: { type: "float" },
28-
// }
29-
// )
30-
// .on("change", ({ value }) => {
31-
// trailsEffect.uniforms.uTailColor = [value.r, value.g, value.b, value.a];
32-
// });
33-
// pane.addBinding(trailsEffect.uniforms, "uTailColorFalloff", { min: 0, max: 0.5 });
34-
35-
// // Set up geometry
36-
// const count = 100;
37-
// const positions = [];
38-
// const indices = [];
39-
40-
// // Fibonacci sphere points
41-
// function fibonacciSpherePoint(index: number, totalPoints: number) {
42-
// const phi = Math.acos(1 - (2 * index) / totalPoints);
43-
// const theta = Math.sqrt(totalPoints * Math.PI) * phi;
44-
45-
// const x = Math.cos(theta) * Math.sin(phi);
46-
// const y = Math.sin(theta) * Math.sin(phi);
47-
// const z = Math.cos(phi);
48-
49-
// return { x, y, z };
50-
// }
51-
52-
// for (let i = 0; i < count; i++) {
53-
// const { x, y, z } = fibonacciSpherePoint(i, count);
54-
// positions.push(x, y, z);
55-
// indices.push(i);
56-
// }
57-
58-
// useWebGLCanvas({
59-
// canvas: "#glCanvas",
60-
// fragment: /* glsl */ `
61-
// varying vec4 vColor;
62-
// void main(){
63-
// gl_FragColor = vColor;
64-
// }
65-
// `,
66-
// vertex: /* glsl */ `
67-
// attribute vec3 aPosition;
68-
// attribute float index;
69-
// uniform float uThreshold;
70-
// uniform float uTime;
71-
// varying vec4 vColor;
72-
73-
// mat4 rotateY(float angle) {
74-
// return mat4(
75-
// cos(angle), 0., sin(angle), 0.,
76-
// 0., 1., 0., 0.,
77-
// -sin(angle), 0., cos(angle), 0.,
78-
// 0., 0., 0., 1.
79-
// );
80-
// }
81-
82-
// void main(){
83-
// vColor = vec4(1., 0.15, 0., floor(index / uThreshold));
84-
// gl_Position = vec4(aPosition * .6, 1.0) * rotateY(uTime / 2.);
85-
// gl_PointSize = (2. - gl_Position.z) / .5 + 4.;
86-
// }
87-
// `,
88-
// uniforms: {
89-
// uThreshold: 0,
90-
// },
91-
// attributes: {
92-
// aPosition: {
93-
// data: positions,
94-
// size: 3,
95-
// },
96-
// index: {
97-
// data: indices,
98-
// size: 1,
99-
// },
100-
// },
101-
// postEffects: [trailsEffect, linearToneMapping({ exposure: 1 })],
102-
// });
103-
104-
["#glCanvas", "#glCanvas2"].forEach((selector, i) => {
105-
useWebGLCanvas({
9+
["#glCanvas", "#glCanvas2", "#glCanvas3"].forEach((selector, i) => {
10+
const { render, uniforms } = useWebGLCanvas({
10611
canvas: selector,
10712
fragment: /* glsl */ `
10813
varying vec2 vUv;
10914
uniform float uTime;
11015
uniform vec2 uResolution;
11116

11217
#define PI 3.14159265359
113-
#define dotRadius 0.02
18+
#define dotRadius 0.04
11419
#define dotColor vec4(1., .6, 0., 1.)
11520
#define circleRadius 0.2
11621
#define count 3
117-
#define speed 2.0
22+
#define speed 1.
11823

11924
vec2 rotate(vec2 uv, float angle) {
12025
float cosA = cos(angle);
@@ -130,12 +35,47 @@ import Layout from "../../layouts/Layout.astro";
13035
for (int i = 0; i < count; i++) {
13136
circleMask += 1. - step(dotRadius, distance(uv, rotate(vec2(circleRadius), float(i) * 2. * PI / float(count))));
13237
}
133-
vec4 color = mix(vec4(0.), dotColor, step(.1, circleMask)) * 1.;
38+
vec4 color = mix(vec4(0), dotColor, step(.1, circleMask)) * 1.;
39+
// color.a = 1.;
13440

13541
gl_FragColor = pow(color, vec4(2.2));
13642
}
13743
`,
138-
postEffects: [trails(), linearToneMapping({ exposure: 1 })],
44+
uniforms: {
45+
uTime: 0,
46+
},
47+
postEffects: [
48+
bloom(),
49+
trails(
50+
i === 0
51+
? {}
52+
: i === 1
53+
? {
54+
erosion: 0.4,
55+
tailColor: [0, 1, 1, 1],
56+
tailColorFalloff: 0.45,
57+
}
58+
: {
59+
fadeout: 0.1,
60+
erosion: 0.6,
61+
tailColor: [1, 0, 0, 1],
62+
tailColorFalloff: 0.5,
63+
}
64+
),
65+
linearToneMapping({ exposure: 3 }),
66+
],
67+
});
68+
69+
let framesRendered = 0;
70+
71+
const { pause } = useLoop(({}) => {
72+
if (++framesRendered > 30) {
73+
pause();
74+
return;
75+
}
76+
uniforms.uTime += 0.03;
77+
render();
78+
incrementRenderCount();
13979
});
14080
});
14181
</script>
@@ -144,26 +84,40 @@ import Layout from "../../layouts/Layout.astro";
14484
<div class="container">
14585
<canvas id="glCanvas"></canvas>
14686
<canvas id="glCanvas2"></canvas>
87+
<canvas id="glCanvas3"></canvas>
14788
</div>
14889
</Layout>
14990

15091
<style>
15192
.container {
15293
display: grid;
15394
width: 95%;
154-
grid-template-columns: 1fr 1fr;
95+
grid-template-columns: 1fr 1fr 1fr;
15596
gap: 1rem;
15697
padding: 1rem;
157-
background: blue;
15898
}
15999

160100
canvas {
161101
width: 100%;
162102

163-
&:nth-of-type(1) {
103+
background-image: linear-gradient(
104+
45deg,
105+
#555 25%,
106+
transparent 25%,
107+
transparent 75%,
108+
#555 75%,
109+
#555
110+
),
111+
linear-gradient(45deg, #555 25%, transparent 25%, transparent 75%, #555 75%, #555);
112+
background-size: 20px 20px;
113+
background-position:
114+
0 0,
115+
10px 10px;
116+
117+
&:nth-of-type(2) {
164118
background: black;
165119
}
166-
&:nth-of-type(2) {
120+
&:nth-of-type(3) {
167121
background: white;
168122
}
169123
}

lib/src/effects/trails/glsl/trails.frag

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,30 @@ uniform float uTailColorFalloff;
88
uniform float uFadeout;
99
uniform vec2 uKernelSize;
1010

11-
const vec3 luminanceWeights = vec3(0.2126, 0.7152, 0.0722);
12-
1311
void main() {
1412
vec4 renderColor = texture2D(uRenderTexture, vUv);
1513
vec4 previousColor = texture2D(uPreviousTrailTexture, vUv);
1614

17-
vec4 localMinimum = previousColor;
15+
vec4 trailColor = previousColor;
16+
17+
// erosion
1818
if (uKernelSize.x > 0.0 || uKernelSize.y > 0.0) {
19-
localMinimum = min(localMinimum, texture2D(uPreviousTrailTexture, vUv + vec2(0.0, uKernelSize.y)));
20-
localMinimum = min(localMinimum, texture2D(uPreviousTrailTexture, vUv + vec2(uKernelSize.x, uKernelSize.y)));
21-
localMinimum = min(localMinimum, texture2D(uPreviousTrailTexture, vUv + vec2(uKernelSize.x, 0.0)));
22-
localMinimum = min(localMinimum, texture2D(uPreviousTrailTexture, vUv + vec2(uKernelSize.x, -uKernelSize.y)));
23-
localMinimum = min(localMinimum, texture2D(uPreviousTrailTexture, vUv + vec2(0.0, -uKernelSize.y)));
24-
localMinimum = min(localMinimum, texture2D(uPreviousTrailTexture, vUv + vec2(-uKernelSize.x, -uKernelSize.y)));
25-
localMinimum = min(localMinimum, texture2D(uPreviousTrailTexture, vUv + vec2(-uKernelSize.x, 0.0)));
26-
localMinimum = min(localMinimum, texture2D(uPreviousTrailTexture, vUv + vec2(-uKernelSize.x, uKernelSize.y)));
19+
trailColor = min(trailColor, texture2D(uPreviousTrailTexture, vUv + vec2(0.0, uKernelSize.y)));
20+
trailColor = min(trailColor, texture2D(uPreviousTrailTexture, vUv + vec2(uKernelSize.x, 0.0)));
21+
trailColor = min(trailColor, texture2D(uPreviousTrailTexture, vUv + vec2(0.0, -uKernelSize.y)));
22+
trailColor = min(trailColor, texture2D(uPreviousTrailTexture, vUv + vec2(-uKernelSize.x, 0.0)));
2723
}
2824

29-
float minimumLuminance = dot(localMinimum.rgb, luminanceWeights);
30-
vec4 trailColor = previousColor * .999;// mix(localMinimum, uTailColor, uTailColorFalloff * minimumLuminance);
31-
// trailColor.a *= 0.;
32-
// trailColor = vec4(1., 0., 0., 0.1);
25+
// blend with a tail color
26+
if (uTailColorFalloff > 0.) {
27+
float blending = pow(uTailColorFalloff, 3.) * trailColor.a;
28+
blending *= smoothstep(0., 1., distance(previousColor.rgb, renderColor.rgb));
29+
trailColor.rgb = mix(trailColor.rgb, uTailColor.rgb, blending);
30+
}
3331

34-
// alpha over
35-
gl_FragColor.a = renderColor.a + trailColor.a * (1.0 - renderColor.a);
36-
gl_FragColor.rgb = (renderColor.rgb * renderColor.a + trailColor.rgb * trailColor.a * (1.0 - renderColor.a));
32+
// fadeout
33+
trailColor *= (1. - pow(uFadeout, 2.));
3734

38-
// gl_FragColor = mix(renderColor, max(renderColor, trailColor * (1.0 - uFadeout)), 1.);
35+
// lighten blend
36+
gl_FragColor = max(renderColor, trailColor);
3937
}

lib/src/effects/trails/index.ts

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import outputFragment from "./glsl/output.frag";
88
export type TrailsParams = {
99
/**
1010
* Intensity of the erosion effect that makes the trails shorter.
11-
* @default 0.2
11+
* @default 0
1212
*/
1313
erosion?: number;
1414
/**
@@ -23,14 +23,14 @@ export type TrailsParams = {
2323
tailColor?: [number, number, number, number];
2424
/**
2525
* How quickly the original color fades to the tail color.
26-
* @default 0.1
26+
* @default 0
2727
*/
2828
tailColorFalloff?: number;
2929
};
3030

3131
export function trails(params?: TrailsParams) {
32-
const { fadeout = 0.2, tailColor = [1, 1, 1, 1], tailColorFalloff = 0 } = params || {};
33-
let erosion = params?.erosion ?? 0.05;
32+
const { fadeout = 0.25, tailColor = [1, 1, 1, 1], tailColorFalloff = 0 } = params || {};
33+
let erosion = Math.pow(params?.erosion ?? 0, 2);
3434

3535
let fboRead: RenderTarget;
3636
let fboWrite: RenderTarget;
@@ -41,19 +41,11 @@ export function trails(params?: TrailsParams) {
4141
fboWrite = temp;
4242
}
4343

44-
let isFirstRender = true;
45-
4644
const trailPass = useEffectPass({
4745
fragment: trailsFragment,
4846
uniforms: {
4947
uRenderTexture: ({ inputPass }) => inputPass.target!.texture,
50-
uPreviousTrailTexture: ({ inputPass }) => {
51-
if (isFirstRender) {
52-
isFirstRender = false;
53-
return inputPass.target!.texture;
54-
}
55-
return fboRead.texture;
56-
},
48+
uPreviousTrailTexture: () => fboRead.texture,
5749
uKernelSize: [0, 0],
5850
uTailColor: tailColor,
5951
uTailColorFalloff: tailColorFalloff,
@@ -74,11 +66,8 @@ export function trails(params?: TrailsParams) {
7466
return erosion;
7567
},
7668
set uErosion(value: number) {
77-
erosion = value;
78-
trailPass.uniforms.uKernelSize = [
79-
Math.sqrt(erosion) / fboRead.width,
80-
Math.sqrt(erosion) / fboRead.height,
81-
];
69+
erosion = Math.pow(value, 2);
70+
trailPass.uniforms.uKernelSize = [erosion / fboRead.width, erosion / fboRead.height];
8271
},
8372
get uFadeout() {
8473
return trailPass.uniforms.uFadeout;
@@ -102,21 +91,17 @@ export function trails(params?: TrailsParams) {
10291

10392
const trailsPass = useCompositeEffectPass([trailPass, outputPass], uniforms);
10493

105-
trailsPass.onResize((width, height) => {
106-
fboRead.setSize(width, height);
107-
fboWrite.setSize(width, height);
108-
109-
trailPass.uniforms.uKernelSize = [
110-
Math.sqrt(erosion) / fboRead.width,
111-
Math.sqrt(erosion) / fboRead.height,
112-
];
113-
});
114-
11594
trailsPass.onInit((gl) => {
11695
fboRead = createRenderTarget(gl, floatTargetConfig);
11796
fboWrite = createRenderTarget(gl, floatTargetConfig);
11897
});
11998

99+
trailsPass.onResize((width, height) => {
100+
fboRead.setSize(width, height);
101+
fboWrite.setSize(width, height);
102+
trailPass.uniforms.uKernelSize = [erosion / fboRead.width, erosion / fboRead.height];
103+
});
104+
120105
trailsPass.onBeforeRender(() => {
121106
trailPass.setTarget(fboWrite);
122107
});
21.6 KB
Loading
48.1 KB
Loading
79.6 KB
Loading
27.9 KB
Loading
65.2 KB
Loading

lib/tests/screenshots.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ const expectedRendersByDemo = {
1616
sepia: /1|2/,
1717
alpha: "2",
1818
blending: "2",
19+
trails: "90",
1920
};
2021

2122
for (const { section, route } of routesToTest) {
2223
test(route, async ({ page, baseURL }) => {
2324
await page.goto(`${baseURL}/${section}/${route}`);
2425

25-
await expect(page.locator("#renders strong")).toHaveText(expectedRendersByDemo[route] || "1");
26+
await expect(page.locator("#renders strong")).toHaveText(expectedRendersByDemo[route] || "1", {
27+
timeout: 10_000,
28+
});
2629
await expect(page.locator("main")).toHaveScreenshot();
2730
});
2831
}

0 commit comments

Comments
 (0)