@fjandin/react-shader 0.0.16 → 0.0.17
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/ReactGpuShader.d.ts +11 -0
- package/dist/ReactGpuShader.d.ts.map +1 -0
- package/dist/example/examples/webgl.d.ts +2 -0
- package/dist/example/examples/webgl.d.ts.map +1 -0
- package/dist/example/examples/webgpu.d.ts +2 -0
- package/dist/example/examples/webgpu.d.ts.map +1 -0
- package/dist/example/frontend.d.ts.map +1 -1
- package/dist/hooks/useWebGPU.d.ts +13 -0
- package/dist/hooks/useWebGPU.d.ts.map +1 -0
- package/dist/index.cjs +822 -54
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +822 -54
- package/dist/shaders/color-palette-gpu.d.ts +2 -0
- package/dist/shaders/color-palette-gpu.d.ts.map +1 -0
- package/dist/shaders/distortion-ripple-gpu.d.ts +16 -0
- package/dist/shaders/distortion-ripple-gpu.d.ts.map +1 -0
- package/dist/shaders/scene-circles-gpu.d.ts +21 -0
- package/dist/shaders/scene-circles-gpu.d.ts.map +1 -0
- package/dist/shaders/simplex-noise-gpu.d.ts +2 -0
- package/dist/shaders/simplex-noise-gpu.d.ts.map +1 -0
- package/package.json +2 -1
- package/dist/example/glsl/cartesian-to-polar.glsl.d.ts +0 -2
- package/dist/example/glsl/cartesian-to-polar.glsl.d.ts.map +0 -1
- package/dist/example/glsl/circles.glsl.d.ts +0 -3
- package/dist/example/glsl/circles.glsl.d.ts.map +0 -1
- package/dist/example/glsl/noise.glsl.d.ts +0 -3
- package/dist/example/glsl/noise.glsl.d.ts.map +0 -1
- package/dist/example/glsl/palette.glsl.d.ts +0 -2
- package/dist/example/glsl/palette.glsl.d.ts.map +0 -1
- package/dist/example/glsl/texture-demo.glsl.d.ts +0 -2
- package/dist/example/glsl/texture-demo.glsl.d.ts.map +0 -1
- package/dist/example/lib/logger.d.ts +0 -2
- package/dist/example/lib/logger.d.ts.map +0 -1
- package/dist/example/shader.d.ts +0 -2
- package/dist/example/shader.d.ts.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -31,11 +31,16 @@ var exports_src = {};
|
|
|
31
31
|
__export(exports_src, {
|
|
32
32
|
useAudio: () => useAudio,
|
|
33
33
|
generateUtilsFunction: () => generateUtilsFunction,
|
|
34
|
+
generateSimplexNoiseFunctionGpu: () => generateSimplexNoiseFunctionGpu,
|
|
34
35
|
generateSimplexNoiseFunction: () => generateSimplexNoiseFunction,
|
|
36
|
+
generateSceneCirclesFunctionGpu: () => generateSceneCirclesFunctionGpu,
|
|
35
37
|
generateSceneCirclesFunction: () => generateSceneCirclesFunction,
|
|
38
|
+
generateDistortionRippleFunctionGpu: () => generateDistortionRippleFunctionGpu,
|
|
36
39
|
generateDistortionRippleFunction: () => generateDistortionRippleFunction,
|
|
40
|
+
generateColorPaletteFunctionGpu: () => generateColorPaletteFunctionGpu,
|
|
37
41
|
generateColorPaletteFunction: () => generateColorPaletteFunction,
|
|
38
|
-
ReactShader: () => ReactShader
|
|
42
|
+
ReactShader: () => ReactShader,
|
|
43
|
+
ReactGpuShader: () => ReactGpuShader
|
|
39
44
|
});
|
|
40
45
|
module.exports = __toCommonJS(exports_src);
|
|
41
46
|
|
|
@@ -247,11 +252,525 @@ function useAudio(options = {}) {
|
|
|
247
252
|
isRunning
|
|
248
253
|
};
|
|
249
254
|
}
|
|
250
|
-
// src/
|
|
255
|
+
// src/ReactGpuShader.tsx
|
|
251
256
|
var import_react3 = require("react");
|
|
252
257
|
|
|
253
|
-
// src/hooks/
|
|
258
|
+
// src/hooks/useWebGPU.ts
|
|
254
259
|
var import_react2 = require("react");
|
|
260
|
+
function isVec2(value) {
|
|
261
|
+
return Array.isArray(value) && value.length === 2 && typeof value[0] === "number";
|
|
262
|
+
}
|
|
263
|
+
function isVec3(value) {
|
|
264
|
+
return Array.isArray(value) && value.length === 3 && typeof value[0] === "number";
|
|
265
|
+
}
|
|
266
|
+
function isVec4(value) {
|
|
267
|
+
return Array.isArray(value) && value.length === 4 && typeof value[0] === "number";
|
|
268
|
+
}
|
|
269
|
+
function isVec4Array(value) {
|
|
270
|
+
return Array.isArray(value) && value.length > 0 && Array.isArray(value[0]) && value[0].length === 4;
|
|
271
|
+
}
|
|
272
|
+
function inferWgslType(value) {
|
|
273
|
+
if (typeof value === "number") {
|
|
274
|
+
return { wgslType: "f32", baseType: "f32", isArray: false, arrayLength: 0 };
|
|
275
|
+
}
|
|
276
|
+
if (isVec4Array(value)) {
|
|
277
|
+
return {
|
|
278
|
+
wgslType: `array<vec4f, ${value.length}>`,
|
|
279
|
+
baseType: "vec4f",
|
|
280
|
+
isArray: true,
|
|
281
|
+
arrayLength: value.length
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
if (isVec4(value)) {
|
|
285
|
+
return { wgslType: "vec4f", baseType: "vec4f", isArray: false, arrayLength: 0 };
|
|
286
|
+
}
|
|
287
|
+
if (isVec3(value)) {
|
|
288
|
+
return { wgslType: "vec3f", baseType: "vec3f", isArray: false, arrayLength: 0 };
|
|
289
|
+
}
|
|
290
|
+
if (isVec2(value)) {
|
|
291
|
+
return { wgslType: "vec2f", baseType: "vec2f", isArray: false, arrayLength: 0 };
|
|
292
|
+
}
|
|
293
|
+
throw new Error(`Unsupported uniform value type: ${typeof value}`);
|
|
294
|
+
}
|
|
295
|
+
function getTypeAlignment(type) {
|
|
296
|
+
switch (type) {
|
|
297
|
+
case "f32":
|
|
298
|
+
return 4;
|
|
299
|
+
case "vec2f":
|
|
300
|
+
return 8;
|
|
301
|
+
case "vec3f":
|
|
302
|
+
case "vec4f":
|
|
303
|
+
return 16;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function getTypeSize(type) {
|
|
307
|
+
switch (type) {
|
|
308
|
+
case "f32":
|
|
309
|
+
return 4;
|
|
310
|
+
case "vec2f":
|
|
311
|
+
return 8;
|
|
312
|
+
case "vec3f":
|
|
313
|
+
return 12;
|
|
314
|
+
case "vec4f":
|
|
315
|
+
return 16;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
var UNIFORM_ARRAY_STRIDE = 16;
|
|
319
|
+
var DEFAULT_UNIFORMS = [
|
|
320
|
+
{ name: "iTime", baseType: "f32" },
|
|
321
|
+
{ name: "iMouseLeftDown", baseType: "f32" },
|
|
322
|
+
{ name: "iResolution", baseType: "vec2f" },
|
|
323
|
+
{ name: "iMouse", baseType: "vec2f" },
|
|
324
|
+
{ name: "iMouseNormalized", baseType: "vec2f" }
|
|
325
|
+
];
|
|
326
|
+
function calculateUniformLayout(customUniforms) {
|
|
327
|
+
const fields = [];
|
|
328
|
+
let offset = 0;
|
|
329
|
+
const addField = (name, baseType) => {
|
|
330
|
+
const alignment = getTypeAlignment(baseType);
|
|
331
|
+
const size = getTypeSize(baseType);
|
|
332
|
+
offset = Math.ceil(offset / alignment) * alignment;
|
|
333
|
+
fields.push({ name, type: baseType, offset, size, isArray: false });
|
|
334
|
+
offset += size;
|
|
335
|
+
};
|
|
336
|
+
const addArrayField = (name, baseType, arrayLength) => {
|
|
337
|
+
offset = Math.ceil(offset / 16) * 16;
|
|
338
|
+
const totalSize = arrayLength * UNIFORM_ARRAY_STRIDE;
|
|
339
|
+
fields.push({
|
|
340
|
+
name,
|
|
341
|
+
type: `array<${baseType}, ${arrayLength}>`,
|
|
342
|
+
offset,
|
|
343
|
+
size: totalSize,
|
|
344
|
+
isArray: true,
|
|
345
|
+
arrayLength,
|
|
346
|
+
elementStride: UNIFORM_ARRAY_STRIDE
|
|
347
|
+
});
|
|
348
|
+
offset += totalSize;
|
|
349
|
+
};
|
|
350
|
+
for (const u of DEFAULT_UNIFORMS) {
|
|
351
|
+
addField(u.name, u.baseType);
|
|
352
|
+
}
|
|
353
|
+
if (customUniforms) {
|
|
354
|
+
const scalarEntries = [];
|
|
355
|
+
const arrayEntries = [];
|
|
356
|
+
for (const [name, value] of Object.entries(customUniforms)) {
|
|
357
|
+
const inferred = inferWgslType(value);
|
|
358
|
+
if (inferred.isArray) {
|
|
359
|
+
arrayEntries.push({ name, inferred });
|
|
360
|
+
} else {
|
|
361
|
+
scalarEntries.push({ name, inferred });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
for (const { name } of arrayEntries) {
|
|
365
|
+
scalarEntries.push({
|
|
366
|
+
name: `${name}_count`,
|
|
367
|
+
inferred: { wgslType: "f32", baseType: "f32", isArray: false, arrayLength: 0 }
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
scalarEntries.sort((a, b) => getTypeAlignment(b.inferred.baseType) - getTypeAlignment(a.inferred.baseType));
|
|
371
|
+
for (const { name, inferred } of scalarEntries) {
|
|
372
|
+
addField(name, inferred.baseType);
|
|
373
|
+
}
|
|
374
|
+
for (const { name, inferred } of arrayEntries) {
|
|
375
|
+
addArrayField(name, inferred.baseType, inferred.arrayLength);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const bufferSize = Math.ceil(offset / 16) * 16;
|
|
379
|
+
return { fields, bufferSize };
|
|
380
|
+
}
|
|
381
|
+
function generateUniformStruct(layout) {
|
|
382
|
+
const members = layout.fields.map((f) => ` ${f.name}: ${f.type},`).join(`
|
|
383
|
+
`);
|
|
384
|
+
return `struct Uniforms {
|
|
385
|
+
${members}
|
|
386
|
+
}`;
|
|
387
|
+
}
|
|
388
|
+
function packUniformValue(field, value, floatData) {
|
|
389
|
+
const floatOffset = field.offset / 4;
|
|
390
|
+
if (field.isArray && field.elementStride && field.arrayLength) {
|
|
391
|
+
const stride = field.elementStride / 4;
|
|
392
|
+
const maxLen = field.arrayLength;
|
|
393
|
+
if (isVec4Array(value)) {
|
|
394
|
+
for (let i = 0;i < value.length && i < maxLen; i++) {
|
|
395
|
+
const elemOffset = floatOffset + i * stride;
|
|
396
|
+
floatData[elemOffset] = value[i][0];
|
|
397
|
+
floatData[elemOffset + 1] = value[i][1];
|
|
398
|
+
floatData[elemOffset + 2] = value[i][2];
|
|
399
|
+
floatData[elemOffset + 3] = value[i][3];
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
} else if (typeof value === "number") {
|
|
403
|
+
floatData[floatOffset] = value;
|
|
404
|
+
} else if (Array.isArray(value) && typeof value[0] === "number") {
|
|
405
|
+
for (let i = 0;i < value.length; i++) {
|
|
406
|
+
floatData[floatOffset + i] = value[i];
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
var VERTEX_SHADER = `
|
|
411
|
+
struct VertexOutput {
|
|
412
|
+
@builtin(position) position: vec4f,
|
|
413
|
+
@location(0) uv: vec2f,
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
@vertex
|
|
417
|
+
fn main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
418
|
+
// Full-screen triangle (covers clip space with a single triangle)
|
|
419
|
+
var pos = array<vec2f, 3>(
|
|
420
|
+
vec2f(-1.0, -1.0),
|
|
421
|
+
vec2f(3.0, -1.0),
|
|
422
|
+
vec2f(-1.0, 3.0)
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
var output: VertexOutput;
|
|
426
|
+
output.position = vec4f(pos[vertexIndex], 0.0, 1.0);
|
|
427
|
+
// UV coordinates: (0,0) at bottom-left, (1,1) at top-right
|
|
428
|
+
output.uv = (pos[vertexIndex] + 1.0) * 0.5;
|
|
429
|
+
return output;
|
|
430
|
+
}
|
|
431
|
+
`;
|
|
432
|
+
function createFragmentShader(userCode, layout) {
|
|
433
|
+
return `
|
|
434
|
+
${generateUniformStruct(layout)}
|
|
435
|
+
|
|
436
|
+
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
437
|
+
|
|
438
|
+
${userCode}
|
|
439
|
+
|
|
440
|
+
@fragment
|
|
441
|
+
fn main(@location(0) uv: vec2f) -> @location(0) vec4f {
|
|
442
|
+
let fragCoord = uv * uniforms.iResolution;
|
|
443
|
+
let correctedUv = ((uv * uniforms.iResolution) - 0.5 * uniforms.iResolution) / uniforms.iResolution.y;
|
|
444
|
+
|
|
445
|
+
return mainImage(correctedUv);
|
|
446
|
+
}
|
|
447
|
+
`;
|
|
448
|
+
}
|
|
449
|
+
async function initializeWebGPU(canvas, fragmentSource, customUniforms) {
|
|
450
|
+
if (!navigator.gpu) {
|
|
451
|
+
throw new Error("WebGPU not supported in this browser");
|
|
452
|
+
}
|
|
453
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
454
|
+
if (!adapter) {
|
|
455
|
+
throw new Error("Failed to get WebGPU adapter");
|
|
456
|
+
}
|
|
457
|
+
const device = await adapter.requestDevice();
|
|
458
|
+
const context = canvas.getContext("webgpu");
|
|
459
|
+
if (!context) {
|
|
460
|
+
throw new Error("Failed to get WebGPU context");
|
|
461
|
+
}
|
|
462
|
+
const format = navigator.gpu.getPreferredCanvasFormat();
|
|
463
|
+
context.configure({
|
|
464
|
+
device,
|
|
465
|
+
format,
|
|
466
|
+
alphaMode: "premultiplied"
|
|
467
|
+
});
|
|
468
|
+
const uniformLayout = calculateUniformLayout(customUniforms);
|
|
469
|
+
const vertexModule = device.createShaderModule({
|
|
470
|
+
label: "vertex shader",
|
|
471
|
+
code: VERTEX_SHADER
|
|
472
|
+
});
|
|
473
|
+
const fragmentModule = device.createShaderModule({
|
|
474
|
+
label: "fragment shader",
|
|
475
|
+
code: createFragmentShader(fragmentSource, uniformLayout)
|
|
476
|
+
});
|
|
477
|
+
const uniformBuffer = device.createBuffer({
|
|
478
|
+
label: "uniforms",
|
|
479
|
+
size: uniformLayout.bufferSize,
|
|
480
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
481
|
+
});
|
|
482
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
483
|
+
entries: [
|
|
484
|
+
{
|
|
485
|
+
binding: 0,
|
|
486
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
487
|
+
buffer: { type: "uniform" }
|
|
488
|
+
}
|
|
489
|
+
]
|
|
490
|
+
});
|
|
491
|
+
const uniformBindGroup = device.createBindGroup({
|
|
492
|
+
layout: bindGroupLayout,
|
|
493
|
+
entries: [
|
|
494
|
+
{
|
|
495
|
+
binding: 0,
|
|
496
|
+
resource: { buffer: uniformBuffer }
|
|
497
|
+
}
|
|
498
|
+
]
|
|
499
|
+
});
|
|
500
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
501
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
502
|
+
});
|
|
503
|
+
const pipeline = device.createRenderPipeline({
|
|
504
|
+
label: "render pipeline",
|
|
505
|
+
layout: pipelineLayout,
|
|
506
|
+
vertex: {
|
|
507
|
+
module: vertexModule,
|
|
508
|
+
entryPoint: "main"
|
|
509
|
+
},
|
|
510
|
+
fragment: {
|
|
511
|
+
module: fragmentModule,
|
|
512
|
+
entryPoint: "main",
|
|
513
|
+
targets: [{ format }]
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
return {
|
|
517
|
+
device,
|
|
518
|
+
context,
|
|
519
|
+
pipeline,
|
|
520
|
+
uniformBuffer,
|
|
521
|
+
uniformBindGroup,
|
|
522
|
+
uniformLayout
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function cleanupWebGPU(state) {
|
|
526
|
+
state.uniformBuffer.destroy();
|
|
527
|
+
state.device.destroy();
|
|
528
|
+
}
|
|
529
|
+
function useWebGPU(options) {
|
|
530
|
+
const canvasRef = import_react2.useRef(null);
|
|
531
|
+
const stateRef = import_react2.useRef(null);
|
|
532
|
+
const animationFrameRef = import_react2.useRef(0);
|
|
533
|
+
const elapsedTimeRef = import_react2.useRef(0);
|
|
534
|
+
const lastFrameTimeRef = import_react2.useRef(0);
|
|
535
|
+
const mouseRef = import_react2.useRef([0, 0]);
|
|
536
|
+
const mouseNormalizedRef = import_react2.useRef([0, 0]);
|
|
537
|
+
const mouseLeftDownRef = import_react2.useRef(false);
|
|
538
|
+
const canvasRectRef = import_react2.useRef(null);
|
|
539
|
+
const onErrorRef = import_react2.useRef(options.onError);
|
|
540
|
+
const fragmentRef = import_react2.useRef(options.fragment);
|
|
541
|
+
const uniformsRef = import_react2.useRef(options.uniforms);
|
|
542
|
+
const dprRef = import_react2.useRef(window.devicePixelRatio || 1);
|
|
543
|
+
onErrorRef.current = options.onError;
|
|
544
|
+
fragmentRef.current = options.fragment;
|
|
545
|
+
uniformsRef.current = options.uniforms;
|
|
546
|
+
const render = import_react2.useCallback((time) => {
|
|
547
|
+
const state = stateRef.current;
|
|
548
|
+
const canvas = canvasRef.current;
|
|
549
|
+
if (!state || !canvas) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
const deltaTime = lastFrameTimeRef.current === 0 ? 0 : (time - lastFrameTimeRef.current) / 1000;
|
|
553
|
+
lastFrameTimeRef.current = time;
|
|
554
|
+
elapsedTimeRef.current += deltaTime;
|
|
555
|
+
const { device, context, pipeline, uniformBuffer, uniformBindGroup, uniformLayout } = state;
|
|
556
|
+
const dpr = dprRef.current;
|
|
557
|
+
const displayWidth = canvas.clientWidth;
|
|
558
|
+
const displayHeight = canvas.clientHeight;
|
|
559
|
+
if (displayWidth === 0 || displayHeight === 0) {
|
|
560
|
+
animationFrameRef.current = requestAnimationFrame(render);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const bufferWidth = Math.round(displayWidth * dpr);
|
|
564
|
+
const bufferHeight = Math.round(displayHeight * dpr);
|
|
565
|
+
if (canvas.width !== bufferWidth || canvas.height !== bufferHeight) {
|
|
566
|
+
canvas.width = bufferWidth;
|
|
567
|
+
canvas.height = bufferHeight;
|
|
568
|
+
}
|
|
569
|
+
const defaultValues = {
|
|
570
|
+
iTime: elapsedTimeRef.current,
|
|
571
|
+
iMouseLeftDown: mouseLeftDownRef.current ? 1 : 0,
|
|
572
|
+
iResolution: [canvas.width, canvas.height],
|
|
573
|
+
iMouse: mouseRef.current,
|
|
574
|
+
iMouseNormalized: mouseNormalizedRef.current
|
|
575
|
+
};
|
|
576
|
+
const allValues = { ...defaultValues, ...uniformsRef.current };
|
|
577
|
+
if (uniformsRef.current) {
|
|
578
|
+
for (const [name, value] of Object.entries(uniformsRef.current)) {
|
|
579
|
+
if (isVec4Array(value)) {
|
|
580
|
+
allValues[`${name}_count`] = value.length;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
const uniformData = new Float32Array(uniformLayout.bufferSize / 4);
|
|
585
|
+
for (const field of uniformLayout.fields) {
|
|
586
|
+
const value = allValues[field.name];
|
|
587
|
+
if (value === undefined) {
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
packUniformValue(field, value, uniformData);
|
|
591
|
+
}
|
|
592
|
+
device.queue.writeBuffer(uniformBuffer, 0, uniformData);
|
|
593
|
+
const commandEncoder = device.createCommandEncoder();
|
|
594
|
+
const textureView = context.getCurrentTexture().createView();
|
|
595
|
+
const renderPass = commandEncoder.beginRenderPass({
|
|
596
|
+
colorAttachments: [
|
|
597
|
+
{
|
|
598
|
+
view: textureView,
|
|
599
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
600
|
+
loadOp: "clear",
|
|
601
|
+
storeOp: "store"
|
|
602
|
+
}
|
|
603
|
+
]
|
|
604
|
+
});
|
|
605
|
+
renderPass.setPipeline(pipeline);
|
|
606
|
+
renderPass.setBindGroup(0, uniformBindGroup);
|
|
607
|
+
renderPass.draw(3);
|
|
608
|
+
renderPass.end();
|
|
609
|
+
device.queue.submit([commandEncoder.finish()]);
|
|
610
|
+
animationFrameRef.current = requestAnimationFrame(render);
|
|
611
|
+
}, []);
|
|
612
|
+
import_react2.useEffect(() => {
|
|
613
|
+
const canvas = canvasRef.current;
|
|
614
|
+
if (!canvas)
|
|
615
|
+
return;
|
|
616
|
+
let mounted = true;
|
|
617
|
+
const initialize = async () => {
|
|
618
|
+
try {
|
|
619
|
+
const state = await initializeWebGPU(canvas, fragmentRef.current, uniformsRef.current);
|
|
620
|
+
if (!mounted) {
|
|
621
|
+
cleanupWebGPU(state);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
stateRef.current = state;
|
|
625
|
+
elapsedTimeRef.current = 0;
|
|
626
|
+
lastFrameTimeRef.current = 0;
|
|
627
|
+
animationFrameRef.current = requestAnimationFrame(render);
|
|
628
|
+
} catch (err) {
|
|
629
|
+
if (!mounted)
|
|
630
|
+
return;
|
|
631
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
632
|
+
if (onErrorRef.current) {
|
|
633
|
+
onErrorRef.current(error);
|
|
634
|
+
} else {
|
|
635
|
+
console.error("WebGPU initialization failed:", error);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
const dprMediaQuery = window.matchMedia(`(resolution: ${dprRef.current}dppx)`);
|
|
640
|
+
const handleDprChange = () => {
|
|
641
|
+
dprRef.current = window.devicePixelRatio || 1;
|
|
642
|
+
};
|
|
643
|
+
dprMediaQuery.addEventListener("change", handleDprChange);
|
|
644
|
+
initialize();
|
|
645
|
+
return () => {
|
|
646
|
+
mounted = false;
|
|
647
|
+
dprMediaQuery.removeEventListener("change", handleDprChange);
|
|
648
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
649
|
+
if (stateRef.current) {
|
|
650
|
+
cleanupWebGPU(stateRef.current);
|
|
651
|
+
stateRef.current = null;
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
}, [render]);
|
|
655
|
+
import_react2.useEffect(() => {
|
|
656
|
+
const canvas = canvasRef.current;
|
|
657
|
+
if (!canvas)
|
|
658
|
+
return;
|
|
659
|
+
const updateRect = () => {
|
|
660
|
+
canvasRectRef.current = canvas.getBoundingClientRect();
|
|
661
|
+
};
|
|
662
|
+
updateRect();
|
|
663
|
+
const resizeObserver = new ResizeObserver(updateRect);
|
|
664
|
+
resizeObserver.observe(canvas);
|
|
665
|
+
window.addEventListener("scroll", updateRect, { passive: true });
|
|
666
|
+
const handleMouseMove = (event) => {
|
|
667
|
+
const rect = canvasRectRef.current;
|
|
668
|
+
if (!rect)
|
|
669
|
+
return;
|
|
670
|
+
const dpr = dprRef.current;
|
|
671
|
+
const x = (event.clientX - rect.left) * dpr;
|
|
672
|
+
const y = (rect.height - (event.clientY - rect.top)) * dpr;
|
|
673
|
+
mouseRef.current = [x, y];
|
|
674
|
+
const minDimension = Math.min(canvas.width, canvas.height) || 1;
|
|
675
|
+
mouseNormalizedRef.current = [
|
|
676
|
+
(mouseRef.current[0] - canvas.width / 2) / minDimension,
|
|
677
|
+
(mouseRef.current[1] - canvas.height / 2) / minDimension
|
|
678
|
+
];
|
|
679
|
+
};
|
|
680
|
+
const handleMouseDown = (event) => {
|
|
681
|
+
if (event.button === 0) {
|
|
682
|
+
mouseLeftDownRef.current = true;
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
const handleMouseUp = (event) => {
|
|
686
|
+
if (event.button === 0) {
|
|
687
|
+
mouseLeftDownRef.current = false;
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
691
|
+
window.addEventListener("mousedown", handleMouseDown);
|
|
692
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
693
|
+
return () => {
|
|
694
|
+
resizeObserver.disconnect();
|
|
695
|
+
window.removeEventListener("scroll", updateRect);
|
|
696
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
697
|
+
window.removeEventListener("mousedown", handleMouseDown);
|
|
698
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
699
|
+
};
|
|
700
|
+
}, []);
|
|
701
|
+
return { canvasRef, mouseRef };
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// src/ReactGpuShader.tsx
|
|
705
|
+
var jsx_dev_runtime = require("react/jsx-dev-runtime");
|
|
706
|
+
var FULLSCREEN_CONTAINER_STYLE = {
|
|
707
|
+
position: "fixed",
|
|
708
|
+
top: 0,
|
|
709
|
+
left: 0,
|
|
710
|
+
width: "100vw",
|
|
711
|
+
height: "100vh",
|
|
712
|
+
zIndex: 9000
|
|
713
|
+
};
|
|
714
|
+
var DEFAULT_CONTAINER_STYLE = {
|
|
715
|
+
position: "relative",
|
|
716
|
+
width: "100%",
|
|
717
|
+
height: "100%"
|
|
718
|
+
};
|
|
719
|
+
var CANVAS_STYLE = {
|
|
720
|
+
display: "block",
|
|
721
|
+
width: "100%",
|
|
722
|
+
height: "100%"
|
|
723
|
+
};
|
|
724
|
+
function ReactGpuShader({ className, fragment, uniforms, fullscreen = false }) {
|
|
725
|
+
const [error, setError] = import_react3.useState(null);
|
|
726
|
+
const handleError = import_react3.useCallback((err) => {
|
|
727
|
+
setError(err.message);
|
|
728
|
+
console.error("ReactGpuShader error:", err);
|
|
729
|
+
}, []);
|
|
730
|
+
import_react3.useEffect(() => {
|
|
731
|
+
setError(null);
|
|
732
|
+
}, [fragment]);
|
|
733
|
+
const { canvasRef } = useWebGPU({
|
|
734
|
+
fragment,
|
|
735
|
+
uniforms,
|
|
736
|
+
onError: handleError
|
|
737
|
+
});
|
|
738
|
+
const containerStyle = import_react3.useMemo(() => fullscreen ? FULLSCREEN_CONTAINER_STYLE : DEFAULT_CONTAINER_STYLE, [fullscreen]);
|
|
739
|
+
if (error) {
|
|
740
|
+
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
|
|
741
|
+
className,
|
|
742
|
+
style: {
|
|
743
|
+
...containerStyle,
|
|
744
|
+
display: "flex",
|
|
745
|
+
alignItems: "center",
|
|
746
|
+
justifyContent: "center",
|
|
747
|
+
backgroundColor: "#1a1a1a",
|
|
748
|
+
color: "#ff6b6b",
|
|
749
|
+
fontFamily: "monospace",
|
|
750
|
+
fontSize: "12px",
|
|
751
|
+
padding: "16px",
|
|
752
|
+
overflow: "auto",
|
|
753
|
+
boxSizing: "border-box",
|
|
754
|
+
width: "100%",
|
|
755
|
+
height: "100%"
|
|
756
|
+
},
|
|
757
|
+
children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV("pre", {
|
|
758
|
+
style: { margin: 0, whiteSpace: "pre-wrap" },
|
|
759
|
+
children: error
|
|
760
|
+
}, undefined, false, undefined, this)
|
|
761
|
+
}, undefined, false, undefined, this);
|
|
762
|
+
}
|
|
763
|
+
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("canvas", {
|
|
764
|
+
ref: canvasRef,
|
|
765
|
+
className,
|
|
766
|
+
style: CANVAS_STYLE
|
|
767
|
+
}, undefined, false, undefined, this);
|
|
768
|
+
}
|
|
769
|
+
// src/ReactShader.tsx
|
|
770
|
+
var import_react5 = require("react");
|
|
771
|
+
|
|
772
|
+
// src/hooks/useWebGL.ts
|
|
773
|
+
var import_react4 = require("react");
|
|
255
774
|
|
|
256
775
|
// src/utils/shader.ts
|
|
257
776
|
function compileShader(gl, type, source) {
|
|
@@ -440,13 +959,13 @@ function cleanupTextures(gl, manager) {
|
|
|
440
959
|
|
|
441
960
|
// src/utils/uniforms.ts
|
|
442
961
|
var MAX_ARRAY_LENGTH = 100;
|
|
443
|
-
function
|
|
962
|
+
function isVec22(value) {
|
|
444
963
|
return Array.isArray(value) && value.length === 2 && typeof value[0] === "number";
|
|
445
964
|
}
|
|
446
|
-
function
|
|
965
|
+
function isVec32(value) {
|
|
447
966
|
return Array.isArray(value) && value.length === 3 && typeof value[0] === "number";
|
|
448
967
|
}
|
|
449
|
-
function
|
|
968
|
+
function isVec42(value) {
|
|
450
969
|
return Array.isArray(value) && value.length === 4 && typeof value[0] === "number";
|
|
451
970
|
}
|
|
452
971
|
function isFloatArray(value) {
|
|
@@ -458,11 +977,11 @@ function isVec2Array(value) {
|
|
|
458
977
|
function isVec3Array(value) {
|
|
459
978
|
return Array.isArray(value) && value.length > 0 && Array.isArray(value[0]) && value[0].length === 3;
|
|
460
979
|
}
|
|
461
|
-
function
|
|
980
|
+
function isVec4Array2(value) {
|
|
462
981
|
return Array.isArray(value) && value.length > 0 && Array.isArray(value[0]) && value[0].length === 4;
|
|
463
982
|
}
|
|
464
983
|
function isArrayUniform(value) {
|
|
465
|
-
return isFloatArray(value) || isVec2Array(value) || isVec3Array(value) ||
|
|
984
|
+
return isFloatArray(value) || isVec2Array(value) || isVec3Array(value) || isVec4Array2(value);
|
|
466
985
|
}
|
|
467
986
|
function setUniform(gl, location, value) {
|
|
468
987
|
if (location === null) {
|
|
@@ -470,7 +989,7 @@ function setUniform(gl, location, value) {
|
|
|
470
989
|
}
|
|
471
990
|
if (typeof value === "number") {
|
|
472
991
|
gl.uniform1f(location, value);
|
|
473
|
-
} else if (
|
|
992
|
+
} else if (isVec4Array2(value)) {
|
|
474
993
|
gl.uniform4fv(location, value.flat());
|
|
475
994
|
} else if (isVec3Array(value)) {
|
|
476
995
|
gl.uniform3fv(location, value.flat());
|
|
@@ -478,11 +997,11 @@ function setUniform(gl, location, value) {
|
|
|
478
997
|
gl.uniform2fv(location, value.flat());
|
|
479
998
|
} else if (isFloatArray(value)) {
|
|
480
999
|
gl.uniform1fv(location, value);
|
|
481
|
-
} else if (
|
|
1000
|
+
} else if (isVec42(value)) {
|
|
482
1001
|
gl.uniform4f(location, value[0], value[1], value[2], value[3]);
|
|
483
|
-
} else if (
|
|
1002
|
+
} else if (isVec32(value)) {
|
|
484
1003
|
gl.uniform3f(location, value[0], value[1], value[2]);
|
|
485
|
-
} else if (
|
|
1004
|
+
} else if (isVec22(value)) {
|
|
486
1005
|
gl.uniform2f(location, value[0], value[1]);
|
|
487
1006
|
}
|
|
488
1007
|
}
|
|
@@ -526,7 +1045,7 @@ function getUniformType(value) {
|
|
|
526
1045
|
if (typeof value === "number") {
|
|
527
1046
|
return "float";
|
|
528
1047
|
}
|
|
529
|
-
if (
|
|
1048
|
+
if (isVec4Array2(value)) {
|
|
530
1049
|
return `vec4[${MAX_ARRAY_LENGTH}]`;
|
|
531
1050
|
}
|
|
532
1051
|
if (isVec3Array(value)) {
|
|
@@ -538,13 +1057,13 @@ function getUniformType(value) {
|
|
|
538
1057
|
if (isFloatArray(value)) {
|
|
539
1058
|
return `float[${MAX_ARRAY_LENGTH}]`;
|
|
540
1059
|
}
|
|
541
|
-
if (
|
|
1060
|
+
if (isVec42(value)) {
|
|
542
1061
|
return "vec4";
|
|
543
1062
|
}
|
|
544
|
-
if (
|
|
1063
|
+
if (isVec32(value)) {
|
|
545
1064
|
return "vec3";
|
|
546
1065
|
}
|
|
547
|
-
if (
|
|
1066
|
+
if (isVec22(value)) {
|
|
548
1067
|
return "vec2";
|
|
549
1068
|
}
|
|
550
1069
|
return "float";
|
|
@@ -616,29 +1135,29 @@ function cleanupWebGL(gl, state) {
|
|
|
616
1135
|
gl.deleteProgram(state.program);
|
|
617
1136
|
}
|
|
618
1137
|
function useWebGL(options) {
|
|
619
|
-
const canvasRef =
|
|
620
|
-
const stateRef =
|
|
621
|
-
const animationFrameRef =
|
|
622
|
-
const elapsedTimeRef =
|
|
623
|
-
const lastFrameTimeRef =
|
|
624
|
-
const mouseRef =
|
|
625
|
-
const mouseNormalizedRef =
|
|
626
|
-
const mouseLeftDownRef =
|
|
627
|
-
const canvasRectRef =
|
|
628
|
-
const contextLostRef =
|
|
629
|
-
const uniformsRef =
|
|
630
|
-
const onErrorRef =
|
|
631
|
-
const onFrameRef =
|
|
632
|
-
const onClickRef =
|
|
633
|
-
const onMouseDownRef =
|
|
634
|
-
const onMouseUpRef =
|
|
635
|
-
const onMouseMoveRef =
|
|
636
|
-
const onMouseWheelRef =
|
|
637
|
-
const timeScaleRef =
|
|
638
|
-
const vertexRef =
|
|
639
|
-
const fragmentRef =
|
|
640
|
-
const dprRef =
|
|
641
|
-
const defaultUniformsRef =
|
|
1138
|
+
const canvasRef = import_react4.useRef(null);
|
|
1139
|
+
const stateRef = import_react4.useRef(null);
|
|
1140
|
+
const animationFrameRef = import_react4.useRef(0);
|
|
1141
|
+
const elapsedTimeRef = import_react4.useRef(0);
|
|
1142
|
+
const lastFrameTimeRef = import_react4.useRef(0);
|
|
1143
|
+
const mouseRef = import_react4.useRef([0, 0]);
|
|
1144
|
+
const mouseNormalizedRef = import_react4.useRef([0, 0]);
|
|
1145
|
+
const mouseLeftDownRef = import_react4.useRef(false);
|
|
1146
|
+
const canvasRectRef = import_react4.useRef(null);
|
|
1147
|
+
const contextLostRef = import_react4.useRef(false);
|
|
1148
|
+
const uniformsRef = import_react4.useRef(options.uniforms);
|
|
1149
|
+
const onErrorRef = import_react4.useRef(options.onError);
|
|
1150
|
+
const onFrameRef = import_react4.useRef(options.onFrame);
|
|
1151
|
+
const onClickRef = import_react4.useRef(options.onClick);
|
|
1152
|
+
const onMouseDownRef = import_react4.useRef(options.onMouseDown);
|
|
1153
|
+
const onMouseUpRef = import_react4.useRef(options.onMouseUp);
|
|
1154
|
+
const onMouseMoveRef = import_react4.useRef(options.onMouseMove);
|
|
1155
|
+
const onMouseWheelRef = import_react4.useRef(options.onMouseWheel);
|
|
1156
|
+
const timeScaleRef = import_react4.useRef(options.timeScale ?? 1);
|
|
1157
|
+
const vertexRef = import_react4.useRef(options.vertex);
|
|
1158
|
+
const fragmentRef = import_react4.useRef(options.fragment);
|
|
1159
|
+
const dprRef = import_react4.useRef(window.devicePixelRatio || 1);
|
|
1160
|
+
const defaultUniformsRef = import_react4.useRef({
|
|
642
1161
|
iTime: 0,
|
|
643
1162
|
iMouse: [0, 0],
|
|
644
1163
|
iMouseNormalized: [0, 0],
|
|
@@ -656,7 +1175,7 @@ function useWebGL(options) {
|
|
|
656
1175
|
timeScaleRef.current = options.timeScale ?? 1;
|
|
657
1176
|
vertexRef.current = options.vertex;
|
|
658
1177
|
fragmentRef.current = options.fragment;
|
|
659
|
-
const render =
|
|
1178
|
+
const render = import_react4.useCallback((time) => {
|
|
660
1179
|
if (contextLostRef.current)
|
|
661
1180
|
return;
|
|
662
1181
|
const state = stateRef.current;
|
|
@@ -708,7 +1227,7 @@ function useWebGL(options) {
|
|
|
708
1227
|
}
|
|
709
1228
|
animationFrameRef.current = requestAnimationFrame(render);
|
|
710
1229
|
}, []);
|
|
711
|
-
|
|
1230
|
+
import_react4.useEffect(() => {
|
|
712
1231
|
const canvas = canvasRef.current;
|
|
713
1232
|
if (!canvas)
|
|
714
1233
|
return;
|
|
@@ -756,7 +1275,7 @@ function useWebGL(options) {
|
|
|
756
1275
|
}
|
|
757
1276
|
};
|
|
758
1277
|
}, [render]);
|
|
759
|
-
|
|
1278
|
+
import_react4.useEffect(() => {
|
|
760
1279
|
const canvas = canvasRef.current;
|
|
761
1280
|
if (!canvas)
|
|
762
1281
|
return;
|
|
@@ -856,7 +1375,7 @@ function useWebGL(options) {
|
|
|
856
1375
|
}
|
|
857
1376
|
|
|
858
1377
|
// src/ReactShader.tsx
|
|
859
|
-
var
|
|
1378
|
+
var jsx_dev_runtime2 = require("react/jsx-dev-runtime");
|
|
860
1379
|
var DEFAULT_VERTEX = `#version 300 es
|
|
861
1380
|
precision highp float;
|
|
862
1381
|
in vec2 a_position;
|
|
@@ -865,7 +1384,7 @@ void main() {
|
|
|
865
1384
|
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
866
1385
|
}
|
|
867
1386
|
`;
|
|
868
|
-
var
|
|
1387
|
+
var FULLSCREEN_CONTAINER_STYLE2 = {
|
|
869
1388
|
position: "fixed",
|
|
870
1389
|
top: 0,
|
|
871
1390
|
left: 0,
|
|
@@ -873,12 +1392,12 @@ var FULLSCREEN_CONTAINER_STYLE = {
|
|
|
873
1392
|
height: "100vh",
|
|
874
1393
|
zIndex: 9000
|
|
875
1394
|
};
|
|
876
|
-
var
|
|
1395
|
+
var DEFAULT_CONTAINER_STYLE2 = {
|
|
877
1396
|
position: "relative",
|
|
878
1397
|
width: "100%",
|
|
879
1398
|
height: "100%"
|
|
880
1399
|
};
|
|
881
|
-
var
|
|
1400
|
+
var CANVAS_STYLE2 = {
|
|
882
1401
|
display: "block",
|
|
883
1402
|
width: "100%",
|
|
884
1403
|
height: "100%"
|
|
@@ -897,12 +1416,12 @@ function ReactShader({
|
|
|
897
1416
|
onMouseUp,
|
|
898
1417
|
onMouseWheel
|
|
899
1418
|
}) {
|
|
900
|
-
const [error, setError] =
|
|
901
|
-
const handleError =
|
|
1419
|
+
const [error, setError] = import_react5.useState(null);
|
|
1420
|
+
const handleError = import_react5.useCallback((err) => {
|
|
902
1421
|
setError(err.message);
|
|
903
1422
|
console.error("ReactShader error:", err);
|
|
904
1423
|
}, []);
|
|
905
|
-
|
|
1424
|
+
import_react5.useEffect(() => {
|
|
906
1425
|
setError(null);
|
|
907
1426
|
}, [fragment, vertex]);
|
|
908
1427
|
const { canvasRef } = useWebGL({
|
|
@@ -918,9 +1437,9 @@ function ReactShader({
|
|
|
918
1437
|
onMouseWheel,
|
|
919
1438
|
timeScale
|
|
920
1439
|
});
|
|
921
|
-
const containerStyle =
|
|
1440
|
+
const containerStyle = import_react5.useMemo(() => fullscreen ? FULLSCREEN_CONTAINER_STYLE2 : DEFAULT_CONTAINER_STYLE2, [fullscreen]);
|
|
922
1441
|
if (error) {
|
|
923
|
-
return /* @__PURE__ */
|
|
1442
|
+
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV("div", {
|
|
924
1443
|
className,
|
|
925
1444
|
style: {
|
|
926
1445
|
...containerStyle,
|
|
@@ -937,16 +1456,16 @@ function ReactShader({
|
|
|
937
1456
|
width: "100%",
|
|
938
1457
|
height: "100%"
|
|
939
1458
|
},
|
|
940
|
-
children: /* @__PURE__ */
|
|
1459
|
+
children: /* @__PURE__ */ jsx_dev_runtime2.jsxDEV("pre", {
|
|
941
1460
|
style: { margin: 0, whiteSpace: "pre-wrap" },
|
|
942
1461
|
children: error
|
|
943
1462
|
}, undefined, false, undefined, this)
|
|
944
1463
|
}, undefined, false, undefined, this);
|
|
945
1464
|
}
|
|
946
|
-
return /* @__PURE__ */
|
|
1465
|
+
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV("canvas", {
|
|
947
1466
|
ref: canvasRef,
|
|
948
1467
|
className,
|
|
949
|
-
style:
|
|
1468
|
+
style: CANVAS_STYLE2
|
|
950
1469
|
}, undefined, false, undefined, this);
|
|
951
1470
|
}
|
|
952
1471
|
// src/shaders/color-palette.ts
|
|
@@ -962,6 +1481,19 @@ function generateColorPaletteFunction(name, paletteString) {
|
|
|
962
1481
|
}
|
|
963
1482
|
`;
|
|
964
1483
|
}
|
|
1484
|
+
// src/shaders/color-palette-gpu.ts
|
|
1485
|
+
function generateColorPaletteFunctionGpu(name, paletteString) {
|
|
1486
|
+
const paletteArray = paletteString.replace("[[", "").replace("]]", "").split("] [").map((s) => s.split(" "));
|
|
1487
|
+
return `
|
|
1488
|
+
fn ${name}(t: f32) -> vec3f {
|
|
1489
|
+
let a = vec3f(${paletteArray[0].join(", ")});
|
|
1490
|
+
let b = vec3f(${paletteArray[1].join(", ")});
|
|
1491
|
+
let c = vec3f(${paletteArray[2].join(", ")});
|
|
1492
|
+
let d = vec3f(${paletteArray[3].join(", ")});
|
|
1493
|
+
return a + b * cos(6.28318 * (c * t + d));
|
|
1494
|
+
}
|
|
1495
|
+
`;
|
|
1496
|
+
}
|
|
965
1497
|
// src/shaders/distortion-ripple.ts
|
|
966
1498
|
function generateDistortionRippleFunction() {
|
|
967
1499
|
return `
|
|
@@ -985,6 +1517,29 @@ function generateDistortionRippleFunction() {
|
|
|
985
1517
|
}
|
|
986
1518
|
`;
|
|
987
1519
|
}
|
|
1520
|
+
// src/shaders/distortion-ripple-gpu.ts
|
|
1521
|
+
function generateDistortionRippleFunctionGpu() {
|
|
1522
|
+
return `
|
|
1523
|
+
fn DistortionRipple(uv: vec2f, center: vec2f, radius: f32, intensity: f32, thickness: f32) -> vec2f {
|
|
1524
|
+
// 1. Calculate vector and distance from center
|
|
1525
|
+
let dir = uv - center;
|
|
1526
|
+
let dist = length(dir);
|
|
1527
|
+
|
|
1528
|
+
// 2. Create a mask so the ripple only exists near the radius
|
|
1529
|
+
// Using smoothstep creates a soft edge for the ripple
|
|
1530
|
+
let mask = smoothstep(radius + thickness, radius, dist) * smoothstep(radius - thickness, radius, dist);
|
|
1531
|
+
|
|
1532
|
+
// 3. Calculate the displacement amount using a Sine wave
|
|
1533
|
+
// We subtract dist from radius to orient the wave correctly
|
|
1534
|
+
let wave = sin((dist - radius) * 20.0);
|
|
1535
|
+
|
|
1536
|
+
// 4. Apply intensity and mask, then offset the UV
|
|
1537
|
+
let offset = normalize(dir) * wave * intensity * mask;
|
|
1538
|
+
|
|
1539
|
+
return offset;
|
|
1540
|
+
}
|
|
1541
|
+
`;
|
|
1542
|
+
}
|
|
988
1543
|
// src/shaders/scene-circles.ts
|
|
989
1544
|
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]]") {
|
|
990
1545
|
return `
|
|
@@ -1019,6 +1574,44 @@ function generateSceneCirclesFunction(paletteString = "[[0.5 0.5 0.5] [0.5 0.5 0
|
|
|
1019
1574
|
}
|
|
1020
1575
|
`;
|
|
1021
1576
|
}
|
|
1577
|
+
// src/shaders/scene-circles-gpu.ts
|
|
1578
|
+
function generateSceneCirclesFunctionGpu(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]]") {
|
|
1579
|
+
return `
|
|
1580
|
+
${generateColorPaletteFunctionGpu("circlesPalette", paletteString)}
|
|
1581
|
+
|
|
1582
|
+
fn glsl_fract(x: vec2f) -> vec2f {
|
|
1583
|
+
return x - floor(x);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
fn SceneCircles(
|
|
1587
|
+
uv0: vec2f,
|
|
1588
|
+
iterations: f32,
|
|
1589
|
+
fractMultiplier: f32,
|
|
1590
|
+
time: f32,
|
|
1591
|
+
waveLength: f32,
|
|
1592
|
+
edgeBlur: f32,
|
|
1593
|
+
contrast: f32
|
|
1594
|
+
) -> vec3f {
|
|
1595
|
+
var col = vec3f(0.0);
|
|
1596
|
+
var uv = uv0;
|
|
1597
|
+
|
|
1598
|
+
for (var i = 0.0; i < iterations; i += 1.0) {
|
|
1599
|
+
uv = fract(uv * fractMultiplier) - 0.5;
|
|
1600
|
+
|
|
1601
|
+
var d = length(uv) * exp(-length(uv0));
|
|
1602
|
+
|
|
1603
|
+
let color = circlesPalette(length(uv0) + i * 0.4 + time * 0.4);
|
|
1604
|
+
|
|
1605
|
+
d = sin(d * waveLength + time) / waveLength;
|
|
1606
|
+
d = abs(d);
|
|
1607
|
+
d = pow(edgeBlur / d, contrast);
|
|
1608
|
+
|
|
1609
|
+
col += color * d;
|
|
1610
|
+
}
|
|
1611
|
+
return col;
|
|
1612
|
+
}
|
|
1613
|
+
`;
|
|
1614
|
+
}
|
|
1022
1615
|
// src/shaders/simplex-noise.ts
|
|
1023
1616
|
function generateSimplexNoiseFunction() {
|
|
1024
1617
|
return `
|
|
@@ -1203,6 +1796,181 @@ function generateSimplexNoiseFunction() {
|
|
|
1203
1796
|
}
|
|
1204
1797
|
`;
|
|
1205
1798
|
}
|
|
1799
|
+
// src/shaders/simplex-noise-gpu.ts
|
|
1800
|
+
function generateSimplexNoiseFunctionGpu() {
|
|
1801
|
+
return `
|
|
1802
|
+
|
|
1803
|
+
fn mod289_4(x: vec4f) -> vec4f { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
1804
|
+
|
|
1805
|
+
fn mod289_3(x: vec3f) -> vec3f { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
1806
|
+
|
|
1807
|
+
fn mod289_1(x: f32) -> f32 { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
1808
|
+
|
|
1809
|
+
fn permute_4(x: vec4f) -> vec4f { return mod289_4(((x * 34.0) + 10.0) * x); }
|
|
1810
|
+
|
|
1811
|
+
fn permute_1(x: f32) -> f32 { return mod289_1(((x * 34.0) + 10.0) * x); }
|
|
1812
|
+
|
|
1813
|
+
fn taylorInvSqrt_4(r: vec4f) -> vec4f { return 1.79284291400159 - 0.85373472095314 * r; }
|
|
1814
|
+
|
|
1815
|
+
fn taylorInvSqrt_1(r: f32) -> f32 { return 1.79284291400159 - 0.85373472095314 * r; }
|
|
1816
|
+
|
|
1817
|
+
fn grad4(j: f32, ip: vec4f) -> vec4f {
|
|
1818
|
+
let ones = vec4f(1.0, 1.0, 1.0, -1.0);
|
|
1819
|
+
var p: vec4f;
|
|
1820
|
+
var s: vec4f;
|
|
1821
|
+
|
|
1822
|
+
p = vec4f(
|
|
1823
|
+
floor(fract(vec3f(j) * ip.xyz) * 7.0) * ip.z - 1.0,
|
|
1824
|
+
0.0
|
|
1825
|
+
);
|
|
1826
|
+
p.w = 1.5 - dot(abs(p.xyz), ones.xyz);
|
|
1827
|
+
s = select(vec4f(0.0), vec4f(1.0), p < vec4f(0.0));
|
|
1828
|
+
p = vec4f(p.xyz + (s.xyz * 2.0 - 1.0) * s.www, p.w);
|
|
1829
|
+
|
|
1830
|
+
return p;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
// (sqrt(5) - 1)/4 = F4
|
|
1834
|
+
const F4: f32 = 0.309016994374947451;
|
|
1835
|
+
|
|
1836
|
+
fn SimplexNoise4D(v: vec4f) -> f32 {
|
|
1837
|
+
let C = vec4f(
|
|
1838
|
+
0.138196601125011, // (5 - sqrt(5))/20 G4
|
|
1839
|
+
0.276393202250021, // 2 * G4
|
|
1840
|
+
0.414589803375032, // 3 * G4
|
|
1841
|
+
-0.447213595499958 // -1 + 4 * G4
|
|
1842
|
+
);
|
|
1843
|
+
|
|
1844
|
+
// First corner
|
|
1845
|
+
var i = floor(v + dot(v, vec4f(F4)));
|
|
1846
|
+
let x0 = v - i + dot(i, C.xxxx);
|
|
1847
|
+
|
|
1848
|
+
// Other corners
|
|
1849
|
+
// Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI)
|
|
1850
|
+
var i0: vec4f;
|
|
1851
|
+
let isX = step(x0.yzw, x0.xxx);
|
|
1852
|
+
let isYZ = step(x0.zww, x0.yyz);
|
|
1853
|
+
i0.x = isX.x + isX.y + isX.z;
|
|
1854
|
+
i0 = vec4f(i0.x, 1.0 - isX);
|
|
1855
|
+
i0.y = i0.y + isYZ.x + isYZ.y;
|
|
1856
|
+
i0 = vec4f(i0.x, i0.y, i0.zw + 1.0 - isYZ.xy);
|
|
1857
|
+
i0.z = i0.z + isYZ.z;
|
|
1858
|
+
i0.w = i0.w + 1.0 - isYZ.z;
|
|
1859
|
+
|
|
1860
|
+
// i0 now contains the unique values 0,1,2,3 in each channel
|
|
1861
|
+
let i3 = clamp(i0, vec4f(0.0), vec4f(1.0));
|
|
1862
|
+
let i2 = clamp(i0 - 1.0, vec4f(0.0), vec4f(1.0));
|
|
1863
|
+
let i1 = clamp(i0 - 2.0, vec4f(0.0), vec4f(1.0));
|
|
1864
|
+
|
|
1865
|
+
let x1 = x0 - i1 + C.xxxx;
|
|
1866
|
+
let x2 = x0 - i2 + C.yyyy;
|
|
1867
|
+
let x3 = x0 - i3 + C.zzzz;
|
|
1868
|
+
let x4 = x0 + C.wwww;
|
|
1869
|
+
|
|
1870
|
+
// Permutations
|
|
1871
|
+
i = mod289_4(i);
|
|
1872
|
+
let j0 = permute_1(permute_1(permute_1(permute_1(i.w) + i.z) + i.y) + i.x);
|
|
1873
|
+
let j1 = permute_4(permute_4(permute_4(permute_4(
|
|
1874
|
+
i.w + vec4f(i1.w, i2.w, i3.w, 1.0))
|
|
1875
|
+
+ i.z + vec4f(i1.z, i2.z, i3.z, 1.0))
|
|
1876
|
+
+ i.y + vec4f(i1.y, i2.y, i3.y, 1.0))
|
|
1877
|
+
+ i.x + vec4f(i1.x, i2.x, i3.x, 1.0));
|
|
1878
|
+
|
|
1879
|
+
// Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope
|
|
1880
|
+
// 7*7*6 = 294, which is close to the ring size 17*17 = 289.
|
|
1881
|
+
let ip = vec4f(1.0 / 294.0, 1.0 / 49.0, 1.0 / 7.0, 0.0);
|
|
1882
|
+
|
|
1883
|
+
var p0 = grad4(j0, ip);
|
|
1884
|
+
var p1 = grad4(j1.x, ip);
|
|
1885
|
+
var p2 = grad4(j1.y, ip);
|
|
1886
|
+
var p3 = grad4(j1.z, ip);
|
|
1887
|
+
var p4 = grad4(j1.w, ip);
|
|
1888
|
+
|
|
1889
|
+
// Normalise gradients
|
|
1890
|
+
let norm = taylorInvSqrt_4(vec4f(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
|
|
1891
|
+
p0 = p0 * norm.x;
|
|
1892
|
+
p1 = p1 * norm.y;
|
|
1893
|
+
p2 = p2 * norm.z;
|
|
1894
|
+
p3 = p3 * norm.w;
|
|
1895
|
+
p4 = p4 * taylorInvSqrt_1(dot(p4, p4));
|
|
1896
|
+
|
|
1897
|
+
// Mix contributions from the five corners
|
|
1898
|
+
var m0 = max(0.6 - vec3f(dot(x0, x0), dot(x1, x1), dot(x2, x2)), vec3f(0.0));
|
|
1899
|
+
var m1 = max(0.6 - vec2f(dot(x3, x3), dot(x4, x4)), vec2f(0.0));
|
|
1900
|
+
m0 = m0 * m0;
|
|
1901
|
+
m1 = m1 * m1;
|
|
1902
|
+
return 49.0 * (dot(m0 * m0, vec3f(dot(p0, x0), dot(p1, x1), dot(p2, x2)))
|
|
1903
|
+
+ dot(m1 * m1, vec2f(dot(p3, x3), dot(p4, x4))));
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
fn SimplexNoise3D(v: vec3f) -> f32 {
|
|
1907
|
+
let C = vec2f(1.0 / 6.0, 1.0 / 3.0);
|
|
1908
|
+
let D = vec4f(0.0, 0.5, 1.0, 2.0);
|
|
1909
|
+
|
|
1910
|
+
// First corner
|
|
1911
|
+
var i = floor(v + dot(v, C.yyy));
|
|
1912
|
+
let x0 = v - i + dot(i, C.xxx);
|
|
1913
|
+
|
|
1914
|
+
// Other corners
|
|
1915
|
+
let g = step(x0.yzx, x0.xyz);
|
|
1916
|
+
let l = 1.0 - g;
|
|
1917
|
+
let i1 = min(g.xyz, l.zxy);
|
|
1918
|
+
let i2 = max(g.xyz, l.zxy);
|
|
1919
|
+
|
|
1920
|
+
let x1 = x0 - i1 + C.xxx;
|
|
1921
|
+
let x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
|
|
1922
|
+
let x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y
|
|
1923
|
+
|
|
1924
|
+
// Permutations
|
|
1925
|
+
i = mod289_3(i);
|
|
1926
|
+
let p = permute_4(permute_4(permute_4(
|
|
1927
|
+
i.z + vec4f(0.0, i1.z, i2.z, 1.0))
|
|
1928
|
+
+ i.y + vec4f(0.0, i1.y, i2.y, 1.0))
|
|
1929
|
+
+ i.x + vec4f(0.0, i1.x, i2.x, 1.0));
|
|
1930
|
+
|
|
1931
|
+
// Gradients: 7x7 points over a square, mapped onto an octahedron.
|
|
1932
|
+
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
|
|
1933
|
+
let n_ = 0.142857142857; // 1.0/7.0
|
|
1934
|
+
let ns = n_ * D.wyz - D.xzx;
|
|
1935
|
+
|
|
1936
|
+
let j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7)
|
|
1937
|
+
|
|
1938
|
+
let x_ = floor(j * ns.z);
|
|
1939
|
+
let y_ = floor(j - 7.0 * x_); // mod(j,N)
|
|
1940
|
+
|
|
1941
|
+
let x = x_ * ns.x + ns.yyyy;
|
|
1942
|
+
let y = y_ * ns.x + ns.yyyy;
|
|
1943
|
+
let h = 1.0 - abs(x) - abs(y);
|
|
1944
|
+
|
|
1945
|
+
let b0 = vec4f(x.xy, y.xy);
|
|
1946
|
+
let b1 = vec4f(x.zw, y.zw);
|
|
1947
|
+
|
|
1948
|
+
let s0 = floor(b0) * 2.0 + 1.0;
|
|
1949
|
+
let s1 = floor(b1) * 2.0 + 1.0;
|
|
1950
|
+
let sh = -step(h, vec4f(0.0));
|
|
1951
|
+
|
|
1952
|
+
let a0 = b0.xzyw + s0.xzyw * sh.xxyy;
|
|
1953
|
+
let a1 = b1.xzyw + s1.xzyw * sh.zzww;
|
|
1954
|
+
|
|
1955
|
+
var p0 = vec3f(a0.xy, h.x);
|
|
1956
|
+
var p1 = vec3f(a0.zw, h.y);
|
|
1957
|
+
var p2 = vec3f(a1.xy, h.z);
|
|
1958
|
+
var p3 = vec3f(a1.zw, h.w);
|
|
1959
|
+
|
|
1960
|
+
// Normalise gradients
|
|
1961
|
+
let norm = taylorInvSqrt_4(vec4f(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
|
|
1962
|
+
p0 = p0 * norm.x;
|
|
1963
|
+
p1 = p1 * norm.y;
|
|
1964
|
+
p2 = p2 * norm.z;
|
|
1965
|
+
p3 = p3 * norm.w;
|
|
1966
|
+
|
|
1967
|
+
// Mix final noise value
|
|
1968
|
+
var m = max(0.5 - vec4f(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), vec4f(0.0));
|
|
1969
|
+
m = m * m;
|
|
1970
|
+
return 105.0 * dot(m * m, vec4f(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
|
|
1971
|
+
}
|
|
1972
|
+
`;
|
|
1973
|
+
}
|
|
1206
1974
|
// src/shaders/utils.ts
|
|
1207
1975
|
function generateUtilsFunction() {
|
|
1208
1976
|
return `
|