@brochington/shader-backgrounds 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/deploy-demo-to-pages.yml +63 -0
- package/PLUGINS.md +269 -0
- package/README.md +159 -0
- package/demo.js +1044 -0
- package/index.html +194 -0
- package/package.json +23 -0
- package/src/index.ts +5 -0
- package/src/lib/components/web-component.ts +198 -0
- package/src/lib/core/ShaderCanvas.ts +235 -0
- package/src/lib/core/types.ts +26 -0
- package/src/lib/plugins/AuroraWavesPlugin.ts +128 -0
- package/src/lib/plugins/CausticsPlugin.ts +128 -0
- package/src/lib/plugins/ContourLinesPlugin.ts +148 -0
- package/src/lib/plugins/DreamyBokehPlugin.ts +191 -0
- package/src/lib/plugins/GradientPlugin.ts +445 -0
- package/src/lib/plugins/GrainyFogPlugin.ts +139 -0
- package/src/lib/plugins/InkWashPlugin.ts +182 -0
- package/src/lib/plugins/LiquidOrbPlugin.ts +140 -0
- package/src/lib/plugins/RetroGridPlugin.ts +77 -0
- package/src/lib/plugins/SoftStarfieldPlugin.ts +156 -0
- package/src/lib/plugins/StainedGlassPlugin.ts +261 -0
- package/src/lib/plugins/index.ts +11 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +19 -0
- package/vite.demo.config.ts +13 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Color } from 'ogl';
|
|
2
|
+
import { ShaderPlugin } from '../core/types';
|
|
3
|
+
|
|
4
|
+
export type AuroraWavesConfig = {
|
|
5
|
+
/** Base background color */
|
|
6
|
+
backgroundColor: string;
|
|
7
|
+
/** Aurora ribbon primary color */
|
|
8
|
+
color1: string;
|
|
9
|
+
/** Aurora ribbon secondary color */
|
|
10
|
+
color2: string;
|
|
11
|
+
/** Overall brightness multiplier */
|
|
12
|
+
intensity?: number; // default 0.9
|
|
13
|
+
/** Motion speed */
|
|
14
|
+
speed?: number; // default 0.6
|
|
15
|
+
/** Noise scale (bigger = larger features) */
|
|
16
|
+
scale?: number; // default 1.6
|
|
17
|
+
/** Subtle film grain */
|
|
18
|
+
grainAmount?: number; // default 0.05
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export class AuroraWavesPlugin implements ShaderPlugin {
|
|
22
|
+
name = 'aurora-waves';
|
|
23
|
+
|
|
24
|
+
fragmentShader = /* glsl */ `
|
|
25
|
+
precision highp float;
|
|
26
|
+
uniform float uTimeInternal;
|
|
27
|
+
uniform vec2 uResolution;
|
|
28
|
+
uniform vec3 uBg;
|
|
29
|
+
uniform vec3 uC1;
|
|
30
|
+
uniform vec3 uC2;
|
|
31
|
+
uniform float uIntensity;
|
|
32
|
+
uniform float uScale;
|
|
33
|
+
uniform float uGrain;
|
|
34
|
+
|
|
35
|
+
varying vec2 vUv;
|
|
36
|
+
|
|
37
|
+
float hash12(vec2 p) {
|
|
38
|
+
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
|
|
39
|
+
p3 += dot(p3, p3.yzx + 33.33);
|
|
40
|
+
return fract((p3.x + p3.y) * p3.z);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
float noise(vec2 p) {
|
|
44
|
+
vec2 i = floor(p);
|
|
45
|
+
vec2 f = fract(p);
|
|
46
|
+
float a = hash12(i);
|
|
47
|
+
float b = hash12(i + vec2(1.0, 0.0));
|
|
48
|
+
float c = hash12(i + vec2(0.0, 1.0));
|
|
49
|
+
float d = hash12(i + vec2(1.0, 1.0));
|
|
50
|
+
vec2 u = f * f * (3.0 - 2.0 * f);
|
|
51
|
+
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
float fbm(vec2 p) {
|
|
55
|
+
float v = 0.0;
|
|
56
|
+
float a = 0.5;
|
|
57
|
+
mat2 rot = mat2(0.80, -0.60, 0.60, 0.80);
|
|
58
|
+
for (int i = 0; i < 5; i++) {
|
|
59
|
+
v += a * noise(p);
|
|
60
|
+
p = rot * p * 2.02 + 19.19;
|
|
61
|
+
a *= 0.55;
|
|
62
|
+
}
|
|
63
|
+
return v;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
void main() {
|
|
67
|
+
float aspect = uResolution.x / uResolution.y;
|
|
68
|
+
vec2 uv = vUv;
|
|
69
|
+
vec2 p = (uv - 0.5) * vec2(aspect, 1.0);
|
|
70
|
+
|
|
71
|
+
float t = uTimeInternal;
|
|
72
|
+
vec3 col = uBg;
|
|
73
|
+
|
|
74
|
+
// Ribbon field: layered sin waves warped by fbm
|
|
75
|
+
float warp = fbm(p * (uScale * 0.9) + vec2(0.0, 0.10 * t));
|
|
76
|
+
float warp2 = fbm(p * (uScale * 1.3) + vec2(4.0, -0.07 * t));
|
|
77
|
+
float y = p.y + (warp - 0.5) * 0.55 + (warp2 - 0.5) * 0.35;
|
|
78
|
+
|
|
79
|
+
// Multiple ribbons at different heights
|
|
80
|
+
float band1 = exp(-pow((y - 0.15 + 0.08 * sin(p.x * 1.2 + t * 0.6)), 2.0) * 18.0);
|
|
81
|
+
float band2 = exp(-pow((y + 0.05 + 0.10 * sin(p.x * 0.9 - t * 0.5)), 2.0) * 14.0);
|
|
82
|
+
float band3 = exp(-pow((y - 0.35 + 0.06 * sin(p.x * 1.6 + t * 0.4)), 2.0) * 22.0);
|
|
83
|
+
|
|
84
|
+
float bands = clamp(band1 + 0.8 * band2 + 0.6 * band3, 0.0, 1.5);
|
|
85
|
+
// Soft flicker-free shimmer via low-frequency fbm
|
|
86
|
+
float shimmer = 0.65 + 0.35 * fbm(p * (uScale * 0.6) + vec2(2.0, t * 0.12));
|
|
87
|
+
|
|
88
|
+
vec3 aur = mix(uC1, uC2, clamp(0.5 + 0.5 * sin(p.x * 0.7 + t * 0.25 + warp * 2.0), 0.0, 1.0));
|
|
89
|
+
col += aur * (bands * shimmer) * uIntensity;
|
|
90
|
+
|
|
91
|
+
// Gentle vertical fade (so content remains readable)
|
|
92
|
+
float fade = smoothstep(-0.9, 0.2, p.y);
|
|
93
|
+
col = mix(col, uBg, fade * 0.35);
|
|
94
|
+
|
|
95
|
+
// Grain
|
|
96
|
+
float g = (hash12(uv * uResolution + t * 60.0) - 0.5) * 2.0;
|
|
97
|
+
col += g * uGrain;
|
|
98
|
+
|
|
99
|
+
gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
uniforms: any;
|
|
104
|
+
private speed: number;
|
|
105
|
+
|
|
106
|
+
constructor(config: AuroraWavesConfig) {
|
|
107
|
+
const bg = new Color(config.backgroundColor);
|
|
108
|
+
const c1 = new Color(config.color1);
|
|
109
|
+
const c2 = new Color(config.color2);
|
|
110
|
+
this.speed = config.speed ?? 0.6;
|
|
111
|
+
|
|
112
|
+
this.uniforms = {
|
|
113
|
+
uBg: { value: [bg.r, bg.g, bg.b] },
|
|
114
|
+
uC1: { value: [c1.r, c1.g, c1.b] },
|
|
115
|
+
uC2: { value: [c2.r, c2.g, c2.b] },
|
|
116
|
+
uIntensity: { value: config.intensity ?? 0.9 },
|
|
117
|
+
uScale: { value: config.scale ?? 1.6 },
|
|
118
|
+
uGrain: { value: config.grainAmount ?? 0.05 },
|
|
119
|
+
uTimeInternal: { value: 0 },
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
onRender(dt: number) {
|
|
124
|
+
this.uniforms.uTimeInternal.value += dt * 0.001 * this.speed;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Color } from 'ogl';
|
|
2
|
+
import { ShaderPlugin } from '../core/types';
|
|
3
|
+
|
|
4
|
+
export type CausticsConfig = {
|
|
5
|
+
color: string; // The light color (e.g. Cyan/White)
|
|
6
|
+
backgroundColor: string; // Deep blue
|
|
7
|
+
intensity?: number; // Brightness of the lines
|
|
8
|
+
speed?: number;
|
|
9
|
+
scale?: number; // Pattern scale, default 2.2
|
|
10
|
+
distortion?: number; // 0..2, default 0.9
|
|
11
|
+
sharpness?: number; // 1..6, default 3.2
|
|
12
|
+
antiAlias?: number; // 0..2, default 1.0 (higher = smoother)
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export class CausticsPlugin implements ShaderPlugin {
|
|
16
|
+
name = 'caustics';
|
|
17
|
+
|
|
18
|
+
fragmentShader = /* glsl */ `
|
|
19
|
+
precision highp float;
|
|
20
|
+
#ifdef GL_OES_standard_derivatives
|
|
21
|
+
#extension GL_OES_standard_derivatives : enable
|
|
22
|
+
#endif
|
|
23
|
+
|
|
24
|
+
uniform float uTimeInternal;
|
|
25
|
+
uniform vec2 uResolution;
|
|
26
|
+
uniform vec3 uColor;
|
|
27
|
+
uniform vec3 uBgColor;
|
|
28
|
+
uniform float uIntensity;
|
|
29
|
+
uniform float uScale;
|
|
30
|
+
uniform float uDistortion;
|
|
31
|
+
uniform float uSharpness;
|
|
32
|
+
uniform float uAA;
|
|
33
|
+
|
|
34
|
+
varying vec2 vUv;
|
|
35
|
+
|
|
36
|
+
float aawidth(float x) {
|
|
37
|
+
#ifdef GL_OES_standard_derivatives
|
|
38
|
+
return fwidth(x);
|
|
39
|
+
#else
|
|
40
|
+
// Fallback: approximate 1 pixel in normalized space.
|
|
41
|
+
return 1.0 / max(1.0, min(uResolution.x, uResolution.y));
|
|
42
|
+
#endif
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
float hash12(vec2 p) {
|
|
46
|
+
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
|
|
47
|
+
p3 += dot(p3, p3.yzx + 33.33);
|
|
48
|
+
return fract((p3.x + p3.y) * p3.z);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Textureless "caustics-ish" pattern: warped cells + exponential falloff
|
|
52
|
+
float caustics(vec2 p, float t) {
|
|
53
|
+
float c = 0.0;
|
|
54
|
+
float a = 1.0;
|
|
55
|
+
|
|
56
|
+
// Add a slow, large-scale warp so it doesn't look like static tiles
|
|
57
|
+
p += uDistortion * vec2(
|
|
58
|
+
sin(p.y * 1.7 + t * 0.8),
|
|
59
|
+
sin(p.x * 1.3 - t * 0.7)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
for (int i = 0; i < 4; i++) {
|
|
63
|
+
// repeat + distance to nearest cell edge (0 at edges)
|
|
64
|
+
vec2 q = abs(fract(p) - 0.5);
|
|
65
|
+
float edgeDist = min(q.x, q.y);
|
|
66
|
+
|
|
67
|
+
// Derivative-based AA: widen the transition as frequency increases
|
|
68
|
+
float w = aawidth(edgeDist) * (1.0 + uAA * 2.0);
|
|
69
|
+
|
|
70
|
+
// Bright lines near edges, with controllable sharpness
|
|
71
|
+
float line = 1.0 - smoothstep(0.0, w, edgeDist);
|
|
72
|
+
// Emphasize peaks without introducing harsh aliasing
|
|
73
|
+
float web = pow(clamp(line, 0.0, 1.0), uSharpness);
|
|
74
|
+
c += web * a;
|
|
75
|
+
|
|
76
|
+
// zoom and drift
|
|
77
|
+
p = p * 1.65 + vec2(0.12 * t, -0.10 * t);
|
|
78
|
+
p += (hash12(p + float(i) * 7.7) - 0.5) * 0.25;
|
|
79
|
+
a *= 0.72;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return c;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
void main() {
|
|
86
|
+
float aspect = uResolution.x / uResolution.y;
|
|
87
|
+
vec2 p = (vUv - 0.5) * vec2(aspect, 1.0);
|
|
88
|
+
float t = uTimeInternal;
|
|
89
|
+
|
|
90
|
+
// scale in aspect-correct domain
|
|
91
|
+
p *= uScale;
|
|
92
|
+
|
|
93
|
+
float c = caustics(p, t);
|
|
94
|
+
|
|
95
|
+
// shape + intensity
|
|
96
|
+
float brightness = pow(clamp(c, 0.0, 2.0), 1.35) * uIntensity;
|
|
97
|
+
|
|
98
|
+
vec3 col = uBgColor + uColor * brightness;
|
|
99
|
+
|
|
100
|
+
gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
|
|
101
|
+
}
|
|
102
|
+
`;
|
|
103
|
+
|
|
104
|
+
uniforms: any;
|
|
105
|
+
private speed: number;
|
|
106
|
+
|
|
107
|
+
constructor(config: CausticsConfig) {
|
|
108
|
+
const c = new Color(config.color);
|
|
109
|
+
const bg = new Color(config.backgroundColor);
|
|
110
|
+
this.speed = config.speed ?? 0.5;
|
|
111
|
+
|
|
112
|
+
this.uniforms = {
|
|
113
|
+
uColor: { value: [c.r, c.g, c.b] },
|
|
114
|
+
uBgColor: { value: [bg.r, bg.g, bg.b] },
|
|
115
|
+
uIntensity: { value: config.intensity ?? 1.0 },
|
|
116
|
+
uScale: { value: config.scale ?? 2.2 },
|
|
117
|
+
uDistortion: { value: config.distortion ?? 0.9 },
|
|
118
|
+
uSharpness: { value: config.sharpness ?? 3.2 },
|
|
119
|
+
uAA: { value: config.antiAlias ?? 1.0 },
|
|
120
|
+
uTimeInternal: { value: 0 },
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
onRender(dt: number) {
|
|
125
|
+
// We update our own time uniform to control the wave speed
|
|
126
|
+
this.uniforms.uTimeInternal.value += dt * 0.001 * this.speed;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Color } from 'ogl';
|
|
2
|
+
import { ShaderPlugin } from '../core/types';
|
|
3
|
+
|
|
4
|
+
export type ContourLinesConfig = {
|
|
5
|
+
backgroundColor: string;
|
|
6
|
+
lineColor: string;
|
|
7
|
+
accentColor?: string; // default derived from lineColor
|
|
8
|
+
/** Lines per unit */
|
|
9
|
+
density?: number; // default 12
|
|
10
|
+
/** Line thickness */
|
|
11
|
+
thickness?: number; // default 0.075
|
|
12
|
+
/** Warp amount */
|
|
13
|
+
warp?: number; // default 0.9
|
|
14
|
+
/** Motion speed */
|
|
15
|
+
speed?: number; // default 0.35
|
|
16
|
+
/** Glow amount */
|
|
17
|
+
glow?: number; // default 0.35
|
|
18
|
+
/** Grain amount */
|
|
19
|
+
grainAmount?: number; // default 0.04
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class ContourLinesPlugin implements ShaderPlugin {
|
|
23
|
+
name = 'contour-lines';
|
|
24
|
+
|
|
25
|
+
fragmentShader = /* glsl */ `
|
|
26
|
+
precision highp float;
|
|
27
|
+
#ifdef GL_OES_standard_derivatives
|
|
28
|
+
#extension GL_OES_standard_derivatives : enable
|
|
29
|
+
#endif
|
|
30
|
+
|
|
31
|
+
uniform float uTimeInternal;
|
|
32
|
+
uniform vec2 uResolution;
|
|
33
|
+
uniform vec3 uBg;
|
|
34
|
+
uniform vec3 uLine;
|
|
35
|
+
uniform vec3 uAccent;
|
|
36
|
+
uniform float uDensity;
|
|
37
|
+
uniform float uThickness;
|
|
38
|
+
uniform float uWarp;
|
|
39
|
+
uniform float uGlow;
|
|
40
|
+
uniform float uGrain;
|
|
41
|
+
|
|
42
|
+
varying vec2 vUv;
|
|
43
|
+
|
|
44
|
+
float hash12(vec2 p) {
|
|
45
|
+
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
|
|
46
|
+
p3 += dot(p3, p3.yzx + 33.33);
|
|
47
|
+
return fract((p3.x + p3.y) * p3.z);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
float noise(vec2 p) {
|
|
51
|
+
vec2 i = floor(p);
|
|
52
|
+
vec2 f = fract(p);
|
|
53
|
+
float a = hash12(i);
|
|
54
|
+
float b = hash12(i + vec2(1.0, 0.0));
|
|
55
|
+
float c = hash12(i + vec2(0.0, 1.0));
|
|
56
|
+
float d = hash12(i + vec2(1.0, 1.0));
|
|
57
|
+
vec2 u = f * f * (3.0 - 2.0 * f);
|
|
58
|
+
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
float fbm(vec2 p) {
|
|
62
|
+
float v = 0.0;
|
|
63
|
+
float a = 0.5;
|
|
64
|
+
mat2 rot = mat2(0.80, -0.60, 0.60, 0.80);
|
|
65
|
+
for (int i = 0; i < 5; i++) {
|
|
66
|
+
v += a * noise(p);
|
|
67
|
+
p = rot * p * 2.02 + 19.19;
|
|
68
|
+
a *= 0.55;
|
|
69
|
+
}
|
|
70
|
+
return v;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
float aawidth(float x) {
|
|
74
|
+
#ifdef GL_OES_standard_derivatives
|
|
75
|
+
return fwidth(x);
|
|
76
|
+
#else
|
|
77
|
+
return 1.0 / max(1.0, min(uResolution.x, uResolution.y));
|
|
78
|
+
#endif
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
void main() {
|
|
82
|
+
float aspect = uResolution.x / uResolution.y;
|
|
83
|
+
vec2 uv = vUv;
|
|
84
|
+
vec2 p = (uv - 0.5) * vec2(aspect, 1.0);
|
|
85
|
+
float t = uTimeInternal;
|
|
86
|
+
|
|
87
|
+
// Height field
|
|
88
|
+
vec2 flow = vec2(0.10 * t, -0.06 * t);
|
|
89
|
+
float h = fbm(p * 1.25 + flow);
|
|
90
|
+
float h2 = fbm(p * 2.10 + vec2(-0.05 * t, 0.08 * t));
|
|
91
|
+
float height = h + 0.55 * h2;
|
|
92
|
+
|
|
93
|
+
// Warp the domain so contours bend naturally
|
|
94
|
+
vec2 w = vec2(
|
|
95
|
+
fbm(p * 1.2 + vec2(2.0, 0.15 * t)),
|
|
96
|
+
fbm(p * 1.2 + vec2(7.0, -0.12 * t))
|
|
97
|
+
);
|
|
98
|
+
p += (w - 0.5) * uWarp;
|
|
99
|
+
|
|
100
|
+
// Contour function: periodic bands
|
|
101
|
+
float c = fract((height + 0.35 * fbm(p * 1.75 + 4.0)) * uDensity);
|
|
102
|
+
float distToLine = min(c, 1.0 - c);
|
|
103
|
+
float wAA = aawidth(distToLine) * 1.4;
|
|
104
|
+
float line = 1.0 - smoothstep(uThickness, uThickness + wAA, distToLine);
|
|
105
|
+
|
|
106
|
+
// Glow around lines
|
|
107
|
+
float glow = smoothstep(0.45, 0.0, distToLine) * uGlow;
|
|
108
|
+
|
|
109
|
+
vec3 col = uBg;
|
|
110
|
+
col += uLine * line;
|
|
111
|
+
col += uAccent * glow;
|
|
112
|
+
|
|
113
|
+
// Grain
|
|
114
|
+
float g = (hash12(uv * uResolution + t * 60.0) - 0.5) * 2.0;
|
|
115
|
+
col += g * uGrain;
|
|
116
|
+
|
|
117
|
+
gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
|
|
118
|
+
}
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
uniforms: any;
|
|
122
|
+
private speed: number;
|
|
123
|
+
|
|
124
|
+
constructor(config: ContourLinesConfig) {
|
|
125
|
+
const bg = new Color(config.backgroundColor);
|
|
126
|
+
const line = new Color(config.lineColor);
|
|
127
|
+
const accent = new Color(config.accentColor ?? config.lineColor);
|
|
128
|
+
this.speed = config.speed ?? 0.35;
|
|
129
|
+
|
|
130
|
+
this.uniforms = {
|
|
131
|
+
uBg: { value: [bg.r, bg.g, bg.b] },
|
|
132
|
+
uLine: { value: [line.r, line.g, line.b] },
|
|
133
|
+
uAccent: { value: [accent.r, accent.g, accent.b] },
|
|
134
|
+
uDensity: { value: config.density ?? 12 },
|
|
135
|
+
uThickness: { value: config.thickness ?? 0.075 },
|
|
136
|
+
uWarp: { value: config.warp ?? 0.9 },
|
|
137
|
+
uGlow: { value: config.glow ?? 0.35 },
|
|
138
|
+
uGrain: { value: config.grainAmount ?? 0.04 },
|
|
139
|
+
uTimeInternal: { value: 0 },
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
onRender(dt: number) {
|
|
144
|
+
this.uniforms.uTimeInternal.value += dt * 0.001 * this.speed;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Color } from 'ogl';
|
|
2
|
+
import { ShaderPlugin } from '../core/types';
|
|
3
|
+
|
|
4
|
+
export type DreamyBokehConfig = {
|
|
5
|
+
/** Background gradient bottom/top */
|
|
6
|
+
backgroundBottom: string;
|
|
7
|
+
backgroundTop: string;
|
|
8
|
+
|
|
9
|
+
/** A 3-color palette for the bokeh highlights */
|
|
10
|
+
colorA?: string; // default "#ffd1f3"
|
|
11
|
+
colorB?: string; // default "#8be9ff"
|
|
12
|
+
colorC?: string; // default "#b7ff9b"
|
|
13
|
+
|
|
14
|
+
/** Bokeh density multiplier (0..3) */
|
|
15
|
+
density?: number; // default 1.0
|
|
16
|
+
/** Bokeh size multiplier (0.5..2) */
|
|
17
|
+
size?: number; // default 1.0
|
|
18
|
+
/** Edge softness / blur multiplier (0.5..2) */
|
|
19
|
+
blur?: number; // default 1.0
|
|
20
|
+
/** Motion speed */
|
|
21
|
+
speed?: number; // default 0.25
|
|
22
|
+
/** Vignette strength (0..1) */
|
|
23
|
+
vignette?: number; // default 0.35
|
|
24
|
+
/** Grain strength (0..0.15) */
|
|
25
|
+
grainAmount?: number; // default 0.03
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export class DreamyBokehPlugin implements ShaderPlugin {
|
|
29
|
+
name = 'dreamy-bokeh';
|
|
30
|
+
|
|
31
|
+
fragmentShader = /* glsl */ `
|
|
32
|
+
precision highp float;
|
|
33
|
+
|
|
34
|
+
uniform float uTimeInternal;
|
|
35
|
+
uniform vec2 uResolution;
|
|
36
|
+
uniform vec3 uBg0;
|
|
37
|
+
uniform vec3 uBg1;
|
|
38
|
+
uniform vec3 uA;
|
|
39
|
+
uniform vec3 uB;
|
|
40
|
+
uniform vec3 uC;
|
|
41
|
+
uniform float uDensity;
|
|
42
|
+
uniform float uSize;
|
|
43
|
+
uniform float uBlur;
|
|
44
|
+
uniform float uVignette;
|
|
45
|
+
uniform float uGrain;
|
|
46
|
+
|
|
47
|
+
varying vec2 vUv;
|
|
48
|
+
|
|
49
|
+
float hash12(vec2 p) {
|
|
50
|
+
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
|
|
51
|
+
p3 += dot(p3, p3.yzx + 33.33);
|
|
52
|
+
return fract((p3.x + p3.y) * p3.z);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
vec2 hash22(vec2 p) {
|
|
56
|
+
float n = hash12(p);
|
|
57
|
+
return vec2(n, hash12(p + n + 19.19));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
float softCircle(vec2 d, float r, float blur) {
|
|
61
|
+
float dist = length(d);
|
|
62
|
+
// inside -> 1, outside -> 0, with soft falloff
|
|
63
|
+
// NOTE: smoothstep requires edge0 < edge1.
|
|
64
|
+
float b = max(0.0001, blur);
|
|
65
|
+
float a = 1.0 - smoothstep(max(0.0, r - b), r, dist);
|
|
66
|
+
return a * a;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
vec3 palette3(float h) {
|
|
70
|
+
// Smoothly mix between 3 user colors
|
|
71
|
+
float t0 = smoothstep(0.0, 1.0, h);
|
|
72
|
+
vec3 ab = mix(uA, uB, smoothstep(0.0, 0.65, t0));
|
|
73
|
+
return mix(ab, uC, smoothstep(0.35, 1.0, t0));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
vec3 bokehLayer(vec2 uv, float scale, float t, float seed, float weight) {
|
|
77
|
+
vec2 p = uv * scale;
|
|
78
|
+
vec2 ip = floor(p);
|
|
79
|
+
vec2 fp = fract(p);
|
|
80
|
+
|
|
81
|
+
vec3 col = vec3(0.0);
|
|
82
|
+
float acc = 0.0;
|
|
83
|
+
|
|
84
|
+
// Look at neighboring cells so circles crossing boundaries still render.
|
|
85
|
+
for (int j = -1; j <= 1; j++) {
|
|
86
|
+
for (int i = -1; i <= 1; i++) {
|
|
87
|
+
vec2 cell = ip + vec2(float(i), float(j));
|
|
88
|
+
float r0 = hash12(cell + seed);
|
|
89
|
+
|
|
90
|
+
// density gate: fewer circles when density is low
|
|
91
|
+
float present = step(0.18, r0) * clamp(uDensity, 0.0, 3.0);
|
|
92
|
+
if (present <= 0.0) continue;
|
|
93
|
+
|
|
94
|
+
vec2 o = hash22(cell + seed * 1.7);
|
|
95
|
+
|
|
96
|
+
// Slow drift to avoid looking tiled/static.
|
|
97
|
+
// Drift amplitude intentionally small for background usage.
|
|
98
|
+
vec2 drift = 0.08 * vec2(
|
|
99
|
+
sin(t * 0.25 + r0 * 6.2831),
|
|
100
|
+
cos(t * 0.21 + r0 * 4.9132)
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Center in this cell (0..1), then shift to neighbor offset
|
|
104
|
+
vec2 c = vec2(float(i), float(j)) + o + drift;
|
|
105
|
+
vec2 d = fp - c;
|
|
106
|
+
|
|
107
|
+
float radius = mix(0.10, 0.44, pow(r0, 2.3)) * uSize;
|
|
108
|
+
float blur = mix(0.05, 0.18, hash12(cell + seed + 7.7)) * uBlur;
|
|
109
|
+
|
|
110
|
+
float a = softCircle(d, radius, blur);
|
|
111
|
+
|
|
112
|
+
// Add a soft “lens glow” lobe (subtle)
|
|
113
|
+
float glow = exp(-dot(d, d) / max(0.0001, radius * radius) * 1.9);
|
|
114
|
+
a = a * 0.72 + glow * 0.28;
|
|
115
|
+
|
|
116
|
+
// Color per circle, slightly biased to highlight variety
|
|
117
|
+
vec3 ccol = palette3(hash12(cell + seed + 3.3));
|
|
118
|
+
|
|
119
|
+
// Gentle twinkle (avoid flicker)
|
|
120
|
+
float tw = 1.0 + 0.12 * sin(t * 1.1 + r0 * 10.0);
|
|
121
|
+
|
|
122
|
+
col += ccol * a * tw;
|
|
123
|
+
acc += a;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Normalize by accumulated alpha to keep brightness stable.
|
|
128
|
+
col *= weight / (1.0 + acc * 0.85);
|
|
129
|
+
return col;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
void main() {
|
|
133
|
+
float aspect = uResolution.x / uResolution.y;
|
|
134
|
+
vec2 uv = vUv;
|
|
135
|
+
vec2 p = (uv - 0.5) * vec2(aspect, 1.0);
|
|
136
|
+
float t = uTimeInternal;
|
|
137
|
+
|
|
138
|
+
// Background gradient with a slight vertical curve.
|
|
139
|
+
float g = smoothstep(-0.7, 0.85, p.y + 0.08 * sin(p.x * 0.7));
|
|
140
|
+
vec3 col = mix(uBg0, uBg1, g);
|
|
141
|
+
|
|
142
|
+
// Multi-scale bokeh (parallax-ish via slight offsets).
|
|
143
|
+
col += bokehLayer(uv + vec2(-0.010 * t, 0.006 * t), 10.0, t, 11.0, 1.0);
|
|
144
|
+
col += bokehLayer(uv + vec2(-0.018 * t, 0.010 * t), 16.0, t + 3.7, 37.0, 0.9);
|
|
145
|
+
col += bokehLayer(uv + vec2(-0.030 * t, 0.016 * t), 26.0, t + 9.1, 83.0, 0.7);
|
|
146
|
+
|
|
147
|
+
// Vignette
|
|
148
|
+
float v = 1.0 - smoothstep(0.25, 1.15, length(p * vec2(1.0, 0.9)));
|
|
149
|
+
col *= mix(1.0, v, clamp(uVignette, 0.0, 1.0));
|
|
150
|
+
|
|
151
|
+
// Grain
|
|
152
|
+
float gr = (hash12(uv * uResolution + t * 61.0) - 0.5) * 2.0;
|
|
153
|
+
col += gr * uGrain;
|
|
154
|
+
|
|
155
|
+
gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
|
|
156
|
+
}
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
uniforms: any;
|
|
160
|
+
private speed: number;
|
|
161
|
+
|
|
162
|
+
constructor(config: DreamyBokehConfig) {
|
|
163
|
+
const bg0 = new Color(config.backgroundBottom);
|
|
164
|
+
const bg1 = new Color(config.backgroundTop);
|
|
165
|
+
const a = new Color(config.colorA ?? '#ffd1f3');
|
|
166
|
+
const b = new Color(config.colorB ?? '#8be9ff');
|
|
167
|
+
const c = new Color(config.colorC ?? '#b7ff9b');
|
|
168
|
+
|
|
169
|
+
this.speed = config.speed ?? 0.25;
|
|
170
|
+
|
|
171
|
+
this.uniforms = {
|
|
172
|
+
uBg0: { value: [bg0.r, bg0.g, bg0.b] },
|
|
173
|
+
uBg1: { value: [bg1.r, bg1.g, bg1.b] },
|
|
174
|
+
uA: { value: [a.r, a.g, a.b] },
|
|
175
|
+
uB: { value: [b.r, b.g, b.b] },
|
|
176
|
+
uC: { value: [c.r, c.g, c.b] },
|
|
177
|
+
uDensity: { value: config.density ?? 1.0 },
|
|
178
|
+
uSize: { value: config.size ?? 1.0 },
|
|
179
|
+
uBlur: { value: config.blur ?? 1.0 },
|
|
180
|
+
uVignette: { value: config.vignette ?? 0.35 },
|
|
181
|
+
uGrain: { value: config.grainAmount ?? 0.03 },
|
|
182
|
+
uTimeInternal: { value: 0 },
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
onRender(dt: number) {
|
|
187
|
+
this.uniforms.uTimeInternal.value += dt * 0.001 * this.speed;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|