Skip to content

Commit eb218e7

Browse files
committed
feat(postprocessing): add a trails effect
1 parent e82387a commit eb218e7

File tree

9 files changed

+507
-152
lines changed

9 files changed

+507
-152
lines changed

lib/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@
3434
"devDependencies": {
3535
"@astrojs/check": "0.9.4",
3636
"@playwright/test": "1.49.1",
37+
"@tweakpane/core": "2.0.5",
3738
"@types/node": "22.16.5",
3839
"astro": "5.12.3",
3940
"changelogen": "0.6.2",
4041
"eslint": "9.32.0",
4142
"eslint-config-unjs": "0.5.0",
4243
"prettier": "3.6.2",
4344
"tsdown": "0.14.1",
45+
"tweakpane": "4.0.5",
4446
"typescript": "5.8.3",
4547
"vite-plugin-glsl": "1.5.1"
4648
},

lib/playground/src/pages/particles/particles.astro

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,41 @@ import Layout from "../../layouts/Layout.astro";
44
---
55

66
<script>
7-
import { useLoop, useWebGLCanvas } from "usegl";
8-
import { incrementRenderCount } from "../../components/renderCount";
7+
import { useLoop, useWebGLCanvas } from "usegl";
8+
import { incrementRenderCount } from "../../components/renderCount";
99

10-
// Set up geometry
11-
const count = 1000;
12-
const positions = [];
13-
const indices = [];
10+
// Set up geometry
11+
const count = 1000;
12+
const positions = [];
13+
const indices = [];
1414

15-
// Fibonacci sphere points
16-
function fibonacciSpherePoint(index: number, totalPoints: number) {
17-
const phi = Math.acos(1 - (2 * index) / totalPoints);
18-
const theta = Math.sqrt(totalPoints * Math.PI) * phi;
15+
// Fibonacci sphere points
16+
function fibonacciSpherePoint(index: number, totalPoints: number) {
17+
const phi = Math.acos(1 - (2 * index) / totalPoints);
18+
const theta = Math.sqrt(totalPoints * Math.PI) * phi;
1919

20-
const x = Math.cos(theta) * Math.sin(phi);
21-
const y = Math.sin(theta) * Math.sin(phi);
22-
const z = Math.cos(phi);
20+
const x = Math.cos(theta) * Math.sin(phi);
21+
const y = Math.sin(theta) * Math.sin(phi);
22+
const z = Math.cos(phi);
2323

24-
return { x, y, z };
25-
}
24+
return { x, y, z };
25+
}
2626

27-
for (let i = 0; i < count; i++) {
28-
const { x, y, z } = fibonacciSpherePoint(i, count);
29-
positions.push(x, y, z);
30-
indices.push(i);
31-
}
27+
for (let i = 0; i < count; i++) {
28+
const { x, y, z } = fibonacciSpherePoint(i, count);
29+
positions.push(x, y, z);
30+
indices.push(i);
31+
}
3232

33-
const { uniforms, onAfterRender } = useWebGLCanvas({
34-
canvas: "#glCanvas",
35-
fragment: /* glsl */ `
33+
const { uniforms, onAfterRender } = useWebGLCanvas({
34+
canvas: "#glCanvas",
35+
fragment: /* glsl */ `
3636
varying vec4 vColor;
3737
void main(){
3838
gl_FragColor = vColor;
3939
}
4040
`,
41-
vertex: /* glsl */ `
41+
vertex: /* glsl */ `
4242
attribute vec3 aPosition;
4343
attribute float index;
4444
uniform float uThreshold;
@@ -60,39 +60,39 @@ import Layout from "../../layouts/Layout.astro";
6060
gl_PointSize = (2. - gl_Position.z) / .5 + 4.;
6161
}
6262
`,
63-
uniforms: {
64-
uThreshold: 0,
65-
},
66-
attributes: {
67-
aPosition: {
68-
data: positions,
69-
size: 3,
70-
},
71-
index: {
72-
data: indices,
73-
size: 1,
74-
},
75-
},
76-
immediate: false,
77-
});
63+
uniforms: {
64+
uThreshold: 0,
65+
},
66+
attributes: {
67+
aPosition: {
68+
data: positions,
69+
size: 3,
70+
},
71+
index: {
72+
data: indices,
73+
size: 1,
74+
},
75+
},
76+
immediate: false,
77+
});
7878

79-
let direction = 1;
79+
let direction = 1;
8080

81-
useLoop(
82-
() => {
83-
uniforms.uThreshold += (count * direction) / 500;
81+
useLoop(
82+
() => {
83+
uniforms.uThreshold += (count * direction) / 500;
8484

85-
if (uniforms.uThreshold > count || uniforms.uThreshold < 1) {
86-
direction = -direction;
87-
}
88-
},
89-
{ immediate: false }
90-
);
85+
if (uniforms.uThreshold > count || uniforms.uThreshold < 1) {
86+
direction = -direction;
87+
}
88+
},
89+
{ immediate: false }
90+
);
9191

92-
onAfterRender(incrementRenderCount);
92+
onAfterRender(incrementRenderCount);
9393
</script>
9494

9595
<Layout title="Particles">
96-
<GlobalPlayPause />
97-
<canvas id="glCanvas"></canvas>
96+
<GlobalPlayPause />
97+
<canvas id="glCanvas"></canvas>
9898
</Layout>
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
---
2+
import Layout from "../../layouts/Layout.astro";
3+
---
4+
5+
<script>
6+
import { useWebGLCanvas, linearToneMapping, trails } from "usegl";
7+
import { Pane } from "tweakpane";
8+
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({
106+
canvas: selector,
107+
fragment: /* glsl */ `
108+
varying vec2 vUv;
109+
uniform float uTime;
110+
uniform vec2 uResolution;
111+
112+
#define PI 3.14159265359
113+
#define dotRadius 0.02
114+
#define dotColor vec4(1., .6, 0., 1.)
115+
#define circleRadius 0.2
116+
#define count 3
117+
#define speed 2.0
118+
119+
vec2 rotate(vec2 uv, float angle) {
120+
float cosA = cos(angle);
121+
float sinA = sin(angle);
122+
mat2 rotation = mat2(cosA, -sinA, sinA, cosA);
123+
return rotation * uv;
124+
}
125+
126+
void main() {
127+
vec2 uv = (vUv - .5) * uResolution / min(uResolution.x, uResolution.y);
128+
uv = rotate(uv, uTime * speed);
129+
float circleMask = 0.;
130+
for (int i = 0; i < count; i++) {
131+
circleMask += 1. - step(dotRadius, distance(uv, rotate(vec2(circleRadius), float(i) * 2. * PI / float(count))));
132+
}
133+
vec4 color = mix(vec4(0.), dotColor, step(.1, circleMask)) * 1.;
134+
135+
gl_FragColor = pow(color, vec4(2.2));
136+
}
137+
`,
138+
postEffects: [trails(), linearToneMapping({ exposure: 1 })],
139+
});
140+
});
141+
</script>
142+
143+
<Layout title="Trails">
144+
<div class="container">
145+
<canvas id="glCanvas"></canvas>
146+
<canvas id="glCanvas2"></canvas>
147+
</div>
148+
</Layout>
149+
150+
<style>
151+
.container {
152+
display: grid;
153+
width: 95%;
154+
grid-template-columns: 1fr 1fr;
155+
gap: 1rem;
156+
padding: 1rem;
157+
background: blue;
158+
}
159+
160+
canvas {
161+
width: 100%;
162+
163+
&:nth-of-type(1) {
164+
background: black;
165+
}
166+
&:nth-of-type(2) {
167+
background: white;
168+
}
169+
}
170+
</style>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
varying vec2 vUv;
2+
uniform sampler2D uTrailTexture;
3+
4+
void main() {
5+
gl_FragColor = texture2D(uTrailTexture, vUv);
6+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
varying vec2 vUv;
2+
3+
uniform sampler2D uRenderTexture;
4+
uniform sampler2D uPreviousTrailTexture;
5+
6+
uniform vec4 uTailColor;
7+
uniform float uTailColorFalloff;
8+
uniform float uFadeout;
9+
uniform vec2 uKernelSize;
10+
11+
const vec3 luminanceWeights = vec3(0.2126, 0.7152, 0.0722);
12+
13+
void main() {
14+
vec4 renderColor = texture2D(uRenderTexture, vUv);
15+
vec4 previousColor = texture2D(uPreviousTrailTexture, vUv);
16+
17+
vec4 localMinimum = previousColor;
18+
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)));
27+
}
28+
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);
33+
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));
37+
38+
// gl_FragColor = mix(renderColor, max(renderColor, trailColor * (1.0 - uFadeout)), 1.);
39+
}

0 commit comments

Comments
 (0)