@fjandin/react-shader 0.0.8 → 0.0.10
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 -3
- package/dist/ReactShader.d.ts.map +1 -1
- package/dist/example/shader.d.ts.map +1 -1
- package/dist/hooks/useWebGL.d.ts +0 -1
- package/dist/hooks/useWebGL.d.ts.map +1 -1
- package/dist/index.cjs +116 -38
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +117 -39
- package/dist/shaders/distortion-ripple.d.ts +16 -0
- package/dist/shaders/distortion-ripple.d.ts.map +1 -0
- package/dist/shaders/scene-circles.d.ts +21 -0
- package/dist/shaders/scene-circles.d.ts.map +1 -0
- package/dist/shaders/utils.d.ts +2 -0
- package/dist/shaders/utils.d.ts.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,8 +48,7 @@ function App() {
|
|
|
48
48
|
| `fragment` | `string` | Yes | - | GLSL fragment shader source code |
|
|
49
49
|
| `vertex` | `string` | No | Default quad shader | GLSL vertex shader source code |
|
|
50
50
|
| `uniforms` | `Record<string, UniformValue>` | No | `{}` | Custom uniform values |
|
|
51
|
-
| `className` | `string` | No | - | CSS class name for the
|
|
52
|
-
| `debug` | `boolean` | No | `false` | Show debug overlay with resolution and mouse info |
|
|
51
|
+
| `className` | `string` | No | - | CSS class name for the canvas |
|
|
53
52
|
| `fullscreen` | `boolean` | No | `false` | Render as fixed fullscreen overlay |
|
|
54
53
|
| `timeScale` | `number` | No | `1` | Scale factor for elapsed time |
|
|
55
54
|
| `onFrame` | `(info: FrameInfo) => void` | No | - | Callback invoked on each frame |
|
|
@@ -175,6 +174,7 @@ The `FrameInfo` object contains:
|
|
|
175
174
|
- `time` - Total elapsed time in seconds
|
|
176
175
|
- `resolution` - Canvas resolution as `[width, height]`
|
|
177
176
|
- `mouse` - Mouse position as `[x, y]`
|
|
177
|
+
- `mouseNormalized` - Aspect-corrected mouse position as `[x, y]`
|
|
178
178
|
- `mouseLeftDown` - Whether left mouse button is pressed
|
|
179
179
|
|
|
180
180
|
## TypeScript
|
|
@@ -215,11 +215,12 @@ const declarations = generateUniformDeclarations({
|
|
|
215
215
|
## Features
|
|
216
216
|
|
|
217
217
|
- WebGL2 with WebGL1 fallback
|
|
218
|
-
- High-DPI display support
|
|
218
|
+
- High-DPI display support with automatic DPR change detection
|
|
219
219
|
- Automatic canvas resizing
|
|
220
220
|
- Shader compilation error display
|
|
221
221
|
- Context loss/restoration handling
|
|
222
222
|
- Mouse tracking with WebGL coordinate convention
|
|
223
|
+
- Optimized render loop with minimal per-frame allocations
|
|
223
224
|
|
|
224
225
|
## Requirements
|
|
225
226
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactShader.d.ts","sourceRoot":"","sources":["../src/ReactShader.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"ReactShader.d.ts","sourceRoot":"","sources":["../src/ReactShader.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAgC/C,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,QAAQ,EACR,MAAuB,EACvB,QAAQ,EACR,UAAkB,EAClB,SAAa,EACb,OAAO,EACP,OAAO,EACP,WAAW,GACZ,EAAE,gBAAgB,2CAuDlB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shader.d.ts","sourceRoot":"","sources":["../../src/example/shader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shader.d.ts","sourceRoot":"","sources":["../../src/example/shader.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,QAAQ,QAwCnB,CAAA"}
|
package/dist/hooks/useWebGL.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebGL.d.ts","sourceRoot":"","sources":["../../src/hooks/useWebGL.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAIvD,UAAU,eAAe;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACvC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAChC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACvC,
|
|
1
|
+
{"version":3,"file":"useWebGL.d.ts","sourceRoot":"","sources":["../../src/hooks/useWebGL.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAIvD,UAAU,eAAe;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACvC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAChC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACvC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAqED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe;;;EAmQhD"}
|
package/dist/index.cjs
CHANGED
|
@@ -29,7 +29,10 @@ var __export = (target, all) => {
|
|
|
29
29
|
// src/index.ts
|
|
30
30
|
var exports_src = {};
|
|
31
31
|
__export(exports_src, {
|
|
32
|
+
generateUtilsFunction: () => generateUtilsFunction,
|
|
32
33
|
generateSimplexNoiseFunction: () => generateSimplexNoiseFunction,
|
|
34
|
+
generateSceneCirclesFunction: () => generateSceneCirclesFunction,
|
|
35
|
+
generateDistortionRippleFunction: () => generateDistortionRippleFunction,
|
|
33
36
|
generateColorPaletteFunction: () => generateColorPaletteFunction,
|
|
34
37
|
ReactShader: () => ReactShader
|
|
35
38
|
});
|
|
@@ -270,6 +273,14 @@ function useWebGL(options) {
|
|
|
270
273
|
const timeScaleRef = import_react.useRef(options.timeScale ?? 1);
|
|
271
274
|
const vertexRef = import_react.useRef(options.vertex);
|
|
272
275
|
const fragmentRef = import_react.useRef(options.fragment);
|
|
276
|
+
const dprRef = import_react.useRef(window.devicePixelRatio || 1);
|
|
277
|
+
const defaultUniformsRef = import_react.useRef({
|
|
278
|
+
iTime: 0,
|
|
279
|
+
iMouse: [0, 0],
|
|
280
|
+
iMouseNormalized: [0, 0],
|
|
281
|
+
iMouseLeftDown: 0,
|
|
282
|
+
iResolution: [0, 0]
|
|
283
|
+
});
|
|
273
284
|
uniformsRef.current = options.uniforms;
|
|
274
285
|
onErrorRef.current = options.onError;
|
|
275
286
|
onFrameRef.current = options.onFrame;
|
|
@@ -287,10 +298,9 @@ function useWebGL(options) {
|
|
|
287
298
|
return;
|
|
288
299
|
const deltaTime = lastFrameTimeRef.current === 0 ? 0 : (time - lastFrameTimeRef.current) / 1000;
|
|
289
300
|
lastFrameTimeRef.current = time;
|
|
290
|
-
elapsedTimeRef.current += deltaTime * timeScaleRef.current;
|
|
291
301
|
const { gl, program, positionAttributeLocation, uniformLocationCache } = state;
|
|
292
302
|
const elapsedTime = elapsedTimeRef.current;
|
|
293
|
-
const dpr =
|
|
303
|
+
const dpr = dprRef.current;
|
|
294
304
|
const displayWidth = canvas.clientWidth;
|
|
295
305
|
const displayHeight = canvas.clientHeight;
|
|
296
306
|
if (displayWidth === 0 || displayHeight === 0) {
|
|
@@ -310,22 +320,13 @@ function useWebGL(options) {
|
|
|
310
320
|
gl.enableVertexAttribArray(positionAttributeLocation);
|
|
311
321
|
gl.bindBuffer(gl.ARRAY_BUFFER, state.positionBuffer);
|
|
312
322
|
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
iMouse: mouseRef.current,
|
|
321
|
-
iMouseNormalized: mouseNormalizedRef.current,
|
|
322
|
-
iMouseLeftDown: mouseLeftDownRef.current ? 1 : 0,
|
|
323
|
-
iResolution: [canvas.width, canvas.height]
|
|
324
|
-
};
|
|
325
|
-
setUniforms(gl, program, defaultUniforms, uniformLocationCache);
|
|
326
|
-
if (uniformsRef.current) {
|
|
327
|
-
setUniforms(gl, program, uniformsRef.current, uniformLocationCache);
|
|
328
|
-
}
|
|
323
|
+
const defaultUniforms = defaultUniformsRef.current;
|
|
324
|
+
defaultUniforms.iTime = elapsedTime;
|
|
325
|
+
defaultUniforms.iMouse = mouseRef.current;
|
|
326
|
+
defaultUniforms.iMouseNormalized = mouseNormalizedRef.current;
|
|
327
|
+
defaultUniforms.iMouseLeftDown = mouseLeftDownRef.current ? 1 : 0;
|
|
328
|
+
defaultUniforms.iResolution = [canvas.width, canvas.height];
|
|
329
|
+
setUniforms(gl, program, { ...defaultUniforms, ...uniformsRef.current }, uniformLocationCache);
|
|
329
330
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
330
331
|
if (onFrameRef.current) {
|
|
331
332
|
onFrameRef.current({
|
|
@@ -368,10 +369,16 @@ function useWebGL(options) {
|
|
|
368
369
|
const handleContextRestored = () => {
|
|
369
370
|
initialize();
|
|
370
371
|
};
|
|
372
|
+
const dprMediaQuery = window.matchMedia(`(resolution: ${dprRef.current}dppx)`);
|
|
373
|
+
const handleDprChange = () => {
|
|
374
|
+
dprRef.current = window.devicePixelRatio || 1;
|
|
375
|
+
};
|
|
376
|
+
dprMediaQuery.addEventListener("change", handleDprChange);
|
|
371
377
|
canvas.addEventListener("webglcontextlost", handleContextLost);
|
|
372
378
|
canvas.addEventListener("webglcontextrestored", handleContextRestored);
|
|
373
379
|
initialize();
|
|
374
380
|
return () => {
|
|
381
|
+
dprMediaQuery.removeEventListener("change", handleDprChange);
|
|
375
382
|
canvas.removeEventListener("webglcontextlost", handleContextLost);
|
|
376
383
|
canvas.removeEventListener("webglcontextrestored", handleContextRestored);
|
|
377
384
|
cancelAnimationFrame(animationFrameRef.current);
|
|
@@ -396,7 +403,7 @@ function useWebGL(options) {
|
|
|
396
403
|
const rect = canvasRectRef.current;
|
|
397
404
|
if (!rect)
|
|
398
405
|
return;
|
|
399
|
-
const dpr =
|
|
406
|
+
const dpr = dprRef.current;
|
|
400
407
|
const x = (event.clientX - rect.left) * dpr;
|
|
401
408
|
const y = (rect.height - (event.clientY - rect.top)) * dpr;
|
|
402
409
|
mouseRef.current = [x, y];
|
|
@@ -461,6 +468,24 @@ void main() {
|
|
|
461
468
|
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
462
469
|
}
|
|
463
470
|
`;
|
|
471
|
+
var FULLSCREEN_CONTAINER_STYLE = {
|
|
472
|
+
position: "fixed",
|
|
473
|
+
top: 0,
|
|
474
|
+
left: 0,
|
|
475
|
+
width: "100vw",
|
|
476
|
+
height: "100vh",
|
|
477
|
+
zIndex: 9000
|
|
478
|
+
};
|
|
479
|
+
var DEFAULT_CONTAINER_STYLE = {
|
|
480
|
+
position: "relative",
|
|
481
|
+
width: "100%",
|
|
482
|
+
height: "100%"
|
|
483
|
+
};
|
|
484
|
+
var CANVAS_STYLE = {
|
|
485
|
+
display: "block",
|
|
486
|
+
width: "100%",
|
|
487
|
+
height: "100%"
|
|
488
|
+
};
|
|
464
489
|
function ReactShader({
|
|
465
490
|
className,
|
|
466
491
|
fragment,
|
|
@@ -477,11 +502,6 @@ function ReactShader({
|
|
|
477
502
|
setError(err.message);
|
|
478
503
|
console.error("ReactShader error:", err);
|
|
479
504
|
}, []);
|
|
480
|
-
const handleFrame = import_react2.useCallback((info) => {
|
|
481
|
-
if (onFrame) {
|
|
482
|
-
onFrame(info);
|
|
483
|
-
}
|
|
484
|
-
}, [onFrame]);
|
|
485
505
|
import_react2.useEffect(() => {
|
|
486
506
|
setError(null);
|
|
487
507
|
}, [fragment, vertex]);
|
|
@@ -490,23 +510,12 @@ function ReactShader({
|
|
|
490
510
|
vertex,
|
|
491
511
|
uniforms,
|
|
492
512
|
onError: handleError,
|
|
493
|
-
onFrame
|
|
513
|
+
onFrame,
|
|
494
514
|
onClick,
|
|
495
515
|
onMouseMove,
|
|
496
516
|
timeScale
|
|
497
517
|
});
|
|
498
|
-
const containerStyle = fullscreen ?
|
|
499
|
-
position: "fixed",
|
|
500
|
-
top: 0,
|
|
501
|
-
left: 0,
|
|
502
|
-
width: "100vw",
|
|
503
|
-
height: "100vh",
|
|
504
|
-
zIndex: 9000
|
|
505
|
-
} : {
|
|
506
|
-
position: "relative",
|
|
507
|
-
width: "100%",
|
|
508
|
-
height: "100%"
|
|
509
|
-
};
|
|
518
|
+
const containerStyle = import_react2.useMemo(() => fullscreen ? FULLSCREEN_CONTAINER_STYLE : DEFAULT_CONTAINER_STYLE, [fullscreen]);
|
|
510
519
|
if (error) {
|
|
511
520
|
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
|
|
512
521
|
className,
|
|
@@ -534,7 +543,7 @@ function ReactShader({
|
|
|
534
543
|
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("canvas", {
|
|
535
544
|
ref: canvasRef,
|
|
536
545
|
className,
|
|
537
|
-
style:
|
|
546
|
+
style: CANVAS_STYLE
|
|
538
547
|
}, undefined, false, undefined, this);
|
|
539
548
|
}
|
|
540
549
|
// src/shaders/color-palette.ts
|
|
@@ -550,6 +559,63 @@ function generateColorPaletteFunction(name, paletteString) {
|
|
|
550
559
|
}
|
|
551
560
|
`;
|
|
552
561
|
}
|
|
562
|
+
// src/shaders/distortion-ripple.ts
|
|
563
|
+
function generateDistortionRippleFunction() {
|
|
564
|
+
return `
|
|
565
|
+
vec2 DistortionRipple(vec2 uv, vec2 center, float radius, float intensity, float thickness) {
|
|
566
|
+
// 1. Calculate vector and distance from center
|
|
567
|
+
vec2 dir = uv - center;
|
|
568
|
+
float dist = length(dir);
|
|
569
|
+
|
|
570
|
+
// 2. Create a mask so the ripple only exists near the radius Z
|
|
571
|
+
// Using smoothstep creates a soft edge for the ripple
|
|
572
|
+
float mask = smoothstep(radius + thickness, radius, dist) * smoothstep(radius - thickness, radius, dist);
|
|
573
|
+
|
|
574
|
+
// 3. Calculate the displacement amount using a Sine wave
|
|
575
|
+
// We subtract dist from radius to orient the wave correctly
|
|
576
|
+
float wave = sin((dist - radius) * 20.0);
|
|
577
|
+
|
|
578
|
+
// 4. Apply intensity and mask, then offset the UV
|
|
579
|
+
vec2 offset = normalize(dir) * wave * intensity * mask;
|
|
580
|
+
|
|
581
|
+
return offset;
|
|
582
|
+
}
|
|
583
|
+
`;
|
|
584
|
+
}
|
|
585
|
+
// src/shaders/scene-circles.ts
|
|
586
|
+
function generateSceneCirclesFunction(paletteString = "[[0.5 0.5 0.5] [0.5 0.5 0.5] [1.0 1.0 1.0] [0.263 0.416 0.557]]") {
|
|
587
|
+
return `
|
|
588
|
+
${generateColorPaletteFunction("circlesPalette", paletteString)}
|
|
589
|
+
|
|
590
|
+
vec3 SceneCircles(
|
|
591
|
+
vec2 uv0,
|
|
592
|
+
float iterations,
|
|
593
|
+
float fractMultiplier,
|
|
594
|
+
float time,
|
|
595
|
+
float waveLength,
|
|
596
|
+
float edgeBlur,
|
|
597
|
+
float contrast
|
|
598
|
+
) {
|
|
599
|
+
vec3 col = vec3(0.0);
|
|
600
|
+
vec2 uv = uv0;
|
|
601
|
+
|
|
602
|
+
for (float i = 0.0; i < iterations; i++) {
|
|
603
|
+
uv = fract(uv * fractMultiplier) - 0.5;
|
|
604
|
+
|
|
605
|
+
float d = length(uv) * exp(-length(uv0));
|
|
606
|
+
|
|
607
|
+
vec3 color = circlesPalette(length(uv0) + i * 0.4 + time * 0.4);
|
|
608
|
+
|
|
609
|
+
d = sin(d * waveLength + time) / waveLength;
|
|
610
|
+
d = abs(d);
|
|
611
|
+
d = pow(edgeBlur / d, contrast);
|
|
612
|
+
|
|
613
|
+
col += color * d;
|
|
614
|
+
}
|
|
615
|
+
return col;
|
|
616
|
+
}
|
|
617
|
+
`;
|
|
618
|
+
}
|
|
553
619
|
// src/shaders/simplex-noise.ts
|
|
554
620
|
function generateSimplexNoiseFunction() {
|
|
555
621
|
return `
|
|
@@ -734,3 +800,15 @@ function generateSimplexNoiseFunction() {
|
|
|
734
800
|
}
|
|
735
801
|
`;
|
|
736
802
|
}
|
|
803
|
+
// src/shaders/utils.ts
|
|
804
|
+
function generateUtilsFunction() {
|
|
805
|
+
return `
|
|
806
|
+
vec2 GetUv(vec2 fragCoord, vec2 resolution) {
|
|
807
|
+
return (fragCoord - 0.5 * resolution) / resolution.y;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
vec2 GetMouse(vec2 mouse, vec2 resolution) {
|
|
811
|
+
return (mouse - 0.5 * resolution) / resolution.y;
|
|
812
|
+
}
|
|
813
|
+
`;
|
|
814
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export { ReactShader } from "./ReactShader";
|
|
2
2
|
export { generateColorPaletteFunction } from "./shaders/color-palette";
|
|
3
|
+
export { generateDistortionRippleFunction } from "./shaders/distortion-ripple";
|
|
4
|
+
export { generateSceneCirclesFunction } from "./shaders/scene-circles";
|
|
3
5
|
export { generateSimplexNoiseFunction } from "./shaders/simplex-noise";
|
|
6
|
+
export { generateUtilsFunction } from "./shaders/utils";
|
|
4
7
|
export type { DefaultUniforms, FloatArray, FrameInfo, ReactShaderProps, UniformValue, Vec2, Vec2Array, Vec3, Vec3Array, Vec4, Vec4Array, } from "./types";
|
|
5
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AACtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AACtE,YAAY,EACV,eAAe,EACf,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,IAAI,EACJ,SAAS,EACT,IAAI,EACJ,SAAS,GACV,MAAM,SAAS,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AACtE,OAAO,EAAE,gCAAgC,EAAE,MAAM,6BAA6B,CAAA;AAC9E,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AACtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AACvD,YAAY,EACV,eAAe,EACf,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,IAAI,EACJ,SAAS,EACT,IAAI,EACJ,SAAS,GACV,MAAM,SAAS,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/ReactShader.tsx
|
|
2
|
-
import { useCallback as useCallback2, useEffect as useEffect2, useState } from "react";
|
|
2
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo, useState } from "react";
|
|
3
3
|
|
|
4
4
|
// src/hooks/useWebGL.ts
|
|
5
5
|
import { useCallback, useEffect, useRef } from "react";
|
|
@@ -233,6 +233,14 @@ function useWebGL(options) {
|
|
|
233
233
|
const timeScaleRef = useRef(options.timeScale ?? 1);
|
|
234
234
|
const vertexRef = useRef(options.vertex);
|
|
235
235
|
const fragmentRef = useRef(options.fragment);
|
|
236
|
+
const dprRef = useRef(window.devicePixelRatio || 1);
|
|
237
|
+
const defaultUniformsRef = useRef({
|
|
238
|
+
iTime: 0,
|
|
239
|
+
iMouse: [0, 0],
|
|
240
|
+
iMouseNormalized: [0, 0],
|
|
241
|
+
iMouseLeftDown: 0,
|
|
242
|
+
iResolution: [0, 0]
|
|
243
|
+
});
|
|
236
244
|
uniformsRef.current = options.uniforms;
|
|
237
245
|
onErrorRef.current = options.onError;
|
|
238
246
|
onFrameRef.current = options.onFrame;
|
|
@@ -250,10 +258,9 @@ function useWebGL(options) {
|
|
|
250
258
|
return;
|
|
251
259
|
const deltaTime = lastFrameTimeRef.current === 0 ? 0 : (time - lastFrameTimeRef.current) / 1000;
|
|
252
260
|
lastFrameTimeRef.current = time;
|
|
253
|
-
elapsedTimeRef.current += deltaTime * timeScaleRef.current;
|
|
254
261
|
const { gl, program, positionAttributeLocation, uniformLocationCache } = state;
|
|
255
262
|
const elapsedTime = elapsedTimeRef.current;
|
|
256
|
-
const dpr =
|
|
263
|
+
const dpr = dprRef.current;
|
|
257
264
|
const displayWidth = canvas.clientWidth;
|
|
258
265
|
const displayHeight = canvas.clientHeight;
|
|
259
266
|
if (displayWidth === 0 || displayHeight === 0) {
|
|
@@ -273,22 +280,13 @@ function useWebGL(options) {
|
|
|
273
280
|
gl.enableVertexAttribArray(positionAttributeLocation);
|
|
274
281
|
gl.bindBuffer(gl.ARRAY_BUFFER, state.positionBuffer);
|
|
275
282
|
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
iMouse: mouseRef.current,
|
|
284
|
-
iMouseNormalized: mouseNormalizedRef.current,
|
|
285
|
-
iMouseLeftDown: mouseLeftDownRef.current ? 1 : 0,
|
|
286
|
-
iResolution: [canvas.width, canvas.height]
|
|
287
|
-
};
|
|
288
|
-
setUniforms(gl, program, defaultUniforms, uniformLocationCache);
|
|
289
|
-
if (uniformsRef.current) {
|
|
290
|
-
setUniforms(gl, program, uniformsRef.current, uniformLocationCache);
|
|
291
|
-
}
|
|
283
|
+
const defaultUniforms = defaultUniformsRef.current;
|
|
284
|
+
defaultUniforms.iTime = elapsedTime;
|
|
285
|
+
defaultUniforms.iMouse = mouseRef.current;
|
|
286
|
+
defaultUniforms.iMouseNormalized = mouseNormalizedRef.current;
|
|
287
|
+
defaultUniforms.iMouseLeftDown = mouseLeftDownRef.current ? 1 : 0;
|
|
288
|
+
defaultUniforms.iResolution = [canvas.width, canvas.height];
|
|
289
|
+
setUniforms(gl, program, { ...defaultUniforms, ...uniformsRef.current }, uniformLocationCache);
|
|
292
290
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
293
291
|
if (onFrameRef.current) {
|
|
294
292
|
onFrameRef.current({
|
|
@@ -331,10 +329,16 @@ function useWebGL(options) {
|
|
|
331
329
|
const handleContextRestored = () => {
|
|
332
330
|
initialize();
|
|
333
331
|
};
|
|
332
|
+
const dprMediaQuery = window.matchMedia(`(resolution: ${dprRef.current}dppx)`);
|
|
333
|
+
const handleDprChange = () => {
|
|
334
|
+
dprRef.current = window.devicePixelRatio || 1;
|
|
335
|
+
};
|
|
336
|
+
dprMediaQuery.addEventListener("change", handleDprChange);
|
|
334
337
|
canvas.addEventListener("webglcontextlost", handleContextLost);
|
|
335
338
|
canvas.addEventListener("webglcontextrestored", handleContextRestored);
|
|
336
339
|
initialize();
|
|
337
340
|
return () => {
|
|
341
|
+
dprMediaQuery.removeEventListener("change", handleDprChange);
|
|
338
342
|
canvas.removeEventListener("webglcontextlost", handleContextLost);
|
|
339
343
|
canvas.removeEventListener("webglcontextrestored", handleContextRestored);
|
|
340
344
|
cancelAnimationFrame(animationFrameRef.current);
|
|
@@ -359,7 +363,7 @@ function useWebGL(options) {
|
|
|
359
363
|
const rect = canvasRectRef.current;
|
|
360
364
|
if (!rect)
|
|
361
365
|
return;
|
|
362
|
-
const dpr =
|
|
366
|
+
const dpr = dprRef.current;
|
|
363
367
|
const x = (event.clientX - rect.left) * dpr;
|
|
364
368
|
const y = (rect.height - (event.clientY - rect.top)) * dpr;
|
|
365
369
|
mouseRef.current = [x, y];
|
|
@@ -424,6 +428,24 @@ void main() {
|
|
|
424
428
|
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
425
429
|
}
|
|
426
430
|
`;
|
|
431
|
+
var FULLSCREEN_CONTAINER_STYLE = {
|
|
432
|
+
position: "fixed",
|
|
433
|
+
top: 0,
|
|
434
|
+
left: 0,
|
|
435
|
+
width: "100vw",
|
|
436
|
+
height: "100vh",
|
|
437
|
+
zIndex: 9000
|
|
438
|
+
};
|
|
439
|
+
var DEFAULT_CONTAINER_STYLE = {
|
|
440
|
+
position: "relative",
|
|
441
|
+
width: "100%",
|
|
442
|
+
height: "100%"
|
|
443
|
+
};
|
|
444
|
+
var CANVAS_STYLE = {
|
|
445
|
+
display: "block",
|
|
446
|
+
width: "100%",
|
|
447
|
+
height: "100%"
|
|
448
|
+
};
|
|
427
449
|
function ReactShader({
|
|
428
450
|
className,
|
|
429
451
|
fragment,
|
|
@@ -440,11 +462,6 @@ function ReactShader({
|
|
|
440
462
|
setError(err.message);
|
|
441
463
|
console.error("ReactShader error:", err);
|
|
442
464
|
}, []);
|
|
443
|
-
const handleFrame = useCallback2((info) => {
|
|
444
|
-
if (onFrame) {
|
|
445
|
-
onFrame(info);
|
|
446
|
-
}
|
|
447
|
-
}, [onFrame]);
|
|
448
465
|
useEffect2(() => {
|
|
449
466
|
setError(null);
|
|
450
467
|
}, [fragment, vertex]);
|
|
@@ -453,23 +470,12 @@ function ReactShader({
|
|
|
453
470
|
vertex,
|
|
454
471
|
uniforms,
|
|
455
472
|
onError: handleError,
|
|
456
|
-
onFrame
|
|
473
|
+
onFrame,
|
|
457
474
|
onClick,
|
|
458
475
|
onMouseMove,
|
|
459
476
|
timeScale
|
|
460
477
|
});
|
|
461
|
-
const containerStyle = fullscreen ?
|
|
462
|
-
position: "fixed",
|
|
463
|
-
top: 0,
|
|
464
|
-
left: 0,
|
|
465
|
-
width: "100vw",
|
|
466
|
-
height: "100vh",
|
|
467
|
-
zIndex: 9000
|
|
468
|
-
} : {
|
|
469
|
-
position: "relative",
|
|
470
|
-
width: "100%",
|
|
471
|
-
height: "100%"
|
|
472
|
-
};
|
|
478
|
+
const containerStyle = useMemo(() => fullscreen ? FULLSCREEN_CONTAINER_STYLE : DEFAULT_CONTAINER_STYLE, [fullscreen]);
|
|
473
479
|
if (error) {
|
|
474
480
|
return /* @__PURE__ */ jsxDEV("div", {
|
|
475
481
|
className,
|
|
@@ -497,7 +503,7 @@ function ReactShader({
|
|
|
497
503
|
return /* @__PURE__ */ jsxDEV("canvas", {
|
|
498
504
|
ref: canvasRef,
|
|
499
505
|
className,
|
|
500
|
-
style:
|
|
506
|
+
style: CANVAS_STYLE
|
|
501
507
|
}, undefined, false, undefined, this);
|
|
502
508
|
}
|
|
503
509
|
// src/shaders/color-palette.ts
|
|
@@ -513,6 +519,63 @@ function generateColorPaletteFunction(name, paletteString) {
|
|
|
513
519
|
}
|
|
514
520
|
`;
|
|
515
521
|
}
|
|
522
|
+
// src/shaders/distortion-ripple.ts
|
|
523
|
+
function generateDistortionRippleFunction() {
|
|
524
|
+
return `
|
|
525
|
+
vec2 DistortionRipple(vec2 uv, vec2 center, float radius, float intensity, float thickness) {
|
|
526
|
+
// 1. Calculate vector and distance from center
|
|
527
|
+
vec2 dir = uv - center;
|
|
528
|
+
float dist = length(dir);
|
|
529
|
+
|
|
530
|
+
// 2. Create a mask so the ripple only exists near the radius Z
|
|
531
|
+
// Using smoothstep creates a soft edge for the ripple
|
|
532
|
+
float mask = smoothstep(radius + thickness, radius, dist) * smoothstep(radius - thickness, radius, dist);
|
|
533
|
+
|
|
534
|
+
// 3. Calculate the displacement amount using a Sine wave
|
|
535
|
+
// We subtract dist from radius to orient the wave correctly
|
|
536
|
+
float wave = sin((dist - radius) * 20.0);
|
|
537
|
+
|
|
538
|
+
// 4. Apply intensity and mask, then offset the UV
|
|
539
|
+
vec2 offset = normalize(dir) * wave * intensity * mask;
|
|
540
|
+
|
|
541
|
+
return offset;
|
|
542
|
+
}
|
|
543
|
+
`;
|
|
544
|
+
}
|
|
545
|
+
// src/shaders/scene-circles.ts
|
|
546
|
+
function generateSceneCirclesFunction(paletteString = "[[0.5 0.5 0.5] [0.5 0.5 0.5] [1.0 1.0 1.0] [0.263 0.416 0.557]]") {
|
|
547
|
+
return `
|
|
548
|
+
${generateColorPaletteFunction("circlesPalette", paletteString)}
|
|
549
|
+
|
|
550
|
+
vec3 SceneCircles(
|
|
551
|
+
vec2 uv0,
|
|
552
|
+
float iterations,
|
|
553
|
+
float fractMultiplier,
|
|
554
|
+
float time,
|
|
555
|
+
float waveLength,
|
|
556
|
+
float edgeBlur,
|
|
557
|
+
float contrast
|
|
558
|
+
) {
|
|
559
|
+
vec3 col = vec3(0.0);
|
|
560
|
+
vec2 uv = uv0;
|
|
561
|
+
|
|
562
|
+
for (float i = 0.0; i < iterations; i++) {
|
|
563
|
+
uv = fract(uv * fractMultiplier) - 0.5;
|
|
564
|
+
|
|
565
|
+
float d = length(uv) * exp(-length(uv0));
|
|
566
|
+
|
|
567
|
+
vec3 color = circlesPalette(length(uv0) + i * 0.4 + time * 0.4);
|
|
568
|
+
|
|
569
|
+
d = sin(d * waveLength + time) / waveLength;
|
|
570
|
+
d = abs(d);
|
|
571
|
+
d = pow(edgeBlur / d, contrast);
|
|
572
|
+
|
|
573
|
+
col += color * d;
|
|
574
|
+
}
|
|
575
|
+
return col;
|
|
576
|
+
}
|
|
577
|
+
`;
|
|
578
|
+
}
|
|
516
579
|
// src/shaders/simplex-noise.ts
|
|
517
580
|
function generateSimplexNoiseFunction() {
|
|
518
581
|
return `
|
|
@@ -697,8 +760,23 @@ function generateSimplexNoiseFunction() {
|
|
|
697
760
|
}
|
|
698
761
|
`;
|
|
699
762
|
}
|
|
763
|
+
// src/shaders/utils.ts
|
|
764
|
+
function generateUtilsFunction() {
|
|
765
|
+
return `
|
|
766
|
+
vec2 GetUv(vec2 fragCoord, vec2 resolution) {
|
|
767
|
+
return (fragCoord - 0.5 * resolution) / resolution.y;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
vec2 GetMouse(vec2 mouse, vec2 resolution) {
|
|
771
|
+
return (mouse - 0.5 * resolution) / resolution.y;
|
|
772
|
+
}
|
|
773
|
+
`;
|
|
774
|
+
}
|
|
700
775
|
export {
|
|
776
|
+
generateUtilsFunction,
|
|
701
777
|
generateSimplexNoiseFunction,
|
|
778
|
+
generateSceneCirclesFunction,
|
|
779
|
+
generateDistortionRippleFunction,
|
|
702
780
|
generateColorPaletteFunction,
|
|
703
781
|
ReactShader
|
|
704
782
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @returns glsl function for distortion ripple
|
|
4
|
+
*
|
|
5
|
+
* vec2 uv: uv coordinates
|
|
6
|
+
*
|
|
7
|
+
* vec2 center: center of the ripple
|
|
8
|
+
*
|
|
9
|
+
* float radius: radius of the ripple
|
|
10
|
+
*
|
|
11
|
+
* float intensity: intensity of the ripple
|
|
12
|
+
*
|
|
13
|
+
* float thickness: thickness of the ripple
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateDistortionRippleFunction(): string;
|
|
16
|
+
//# sourceMappingURL=distortion-ripple.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"distortion-ripple.d.ts","sourceRoot":"","sources":["../../src/shaders/distortion-ripple.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gCAAgC,WAqB/C"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param paletteString
|
|
4
|
+
* @returns glsl function for rendering circles
|
|
5
|
+
*
|
|
6
|
+
* vec2 uv0: uv coordinates
|
|
7
|
+
*
|
|
8
|
+
* float iterations: number of iterations
|
|
9
|
+
*
|
|
10
|
+
* float fractMultiplier: fract multiplier
|
|
11
|
+
*
|
|
12
|
+
* float time: time
|
|
13
|
+
*
|
|
14
|
+
* float waveLength: wave length
|
|
15
|
+
*
|
|
16
|
+
* float edgeBlur: edge blur
|
|
17
|
+
*
|
|
18
|
+
* float contrast: contrast
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateSceneCirclesFunction(paletteString?: string): string;
|
|
21
|
+
//# sourceMappingURL=scene-circles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-circles.d.ts","sourceRoot":"","sources":["../../src/shaders/scene-circles.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,4BAA4B,CAC1C,aAAa,SAAoE,UAiClF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/shaders/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,qBAAqB,WAUpC"}
|