@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.
@@ -0,0 +1,182 @@
1
+ import { Color } from 'ogl';
2
+ import { ShaderPlugin } from '../core/types';
3
+
4
+ export type InkWashConfig = {
5
+ /** Paper/background color */
6
+ paperColor: string;
7
+ /** Ink/pigment color */
8
+ inkColor: string;
9
+
10
+ /** Overall pattern scale */
11
+ scale?: number; // default 1.4
12
+ /** Motion speed */
13
+ speed?: number; // default 0.18
14
+ /** Flow / warping amount */
15
+ flow?: number; // default 0.85
16
+ /** Contrast / punch of ink */
17
+ contrast?: number; // default 1.15
18
+ /** Granulation (pigment clumping) */
19
+ granulation?: number; // default 0.35
20
+ /** Vignette (0..1) */
21
+ vignette?: number; // default 0.35
22
+ /** Grain strength */
23
+ grainAmount?: number; // default 0.03
24
+ };
25
+
26
+ export class InkWashPlugin implements ShaderPlugin {
27
+ name = 'ink-wash';
28
+
29
+ fragmentShader = /* glsl */ `
30
+ precision highp float;
31
+ #ifdef GL_OES_standard_derivatives
32
+ #extension GL_OES_standard_derivatives : enable
33
+ #endif
34
+
35
+ uniform float uTimeInternal;
36
+ uniform vec2 uResolution;
37
+ uniform vec3 uPaper;
38
+ uniform vec3 uInk;
39
+ uniform float uScale;
40
+ uniform float uFlow;
41
+ uniform float uContrast;
42
+ uniform float uGran;
43
+ uniform float uVignette;
44
+ uniform float uGrain;
45
+
46
+ varying vec2 vUv;
47
+
48
+ float hash12(vec2 p) {
49
+ vec3 p3 = fract(vec3(p.xyx) * 0.1031);
50
+ p3 += dot(p3, p3.yzx + 33.33);
51
+ return fract((p3.x + p3.y) * p3.z);
52
+ }
53
+
54
+ float noise(vec2 p) {
55
+ vec2 i = floor(p);
56
+ vec2 f = fract(p);
57
+ float a = hash12(i);
58
+ float b = hash12(i + vec2(1.0, 0.0));
59
+ float c = hash12(i + vec2(0.0, 1.0));
60
+ float d = hash12(i + vec2(1.0, 1.0));
61
+ vec2 u = f * f * (3.0 - 2.0 * f);
62
+ return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
63
+ }
64
+
65
+ float fbm(vec2 p) {
66
+ float v = 0.0;
67
+ float a = 0.5;
68
+ mat2 rot = mat2(0.80, -0.60, 0.60, 0.80);
69
+ for (int i = 0; i < 6; i++) {
70
+ v += a * noise(p);
71
+ p = rot * p * 2.02 + 17.17;
72
+ a *= 0.55;
73
+ }
74
+ return v;
75
+ }
76
+
77
+ vec2 flowField(vec2 p, float t) {
78
+ // Curl-ish flow from two fbm samples
79
+ float n1 = fbm(p * 1.10 + vec2(0.0, t * 0.12));
80
+ float n2 = fbm(p * 1.10 + vec2(13.1, -t * 0.10));
81
+ vec2 f = vec2(n1 - 0.5, n2 - 0.5);
82
+ return f;
83
+ }
84
+
85
+ float aawidth(float x) {
86
+ #ifdef GL_OES_standard_derivatives
87
+ return fwidth(x);
88
+ #else
89
+ return 1.0 / max(1.0, min(uResolution.x, uResolution.y));
90
+ #endif
91
+ }
92
+
93
+ void main() {
94
+ float aspect = uResolution.x / uResolution.y;
95
+ vec2 uv = vUv;
96
+ vec2 p = (uv - 0.5) * vec2(aspect, 1.0);
97
+ float t = uTimeInternal;
98
+
99
+ // Base paper tone + subtle fiber texture
100
+ float fiber = noise(uv * vec2(uResolution.x, uResolution.y) * 0.0025);
101
+ float fiber2 = noise(uv * vec2(uResolution.x, uResolution.y) * 0.0060 + 19.19);
102
+ vec3 col = uPaper + (fiber - 0.5) * 0.05 + (fiber2 - 0.5) * 0.03;
103
+
104
+ // Domain-warped pigment field
105
+ vec2 q = p * uScale;
106
+ vec2 f = flowField(q, t) * (0.55 * uFlow);
107
+ vec2 r = flowField(q + f * 1.35, t + 7.7) * (0.55 * uFlow);
108
+ vec2 w = f + r;
109
+
110
+ float base = fbm(q + w);
111
+ float detail = fbm(q * 2.10 - w * 0.70 + vec2(-0.07 * t, 0.05 * t));
112
+ float field = base * 0.72 + detail * 0.35;
113
+
114
+ // Contrast shaping
115
+ field = clamp(field, 0.0, 1.0);
116
+ field = pow(field, 1.0 / max(0.001, uContrast));
117
+
118
+ // Pigment coverage (soft threshold)
119
+ float edgeW = 0.08 + 0.06 * (1.0 - uFlow);
120
+ float ink = smoothstep(0.38 - edgeW, 0.68 + edgeW, field);
121
+
122
+ // “Tide lines”: emphasize places where the field changes quickly
123
+ float grad = 0.0;
124
+ #ifdef GL_OES_standard_derivatives
125
+ grad = (abs(dFdx(field)) + abs(dFdy(field))) * 6.0;
126
+ #else
127
+ grad = aawidth(field) * 120.0;
128
+ #endif
129
+ float tide = smoothstep(0.20, 0.95, grad);
130
+ tide *= (1.0 - ink) * 0.55 + ink * 0.25; // strongest near transitions
131
+
132
+ // Pigment granulation: high-frequency noise visible where ink exists
133
+ float gran = noise(q * 10.0 + vec2(31.2, 17.8)) - 0.5;
134
+ float gran2 = noise(q * 18.0 + vec2(9.7, 53.1)) - 0.5;
135
+ float granTex = gran * 0.8 + gran2 * 0.6;
136
+ float granAmt = uGran * (0.35 + 0.65 * ink);
137
+
138
+ float pigment = clamp(ink + tide * 0.35 + granTex * granAmt, 0.0, 1.0);
139
+
140
+ // Mix ink into paper
141
+ col = mix(col, mix(col, uInk, 0.92), pigment);
142
+
143
+ // Vignette
144
+ float v = 1.0 - smoothstep(0.25, 1.15, length(p * vec2(1.0, 0.95)));
145
+ col *= mix(1.0, v, clamp(uVignette, 0.0, 1.0));
146
+
147
+ // Grain
148
+ float g = (hash12(uv * uResolution + t * 61.0) - 0.5) * 2.0;
149
+ col += g * uGrain;
150
+
151
+ gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
152
+ }
153
+ `;
154
+
155
+ uniforms: any;
156
+ private speed: number;
157
+
158
+ constructor(config: InkWashConfig) {
159
+ const paper = new Color(config.paperColor);
160
+ const ink = new Color(config.inkColor);
161
+
162
+ this.speed = config.speed ?? 0.18;
163
+
164
+ this.uniforms = {
165
+ uPaper: { value: [paper.r, paper.g, paper.b] },
166
+ uInk: { value: [ink.r, ink.g, ink.b] },
167
+ uScale: { value: config.scale ?? 1.4 },
168
+ uFlow: { value: config.flow ?? 0.85 },
169
+ uContrast: { value: config.contrast ?? 1.15 },
170
+ uGran: { value: config.granulation ?? 0.35 },
171
+ uVignette: { value: config.vignette ?? 0.35 },
172
+ uGrain: { value: config.grainAmount ?? 0.03 },
173
+ uTimeInternal: { value: 0 },
174
+ };
175
+ }
176
+
177
+ onRender(dt: number) {
178
+ this.uniforms.uTimeInternal.value += dt * 0.001 * this.speed;
179
+ }
180
+ }
181
+
182
+
@@ -0,0 +1,140 @@
1
+ import { Color } from 'ogl';
2
+ import { ShaderPlugin } from '../core/types';
3
+
4
+ export type LiquidOrbConfig = {
5
+ color: string;
6
+ backgroundColor: string;
7
+ count?: number; // Number of blobs, max 20
8
+ speed?: number;
9
+ gooeyness?: number; // Smooth-min blending factor, default 0.3
10
+ edgeSoftness?: number; // Edge AA/softness, default 0.02
11
+ };
12
+
13
+ type Orb = {
14
+ x: number;
15
+ y: number;
16
+ vx: number;
17
+ vy: number;
18
+ radius: number;
19
+ };
20
+
21
+ export class LiquidOrbPlugin implements ShaderPlugin {
22
+ name = 'liquid-orb';
23
+ private static MAX_ORBS = 20;
24
+
25
+ fragmentShader = /* glsl */ `
26
+ precision highp float;
27
+ uniform vec2 uResolution;
28
+ uniform vec3 uColor;
29
+ uniform vec3 uBgColor;
30
+ uniform float uGooeyness;
31
+ uniform float uEdgeSoftness;
32
+
33
+ uniform int uCount;
34
+ uniform vec3 uOrbs[${LiquidOrbPlugin.MAX_ORBS}]; // x, y, radius
35
+
36
+ varying vec2 vUv;
37
+
38
+ // Smooth Minimum function (The "Goo" math)
39
+ float smin(float a, float b, float k) {
40
+ float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
41
+ return mix(b, a, h) - k * h * (1.0 - h);
42
+ }
43
+
44
+ void main() {
45
+ float aspect = uResolution.x / uResolution.y;
46
+ vec2 uv = vUv * 2.0 - 1.0;
47
+ uv.x *= aspect;
48
+
49
+ // Calculate Signed Distance Field (SDF)
50
+ // Start with a large distance
51
+ float d = 100.0;
52
+
53
+ for (int i = 0; i < ${LiquidOrbPlugin.MAX_ORBS}; i++) {
54
+ if (i >= uCount) break;
55
+
56
+ vec3 orb = uOrbs[i];
57
+ vec2 pos = orb.xy;
58
+ pos.x *= aspect; // Correct aspect for orb position too
59
+
60
+ float radius = orb.z;
61
+
62
+ // Distance from pixel to orb center minus radius
63
+ float dist = length(uv - pos) - radius;
64
+
65
+ // Smoothly blend distances
66
+ d = smin(d, dist, uGooeyness);
67
+ }
68
+
69
+ // Render based on threshold
70
+ // If d < 0.0, we are inside the goo
71
+ // We use smoothstep for antialiasing the edge
72
+ float alpha = 1.0 - smoothstep(0.0, max(0.0001, uEdgeSoftness), d);
73
+
74
+ vec3 color = mix(uBgColor, uColor, alpha);
75
+
76
+ gl_FragColor = vec4(color, 1.0);
77
+ }
78
+ `;
79
+
80
+ uniforms: any;
81
+ private orbs: Orb[] = [];
82
+ private orbData: Array<[number, number, number]> = [];
83
+ private speedMultiplier: number;
84
+
85
+ constructor(config: LiquidOrbConfig) {
86
+ const fg = new Color(config.color);
87
+ const bg = new Color(config.backgroundColor);
88
+ const count = Math.min(config.count ?? 5, LiquidOrbPlugin.MAX_ORBS);
89
+ this.speedMultiplier = config.speed ?? 0.5;
90
+
91
+ // Initialize Physics
92
+ for (let i = 0; i < count; i++) {
93
+ this.orbs.push({
94
+ x: (Math.random() * 2 - 1) * 0.8,
95
+ y: (Math.random() * 2 - 1) * 0.8,
96
+ vx: (Math.random() - 0.5) * 0.01,
97
+ vy: (Math.random() - 0.5) * 0.01,
98
+ radius: 0.2 + Math.random() * 0.2,
99
+ });
100
+ }
101
+
102
+ // IMPORTANT: OGL expects uniform vec3[] values as an array of vec3-ish triplets,
103
+ // not a packed Float32Array. If we pass a packed typed array, it will not map
104
+ // correctly to `uniform vec3 uOrbs[MAX]` and you'll effectively see zeros.
105
+ this.orbData = Array.from({ length: LiquidOrbPlugin.MAX_ORBS }, () => [0, 0, 0]);
106
+
107
+ this.uniforms = {
108
+ uColor: { value: [fg.r, fg.g, fg.b] },
109
+ uBgColor: { value: [bg.r, bg.g, bg.b] },
110
+ uCount: { value: count },
111
+ uOrbs: { value: this.orbData },
112
+ uGooeyness: { value: config.gooeyness ?? 0.3 },
113
+ uEdgeSoftness: { value: config.edgeSoftness ?? 0.02 },
114
+ };
115
+ }
116
+
117
+ onRender(dt: number) {
118
+ // Physics Step
119
+ // dt is usually ~16ms.
120
+
121
+ let i = 0;
122
+ for (const orb of this.orbs) {
123
+ orb.x += orb.vx * this.speedMultiplier * (dt / 16);
124
+ orb.y += orb.vy * this.speedMultiplier * (dt / 16);
125
+
126
+ // Bounce off walls
127
+ if (orb.x < -1.0 || orb.x > 1.0) orb.vx *= -1;
128
+ if (orb.y < -1.0 || orb.y > 1.0) orb.vy *= -1;
129
+
130
+ // Update vec3[] triplets
131
+ const v = this.orbData[i];
132
+ v[0] = orb.x;
133
+ v[1] = orb.y;
134
+ v[2] = orb.radius;
135
+ i++;
136
+ }
137
+
138
+ // No need to reassign; values are updated in-place.
139
+ }
140
+ }
@@ -0,0 +1,77 @@
1
+ import { Color } from 'ogl';
2
+ import { ShaderPlugin } from '../core/types';
3
+
4
+ export type RetroGridConfig = {
5
+ gridColor: string;
6
+ backgroundColor: string;
7
+ speed?: number; // default 1.0
8
+ };
9
+
10
+ export class RetroGridPlugin implements ShaderPlugin {
11
+ name = 'retro-grid';
12
+
13
+ fragmentShader = /* glsl */ `
14
+ precision highp float;
15
+ uniform float uTime;
16
+ uniform vec3 uGridColor;
17
+ uniform vec3 uBgColor;
18
+ uniform float uSpeed;
19
+
20
+ varying vec2 vUv;
21
+
22
+ void main() {
23
+ // Normalize UV to -1 to 1
24
+ vec2 uv = vUv * 2.0 - 1.0;
25
+
26
+ // Hoziron offset
27
+ float horizon = 0.0;
28
+ float fov = 0.5;
29
+
30
+ // 3D Projection Logic
31
+ // We only care about the bottom half for the floor
32
+ if (uv.y > horizon) {
33
+ // Sky (simple gradient or solid)
34
+ gl_FragColor = vec4(uBgColor, 1.0);
35
+ return;
36
+ }
37
+
38
+ // Project the 2D pixel to 3D floor coordinates
39
+ // x = uv.x / |y| to flare out perspective
40
+ // z = 1.0 / |y| to simulate depth
41
+ float floorY = abs(uv.y - horizon);
42
+ vec3 coord = vec3(uv.x / floorY, floorY, 1.0 / floorY);
43
+
44
+ // Move the grid by time
45
+ coord.z += uTime * uSpeed;
46
+
47
+ // Grid Logic
48
+ vec2 gridUV = coord.xz * fov;
49
+ vec2 grid = fract(gridUV) - 0.5;
50
+
51
+ // Thickness of lines (derivative for anti-aliasing approximation or hard coded)
52
+ float line = min(abs(grid.x), abs(grid.y));
53
+
54
+ float gridVal = 1.0 - smoothstep(0.0, 0.05 * coord.z, line);
55
+
56
+ // Fade out grid near horizon (fog)
57
+ float fog = smoothstep(0.0, 1.5, floorY);
58
+
59
+ vec3 color = mix(uBgColor, uGridColor, gridVal * fog);
60
+
61
+ gl_FragColor = vec4(color, 1.0);
62
+ }
63
+ `;
64
+
65
+ uniforms: any;
66
+
67
+ constructor(config: RetroGridConfig) {
68
+ const grid = new Color(config.gridColor);
69
+ const bg = new Color(config.backgroundColor);
70
+
71
+ this.uniforms = {
72
+ uGridColor: { value: [grid.r, grid.g, grid.b] },
73
+ uBgColor: { value: [bg.r, bg.g, bg.b] },
74
+ uSpeed: { value: config.speed ?? 1.0 },
75
+ };
76
+ }
77
+ }
@@ -0,0 +1,156 @@
1
+ import { Color } from 'ogl';
2
+ import { ShaderPlugin } from '../core/types';
3
+
4
+ export type SoftStarfieldConfig = {
5
+ /** Background gradient bottom/top */
6
+ backgroundBottom: string;
7
+ backgroundTop: string;
8
+ /** Star color (usually near-white) */
9
+ starColor?: string; // default "#ffffff"
10
+ /** Star density multiplier */
11
+ density?: number; // default 1.0
12
+ /** Star size multiplier */
13
+ size?: number; // default 1.0
14
+ /** Twinkle amount */
15
+ twinkle?: number; // default 0.35
16
+ /** Nebula tint */
17
+ nebulaColor?: string; // default "#6a5cff"
18
+ /** Nebula strength */
19
+ nebula?: number; // default 0.35
20
+ /** Motion speed */
21
+ speed?: number; // default 0.2
22
+ /** Grain 0.. */
23
+ grainAmount?: number; // default 0.04
24
+ };
25
+
26
+ export class SoftStarfieldPlugin implements ShaderPlugin {
27
+ name = 'soft-starfield';
28
+
29
+ fragmentShader = /* glsl */ `
30
+ precision highp float;
31
+ uniform float uTimeInternal;
32
+ uniform vec2 uResolution;
33
+ uniform vec3 uBg0;
34
+ uniform vec3 uBg1;
35
+ uniform vec3 uStar;
36
+ uniform vec3 uNebula;
37
+ uniform float uDensity;
38
+ uniform float uSize;
39
+ uniform float uTwinkle;
40
+ uniform float uNebulaAmt;
41
+ uniform float uGrain;
42
+
43
+ varying vec2 vUv;
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
+ float noise(vec2 p) {
52
+ vec2 i = floor(p);
53
+ vec2 f = fract(p);
54
+ float a = hash12(i);
55
+ float b = hash12(i + vec2(1.0, 0.0));
56
+ float c = hash12(i + vec2(0.0, 1.0));
57
+ float d = hash12(i + vec2(1.0, 1.0));
58
+ vec2 u = f * f * (3.0 - 2.0 * f);
59
+ return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
60
+ }
61
+
62
+ float fbm(vec2 p) {
63
+ float v = 0.0;
64
+ float a = 0.5;
65
+ mat2 rot = mat2(0.80, -0.60, 0.60, 0.80);
66
+ for (int i = 0; i < 5; i++) {
67
+ v += a * noise(p);
68
+ p = rot * p * 2.02 + 19.19;
69
+ a *= 0.55;
70
+ }
71
+ return v;
72
+ }
73
+
74
+ // Simple cell-star: one star candidate per cell, softly drawn.
75
+ float starLayer(vec2 uv, float scale, float t) {
76
+ vec2 p = uv * scale;
77
+ vec2 i = floor(p);
78
+ vec2 f = fract(p) - 0.5;
79
+ float rnd = hash12(i);
80
+
81
+ // place star within cell
82
+ vec2 o = vec2(hash12(i + 13.1), hash12(i + 71.7)) - 0.5;
83
+ vec2 d = f - o * 0.45;
84
+
85
+ // size distribution: many small, few larger
86
+ float sz = mix(0.012, 0.035, pow(rnd, 7.0)) * uSize;
87
+ float core = exp(-dot(d, d) / (sz * sz));
88
+
89
+ // twinkle: slow, subtle (avoid distracting flicker)
90
+ float tw = 1.0 + (sin((rnd * 12.0) + t * 1.5) * 0.5 + 0.5) * uTwinkle;
91
+
92
+ // probability via rnd threshold (density)
93
+ float present = step(0.55, rnd) * clamp(uDensity, 0.0, 3.0);
94
+ return core * tw * present;
95
+ }
96
+
97
+ void main() {
98
+ float aspect = uResolution.x / uResolution.y;
99
+ vec2 uv = vUv;
100
+ vec2 p = (uv - 0.5) * vec2(aspect, 1.0);
101
+ float t = uTimeInternal;
102
+
103
+ // Background gradient
104
+ vec3 col = mix(uBg0, uBg1, smoothstep(-0.6, 0.8, p.y));
105
+
106
+ // Nebula: soft, low-contrast clouds
107
+ float n = fbm(p * 1.35 + vec2(-0.05 * t, 0.02 * t));
108
+ float n2 = fbm(p * 2.10 + vec2(0.03 * t, -0.04 * t));
109
+ float neb = smoothstep(0.25, 0.85, n * 0.75 + n2 * 0.35);
110
+ col += uNebula * neb * uNebulaAmt;
111
+
112
+ // Stars: 3 layers parallax-ish
113
+ float s1 = starLayer(uv + vec2(-0.010 * t, 0.006 * t), 55.0, t);
114
+ float s2 = starLayer(uv + vec2(-0.020 * t, 0.010 * t), 90.0, t + 7.7) * 0.8;
115
+ float s3 = starLayer(uv + vec2(-0.035 * t, 0.016 * t), 140.0, t + 13.3) * 0.6;
116
+ float stars = clamp(s1 + s2 + s3, 0.0, 1.75);
117
+ col += uStar * stars;
118
+
119
+ // Grain
120
+ float g = (hash12(uv * uResolution + t * 60.0) - 0.5) * 2.0;
121
+ col += g * uGrain;
122
+
123
+ gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
124
+ }
125
+ `;
126
+
127
+ uniforms: any;
128
+ private speed: number;
129
+
130
+ constructor(config: SoftStarfieldConfig) {
131
+ const bg0 = new Color(config.backgroundBottom);
132
+ const bg1 = new Color(config.backgroundTop);
133
+ const star = new Color(config.starColor ?? '#ffffff');
134
+ const neb = new Color(config.nebulaColor ?? '#6a5cff');
135
+ this.speed = config.speed ?? 0.2;
136
+
137
+ this.uniforms = {
138
+ uBg0: { value: [bg0.r, bg0.g, bg0.b] },
139
+ uBg1: { value: [bg1.r, bg1.g, bg1.b] },
140
+ uStar: { value: [star.r, star.g, star.b] },
141
+ uNebula: { value: [neb.r, neb.g, neb.b] },
142
+ uDensity: { value: config.density ?? 1.0 },
143
+ uSize: { value: config.size ?? 1.0 },
144
+ uTwinkle: { value: config.twinkle ?? 0.35 },
145
+ uNebulaAmt: { value: config.nebula ?? 0.35 },
146
+ uGrain: { value: config.grainAmount ?? 0.04 },
147
+ uTimeInternal: { value: 0 },
148
+ };
149
+ }
150
+
151
+ onRender(dt: number) {
152
+ this.uniforms.uTimeInternal.value += dt * 0.001 * this.speed;
153
+ }
154
+ }
155
+
156
+