chroma-noise 0.0.1 → 0.0.3
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/README.md +4 -6
- package/dist/Gradient.svelte +168 -221
- package/dist/Gradient.svelte.d.ts +2 -19
- package/dist/fragment.txt +157 -0
- package/dist/gradient.worker.d.ts +1 -0
- package/dist/gradient.worker.js +83 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +22 -0
- package/dist/utils.js +115 -0
- package/package.json +13 -12
- package/dist/gradient.frag.glsl +0 -154
- package/dist/gradient.vert.glsl +0 -4
package/README.md
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# chroma-noise
|
|
2
2
|
|
|
3
|
-
Gradient noise for Svelte
|
|
3
|
+
Gradient noise for Svelte. With warp and grain.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### Site & playground
|
|
6
|
+
https://chromanoise.netlify.app
|
|
6
7
|
|
|
7
8
|
### Roadmap
|
|
8
9
|
- [] docs
|
|
9
|
-
- []
|
|
10
|
-
|
|
11
|
-
### Site & playground
|
|
12
|
-
https://chromanoise.netlify.app
|
|
10
|
+
- [] fix grain size option
|
package/dist/Gradient.svelte
CHANGED
|
@@ -1,221 +1,168 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { hexToRgb } from '
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// @ts-ignore
|
|
170
|
-
u_resolution = gl.getUniformLocation(program, 'u_resolution');
|
|
171
|
-
// @ts-ignore
|
|
172
|
-
u_time = gl.getUniformLocation(program, 'u_time');
|
|
173
|
-
// @ts-ignore
|
|
174
|
-
u_count = gl.getUniformLocation(program, 'u_count');
|
|
175
|
-
// @ts-ignore
|
|
176
|
-
u_colors = gl.getUniformLocation(program, 'u_colors');
|
|
177
|
-
// @ts-ignore
|
|
178
|
-
u_positions = gl.getUniformLocation(program, 'u_positions');
|
|
179
|
-
// @ts-ignore
|
|
180
|
-
u_radius = gl.getUniformLocation(program, 'u_radius');
|
|
181
|
-
// @ts-ignore
|
|
182
|
-
u_intensity = gl.getUniformLocation(program, 'u_intensity');
|
|
183
|
-
// @ts-ignore
|
|
184
|
-
u_warpMode = gl.getUniformLocation(program, 'u_warpMode');
|
|
185
|
-
// @ts-ignore
|
|
186
|
-
u_warpSize = gl.getUniformLocation(program, 'u_warpSize');
|
|
187
|
-
// @ts-ignore
|
|
188
|
-
u_warpAmount = gl.getUniformLocation(program, 'u_warpAmount');
|
|
189
|
-
// @ts-ignore
|
|
190
|
-
u_grainAmount = gl.getUniformLocation(program, 'u_grainAmount');
|
|
191
|
-
// @ts-ignore
|
|
192
|
-
u_grainSize = gl.getUniformLocation(program, 'u_grainSize');
|
|
193
|
-
// @ts-ignore
|
|
194
|
-
u_grainAnimate = gl.getUniformLocation(program, 'u_grainAnimate');
|
|
195
|
-
// @ts-ignore
|
|
196
|
-
u_seed = gl.getUniformLocation(program, 'u_seed');
|
|
197
|
-
|
|
198
|
-
frameId = requestAnimationFrame(render);
|
|
199
|
-
window.addEventListener('resize', updateUniforms);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
onDestroy(() => {
|
|
203
|
-
if (!browser) return;
|
|
204
|
-
|
|
205
|
-
running = false;
|
|
206
|
-
if (frameId) cancelAnimationFrame(frameId);
|
|
207
|
-
window.removeEventListener('resize', updateUniforms);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
$effect(() => {
|
|
211
|
-
if (gl && program) {
|
|
212
|
-
updateUniforms();
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
</script>
|
|
216
|
-
|
|
217
|
-
<canvas bind:this={canvas}></canvas>
|
|
218
|
-
|
|
219
|
-
<style>
|
|
220
|
-
canvas { width: 100%; height: 100%; display: block; }
|
|
221
|
-
</style>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import frag from './fragment.txt?raw';
|
|
3
|
+
import { onMount, onDestroy } from 'svelte';
|
|
4
|
+
import type { Warp, Grain, GradientOptions } from './index.js';
|
|
5
|
+
import { hexToRgb, isBrowser } from './utils.js';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
points = [],
|
|
9
|
+
radius = 0.6,
|
|
10
|
+
intensity = 1.0,
|
|
11
|
+
warp: initialWarp = {},
|
|
12
|
+
speed = 1.0,
|
|
13
|
+
grain: initialGrain = {},
|
|
14
|
+
maxPoints = 12,
|
|
15
|
+
seed = Math.random(),
|
|
16
|
+
currentState = $bindable('loading'),
|
|
17
|
+
class: c = '',
|
|
18
|
+
...rest
|
|
19
|
+
}: GradientOptions = $props();
|
|
20
|
+
|
|
21
|
+
const defaultWarp: Required<Warp> = { mode: 0, amount: 0, size: 1 };
|
|
22
|
+
const defaultGrain: Required<Grain> = { amount: 0, size: 1 };
|
|
23
|
+
|
|
24
|
+
const warp: Required<Warp> = $derived({ ...defaultWarp, ...initialWarp });
|
|
25
|
+
const grain: Required<Grain> = $derived({ ...defaultGrain, ...initialGrain });
|
|
26
|
+
|
|
27
|
+
let canvas: HTMLCanvasElement | undefined = $state();
|
|
28
|
+
let worker: Worker | undefined = $state();
|
|
29
|
+
let frame: number;
|
|
30
|
+
let running = true;
|
|
31
|
+
let lastTime = 0;
|
|
32
|
+
let animTime = 0;
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
function updateUniforms() {
|
|
36
|
+
if (!worker) return;
|
|
37
|
+
|
|
38
|
+
const colorsArr = new Float32Array(maxPoints * 3);
|
|
39
|
+
const posArr = new Float32Array(maxPoints * 2);
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < points.length && i < maxPoints; i++) {
|
|
42
|
+
const [r, g, b] = hexToRgb(points[i].color);
|
|
43
|
+
colorsArr.set([r, g, b], i * 3);
|
|
44
|
+
posArr.set([points[i].x, points[i].y], i * 2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
worker.postMessage({
|
|
48
|
+
type: 'updateUniforms',
|
|
49
|
+
uniforms: {
|
|
50
|
+
u_colors: colorsArr,
|
|
51
|
+
u_positions: posArr,
|
|
52
|
+
u_count: Math.min(points.length, maxPoints),
|
|
53
|
+
u_radius: radius,
|
|
54
|
+
u_intensity: intensity,
|
|
55
|
+
u_warpMode: warp.mode,
|
|
56
|
+
u_warpSize: warp.size,
|
|
57
|
+
u_warpAmount: warp.amount,
|
|
58
|
+
u_grainAmount: grain.amount,
|
|
59
|
+
u_grainSize: grain.size,
|
|
60
|
+
u_seed: seed
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function updateResolution() {
|
|
66
|
+
if (!worker || !canvas) return;
|
|
67
|
+
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
|
68
|
+
const width = Math.floor(canvas.clientWidth * dpr);
|
|
69
|
+
const height = Math.floor(canvas.clientHeight * dpr);
|
|
70
|
+
worker.postMessage({
|
|
71
|
+
type: 'updateUniforms',
|
|
72
|
+
uniforms: {
|
|
73
|
+
u_resolution: [width, height]
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function render(time: number) {
|
|
79
|
+
if (worker) {
|
|
80
|
+
if (lastTime === 0) lastTime = time;
|
|
81
|
+
const dt = (time - lastTime) * 0.001;
|
|
82
|
+
lastTime = time;
|
|
83
|
+
animTime += dt * speed;
|
|
84
|
+
|
|
85
|
+
worker.postMessage({
|
|
86
|
+
type: 'render',
|
|
87
|
+
time: animTime
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (running) {
|
|
91
|
+
currentState = 'playing';
|
|
92
|
+
frame = requestAnimationFrame(render);
|
|
93
|
+
} else {
|
|
94
|
+
currentState = 'paused';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
onMount(() => {
|
|
100
|
+
if (!canvas || !isBrowser()) return;
|
|
101
|
+
currentState = 'loading';
|
|
102
|
+
|
|
103
|
+
worker = new Worker(new URL('./gradient.worker.ts', import.meta.url), { type: 'module' });
|
|
104
|
+
|
|
105
|
+
worker.onmessage = (event: MessageEvent) => {
|
|
106
|
+
const { type } = event.data;
|
|
107
|
+
if (type === 'ready') {
|
|
108
|
+
updateUniforms();
|
|
109
|
+
currentState = 'playing';
|
|
110
|
+
} else if (type === 'rendering') {
|
|
111
|
+
currentState = 'playing';
|
|
112
|
+
} else if (type === 'paused') {
|
|
113
|
+
currentState = 'paused';
|
|
114
|
+
} else if (type === 'error') {
|
|
115
|
+
console.error('Worker error:', event.data.error);
|
|
116
|
+
currentState = 'paused';
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
|
121
|
+
const width = Math.floor(canvas.clientWidth * dpr);
|
|
122
|
+
const height = Math.floor(canvas.clientHeight * dpr);
|
|
123
|
+
canvas.width = width;
|
|
124
|
+
canvas.height = height;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const offscreenCanvas = canvas.transferControlToOffscreen();
|
|
128
|
+
worker.postMessage({
|
|
129
|
+
type: 'init',
|
|
130
|
+
offscreenCanvas,
|
|
131
|
+
fragShader: frag,
|
|
132
|
+
speed
|
|
133
|
+
}, [offscreenCanvas]);
|
|
134
|
+
|
|
135
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
136
|
+
updateResolution();
|
|
137
|
+
});
|
|
138
|
+
resizeObserver.observe(canvas);
|
|
139
|
+
frame = requestAnimationFrame(render);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('Could not transfer canvas to worker: ', error);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
onDestroy(() => {
|
|
146
|
+
running = false;
|
|
147
|
+
if (!isBrowser()) return;
|
|
148
|
+
|
|
149
|
+
if (worker) {
|
|
150
|
+
worker.postMessage({ type: 'destroy' });
|
|
151
|
+
worker.terminate();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
$effect(() => {
|
|
156
|
+
if(worker && (points.length || radius || intensity || warp || grain || seed)) {
|
|
157
|
+
updateUniforms();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<canvas bind:this={canvas} {...rest} class="{c} {currentState}"></canvas>
|
|
163
|
+
|
|
164
|
+
<style>
|
|
165
|
+
canvas { width: 100%; height: 100%; display: block; transition: opacity 1s ease-out }
|
|
166
|
+
canvas.loading { opacity: 0; }
|
|
167
|
+
canvas.playing { opacity: 1; }
|
|
168
|
+
</style>
|
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
color: string;
|
|
4
|
-
x: number;
|
|
5
|
-
y: number;
|
|
6
|
-
}[];
|
|
7
|
-
radius?: number;
|
|
8
|
-
timeAmount?: number;
|
|
9
|
-
intensity?: number;
|
|
10
|
-
warpMode?: number;
|
|
11
|
-
warpSize?: number;
|
|
12
|
-
warpAmount?: number;
|
|
13
|
-
grainAmount?: number;
|
|
14
|
-
grainSize?: number;
|
|
15
|
-
seed?: number;
|
|
16
|
-
}
|
|
17
|
-
declare const Gradient: import("svelte").Component<Props, {
|
|
18
|
-
MAX_POINTS: 12;
|
|
19
|
-
}, "">;
|
|
1
|
+
import type { GradientOptions } from './index.js';
|
|
2
|
+
declare const Gradient: import("svelte").Component<GradientOptions, {}, "currentState">;
|
|
20
3
|
type Gradient = ReturnType<typeof Gradient>;
|
|
21
4
|
export default Gradient;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#ifdef GL_ES
|
|
2
|
+
precision mediump float;
|
|
3
|
+
#endif
|
|
4
|
+
|
|
5
|
+
#define MAX_POINTS 12
|
|
6
|
+
|
|
7
|
+
uniform vec2 u_resolution;
|
|
8
|
+
uniform float u_time;
|
|
9
|
+
uniform int u_count;
|
|
10
|
+
uniform vec3 u_colors[MAX_POINTS];
|
|
11
|
+
uniform vec2 u_positions[MAX_POINTS];
|
|
12
|
+
uniform float u_radius;
|
|
13
|
+
uniform float u_intensity;
|
|
14
|
+
uniform float u_grainAmount;
|
|
15
|
+
uniform float u_grainSize;
|
|
16
|
+
uniform int u_warpMode;
|
|
17
|
+
uniform float u_warpSize;
|
|
18
|
+
uniform float u_warpAmount;
|
|
19
|
+
uniform float u_seed;
|
|
20
|
+
|
|
21
|
+
vec3 permute(vec3 x) {
|
|
22
|
+
return mod(((x * 34.0) + 1.0) * x, 289.0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
float snoise(vec2 v) {
|
|
26
|
+
const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);
|
|
27
|
+
vec2 i = floor(v + dot(v, C.yy));
|
|
28
|
+
vec2 x0 = v - i + dot(i, C.xx);
|
|
29
|
+
vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
|
30
|
+
vec4 x12 = x0.xyxy + C.xxzz;
|
|
31
|
+
x12.xy -= i1;
|
|
32
|
+
i = mod(i, 289.0);
|
|
33
|
+
vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
|
|
34
|
+
vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
|
|
35
|
+
m = m * m * m * m;
|
|
36
|
+
vec3 x = 2.0 * fract(p * C.www) - 1.0;
|
|
37
|
+
vec3 h = abs(x) - 0.5;
|
|
38
|
+
vec3 ox = floor(x + 0.5);
|
|
39
|
+
vec3 a0 = x - ox;
|
|
40
|
+
m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
|
|
41
|
+
vec3 g;
|
|
42
|
+
g.x = a0.x * x0.x + h.x * x0.y;
|
|
43
|
+
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
|
44
|
+
return 130.0 * dot(m, g);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
float fbm(vec2 p) {
|
|
48
|
+
float f = 0.0;
|
|
49
|
+
float amp = 0.5;
|
|
50
|
+
for (int i = 0; i < 5; i++) {
|
|
51
|
+
f += amp * snoise(p);
|
|
52
|
+
p *= 2.0;
|
|
53
|
+
amp *= 0.5;
|
|
54
|
+
}
|
|
55
|
+
return f;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
vec2 warpWave(vec2 uv, float t, float seed) {
|
|
59
|
+
float phase = snoise(uv * (u_warpSize * 1.5 + 0.1) + vec2(seed * 5.0));
|
|
60
|
+
float w1 = sin((uv.y * u_warpSize * 10.0) + t * 0.6 + phase * 6.28318);
|
|
61
|
+
float w2 = cos((uv.x * u_warpSize * 8.0) + t * 0.4 - phase * 6.28318 * 0.5);
|
|
62
|
+
vec2 disp = vec2(w1, w2) * (u_warpAmount * 0.5);
|
|
63
|
+
disp.x *= u_resolution.x / u_resolution.y;
|
|
64
|
+
return uv + disp;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
vec2 warpSimplex(vec2 uv, float t, float seed) {
|
|
68
|
+
vec2 noiseUv = uv * (u_warpSize * 4.0 + 0.001) + vec2(seed * 10.0, seed * 20.0);
|
|
69
|
+
float n = snoise(noiseUv + vec2(t * 0.05));
|
|
70
|
+
vec2 disp = vec2(n, snoise(noiseUv + vec2(12.34))) * u_warpAmount * 0.8;
|
|
71
|
+
disp.x *= u_resolution.x / u_resolution.y;
|
|
72
|
+
return uv + disp;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
vec2 warpFBM(vec2 uv, float t, float seed) {
|
|
76
|
+
vec2 noiseUv = uv * (u_warpSize * 3.0) + vec2(seed * 7.0);
|
|
77
|
+
float f = fbm(noiseUv + vec2(t * 0.02));
|
|
78
|
+
vec2 disp = vec2(f, fbm(noiseUv + vec2(31.4))) * u_warpAmount;
|
|
79
|
+
disp.x *= u_resolution.x / u_resolution.y;
|
|
80
|
+
return uv + disp;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
vec2 warpRidged(vec2 uv, float t, float seed) {
|
|
84
|
+
vec2 nUv = uv * (u_warpSize * 2.5) + vec2(seed * 9.0);
|
|
85
|
+
float r = 1.0 - abs(fbm(nUv));
|
|
86
|
+
vec2 disp = vec2(r, r * 0.5) * (u_warpAmount * 1.2);
|
|
87
|
+
disp.x *= u_resolution.x / u_resolution.y;
|
|
88
|
+
return uv + disp;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
vec2 warpSwirl(vec2 uv, float t) {
|
|
92
|
+
vec2 centered = uv - 0.5;
|
|
93
|
+
float radius = length(centered);
|
|
94
|
+
float angle = radius * u_warpSize * 5.0 - t * 0.5;
|
|
95
|
+
float s = sin(angle);
|
|
96
|
+
float c = cos(angle);
|
|
97
|
+
vec2 warped = vec2(c * centered.x - s * centered.y, s * centered.x + c * centered.y);
|
|
98
|
+
warped.x *= u_resolution.x / u_resolution.y;
|
|
99
|
+
warped = warped + 0.5;
|
|
100
|
+
return mix(uv, warped, u_warpAmount);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
vec2 warpRadial(vec2 uv, float t) {
|
|
104
|
+
vec2 centered = uv - 0.5;
|
|
105
|
+
float r = length(centered);
|
|
106
|
+
float theta = atan(centered.y, centered.x);
|
|
107
|
+
float wave = sin(r * u_warpSize * 20.0 - t * 2.0);
|
|
108
|
+
float r_warped = r + wave;
|
|
109
|
+
vec2 p;
|
|
110
|
+
p.x = r_warped * cos(theta);
|
|
111
|
+
p.y = r_warped * sin(theta);
|
|
112
|
+
p.x *= u_resolution.x / u_resolution.y;
|
|
113
|
+
p = p + 0.5;
|
|
114
|
+
return mix(uv, p, u_warpAmount);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
vec2 applyWarp(vec2 uv) {
|
|
118
|
+
if (u_warpMode == 1) return warpWave(uv, u_time, u_seed);
|
|
119
|
+
else if (u_warpMode == 2) return warpSimplex(uv, u_time, u_seed);
|
|
120
|
+
else if (u_warpMode == 3) return warpFBM(uv, u_time, u_seed);
|
|
121
|
+
else if (u_warpMode == 4) return warpRidged(uv, u_time, u_seed);
|
|
122
|
+
else if (u_warpMode == 5) return warpSwirl(uv, u_time);
|
|
123
|
+
else if (u_warpMode == 6) return warpRadial(uv, u_time);
|
|
124
|
+
return uv;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
float grain(vec2 uv) {
|
|
128
|
+
float n = fract(sin(dot(uv * u_grainSize, vec2(12.9898, 78.233))) * 43758.5453);
|
|
129
|
+
return n - 0.5;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
void main() {
|
|
133
|
+
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
|
|
134
|
+
vec2 centered = uv - 0.5;
|
|
135
|
+
uv = applyWarp(uv);
|
|
136
|
+
|
|
137
|
+
float g = grain(gl_FragCoord.xy / u_resolution.xy);
|
|
138
|
+
|
|
139
|
+
vec3 accum = vec3(0.0);
|
|
140
|
+
float totalWeight = 0.0;
|
|
141
|
+
for (int i = 0; i < MAX_POINTS; ++i) {
|
|
142
|
+
if (i >= u_count) break;
|
|
143
|
+
vec2 diff = uv - u_positions[i];
|
|
144
|
+
diff.x *= u_resolution.x / u_resolution.y;
|
|
145
|
+
float d = length(diff);
|
|
146
|
+
float w = exp(- (d * d) / (u_radius * u_radius));
|
|
147
|
+
w = pow(w, max(0.001, u_intensity));
|
|
148
|
+
accum += u_colors[i] * w;
|
|
149
|
+
totalWeight += w;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
vec3 color = totalWeight > 0.0 ? accum / totalWeight : vec3(0.95);
|
|
153
|
+
float vign = 1.0 - smoothstep(0.6, 0.9, length(centered));
|
|
154
|
+
color *= mix(0.98, 1.02, vign);
|
|
155
|
+
color += g * u_grainAmount;
|
|
156
|
+
gl_FragColor = vec4(color, 1.0);
|
|
157
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { SHADER_UNIFORMS, VERTEX_SHADER, createProgram, getUniform, setupQuadBuffer, setUniform } from './utils.js';
|
|
2
|
+
let gl = null;
|
|
3
|
+
let program = null;
|
|
4
|
+
let uniforms = {};
|
|
5
|
+
let currentUniforms = {};
|
|
6
|
+
function initWorker(offscreenCanvas, fragShader) {
|
|
7
|
+
try {
|
|
8
|
+
gl = offscreenCanvas.getContext('webgl2', { antialias: true });
|
|
9
|
+
if (!gl) {
|
|
10
|
+
throw new Error('WebGL2 not supported');
|
|
11
|
+
}
|
|
12
|
+
program = createProgram(gl, VERTEX_SHADER, fragShader);
|
|
13
|
+
gl.useProgram(program);
|
|
14
|
+
setupQuadBuffer(gl, program);
|
|
15
|
+
for (const name of SHADER_UNIFORMS) {
|
|
16
|
+
uniforms[name] = getUniform(gl, program, name);
|
|
17
|
+
}
|
|
18
|
+
self.postMessage({ type: 'ready' });
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
self.postMessage({ type: 'error', error: error.message });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function performRender(time) {
|
|
25
|
+
if (!gl || !program || Object.keys(currentUniforms).length === 0) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
|
29
|
+
gl.clearColor(0, 0, 0, 0);
|
|
30
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
31
|
+
gl.useProgram(program);
|
|
32
|
+
gl.uniform1f(uniforms.u_time, time);
|
|
33
|
+
for (const [key, value] of Object.entries(currentUniforms)) {
|
|
34
|
+
const uniform = uniforms[key];
|
|
35
|
+
if (uniform)
|
|
36
|
+
setUniform(gl, uniform, key, value);
|
|
37
|
+
}
|
|
38
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
39
|
+
}
|
|
40
|
+
self.onmessage = (event) => {
|
|
41
|
+
const { type } = event.data;
|
|
42
|
+
switch (type) {
|
|
43
|
+
case 'init': {
|
|
44
|
+
const { offscreenCanvas, fragShader } = event.data;
|
|
45
|
+
if (offscreenCanvas && fragShader) {
|
|
46
|
+
initWorker(offscreenCanvas, fragShader);
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case 'render': {
|
|
51
|
+
const { time } = event.data;
|
|
52
|
+
if (time !== undefined) {
|
|
53
|
+
performRender(time);
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case 'updateUniforms': {
|
|
58
|
+
const { uniforms: newUniforms } = event.data;
|
|
59
|
+
if (newUniforms) {
|
|
60
|
+
currentUniforms = newUniforms;
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case 'resize': {
|
|
65
|
+
const { width, height, dpr } = event.data;
|
|
66
|
+
if (gl && width && height && dpr) {
|
|
67
|
+
const scaledWidth = Math.floor(width);
|
|
68
|
+
const scaledHeight = Math.floor(height);
|
|
69
|
+
gl.canvas.width = scaledWidth;
|
|
70
|
+
gl.canvas.height = scaledHeight;
|
|
71
|
+
currentUniforms = {
|
|
72
|
+
...currentUniforms,
|
|
73
|
+
u_resolution: [scaledWidth, scaledHeight]
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case 'destroy': {
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
export {};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a color position in 2D space.
|
|
3
|
+
*/
|
|
4
|
+
export interface Point {
|
|
5
|
+
/** Color of the point in hex format, e.g. "#FF0000" */
|
|
6
|
+
color: string;
|
|
7
|
+
/** X coordinate of the point (0 to 1) */
|
|
8
|
+
x: number;
|
|
9
|
+
/** Y coordinate of the point (0 to 1) */
|
|
10
|
+
y: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Configuration for warp effect.
|
|
14
|
+
*/
|
|
15
|
+
export interface Warp {
|
|
16
|
+
/** Warp mode, default: 0 */
|
|
17
|
+
mode?: number;
|
|
18
|
+
/** Warp amount, default: 0 (if 0, warp is not applied) */
|
|
19
|
+
amount?: number;
|
|
20
|
+
/** Warp size, default: 1 */
|
|
21
|
+
size?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Configuration for grain effect.
|
|
25
|
+
*/
|
|
26
|
+
export interface Grain {
|
|
27
|
+
/** Grain intensity, default: 0 */
|
|
28
|
+
amount?: number;
|
|
29
|
+
/** Grain size, default: 1 */
|
|
30
|
+
size?: number;
|
|
31
|
+
}
|
|
32
|
+
export type shaderState = 'loading' | 'playing' | 'paused';
|
|
33
|
+
/**
|
|
34
|
+
* Configuration for the gradient.
|
|
35
|
+
*/
|
|
36
|
+
export interface GradientOptions {
|
|
37
|
+
/** Array of color positions */
|
|
38
|
+
points?: Point[];
|
|
39
|
+
/** Maximum number of points */
|
|
40
|
+
maxPoints?: number;
|
|
41
|
+
/** Blend radius, default: 0.6 */
|
|
42
|
+
radius?: number;
|
|
43
|
+
/** Blend intensity, default: 1 */
|
|
44
|
+
intensity?: number;
|
|
45
|
+
/** Warp effect configuration */
|
|
46
|
+
warp?: Warp;
|
|
47
|
+
/** Warp animation speed, default: 1 */
|
|
48
|
+
speed?: number;
|
|
49
|
+
/** Seed for warp effect, default: random */
|
|
50
|
+
seed?: number;
|
|
51
|
+
/** Grain effect configuration */
|
|
52
|
+
grain?: Grain;
|
|
53
|
+
/** Current state: 'loading', 'playing', or 'paused' */
|
|
54
|
+
currentState?: shaderState;
|
|
55
|
+
/** Class applied to the canvas element **/
|
|
56
|
+
class?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface RenderMessage {
|
|
59
|
+
type: 'init' | 'render' | 'resize' | 'updateUniforms' | 'destroy';
|
|
60
|
+
offscreenCanvas?: OffscreenCanvas;
|
|
61
|
+
fragShader?: string;
|
|
62
|
+
width?: number;
|
|
63
|
+
height?: number;
|
|
64
|
+
dpr?: number;
|
|
65
|
+
uniforms?: Record<string, any>;
|
|
66
|
+
time?: number;
|
|
67
|
+
speed?: number;
|
|
68
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare function hexToRgb(hex: string): [number, number, number];
|
|
2
|
+
export declare const SHADER_UNIFORMS: readonly ["u_resolution", "u_time", "u_count", "u_colors", "u_positions", "u_radius", "u_intensity", "u_warpMode", "u_warpSize", "u_warpAmount", "u_grainAmount", "u_grainSize", "u_seed"];
|
|
3
|
+
export declare const VERTEX_SHADER = "attribute vec2 a_pos;void main() {gl_Position = vec4(a_pos, 0.0, 1.0);}";
|
|
4
|
+
export declare const FULLSCREEN_QUAD: Float32Array<ArrayBuffer>;
|
|
5
|
+
export declare function createShader(gl: WebGL2RenderingContext | WebGLRenderingContext, type: number, src: string): WebGLShader;
|
|
6
|
+
export declare function createProgram(gl: WebGL2RenderingContext | WebGLRenderingContext, vsSrc: string, fsSrc: string): WebGLProgram;
|
|
7
|
+
export declare function getUniform(gl: WebGL2RenderingContext | WebGLRenderingContext, program: WebGLProgram, name: string): WebGLUniformLocation;
|
|
8
|
+
export declare function setupQuadBuffer(gl: WebGL2RenderingContext | WebGLRenderingContext, program: WebGLProgram): void;
|
|
9
|
+
export declare function getCanvasSize(canvas: HTMLCanvasElement): {
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
dpr: number;
|
|
13
|
+
};
|
|
14
|
+
export declare const isBrowser: () => boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Uniform type metadata for WebGL setters
|
|
17
|
+
*/
|
|
18
|
+
export declare const UNIFORM_TYPES: Record<string, 'vec2' | 'vec3f' | 'vec2f' | 'i32' | 'f32'>;
|
|
19
|
+
/**
|
|
20
|
+
* Set a WebGL uniform based on its type
|
|
21
|
+
*/
|
|
22
|
+
export declare function setUniform(gl: WebGL2RenderingContext, uniform: WebGLUniformLocation, key: string, value: any): void;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export function hexToRgb(hex) {
|
|
2
|
+
hex = hex.replace('#', '');
|
|
3
|
+
if (hex.length === 3)
|
|
4
|
+
hex = hex.split('').map(c => c + c).join('');
|
|
5
|
+
const num = parseInt(hex, 16);
|
|
6
|
+
const r = ((num >> 16) & 255) / 255;
|
|
7
|
+
const g = ((num >> 8) & 255) / 255;
|
|
8
|
+
const b = (num & 255) / 255;
|
|
9
|
+
return [r, g, b];
|
|
10
|
+
}
|
|
11
|
+
export const SHADER_UNIFORMS = [
|
|
12
|
+
'u_resolution',
|
|
13
|
+
'u_time',
|
|
14
|
+
'u_count',
|
|
15
|
+
'u_colors',
|
|
16
|
+
'u_positions',
|
|
17
|
+
'u_radius',
|
|
18
|
+
'u_intensity',
|
|
19
|
+
'u_warpMode',
|
|
20
|
+
'u_warpSize',
|
|
21
|
+
'u_warpAmount',
|
|
22
|
+
'u_grainAmount',
|
|
23
|
+
'u_grainSize',
|
|
24
|
+
'u_seed'
|
|
25
|
+
];
|
|
26
|
+
export const VERTEX_SHADER = `attribute vec2 a_pos;void main() {gl_Position = vec4(a_pos, 0.0, 1.0);}`;
|
|
27
|
+
export const FULLSCREEN_QUAD = new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]);
|
|
28
|
+
export function createShader(gl, type, src) {
|
|
29
|
+
const s = gl.createShader(type);
|
|
30
|
+
gl.shaderSource(s, src);
|
|
31
|
+
gl.compileShader(s);
|
|
32
|
+
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
|
|
33
|
+
const error = gl.getShaderInfoLog(s);
|
|
34
|
+
gl.deleteShader(s);
|
|
35
|
+
throw new Error(`Shader compile error: ${error}`);
|
|
36
|
+
}
|
|
37
|
+
return s;
|
|
38
|
+
}
|
|
39
|
+
export function createProgram(gl, vsSrc, fsSrc) {
|
|
40
|
+
const vs = createShader(gl, gl.VERTEX_SHADER, vsSrc);
|
|
41
|
+
const fs = createShader(gl, gl.FRAGMENT_SHADER, fsSrc);
|
|
42
|
+
const p = gl.createProgram();
|
|
43
|
+
gl.attachShader(p, vs);
|
|
44
|
+
gl.attachShader(p, fs);
|
|
45
|
+
gl.linkProgram(p);
|
|
46
|
+
if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
|
|
47
|
+
const error = gl.getProgramInfoLog(p);
|
|
48
|
+
gl.deleteProgram(p);
|
|
49
|
+
throw new Error(`Program link error: ${error}`);
|
|
50
|
+
}
|
|
51
|
+
return p;
|
|
52
|
+
}
|
|
53
|
+
export function getUniform(gl, program, name) {
|
|
54
|
+
const loc = gl.getUniformLocation(program, name);
|
|
55
|
+
if (!loc)
|
|
56
|
+
throw new Error(`Uniform ${name} not found`);
|
|
57
|
+
return loc;
|
|
58
|
+
}
|
|
59
|
+
export function setupQuadBuffer(gl, program) {
|
|
60
|
+
const vbo = gl.createBuffer();
|
|
61
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
62
|
+
gl.bufferData(gl.ARRAY_BUFFER, FULLSCREEN_QUAD, gl.STATIC_DRAW);
|
|
63
|
+
const a_pos = gl.getAttribLocation(program, 'a_pos');
|
|
64
|
+
gl.enableVertexAttribArray(a_pos);
|
|
65
|
+
gl.vertexAttribPointer(a_pos, 2, gl.FLOAT, false, 0, 0);
|
|
66
|
+
}
|
|
67
|
+
export function getCanvasSize(canvas) {
|
|
68
|
+
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
|
69
|
+
const width = Math.floor(canvas.clientWidth * dpr);
|
|
70
|
+
const height = Math.floor(canvas.clientHeight * dpr);
|
|
71
|
+
return { width, height, dpr };
|
|
72
|
+
}
|
|
73
|
+
export const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
74
|
+
/**
|
|
75
|
+
* Uniform type metadata for WebGL setters
|
|
76
|
+
*/
|
|
77
|
+
export const UNIFORM_TYPES = {
|
|
78
|
+
u_resolution: 'vec2',
|
|
79
|
+
u_colors: 'vec3f',
|
|
80
|
+
u_positions: 'vec2f',
|
|
81
|
+
u_count: 'i32',
|
|
82
|
+
u_warpMode: 'i32',
|
|
83
|
+
u_radius: 'f32',
|
|
84
|
+
u_intensity: 'f32',
|
|
85
|
+
u_warpSize: 'f32',
|
|
86
|
+
u_warpAmount: 'f32',
|
|
87
|
+
u_grainAmount: 'f32',
|
|
88
|
+
u_grainSize: 'f32',
|
|
89
|
+
u_seed: 'f32'
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Set a WebGL uniform based on its type
|
|
93
|
+
*/
|
|
94
|
+
export function setUniform(gl, uniform, key, value) {
|
|
95
|
+
const type = UNIFORM_TYPES[key];
|
|
96
|
+
if (!type)
|
|
97
|
+
return;
|
|
98
|
+
switch (type) {
|
|
99
|
+
case 'vec2':
|
|
100
|
+
gl.uniform2f(uniform, value[0], value[1]);
|
|
101
|
+
break;
|
|
102
|
+
case 'vec3f':
|
|
103
|
+
gl.uniform3fv(uniform, value);
|
|
104
|
+
break;
|
|
105
|
+
case 'vec2f':
|
|
106
|
+
gl.uniform2fv(uniform, value);
|
|
107
|
+
break;
|
|
108
|
+
case 'i32':
|
|
109
|
+
gl.uniform1i(uniform, value);
|
|
110
|
+
break;
|
|
111
|
+
case 'f32':
|
|
112
|
+
gl.uniform1f(uniform, value);
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chroma-noise",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && npm run prepack",
|
|
@@ -30,9 +30,7 @@
|
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"svelte": "^5.0.0"
|
|
34
|
-
"ventoui-utils": "^0.0.2",
|
|
35
|
-
"@sveltejs/kit": "^2.0.0"
|
|
33
|
+
"svelte": "^5.0.0"
|
|
36
34
|
},
|
|
37
35
|
"devDependencies": {
|
|
38
36
|
"@sveltejs/adapter-auto": "^6.1.0",
|
|
@@ -45,22 +43,25 @@
|
|
|
45
43
|
"publint": "^0.3.13",
|
|
46
44
|
"svelte": "^5.39.5",
|
|
47
45
|
"svelte-check": "^4.3.2",
|
|
46
|
+
"svelte-fancy-transitions": "^0.0.7",
|
|
48
47
|
"typescript": "^5.9.2",
|
|
48
|
+
"ventoui-button": "^0.0.2",
|
|
49
|
+
"ventoui-slider": "^0.0.1",
|
|
50
|
+
"ventoui-styles": "^0.0.2",
|
|
49
51
|
"vite": "^7.1.7"
|
|
50
52
|
},
|
|
51
53
|
"keywords": [
|
|
52
|
-
"svelte",
|
|
54
|
+
"svelte",
|
|
55
|
+
"simplex",
|
|
56
|
+
"noise",
|
|
57
|
+
"gradient",
|
|
58
|
+
"chroma",
|
|
59
|
+
"chroma-noise"
|
|
53
60
|
],
|
|
54
61
|
"pnpm": {
|
|
55
62
|
"onlyBuiltDependencies": [
|
|
56
63
|
"esbuild"
|
|
57
64
|
]
|
|
58
65
|
},
|
|
59
|
-
"packageManager": "pnpm@10.12.1+sha512.f0dda8580f0ee9481c5c79a1d927b9164f2c478e90992ad268bbb2465a736984391d6333d2c327913578b2804af33474ca554ba29c04a8b13060a717675ae3ac"
|
|
60
|
-
"dependencies": {
|
|
61
|
-
"ventoui-button": "^0.0.2",
|
|
62
|
-
"ventoui-slider": "^0.0.1",
|
|
63
|
-
"ventoui-styles": "^0.0.2",
|
|
64
|
-
"ventoui-utils": "^0.0.2"
|
|
65
|
-
}
|
|
66
|
+
"packageManager": "pnpm@10.12.1+sha512.f0dda8580f0ee9481c5c79a1d927b9164f2c478e90992ad268bbb2465a736984391d6333d2c327913578b2804af33474ca554ba29c04a8b13060a717675ae3ac"
|
|
66
67
|
}
|
package/dist/gradient.frag.glsl
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
#ifdef GL_ES
|
|
2
|
-
precision mediump float;
|
|
3
|
-
#endif
|
|
4
|
-
|
|
5
|
-
#define MAX_POINTS 12
|
|
6
|
-
|
|
7
|
-
uniform vec2 u_resolution;
|
|
8
|
-
uniform float u_time; // теперь это интегрированное animTime из компонента
|
|
9
|
-
uniform int u_count;
|
|
10
|
-
uniform vec3 u_colors[MAX_POINTS];
|
|
11
|
-
uniform vec2 u_positions[MAX_POINTS];
|
|
12
|
-
uniform float u_radius;
|
|
13
|
-
uniform float u_intensity;
|
|
14
|
-
uniform float u_grainAmount;
|
|
15
|
-
uniform float u_grainSize;
|
|
16
|
-
|
|
17
|
-
// warp uniforms
|
|
18
|
-
uniform int u_warpMode; // 0 = none, 1 = waves, 2 = simplex, 3 = fbm, 4 = ridged
|
|
19
|
-
uniform float u_warpSize;
|
|
20
|
-
uniform float u_warpAmount;
|
|
21
|
-
uniform float u_seed; // новый
|
|
22
|
-
|
|
23
|
-
// --- Simplex (from Ashima / webgl-noise) ---
|
|
24
|
-
vec3 permute(vec3 x) {
|
|
25
|
-
return mod(((x * 34.0) + 1.0) * x, 289.0);
|
|
26
|
-
}
|
|
27
|
-
float snoise(vec2 v) {
|
|
28
|
-
const vec4 C = vec4(0.211324865405187, /* (3.0-sqrt(3.0))/6.0 */
|
|
29
|
-
0.366025403784439, /* (sqrt(3.0)-1.0)/2.0 */
|
|
30
|
-
-0.577350269189626, /* -1.0 + 2.0 * C.x */
|
|
31
|
-
0.024390243902439); /* 1.0 / 41.0 */
|
|
32
|
-
vec2 i = floor(v + dot(v, C.yy) );
|
|
33
|
-
vec2 x0 = v - i + dot(i, C.xx);
|
|
34
|
-
|
|
35
|
-
vec2 i1;
|
|
36
|
-
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
|
37
|
-
vec4 x12 = x0.xyxy + C.xxzz;
|
|
38
|
-
x12.xy -= i1;
|
|
39
|
-
|
|
40
|
-
i = mod(i, 289.0);
|
|
41
|
-
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0) )
|
|
42
|
-
+ i.x + vec3(0.0, i1.x, 1.0) );
|
|
43
|
-
|
|
44
|
-
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
|
|
45
|
-
m = m * m;
|
|
46
|
-
m = m * m;
|
|
47
|
-
|
|
48
|
-
vec3 x = 2.0 * fract(p * C.www) - 1.0;
|
|
49
|
-
vec3 h = abs(x) - 0.5;
|
|
50
|
-
vec3 ox = floor(x + 0.5);
|
|
51
|
-
vec3 a0 = x - ox;
|
|
52
|
-
|
|
53
|
-
m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
|
|
54
|
-
|
|
55
|
-
vec3 g;
|
|
56
|
-
g.x = a0.x * x0.x + h.x * x0.y;
|
|
57
|
-
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
|
58
|
-
|
|
59
|
-
return 130.0 * dot(m, g);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// fbm helper
|
|
63
|
-
float fbm(vec2 p) {
|
|
64
|
-
float f = 0.0;
|
|
65
|
-
float amp = 0.5;
|
|
66
|
-
for (int i = 0; i < 5; i++) {
|
|
67
|
-
f += amp * snoise(p);
|
|
68
|
-
p *= 2.0;
|
|
69
|
-
amp *= 0.5;
|
|
70
|
-
}
|
|
71
|
-
return f;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Wave warp — теперь использует seed и animTime
|
|
75
|
-
vec2 waveWarp(vec2 uv, float t, float seed) {
|
|
76
|
-
// phase field from noise so waves are not uniform / repeatless across seed
|
|
77
|
-
float phase = snoise(uv * (u_warpSize * 1.5 + 0.1) + vec2(seed * 5.0));
|
|
78
|
-
float w1 = sin((uv.y * u_warpSize * 10.0) + t * 0.6 + phase * 6.28318);
|
|
79
|
-
float w2 = cos((uv.x * u_warpSize * 8.0) + t * 0.4 - phase * 6.28318 * 0.5);
|
|
80
|
-
vec2 disp = vec2(w1, w2) * (u_warpAmount * 0.5);
|
|
81
|
-
// adjust for aspect
|
|
82
|
-
disp.x *= u_resolution.x / u_resolution.y;
|
|
83
|
-
return uv + disp;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
void main() {
|
|
87
|
-
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
|
|
88
|
-
vec2 centered = uv - 0.5;
|
|
89
|
-
// keep aspect-correct uv for distance calculations later
|
|
90
|
-
vec2 aspectUV = uv;
|
|
91
|
-
aspectUV.x *= u_resolution.x / u_resolution.y;
|
|
92
|
-
|
|
93
|
-
// --- Handle warp modes (use u_time as smooth animTime, and u_seed to randomize field) ---
|
|
94
|
-
if (u_warpMode == 1) {
|
|
95
|
-
uv = waveWarp(uv, u_time, u_seed);
|
|
96
|
-
} else if (u_warpMode == 2) {
|
|
97
|
-
// Simplex single-octave with seed; animated slowly (u_time influences motion but smoothly)
|
|
98
|
-
vec2 noiseUv = uv * (u_warpSize * 4.0 + 0.001) + vec2(u_seed * 10.0, u_seed * 20.0);
|
|
99
|
-
float n = snoise(noiseUv + vec2(u_time * 0.05)); // slow drift
|
|
100
|
-
vec2 disp = vec2(n, snoise(noiseUv + vec2(12.34))) * u_warpAmount * 0.8;
|
|
101
|
-
disp.x *= u_resolution.x / u_resolution.y;
|
|
102
|
-
uv += disp;
|
|
103
|
-
} else if (u_warpMode == 3) {
|
|
104
|
-
// fbm turbulence
|
|
105
|
-
vec2 noiseUv = uv * (u_warpSize * 3.0) + vec2(u_seed * 7.0);
|
|
106
|
-
float f = fbm(noiseUv + vec2(u_time * 0.02));
|
|
107
|
-
vec2 disp = vec2(f, fbm(noiseUv + vec2(31.4))) * u_warpAmount;
|
|
108
|
-
disp.x *= u_resolution.x / u_resolution.y;
|
|
109
|
-
uv += disp;
|
|
110
|
-
} else if (u_warpMode == 4) {
|
|
111
|
-
// ridged noise variation
|
|
112
|
-
vec2 nUv = uv * (u_warpSize * 2.5) + vec2(u_seed * 9.0);
|
|
113
|
-
float r = 1.0 - abs(fbm(nUv));
|
|
114
|
-
vec2 disp = vec2(r, r * 0.5) * (u_warpAmount * 1.2);
|
|
115
|
-
disp.x *= u_resolution.x / u_resolution.y;
|
|
116
|
-
uv += disp;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// --- Grain calculation (apply animation BEFORE sampling the pseudo-random) ---
|
|
120
|
-
vec2 grainUv = gl_FragCoord.xy / u_resolution.xy;
|
|
121
|
-
// grainSize: bigger -> larger grains. so invert (1/size)
|
|
122
|
-
float grainScale = max(u_grainSize, 0.0001);
|
|
123
|
-
grainUv *= 1.0 / grainScale;
|
|
124
|
-
// classic hash -> value in [0,1)
|
|
125
|
-
float grain = fract(sin(dot(grainUv, vec2(12.9898, 78.233))) * 43758.5453);
|
|
126
|
-
grain = grain - 0.5; // center at 0
|
|
127
|
-
|
|
128
|
-
// --- Accumulate colors from points ---
|
|
129
|
-
vec3 accum = vec3(0.0);
|
|
130
|
-
float totalWeight = 0.0;
|
|
131
|
-
for (int i = 0; i < MAX_POINTS; ++i) {
|
|
132
|
-
if (i >= u_count) break;
|
|
133
|
-
vec2 p = u_positions[i];
|
|
134
|
-
vec2 diff = (uv - p);
|
|
135
|
-
diff.x *= u_resolution.x / u_resolution.y;
|
|
136
|
-
float d = length(diff);
|
|
137
|
-
float w = exp(- (d * d) / (u_radius * u_radius)); // base gaussian weight in (0,1]
|
|
138
|
-
// intensity now controls sharpness/contrast via power:
|
|
139
|
-
float intensity = max(0.001, u_intensity);
|
|
140
|
-
w = pow(w, intensity); // intensity >1 -> sharper peaks; <1 -> broader
|
|
141
|
-
accum += u_colors[i] * w;
|
|
142
|
-
totalWeight += w;
|
|
143
|
-
}
|
|
144
|
-
vec3 color = totalWeight > 0.0 ? accum / totalWeight : vec3(0.95);
|
|
145
|
-
|
|
146
|
-
// simple vignette
|
|
147
|
-
float vign = 1.0 - smoothstep(0.6, 0.9, length(centered));
|
|
148
|
-
color *= mix(0.98, 1.02, vign);
|
|
149
|
-
|
|
150
|
-
// add grain
|
|
151
|
-
color += grain * u_grainAmount;
|
|
152
|
-
|
|
153
|
-
gl_FragColor = vec4(color, 1.0);
|
|
154
|
-
}
|
package/dist/gradient.vert.glsl
DELETED