chroma-noise 0.0.5 → 0.0.6
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/dist/Gradient.svelte +119 -89
- package/dist/fragment.txt +6 -1
- package/dist/gradient.worker.js +8 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +19 -0
- package/dist/utils.d.ts +5 -3
- package/dist/utils.js +31 -6
- package/dist/workerFactory.d.ts +5 -0
- package/dist/workerFactory.js +196 -0
- package/package.json +2 -1
package/dist/Gradient.svelte
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import frag from './fragment.txt?raw';
|
|
3
3
|
import { onMount, onDestroy } from 'svelte';
|
|
4
|
+
import { hexToRgb, isBrowser, createGradientWorker, getTargetSize } from './index.js';
|
|
4
5
|
import type { Warp, Grain, GradientOptions } from './index.js';
|
|
5
|
-
import { hexToRgb, isBrowser } from './utils.js';
|
|
6
|
-
import gradientWorker from './gradient.worker.js?worker&module';
|
|
7
6
|
|
|
8
7
|
let {
|
|
9
8
|
points = [],
|
|
@@ -16,6 +15,8 @@
|
|
|
16
15
|
seed = Math.random(),
|
|
17
16
|
currentState = $bindable('loading'),
|
|
18
17
|
class: c = '',
|
|
18
|
+
resolution = {},
|
|
19
|
+
blendMode = 'legacy',
|
|
19
20
|
...rest
|
|
20
21
|
}: GradientOptions = $props();
|
|
21
22
|
|
|
@@ -27,11 +28,13 @@
|
|
|
27
28
|
|
|
28
29
|
let canvas: HTMLCanvasElement | undefined = $state();
|
|
29
30
|
let worker: Worker | undefined = $state();
|
|
30
|
-
let
|
|
31
|
-
let running = true;
|
|
31
|
+
let running = $state(false);
|
|
32
32
|
let lastTime = 0;
|
|
33
33
|
let animTime = 0;
|
|
34
|
+
let frame: number | null = null;
|
|
35
|
+
let currentCanvasSize = $state({ width: 0, height: 0 });
|
|
34
36
|
|
|
37
|
+
let lastUniforms = $state<Record<string, any>>({});
|
|
35
38
|
|
|
36
39
|
function updateUniforms() {
|
|
37
40
|
if (!worker) return;
|
|
@@ -45,55 +48,80 @@
|
|
|
45
48
|
posArr.set([points[i].x, points[i].y], i * 2);
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
51
|
+
const newUniforms = {
|
|
52
|
+
u_colors: colorsArr,
|
|
53
|
+
u_positions: posArr,
|
|
54
|
+
u_count: Math.min(points.length, maxPoints),
|
|
55
|
+
u_radius: radius,
|
|
56
|
+
u_intensity: intensity,
|
|
57
|
+
u_warpMode: Number(warp.mode),
|
|
58
|
+
u_warpSize: warp.size,
|
|
59
|
+
u_warpAmount: warp.amount,
|
|
60
|
+
u_grainAmount: grain.amount,
|
|
61
|
+
u_grainSize: grain.size,
|
|
62
|
+
u_seed: seed,
|
|
63
|
+
u_blendMode: blendMode === 'new' ? 0 : 1
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const changedUniforms: Record<string, any> = {};
|
|
67
|
+
for (const [key, value] of Object.entries(newUniforms)) {
|
|
68
|
+
if (JSON.stringify(lastUniforms[key]) !== JSON.stringify(value)) {
|
|
69
|
+
changedUniforms[key] = value;
|
|
62
70
|
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
71
|
+
}
|
|
65
72
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
uniforms: {
|
|
74
|
-
u_resolution: [width, height]
|
|
75
|
-
}
|
|
76
|
-
});
|
|
73
|
+
if (Object.keys(changedUniforms).length > 0) {
|
|
74
|
+
worker.postMessage({
|
|
75
|
+
type: 'updateUniforms',
|
|
76
|
+
uniforms: changedUniforms
|
|
77
|
+
});
|
|
78
|
+
lastUniforms = { ...newUniforms };
|
|
79
|
+
}
|
|
77
80
|
}
|
|
78
81
|
|
|
79
|
-
function
|
|
80
|
-
if (worker)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
lastTime = time;
|
|
84
|
-
animTime += dt * speed;
|
|
82
|
+
function resizeCanvasToDisplaySize() {
|
|
83
|
+
if (!canvas || !worker) return;
|
|
84
|
+
|
|
85
|
+
const { width, height } = getTargetSize(canvas, resolution);
|
|
85
86
|
|
|
87
|
+
if (currentCanvasSize.width !== width || currentCanvasSize.height !== height) {
|
|
88
|
+
currentCanvasSize = { width, height };
|
|
86
89
|
worker.postMessage({
|
|
87
|
-
type: '
|
|
88
|
-
|
|
90
|
+
type: 'resize',
|
|
91
|
+
width,
|
|
92
|
+
height
|
|
89
93
|
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
90
96
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
$effect(() => {
|
|
98
|
+
if (worker) {
|
|
99
|
+
updateUniforms();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
$effect(() => {
|
|
104
|
+
if (worker) {
|
|
105
|
+
resizeCanvasToDisplaySize();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
function render(time: number) {
|
|
110
|
+
if (!worker) return;
|
|
111
|
+
|
|
112
|
+
if (lastTime === 0) lastTime = time;
|
|
113
|
+
const dt = (time - lastTime) * 0.001;
|
|
114
|
+
lastTime = time;
|
|
115
|
+
animTime += dt * speed;
|
|
116
|
+
|
|
117
|
+
worker.postMessage({
|
|
118
|
+
type: 'render',
|
|
119
|
+
time: animTime
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (running) {
|
|
123
|
+
currentState = 'playing';
|
|
124
|
+
frame = requestAnimationFrame(render);
|
|
97
125
|
}
|
|
98
126
|
}
|
|
99
127
|
|
|
@@ -101,52 +129,45 @@
|
|
|
101
129
|
if (!canvas || !isBrowser()) return;
|
|
102
130
|
currentState = 'loading';
|
|
103
131
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
worker.onmessage = (event: MessageEvent) => {
|
|
107
|
-
const { type } = event.data;
|
|
108
|
-
if (type === 'ready') {
|
|
109
|
-
updateUniforms();
|
|
110
|
-
currentState = 'playing';
|
|
111
|
-
} else if (type === 'rendering') {
|
|
112
|
-
currentState = 'playing';
|
|
113
|
-
} else if (type === 'paused') {
|
|
114
|
-
currentState = 'paused';
|
|
115
|
-
} else if (type === 'error') {
|
|
116
|
-
console.error('Worker error:', event.data.error);
|
|
117
|
-
currentState = 'paused';
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
|
122
|
-
const width = Math.floor(canvas.clientWidth * dpr);
|
|
123
|
-
const height = Math.floor(canvas.clientHeight * dpr);
|
|
124
|
-
canvas.width = width;
|
|
125
|
-
canvas.height = height;
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
const offscreenCanvas = canvas.transferControlToOffscreen();
|
|
129
|
-
worker.postMessage({
|
|
130
|
-
type: 'init',
|
|
131
|
-
offscreenCanvas,
|
|
132
|
-
fragShader: frag,
|
|
133
|
-
speed
|
|
134
|
-
}, [offscreenCanvas]);
|
|
132
|
+
worker = createGradientWorker();
|
|
135
133
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
134
|
+
worker.onmessage = (event: MessageEvent) => {
|
|
135
|
+
const { type } = event.data;
|
|
136
|
+
if (type === 'ready') {
|
|
137
|
+
updateUniforms();
|
|
138
|
+
currentState = 'playing';
|
|
139
|
+
frame = requestAnimationFrame(render);
|
|
140
|
+
} else if (type === 'error') {
|
|
141
|
+
console.error('Worker error:', event.data.error);
|
|
142
|
+
currentState = 'paused';
|
|
143
143
|
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const { width, height } = getTargetSize(canvas, resolution);
|
|
147
|
+
currentCanvasSize = { width, height };
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const offscreenCanvas = canvas.transferControlToOffscreen();
|
|
151
|
+
worker.postMessage(
|
|
152
|
+
{
|
|
153
|
+
type: 'init',
|
|
154
|
+
offscreenCanvas,
|
|
155
|
+
fragShader: frag
|
|
156
|
+
},
|
|
157
|
+
[offscreenCanvas]
|
|
158
|
+
);
|
|
159
|
+
worker.postMessage({ type: 'resize', width, height });
|
|
160
|
+
running = true;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error('Could not transfer canvas to worker: ', error);
|
|
163
|
+
}
|
|
144
164
|
});
|
|
145
165
|
|
|
146
166
|
onDestroy(() => {
|
|
147
167
|
running = false;
|
|
148
168
|
if (!isBrowser()) return;
|
|
149
|
-
|
|
169
|
+
|
|
170
|
+
if (frame) cancelAnimationFrame(frame);
|
|
150
171
|
if (worker) {
|
|
151
172
|
worker.postMessage({ type: 'destroy' });
|
|
152
173
|
worker.terminate();
|
|
@@ -154,16 +175,25 @@
|
|
|
154
175
|
});
|
|
155
176
|
|
|
156
177
|
$effect(() => {
|
|
157
|
-
if(worker && (points.length || radius || intensity || warp || grain || seed)) {
|
|
178
|
+
if (worker && (points.length || radius || intensity || warp || grain || seed)) {
|
|
158
179
|
updateUniforms();
|
|
159
180
|
}
|
|
160
181
|
});
|
|
161
182
|
</script>
|
|
162
183
|
|
|
163
|
-
<canvas bind:this={canvas} {...rest} class="{
|
|
184
|
+
<canvas bind:this={canvas} {...rest} class="{currentState} {c}"></canvas>
|
|
164
185
|
|
|
165
186
|
<style>
|
|
166
|
-
canvas {
|
|
167
|
-
|
|
168
|
-
|
|
187
|
+
canvas {
|
|
188
|
+
width: 100%;
|
|
189
|
+
height: 100%;
|
|
190
|
+
display: block;
|
|
191
|
+
transition: opacity 1s ease-out;
|
|
192
|
+
}
|
|
193
|
+
canvas.loading {
|
|
194
|
+
opacity: 0;
|
|
195
|
+
}
|
|
196
|
+
canvas.playing {
|
|
197
|
+
opacity: 1;
|
|
198
|
+
}
|
|
169
199
|
</style>
|
package/dist/fragment.txt
CHANGED
|
@@ -17,6 +17,7 @@ uniform int u_warpMode;
|
|
|
17
17
|
uniform float u_warpSize;
|
|
18
18
|
uniform float u_warpAmount;
|
|
19
19
|
uniform float u_seed;
|
|
20
|
+
uniform int u_blendMode;
|
|
20
21
|
|
|
21
22
|
vec3 permute(vec3 x) {
|
|
22
23
|
return mod(((x * 34.0) + 1.0) * x, 289.0);
|
|
@@ -136,6 +137,10 @@ void main() {
|
|
|
136
137
|
|
|
137
138
|
float g = grain(gl_FragCoord.xy / u_resolution.xy);
|
|
138
139
|
|
|
140
|
+
float radiusToUse = (u_blendMode == 0)
|
|
141
|
+
? u_radius * min(u_resolution.x / u_resolution.y, 1.0)
|
|
142
|
+
: u_radius;
|
|
143
|
+
|
|
139
144
|
vec3 accum = vec3(0.0);
|
|
140
145
|
float totalWeight = 0.0;
|
|
141
146
|
for (int i = 0; i < MAX_POINTS; ++i) {
|
|
@@ -143,7 +148,7 @@ void main() {
|
|
|
143
148
|
vec2 diff = uv - u_positions[i];
|
|
144
149
|
diff.x *= u_resolution.x / u_resolution.y;
|
|
145
150
|
float d = length(diff);
|
|
146
|
-
float w = exp(- (d * d) / (
|
|
151
|
+
float w = exp(- (d * d) / (radiusToUse * radiusToUse));
|
|
147
152
|
w = pow(w, max(0.001, u_intensity));
|
|
148
153
|
accum += u_colors[i] * w;
|
|
149
154
|
totalWeight += w;
|
package/dist/gradient.worker.js
CHANGED
|
@@ -57,13 +57,18 @@ self.onmessage = (event) => {
|
|
|
57
57
|
case 'updateUniforms': {
|
|
58
58
|
const { uniforms: newUniforms } = event.data;
|
|
59
59
|
if (newUniforms) {
|
|
60
|
-
|
|
60
|
+
console.log('Worker received uniforms:', newUniforms);
|
|
61
|
+
currentUniforms = {
|
|
62
|
+
...currentUniforms,
|
|
63
|
+
...newUniforms
|
|
64
|
+
};
|
|
65
|
+
console.log('Current uniforms after update:', currentUniforms);
|
|
61
66
|
}
|
|
62
67
|
break;
|
|
63
68
|
}
|
|
64
69
|
case 'resize': {
|
|
65
|
-
const { width, height
|
|
66
|
-
if (gl && width && height
|
|
70
|
+
const { width, height } = event.data;
|
|
71
|
+
if (gl && width && height) {
|
|
67
72
|
const scaledWidth = Math.floor(width);
|
|
68
73
|
const scaledHeight = Math.floor(height);
|
|
69
74
|
gl.canvas.width = scaledWidth;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -30,6 +30,21 @@ export interface Grain {
|
|
|
30
30
|
size?: number;
|
|
31
31
|
}
|
|
32
32
|
export type shaderState = 'loading' | 'playing' | 'paused';
|
|
33
|
+
/**
|
|
34
|
+
* Blend modes for gradient rendering
|
|
35
|
+
*/
|
|
36
|
+
export type BlendMode = 'new' | 'legacy';
|
|
37
|
+
/** Configuration for resolution. */
|
|
38
|
+
export interface Resolution {
|
|
39
|
+
/** Canvas resolution width, default: canvas.clientWidth */
|
|
40
|
+
width?: number;
|
|
41
|
+
/** Canvas resolution height, default: canvas.clientHeight */
|
|
42
|
+
height?: number;
|
|
43
|
+
/** Canvas resolution modifier, default: 1, min: 0.01, max: 2 */
|
|
44
|
+
modifier?: number;
|
|
45
|
+
/** Determines whether to use DPR, default: false */
|
|
46
|
+
useDPR?: boolean;
|
|
47
|
+
}
|
|
33
48
|
/**
|
|
34
49
|
* Configuration for the gradient.
|
|
35
50
|
*/
|
|
@@ -54,6 +69,10 @@ export interface GradientOptions {
|
|
|
54
69
|
currentState?: shaderState;
|
|
55
70
|
/** Class applied to the canvas element **/
|
|
56
71
|
class?: string;
|
|
72
|
+
/** Resolution settings */
|
|
73
|
+
resolution?: Resolution;
|
|
74
|
+
/** Blend mode for gradient rendering */
|
|
75
|
+
blendMode?: BlendMode;
|
|
57
76
|
}
|
|
58
77
|
export interface RenderMessage {
|
|
59
78
|
type: 'init' | 'render' | 'resize' | 'updateUniforms' | 'destroy';
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
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"];
|
|
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", "u_blendMode"];
|
|
3
3
|
export declare const VERTEX_SHADER = "attribute vec2 a_pos;void main() {gl_Position = vec4(a_pos, 0.0, 1.0);}";
|
|
4
4
|
export declare const FULLSCREEN_QUAD: Float32Array<ArrayBuffer>;
|
|
5
5
|
export declare function createShader(gl: WebGL2RenderingContext | WebGLRenderingContext, type: number, src: string): WebGLShader;
|
|
6
6
|
export declare function createProgram(gl: WebGL2RenderingContext | WebGLRenderingContext, vsSrc: string, fsSrc: string): WebGLProgram;
|
|
7
7
|
export declare function getUniform(gl: WebGL2RenderingContext | WebGLRenderingContext, program: WebGLProgram, name: string): WebGLUniformLocation;
|
|
8
8
|
export declare function setupQuadBuffer(gl: WebGL2RenderingContext | WebGLRenderingContext, program: WebGLProgram): void;
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Calculate target canvas size based on resolution settings
|
|
11
|
+
*/
|
|
12
|
+
export declare function getTargetSize(canvas: HTMLCanvasElement, resolution: import('./types.js').Resolution): {
|
|
10
13
|
width: number;
|
|
11
14
|
height: number;
|
|
12
|
-
dpr: number;
|
|
13
15
|
};
|
|
14
16
|
export declare const isBrowser: () => boolean;
|
|
15
17
|
/**
|
package/dist/utils.js
CHANGED
|
@@ -21,7 +21,8 @@ export const SHADER_UNIFORMS = [
|
|
|
21
21
|
'u_warpAmount',
|
|
22
22
|
'u_grainAmount',
|
|
23
23
|
'u_grainSize',
|
|
24
|
-
'u_seed'
|
|
24
|
+
'u_seed',
|
|
25
|
+
'u_blendMode'
|
|
25
26
|
];
|
|
26
27
|
export const VERTEX_SHADER = `attribute vec2 a_pos;void main() {gl_Position = vec4(a_pos, 0.0, 1.0);}`;
|
|
27
28
|
export const FULLSCREEN_QUAD = new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]);
|
|
@@ -64,11 +65,34 @@ export function setupQuadBuffer(gl, program) {
|
|
|
64
65
|
gl.enableVertexAttribArray(a_pos);
|
|
65
66
|
gl.vertexAttribPointer(a_pos, 2, gl.FLOAT, false, 0, 0);
|
|
66
67
|
}
|
|
67
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Calculate target canvas size based on resolution settings
|
|
70
|
+
*/
|
|
71
|
+
export function getTargetSize(canvas, resolution) {
|
|
68
72
|
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
73
|
+
const clientWidth = canvas.clientWidth;
|
|
74
|
+
const clientHeight = canvas.clientHeight;
|
|
75
|
+
let width = clientWidth;
|
|
76
|
+
let height = clientHeight;
|
|
77
|
+
if (resolution.width !== undefined) {
|
|
78
|
+
width = resolution.width;
|
|
79
|
+
}
|
|
80
|
+
if (resolution.height !== undefined) {
|
|
81
|
+
height = resolution.height;
|
|
82
|
+
}
|
|
83
|
+
if (resolution.modifier !== undefined) {
|
|
84
|
+
const modifier = Math.max(0.01, Math.min(2.0, resolution.modifier));
|
|
85
|
+
width = width * modifier;
|
|
86
|
+
height = height * modifier;
|
|
87
|
+
}
|
|
88
|
+
if (resolution.useDPR !== false) {
|
|
89
|
+
width = width * dpr;
|
|
90
|
+
height = height * dpr;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
width: Math.floor(width),
|
|
94
|
+
height: Math.floor(height)
|
|
95
|
+
};
|
|
72
96
|
}
|
|
73
97
|
export const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
74
98
|
/**
|
|
@@ -86,7 +110,8 @@ export const UNIFORM_TYPES = {
|
|
|
86
110
|
u_warpAmount: 'f32',
|
|
87
111
|
u_grainAmount: 'f32',
|
|
88
112
|
u_grainSize: 'f32',
|
|
89
|
-
u_seed: 'f32'
|
|
113
|
+
u_seed: 'f32',
|
|
114
|
+
u_blendMode: 'i32'
|
|
90
115
|
};
|
|
91
116
|
/**
|
|
92
117
|
* Set a WebGL uniform based on its type
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker factory that creates a gradient rendering worker as a Blob
|
|
3
|
+
* This approach ensures the worker works correctly
|
|
4
|
+
*/
|
|
5
|
+
export function createGradientWorker() {
|
|
6
|
+
const workerCode = `
|
|
7
|
+
const SHADER_UNIFORMS = [
|
|
8
|
+
'u_resolution',
|
|
9
|
+
'u_time',
|
|
10
|
+
'u_count',
|
|
11
|
+
'u_colors',
|
|
12
|
+
'u_positions',
|
|
13
|
+
'u_radius',
|
|
14
|
+
'u_intensity',
|
|
15
|
+
'u_warpMode',
|
|
16
|
+
'u_warpSize',
|
|
17
|
+
'u_warpAmount',
|
|
18
|
+
'u_grainAmount',
|
|
19
|
+
'u_grainSize',
|
|
20
|
+
'u_seed'
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const VERTEX_SHADER = 'attribute vec2 a_pos;void main() {gl_Position = vec4(a_pos, 0.0, 1.0);}';
|
|
24
|
+
const FULLSCREEN_QUAD = new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]);
|
|
25
|
+
|
|
26
|
+
const UNIFORM_TYPES = {
|
|
27
|
+
u_resolution: 'vec2',
|
|
28
|
+
u_colors: 'vec3f',
|
|
29
|
+
u_positions: 'vec2f',
|
|
30
|
+
u_count: 'i32',
|
|
31
|
+
u_warpMode: 'i32',
|
|
32
|
+
u_radius: 'f32',
|
|
33
|
+
u_intensity: 'f32',
|
|
34
|
+
u_warpSize: 'f32',
|
|
35
|
+
u_warpAmount: 'f32',
|
|
36
|
+
u_grainAmount: 'f32',
|
|
37
|
+
u_grainSize: 'f32',
|
|
38
|
+
u_seed: 'f32'
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
let gl = null;
|
|
42
|
+
let program = null;
|
|
43
|
+
let uniforms = {};
|
|
44
|
+
let currentUniforms = {};
|
|
45
|
+
|
|
46
|
+
function createShader(gl, type, src) {
|
|
47
|
+
const s = gl.createShader(type);
|
|
48
|
+
gl.shaderSource(s, src);
|
|
49
|
+
gl.compileShader(s);
|
|
50
|
+
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
|
|
51
|
+
const error = gl.getShaderInfoLog(s);
|
|
52
|
+
gl.deleteShader(s);
|
|
53
|
+
throw new Error(\`Shader compile error: \${error}\`);
|
|
54
|
+
}
|
|
55
|
+
return s;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function createProgram(gl, vsSrc, fsSrc) {
|
|
59
|
+
const vs = createShader(gl, gl.VERTEX_SHADER, vsSrc);
|
|
60
|
+
const fs = createShader(gl, gl.FRAGMENT_SHADER, fsSrc);
|
|
61
|
+
const p = gl.createProgram();
|
|
62
|
+
gl.attachShader(p, vs);
|
|
63
|
+
gl.attachShader(p, fs);
|
|
64
|
+
gl.linkProgram(p);
|
|
65
|
+
if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
|
|
66
|
+
const error = gl.getProgramInfoLog(p);
|
|
67
|
+
gl.deleteProgram(p);
|
|
68
|
+
throw new Error(\`Program link error: \${error}\`);
|
|
69
|
+
}
|
|
70
|
+
return p;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getUniform(gl, program, name) {
|
|
74
|
+
const loc = gl.getUniformLocation(program, name);
|
|
75
|
+
if (!loc) throw new Error(\`Uniform \${name} not found\`);
|
|
76
|
+
return loc;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function setupQuadBuffer(gl, program) {
|
|
80
|
+
const vbo = gl.createBuffer();
|
|
81
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
82
|
+
gl.bufferData(gl.ARRAY_BUFFER, FULLSCREEN_QUAD, gl.STATIC_DRAW);
|
|
83
|
+
const a_pos = gl.getAttribLocation(program, 'a_pos');
|
|
84
|
+
gl.enableVertexAttribArray(a_pos);
|
|
85
|
+
gl.vertexAttribPointer(a_pos, 2, gl.FLOAT, false, 0, 0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function setUniform(gl, uniform, key, value) {
|
|
89
|
+
const type = UNIFORM_TYPES[key];
|
|
90
|
+
if (!type) return;
|
|
91
|
+
|
|
92
|
+
switch (type) {
|
|
93
|
+
case 'vec2':
|
|
94
|
+
gl.uniform2f(uniform, value[0], value[1]);
|
|
95
|
+
break;
|
|
96
|
+
case 'vec3f':
|
|
97
|
+
gl.uniform3fv(uniform, value);
|
|
98
|
+
break;
|
|
99
|
+
case 'vec2f':
|
|
100
|
+
gl.uniform2fv(uniform, value);
|
|
101
|
+
break;
|
|
102
|
+
case 'i32':
|
|
103
|
+
gl.uniform1i(uniform, value);
|
|
104
|
+
break;
|
|
105
|
+
case 'f32':
|
|
106
|
+
gl.uniform1f(uniform, value);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function initWorker(offscreenCanvas, fragShader) {
|
|
112
|
+
try {
|
|
113
|
+
gl = offscreenCanvas.getContext('webgl2', { antialias: true });
|
|
114
|
+
if (!gl) {
|
|
115
|
+
throw new Error('WebGL2 not supported');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
program = createProgram(gl, VERTEX_SHADER, fragShader);
|
|
119
|
+
gl.useProgram(program);
|
|
120
|
+
|
|
121
|
+
setupQuadBuffer(gl, program);
|
|
122
|
+
|
|
123
|
+
for (const name of SHADER_UNIFORMS) {
|
|
124
|
+
uniforms[name] = getUniform(gl, program, name);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
self.postMessage({ type: 'ready' });
|
|
128
|
+
} catch (error) {
|
|
129
|
+
self.postMessage({ type: 'error', error: error.message });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function performRender(time) {
|
|
134
|
+
if (!gl || !program) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
|
139
|
+
gl.clearColor(0, 0, 0, 0);
|
|
140
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
141
|
+
|
|
142
|
+
gl.useProgram(program);
|
|
143
|
+
gl.uniform1f(uniforms.u_time, time);
|
|
144
|
+
gl.uniform2f(uniforms.u_resolution, gl.canvas.width, gl.canvas.height);
|
|
145
|
+
|
|
146
|
+
for (const [key, value] of Object.entries(currentUniforms)) {
|
|
147
|
+
const uniform = uniforms[key];
|
|
148
|
+
if (uniform && key !== 'u_resolution') setUniform(gl, uniform, key, value);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
self.onmessage = (event) => {
|
|
155
|
+
const { type } = event.data;
|
|
156
|
+
|
|
157
|
+
switch (type) {
|
|
158
|
+
case 'init': {
|
|
159
|
+
const { offscreenCanvas, fragShader } = event.data;
|
|
160
|
+
if (offscreenCanvas && fragShader) {
|
|
161
|
+
initWorker(offscreenCanvas, fragShader);
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case 'render': {
|
|
166
|
+
const { time } = event.data;
|
|
167
|
+
if (time !== undefined) {
|
|
168
|
+
performRender(time);
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case 'updateUniforms': {
|
|
173
|
+
const { uniforms: newUniforms } = event.data;
|
|
174
|
+
if (newUniforms) {
|
|
175
|
+
currentUniforms = { ...currentUniforms, ...newUniforms };
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case 'resize': {
|
|
180
|
+
const { width, height } = event.data;
|
|
181
|
+
if (gl && width && height) {
|
|
182
|
+
gl.canvas.width = width;
|
|
183
|
+
gl.canvas.height = height;
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case 'destroy': {
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
`;
|
|
193
|
+
const blob = new Blob([workerCode], { type: 'application/javascript' });
|
|
194
|
+
const workerUrl = URL.createObjectURL(blob);
|
|
195
|
+
return new Worker(workerUrl);
|
|
196
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chroma-noise",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && npm run prepack",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"@sveltejs/kit": "^2.43.2",
|
|
39
39
|
"@sveltejs/package": "^2.5.4",
|
|
40
40
|
"@sveltejs/vite-plugin-svelte": "^6.2.0",
|
|
41
|
+
"patch-package": "^8.0.1",
|
|
41
42
|
"prettier": "^3.6.2",
|
|
42
43
|
"prettier-plugin-svelte": "^3.4.0",
|
|
43
44
|
"publint": "^0.3.13",
|