@toriistudio/shader-ui 0.0.7 → 0.0.8

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/index.d.mts CHANGED
@@ -278,23 +278,26 @@ type NoiseWarpPassUniforms = {
278
278
  radius: number;
279
279
  };
280
280
 
281
+ type CombineMode = "add" | "screen" | "multiply" | "overlay" | "max" | "min" | "difference" | "alphaOver" | "premultipliedOver" | "lerp" | "mask";
282
+
281
283
  type DitherPulseRingProps = {
282
284
  spriteTextureSrc?: string;
283
285
  glyphDitherEnabled?: boolean;
284
286
  diffuseEnabled?: boolean;
285
287
  blurEnabled?: boolean;
286
288
  noiseWarpEnabled?: boolean;
289
+ combineMode?: CombineMode;
287
290
  noiseWarpRadius?: NoiseWarpPassUniforms["radius"];
288
291
  noiseWarpStrength?: NoiseWarpPassUniforms["strength"];
289
292
  diffuseRadius?: DiffusePassUniforms["diffuseRadius"];
290
293
  blurRadius?: BlurPassUniforms["blurRadius"];
291
294
  borderThickness?: number;
292
295
  borderIntensity?: number;
293
- borderColor?: BorderBeamPassUniforms["color"];
296
+ borderColor?: BorderBeamPassUniforms["color"] | string;
294
297
  borderDitherStrength?: number;
295
298
  borderTonemap?: boolean;
296
299
  borderAlpha?: number;
297
- ringColor?: ExpandingRingPassUniforms["color"];
300
+ ringColor?: ExpandingRingPassUniforms["color"] | string;
298
301
  ringSpeed?: number;
299
302
  ringPosition?: ExpandingRingPassUniforms["position"];
300
303
  ringAlpha?: ExpandingRingPassUniforms["alpha"];
@@ -303,6 +306,6 @@ type DitherPulseRingProps = {
303
306
  className?: string;
304
307
  style?: React.CSSProperties;
305
308
  };
306
- declare function DitherPulseRing({ spriteTextureSrc, glyphDitherEnabled, diffuseEnabled, blurEnabled, noiseWarpEnabled, noiseWarpRadius, noiseWarpStrength, diffuseRadius, blurRadius, borderThickness, borderIntensity, borderColor, borderDitherStrength, borderTonemap, borderAlpha, ringColor, ringSpeed, ringPosition, ringAlpha, width, height, className, style, }: DitherPulseRingProps): react_jsx_runtime.JSX.Element;
309
+ declare function DitherPulseRing({ spriteTextureSrc, glyphDitherEnabled, diffuseEnabled, blurEnabled, noiseWarpEnabled, combineMode, noiseWarpRadius, noiseWarpStrength, diffuseRadius, blurRadius, borderThickness, borderIntensity, borderColor, borderDitherStrength, borderTonemap, borderAlpha, ringColor, ringSpeed, ringPosition, ringAlpha, width, height, className, style, }: DitherPulseRingProps): react_jsx_runtime.JSX.Element;
307
310
 
308
- export { AnimatedDrawingSVG, DitherPulseRing, EFECTO_ASCII_COMPONENT_DEFAULTS, EFECTO_ASCII_POST_PROCESSING_DEFAULTS, Efecto, type EfectoAsciiColorPalette, type EfectoAsciiStyle, type PublicPostProcessingSettings as EfectoPublicAsciiPostProcessingSettings, FractalFlower, MenuGlitch, type MenuGlitchUniforms, OranoParticles, type OranoParticlesUniforms, RippleWave, ShaderArt, type ShaderArtUniforms, Snow, WANDY_HAND_DEFAULTS, WandyHand };
311
+ export { AnimatedDrawingSVG, type CombineMode as CombineShaderMode, DitherPulseRing, EFECTO_ASCII_COMPONENT_DEFAULTS, EFECTO_ASCII_POST_PROCESSING_DEFAULTS, Efecto, type EfectoAsciiColorPalette, type EfectoAsciiStyle, type PublicPostProcessingSettings as EfectoPublicAsciiPostProcessingSettings, FractalFlower, MenuGlitch, type MenuGlitchUniforms, OranoParticles, type OranoParticlesUniforms, RippleWave, ShaderArt, type ShaderArtUniforms, Snow, WANDY_HAND_DEFAULTS, WandyHand };
package/dist/index.d.ts CHANGED
@@ -278,23 +278,26 @@ type NoiseWarpPassUniforms = {
278
278
  radius: number;
279
279
  };
280
280
 
281
+ type CombineMode = "add" | "screen" | "multiply" | "overlay" | "max" | "min" | "difference" | "alphaOver" | "premultipliedOver" | "lerp" | "mask";
282
+
281
283
  type DitherPulseRingProps = {
282
284
  spriteTextureSrc?: string;
283
285
  glyphDitherEnabled?: boolean;
284
286
  diffuseEnabled?: boolean;
285
287
  blurEnabled?: boolean;
286
288
  noiseWarpEnabled?: boolean;
289
+ combineMode?: CombineMode;
287
290
  noiseWarpRadius?: NoiseWarpPassUniforms["radius"];
288
291
  noiseWarpStrength?: NoiseWarpPassUniforms["strength"];
289
292
  diffuseRadius?: DiffusePassUniforms["diffuseRadius"];
290
293
  blurRadius?: BlurPassUniforms["blurRadius"];
291
294
  borderThickness?: number;
292
295
  borderIntensity?: number;
293
- borderColor?: BorderBeamPassUniforms["color"];
296
+ borderColor?: BorderBeamPassUniforms["color"] | string;
294
297
  borderDitherStrength?: number;
295
298
  borderTonemap?: boolean;
296
299
  borderAlpha?: number;
297
- ringColor?: ExpandingRingPassUniforms["color"];
300
+ ringColor?: ExpandingRingPassUniforms["color"] | string;
298
301
  ringSpeed?: number;
299
302
  ringPosition?: ExpandingRingPassUniforms["position"];
300
303
  ringAlpha?: ExpandingRingPassUniforms["alpha"];
@@ -303,6 +306,6 @@ type DitherPulseRingProps = {
303
306
  className?: string;
304
307
  style?: React.CSSProperties;
305
308
  };
306
- declare function DitherPulseRing({ spriteTextureSrc, glyphDitherEnabled, diffuseEnabled, blurEnabled, noiseWarpEnabled, noiseWarpRadius, noiseWarpStrength, diffuseRadius, blurRadius, borderThickness, borderIntensity, borderColor, borderDitherStrength, borderTonemap, borderAlpha, ringColor, ringSpeed, ringPosition, ringAlpha, width, height, className, style, }: DitherPulseRingProps): react_jsx_runtime.JSX.Element;
309
+ declare function DitherPulseRing({ spriteTextureSrc, glyphDitherEnabled, diffuseEnabled, blurEnabled, noiseWarpEnabled, combineMode, noiseWarpRadius, noiseWarpStrength, diffuseRadius, blurRadius, borderThickness, borderIntensity, borderColor, borderDitherStrength, borderTonemap, borderAlpha, ringColor, ringSpeed, ringPosition, ringAlpha, width, height, className, style, }: DitherPulseRingProps): react_jsx_runtime.JSX.Element;
307
310
 
308
- export { AnimatedDrawingSVG, DitherPulseRing, EFECTO_ASCII_COMPONENT_DEFAULTS, EFECTO_ASCII_POST_PROCESSING_DEFAULTS, Efecto, type EfectoAsciiColorPalette, type EfectoAsciiStyle, type PublicPostProcessingSettings as EfectoPublicAsciiPostProcessingSettings, FractalFlower, MenuGlitch, type MenuGlitchUniforms, OranoParticles, type OranoParticlesUniforms, RippleWave, ShaderArt, type ShaderArtUniforms, Snow, WANDY_HAND_DEFAULTS, WandyHand };
311
+ export { AnimatedDrawingSVG, type CombineMode as CombineShaderMode, DitherPulseRing, EFECTO_ASCII_COMPONENT_DEFAULTS, EFECTO_ASCII_POST_PROCESSING_DEFAULTS, Efecto, type EfectoAsciiColorPalette, type EfectoAsciiStyle, type PublicPostProcessingSettings as EfectoPublicAsciiPostProcessingSettings, FractalFlower, MenuGlitch, type MenuGlitchUniforms, OranoParticles, type OranoParticlesUniforms, RippleWave, ShaderArt, type ShaderArtUniforms, Snow, WANDY_HAND_DEFAULTS, WandyHand };
package/dist/index.js CHANGED
@@ -3550,7 +3550,7 @@ var THREE9 = __toESM(require("three"));
3550
3550
  var import_react11 = require("react");
3551
3551
  var THREE8 = __toESM(require("three"));
3552
3552
 
3553
- // src/hooks/SceneProvider.tsx
3553
+ // src/context/SceneProvider.tsx
3554
3554
  var import_react10 = require("react");
3555
3555
  var import_jsx_runtime11 = require("react/jsx-runtime");
3556
3556
  var SceneProviderContext = (0, import_react10.createContext)(null);
@@ -3746,7 +3746,15 @@ function ShaderPass({
3746
3746
  context.renderer.setRenderTarget(prevTarget);
3747
3747
  context.renderer.autoClear = prevAutoClear;
3748
3748
  },
3749
- [clear, clearColor, enabled, resolutionUniform, target, timeUniform, uniforms]
3749
+ [
3750
+ clear,
3751
+ clearColor,
3752
+ enabled,
3753
+ resolutionUniform,
3754
+ target,
3755
+ timeUniform,
3756
+ uniforms
3757
+ ]
3750
3758
  );
3751
3759
  const sharedContextRef = sharedScene?.contextRef;
3752
3760
  const shouldCreateOwnScene = !sharedContextRef;
@@ -3776,13 +3784,10 @@ function ShaderPass({
3776
3784
  renderOnce();
3777
3785
  return;
3778
3786
  }
3779
- const unregister = register(
3780
- (ctx, delta, elapsed) => {
3781
- void elapsed;
3782
- handleRender(ctx, delta);
3783
- },
3784
- priority
3785
- );
3787
+ const unregister = register((ctx, delta, elapsed) => {
3788
+ void elapsed;
3789
+ handleRender(ctx, delta);
3790
+ }, priority);
3786
3791
  return () => {
3787
3792
  unregister?.();
3788
3793
  };
@@ -4238,75 +4243,14 @@ function ExpandingRingPass({
4238
4243
  var import_react19 = require("react");
4239
4244
  var THREE16 = __toESM(require("three"));
4240
4245
 
4241
- // src/components/CombineShaderPass.tsx
4246
+ // src/components/TargetPreview.tsx
4242
4247
  var import_react17 = require("react");
4243
4248
  var THREE14 = __toESM(require("three"));
4244
4249
  var import_jsx_runtime18 = require("react/jsx-runtime");
4245
- var VERT = `
4246
- out vec2 vUv;
4247
-
4248
- void main() {
4249
- vUv = uv;
4250
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
4251
- }
4252
- `;
4253
- var FRAG = `
4254
- precision highp float;
4255
-
4256
- in vec2 vUv;
4257
-
4258
- uniform sampler2D uTextureA;
4259
- uniform sampler2D uTextureB;
4260
-
4261
- out vec4 fragColor;
4262
-
4263
- void main() {
4264
- vec4 a = texture(uTextureA, vUv);
4265
- vec4 b = texture(uTextureB, vUv);
4266
- vec3 rgb = a.rgb + b.rgb;
4267
- float alpha = max(a.a, b.a);
4268
- fragColor = vec4(rgb, alpha);
4269
- }
4270
- `;
4271
- function CombineShaderPass({
4272
- inputA,
4273
- inputB,
4274
- target = null,
4275
- clear = true,
4276
- priority = 0
4277
- }) {
4278
- const uniforms = (0, import_react17.useMemo)(
4279
- () => ({
4280
- uTextureA: { value: inputA },
4281
- uTextureB: { value: inputB }
4282
- }),
4283
- // eslint-disable-next-line react-hooks/exhaustive-deps
4284
- []
4285
- );
4286
- uniforms.uTextureA.value = inputA;
4287
- uniforms.uTextureB.value = inputB;
4288
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4289
- ShaderPass,
4290
- {
4291
- vertexShader: VERT,
4292
- fragmentShader: FRAG,
4293
- uniforms,
4294
- target,
4295
- clear,
4296
- priority,
4297
- blending: THREE14.NoBlending
4298
- }
4299
- );
4300
- }
4301
-
4302
- // src/components/TargetPreview.tsx
4303
- var import_react18 = require("react");
4304
- var THREE15 = __toESM(require("three"));
4305
- var import_jsx_runtime19 = require("react/jsx-runtime");
4306
4250
  function TargetPreview({
4307
4251
  target,
4308
4252
  renderOrder = 0,
4309
- blending = THREE15.NoBlending,
4253
+ blending = THREE14.NoBlending,
4310
4254
  transparent = true,
4311
4255
  toneMapped = false,
4312
4256
  className,
@@ -4315,15 +4259,15 @@ function TargetPreview({
4315
4259
  height,
4316
4260
  ...divProps
4317
4261
  }) {
4318
- const assetsRef = (0, import_react18.useRef)(null);
4262
+ const assetsRef = (0, import_react17.useRef)(null);
4319
4263
  const sharedScene = useSceneContext();
4320
4264
  const sharedReady = sharedScene?.ready ?? true;
4321
- const handleCreate = (0, import_react18.useCallback)(() => {
4322
- const scene = new THREE15.Scene();
4323
- const camera = new THREE15.OrthographicCamera(-1, 1, 1, -1, 0, 1);
4265
+ const handleCreate = (0, import_react17.useCallback)(() => {
4266
+ const scene = new THREE14.Scene();
4267
+ const camera = new THREE14.OrthographicCamera(-1, 1, 1, -1, 0, 1);
4324
4268
  camera.position.z = 1;
4325
- const geometry = new THREE15.PlaneGeometry(2, 2);
4326
- const material = new THREE15.MeshBasicMaterial({
4269
+ const geometry = new THREE14.PlaneGeometry(2, 2);
4270
+ const material = new THREE14.MeshBasicMaterial({
4327
4271
  map: target.texture,
4328
4272
  toneMapped,
4329
4273
  transparent,
@@ -4331,7 +4275,7 @@ function TargetPreview({
4331
4275
  depthWrite: false,
4332
4276
  depthTest: false
4333
4277
  });
4334
- const mesh = new THREE15.Mesh(geometry, material);
4278
+ const mesh = new THREE14.Mesh(geometry, material);
4335
4279
  mesh.renderOrder = renderOrder;
4336
4280
  scene.add(mesh);
4337
4281
  assetsRef.current = { scene, camera, mesh, geometry, material };
@@ -4342,7 +4286,7 @@ function TargetPreview({
4342
4286
  assetsRef.current = null;
4343
4287
  };
4344
4288
  }, [blending, renderOrder, target.texture, toneMapped, transparent]);
4345
- const handleRender = (0, import_react18.useCallback)((context) => {
4289
+ const handleRender = (0, import_react17.useCallback)((context) => {
4346
4290
  const assets = assetsRef.current;
4347
4291
  if (!assets) return;
4348
4292
  const prevTarget = context.renderer.getRenderTarget();
@@ -4356,13 +4300,13 @@ function TargetPreview({
4356
4300
  onCreate: shouldCreateOwnScene ? handleCreate : void 0,
4357
4301
  onRender: shouldCreateOwnScene ? handleRender : void 0
4358
4302
  });
4359
- const containerRef = shouldCreateOwnScene ? ownScene.containerRef : (0, import_react18.useRef)(null);
4360
- (0, import_react18.useEffect)(() => {
4303
+ const containerRef = shouldCreateOwnScene ? ownScene.containerRef : (0, import_react17.useRef)(null);
4304
+ (0, import_react17.useEffect)(() => {
4361
4305
  if (!sharedContextRef || !sharedReady) return;
4362
4306
  const cleanup = handleCreate();
4363
4307
  return cleanup;
4364
4308
  }, [sharedContextRef, handleCreate, sharedReady]);
4365
- (0, import_react18.useEffect)(() => {
4309
+ (0, import_react17.useEffect)(() => {
4366
4310
  if (!sharedContextRef?.current || !sharedReady) return;
4367
4311
  const context = sharedContextRef.current;
4368
4312
  const register = context.registerRenderCallback;
@@ -4370,23 +4314,20 @@ function TargetPreview({
4370
4314
  handleRender(context);
4371
4315
  return;
4372
4316
  }
4373
- const unregister = register(
4374
- (ctx) => {
4375
- handleRender(ctx);
4376
- },
4377
- renderOrder
4378
- );
4317
+ const unregister = register((ctx) => {
4318
+ handleRender(ctx);
4319
+ }, renderOrder);
4379
4320
  return () => {
4380
4321
  unregister?.();
4381
4322
  };
4382
4323
  }, [sharedContextRef, handleRender, renderOrder, sharedReady]);
4383
- (0, import_react18.useEffect)(() => {
4324
+ (0, import_react17.useEffect)(() => {
4384
4325
  const assets = assetsRef.current;
4385
4326
  if (!assets) return;
4386
4327
  assets.material.map = target.texture;
4387
4328
  assets.material.needsUpdate = true;
4388
4329
  }, [target.texture]);
4389
- (0, import_react18.useEffect)(() => {
4330
+ (0, import_react17.useEffect)(() => {
4390
4331
  const assets = assetsRef.current;
4391
4332
  if (!assets) return;
4392
4333
  assets.material.blending = blending;
@@ -4397,7 +4338,7 @@ function TargetPreview({
4397
4338
  if (!shouldCreateOwnScene) {
4398
4339
  return null;
4399
4340
  }
4400
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4341
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4401
4342
  "div",
4402
4343
  {
4403
4344
  ref: containerRef,
@@ -4412,6 +4353,232 @@ function TargetPreview({
4412
4353
  );
4413
4354
  }
4414
4355
 
4356
+ // src/components/CombineShaderPass.tsx
4357
+ var import_react18 = require("react");
4358
+ var THREE15 = __toESM(require("three"));
4359
+ var import_jsx_runtime19 = require("react/jsx-runtime");
4360
+ var COMBINE_MODE = {
4361
+ add: 0,
4362
+ screen: 1,
4363
+ multiply: 2,
4364
+ overlay: 3,
4365
+ max: 4,
4366
+ min: 5,
4367
+ difference: 6,
4368
+ alphaOver: 7,
4369
+ premultipliedOver: 8,
4370
+ lerp: 9,
4371
+ mask: 10
4372
+ };
4373
+ var TONEMAP = {
4374
+ none: 0,
4375
+ reinhard: 1,
4376
+ aces: 2
4377
+ };
4378
+ var VERT = `
4379
+ out vec2 vUv;
4380
+
4381
+ void main() {
4382
+ vUv = uv;
4383
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
4384
+ }
4385
+ `;
4386
+ var FRAG = `
4387
+ precision highp float;
4388
+
4389
+ in vec2 vUv;
4390
+
4391
+ uniform sampler2D uTextureA;
4392
+ uniform sampler2D uTextureB;
4393
+ uniform sampler2D uMaskTexture;
4394
+
4395
+ uniform int uMode;
4396
+ uniform float uOpacityA;
4397
+ uniform float uOpacityB;
4398
+ uniform float uMix;
4399
+ uniform int uMaskChannel;
4400
+ uniform float uMaskThreshold;
4401
+ uniform bool uInvertMask;
4402
+ uniform bool uClampOutput;
4403
+ uniform int uTonemap;
4404
+ uniform bool uHasMask;
4405
+
4406
+ out vec4 fragColor;
4407
+
4408
+ float overlayChannel(float base, float blend) {
4409
+ return base < 0.5
4410
+ ? (2.0 * base * blend)
4411
+ : (1.0 - 2.0 * (1.0 - base) * (1.0 - blend));
4412
+ }
4413
+
4414
+ vec3 overlayBlend(vec3 base, vec3 blend) {
4415
+ return vec3(
4416
+ overlayChannel(base.r, blend.r),
4417
+ overlayChannel(base.g, blend.g),
4418
+ overlayChannel(base.b, blend.b)
4419
+ );
4420
+ }
4421
+
4422
+ float sampleMask(vec2 uv) {
4423
+ vec4 maskSample = texture(uMaskTexture, uv);
4424
+ float m = maskSample.r;
4425
+ if (uMaskChannel == 1) {
4426
+ m = maskSample.g;
4427
+ } else if (uMaskChannel == 2) {
4428
+ m = maskSample.b;
4429
+ } else if (uMaskChannel == 3) {
4430
+ m = maskSample.a;
4431
+ }
4432
+ if (uInvertMask) {
4433
+ m = 1.0 - m;
4434
+ }
4435
+ return m;
4436
+ }
4437
+
4438
+ vec3 tonemapReinhard(vec3 color) {
4439
+ return color / (color + vec3(1.0));
4440
+ }
4441
+
4442
+ vec3 tonemapAces(vec3 color) {
4443
+ // ACES approximation by Krzysztof Narkowicz
4444
+ float a = 2.51;
4445
+ float b = 0.03;
4446
+ float c = 2.43;
4447
+ float d = 0.59;
4448
+ float e = 0.14;
4449
+ return clamp((color * (a * color + b)) / (color * (c * color + d) + e), 0.0, 1.0);
4450
+ }
4451
+
4452
+ void main() {
4453
+ vec4 a = texture(uTextureA, vUv);
4454
+ vec4 b = texture(uTextureB, vUv);
4455
+
4456
+ a.rgb *= uOpacityA;
4457
+ a.a *= uOpacityA;
4458
+ b.rgb *= uOpacityB;
4459
+ b.a *= uOpacityB;
4460
+
4461
+ vec3 rgb = vec3(0.0);
4462
+ float alpha = 0.0;
4463
+
4464
+ if (uMode == 0) {
4465
+ rgb = a.rgb + b.rgb;
4466
+ alpha = max(a.a, b.a);
4467
+ } else if (uMode == 1) {
4468
+ rgb = 1.0 - (1.0 - a.rgb) * (1.0 - b.rgb);
4469
+ alpha = max(a.a, b.a);
4470
+ } else if (uMode == 2) {
4471
+ rgb = a.rgb * b.rgb;
4472
+ alpha = max(a.a, b.a);
4473
+ } else if (uMode == 3) {
4474
+ rgb = overlayBlend(a.rgb, b.rgb);
4475
+ alpha = max(a.a, b.a);
4476
+ } else if (uMode == 4) {
4477
+ rgb = max(a.rgb, b.rgb);
4478
+ alpha = max(a.a, b.a);
4479
+ } else if (uMode == 5) {
4480
+ rgb = min(a.rgb, b.rgb);
4481
+ alpha = max(a.a, b.a);
4482
+ } else if (uMode == 6) {
4483
+ rgb = abs(a.rgb - b.rgb);
4484
+ alpha = max(a.a, b.a);
4485
+ } else if (uMode == 7) {
4486
+ float outA = a.a + b.a * (1.0 - a.a);
4487
+ vec3 premult = a.rgb * a.a + b.rgb * b.a * (1.0 - a.a);
4488
+ vec3 outRgb = outA > 1e-6 ? premult / outA : vec3(0.0);
4489
+ rgb = outRgb;
4490
+ alpha = outA;
4491
+ } else if (uMode == 8) {
4492
+ rgb = a.rgb + b.rgb * (1.0 - a.a);
4493
+ alpha = a.a + b.a * (1.0 - a.a);
4494
+ } else if (uMode == 9) {
4495
+ float t = uHasMask ? sampleMask(vUv) : uMix;
4496
+ rgb = mix(b.rgb, a.rgb, t);
4497
+ alpha = mix(b.a, a.a, t);
4498
+ } else if (uMode == 10) {
4499
+ float t = uHasMask ? sampleMask(vUv) : uMix;
4500
+ bool chooseA = t > uMaskThreshold;
4501
+ rgb = chooseA ? a.rgb : b.rgb;
4502
+ alpha = chooseA ? a.a : b.a;
4503
+ }
4504
+
4505
+ if (uTonemap == 1) {
4506
+ rgb = tonemapReinhard(rgb);
4507
+ } else if (uTonemap == 2) {
4508
+ rgb = tonemapAces(rgb);
4509
+ }
4510
+
4511
+ if (uClampOutput) {
4512
+ rgb = clamp(rgb, 0.0, 1.0);
4513
+ alpha = clamp(alpha, 0.0, 1.0);
4514
+ }
4515
+
4516
+ fragColor = vec4(rgb, alpha);
4517
+ }
4518
+ `;
4519
+ function CombineShaderPass({
4520
+ inputA,
4521
+ inputB,
4522
+ mode = "add",
4523
+ opacityA = 1,
4524
+ opacityB = 1,
4525
+ clampOutput = true,
4526
+ mix = 0.5,
4527
+ maskTexture,
4528
+ maskChannel = 0,
4529
+ maskThreshold = 0.5,
4530
+ invertMask = false,
4531
+ tonemap = "none",
4532
+ target = null,
4533
+ clear = true,
4534
+ priority = 0
4535
+ }) {
4536
+ const uniforms = (0, import_react18.useMemo)(
4537
+ () => ({
4538
+ uTextureA: { value: inputA },
4539
+ uTextureB: { value: inputB },
4540
+ uMaskTexture: { value: maskTexture ?? inputA },
4541
+ uMode: { value: COMBINE_MODE[mode] },
4542
+ uOpacityA: { value: opacityA },
4543
+ uOpacityB: { value: opacityB },
4544
+ uMix: { value: mix },
4545
+ uMaskChannel: { value: maskChannel },
4546
+ uMaskThreshold: { value: maskThreshold },
4547
+ uInvertMask: { value: invertMask },
4548
+ uClampOutput: { value: clampOutput },
4549
+ uTonemap: { value: TONEMAP[tonemap] },
4550
+ uHasMask: { value: Boolean(maskTexture) }
4551
+ }),
4552
+ // eslint-disable-next-line react-hooks/exhaustive-deps
4553
+ []
4554
+ );
4555
+ uniforms.uTextureA.value = inputA;
4556
+ uniforms.uTextureB.value = inputB;
4557
+ uniforms.uMaskTexture.value = maskTexture ?? inputA;
4558
+ uniforms.uMode.value = COMBINE_MODE[mode];
4559
+ uniforms.uOpacityA.value = opacityA;
4560
+ uniforms.uOpacityB.value = opacityB;
4561
+ uniforms.uMix.value = mix;
4562
+ uniforms.uMaskChannel.value = maskChannel;
4563
+ uniforms.uMaskThreshold.value = maskThreshold;
4564
+ uniforms.uInvertMask.value = invertMask;
4565
+ uniforms.uClampOutput.value = clampOutput;
4566
+ uniforms.uTonemap.value = TONEMAP[tonemap];
4567
+ uniforms.uHasMask.value = Boolean(maskTexture);
4568
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4569
+ ShaderPass,
4570
+ {
4571
+ vertexShader: VERT,
4572
+ fragmentShader: FRAG,
4573
+ uniforms,
4574
+ target,
4575
+ clear,
4576
+ priority,
4577
+ blending: THREE15.NoBlending
4578
+ }
4579
+ );
4580
+ }
4581
+
4415
4582
  // src/components/RenderPipeline.tsx
4416
4583
  var import_jsx_runtime20 = require("react/jsx-runtime");
4417
4584
  var DEFAULT_TARGET_OPTIONS = {
@@ -4420,6 +4587,7 @@ var DEFAULT_TARGET_OPTIONS = {
4420
4587
  };
4421
4588
  function RenderPipeline({
4422
4589
  passes,
4590
+ combine,
4423
4591
  preview = true,
4424
4592
  previewProps
4425
4593
  }) {
@@ -4453,7 +4621,9 @@ function RenderPipeline({
4453
4621
  throw new Error("RenderPipeline requires at least 2 passes.");
4454
4622
  }
4455
4623
  const passTargets = targets.filter((entry) => entry.key.startsWith("pass_"));
4456
- const combineTarget = targets.find((entry) => entry.key === "combine")?.target;
4624
+ const combineTarget = targets.find(
4625
+ (entry) => entry.key === "combine"
4626
+ )?.target;
4457
4627
  const firstEnabledIndex = passes.findIndex((pass) => pass.enabled !== false);
4458
4628
  const secondEnabledIndex = passes.findIndex(
4459
4629
  (pass, index) => index > firstEnabledIndex && pass.enabled !== false
@@ -4489,17 +4659,18 @@ function RenderPipeline({
4489
4659
  const inputB = passOutputTarget(secondEnabledIndex)?.texture ?? inputTexture;
4490
4660
  combineRendered = true;
4491
4661
  previousOutput = combineTarget;
4492
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
4662
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react19.Fragment, { children: [
4493
4663
  element,
4494
4664
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4495
4665
  CombineShaderPass,
4496
4666
  {
4667
+ ...combine,
4497
4668
  inputA,
4498
4669
  inputB,
4499
4670
  target: combineTarget
4500
4671
  }
4501
4672
  )
4502
- ] });
4673
+ ] }, `combine_${index}`);
4503
4674
  }
4504
4675
  previousOutput = target;
4505
4676
  return element;
@@ -4770,6 +4941,7 @@ function DitherPulseRing({
4770
4941
  diffuseEnabled = false,
4771
4942
  blurEnabled = false,
4772
4943
  noiseWarpEnabled = false,
4944
+ combineMode,
4773
4945
  noiseWarpRadius,
4774
4946
  noiseWarpStrength,
4775
4947
  diffuseRadius,
@@ -4806,6 +4978,7 @@ function DitherPulseRing({
4806
4978
  diffuseEnabled,
4807
4979
  blurEnabled,
4808
4980
  noiseWarpEnabled,
4981
+ combineMode,
4809
4982
  noiseWarpRadius,
4810
4983
  noiseWarpStrength,
4811
4984
  diffuseRadius,
@@ -4831,6 +5004,7 @@ function DitherPulseRingContent({
4831
5004
  diffuseEnabled = false,
4832
5005
  blurEnabled = false,
4833
5006
  noiseWarpEnabled = false,
5007
+ combineMode,
4834
5008
  noiseWarpRadius,
4835
5009
  noiseWarpStrength,
4836
5010
  diffuseRadius,
@@ -4846,6 +5020,22 @@ function DitherPulseRingContent({
4846
5020
  ringPosition,
4847
5021
  ringAlpha
4848
5022
  }) {
5023
+ const normalizeHexColor = (hex) => {
5024
+ if (!hex) return null;
5025
+ const normalized = hex.replace("#", "");
5026
+ if (normalized.length !== 6) return null;
5027
+ const r = parseInt(normalized.slice(0, 2), 16) / 255;
5028
+ const g = parseInt(normalized.slice(2, 4), 16) / 255;
5029
+ const b = parseInt(normalized.slice(4, 6), 16) / 255;
5030
+ return [r, g, b];
5031
+ };
5032
+ const resolveColor = (color) => {
5033
+ if (!color) return null;
5034
+ if (typeof color === "string") {
5035
+ return normalizeHexColor(color);
5036
+ }
5037
+ return color;
5038
+ };
4849
5039
  const glyphUniforms = {};
4850
5040
  glyphUniforms.trackMouse = false;
4851
5041
  const noiseUniforms = {};
@@ -4869,52 +5059,74 @@ function DitherPulseRingContent({
4869
5059
  const borderUniforms = {};
4870
5060
  if (borderThickness !== void 0) borderUniforms.thickness = borderThickness;
4871
5061
  if (borderIntensity !== void 0) borderUniforms.intensity = borderIntensity;
4872
- if (borderColor) borderUniforms.color = borderColor;
5062
+ const resolvedBorderColor = resolveColor(borderColor);
5063
+ if (resolvedBorderColor) borderUniforms.color = resolvedBorderColor;
4873
5064
  if (borderDitherStrength !== void 0) {
4874
5065
  borderUniforms.ditherStrength = borderDitherStrength;
4875
5066
  }
4876
5067
  if (borderTonemap !== void 0) borderUniforms.tonemap = borderTonemap;
4877
5068
  if (borderAlpha !== void 0) borderUniforms.alpha = borderAlpha;
4878
5069
  const ringUniforms = {};
4879
- if (ringColor) ringUniforms.color = ringColor;
5070
+ const resolvedRingColor = resolveColor(ringColor);
5071
+ if (resolvedRingColor) ringUniforms.color = resolvedRingColor;
4880
5072
  if (ringSpeed !== void 0) ringUniforms.speed = ringSpeed;
4881
5073
  if (ringPosition) ringUniforms.position = ringPosition;
4882
5074
  if (ringAlpha !== void 0) ringUniforms.alpha = ringAlpha;
4883
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4884
- RenderPipeline,
4885
- {
4886
- passes: [
4887
- {
4888
- component: BorderBeamPass,
4889
- props: { uniforms: borderUniforms }
4890
- },
4891
- {
4892
- component: ExpandingRingPass,
4893
- props: { clear: true, uniforms: ringUniforms }
4894
- },
4895
- {
4896
- component: NoiseWarpPass,
4897
- enabled: noiseWarpEnabled,
4898
- props: { uniforms: noiseUniforms }
4899
- },
4900
- {
4901
- component: GlyphDitherPass,
4902
- enabled: glyphDitherEnabled,
4903
- props: { spriteTextureSrc, uniforms: glyphUniforms }
4904
- },
4905
- {
4906
- component: DiffusePass,
4907
- enabled: diffuseEnabled,
4908
- props: { uniforms: diffuseUniforms }
4909
- },
4910
- {
4911
- component: BlurPass,
4912
- enabled: blurEnabled,
4913
- props: { uniforms: blurUniforms }
4914
- }
4915
- ]
4916
- }
4917
- );
5075
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
5076
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5077
+ RenderPipeline,
5078
+ {
5079
+ passes: [
5080
+ {
5081
+ component: BorderBeamPass,
5082
+ props: { uniforms: borderUniforms }
5083
+ },
5084
+ {
5085
+ component: GlyphDitherPass,
5086
+ enabled: glyphDitherEnabled,
5087
+ props: { spriteTextureSrc, uniforms: glyphUniforms }
5088
+ }
5089
+ ],
5090
+ combine: combineMode ? { mode: combineMode } : void 0
5091
+ }
5092
+ ),
5093
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5094
+ RenderPipeline,
5095
+ {
5096
+ passes: [
5097
+ {
5098
+ component: BorderBeamPass,
5099
+ props: { uniforms: borderUniforms }
5100
+ },
5101
+ {
5102
+ component: ExpandingRingPass,
5103
+ props: { uniforms: ringUniforms }
5104
+ },
5105
+ {
5106
+ component: NoiseWarpPass,
5107
+ enabled: noiseWarpEnabled,
5108
+ props: { uniforms: noiseUniforms }
5109
+ },
5110
+ {
5111
+ component: GlyphDitherPass,
5112
+ enabled: glyphDitherEnabled,
5113
+ props: { spriteTextureSrc, uniforms: glyphUniforms }
5114
+ },
5115
+ {
5116
+ component: DiffusePass,
5117
+ enabled: diffuseEnabled,
5118
+ props: { uniforms: diffuseUniforms }
5119
+ },
5120
+ {
5121
+ component: BlurPass,
5122
+ enabled: blurEnabled,
5123
+ props: { uniforms: blurUniforms }
5124
+ }
5125
+ ],
5126
+ combine: combineMode ? { mode: combineMode } : void 0
5127
+ }
5128
+ )
5129
+ ] });
4918
5130
  }
4919
5131
  // Annotate the CommonJS export names for ESM import in node:
4920
5132
  0 && (module.exports = {
package/dist/index.mjs CHANGED
@@ -3533,11 +3533,8 @@ import {
3533
3533
  } from "react";
3534
3534
  import * as THREE8 from "three";
3535
3535
 
3536
- // src/hooks/SceneProvider.tsx
3537
- import {
3538
- createContext,
3539
- useContext
3540
- } from "react";
3536
+ // src/context/SceneProvider.tsx
3537
+ import { createContext, useContext } from "react";
3541
3538
  import { jsx as jsx11 } from "react/jsx-runtime";
3542
3539
  var SceneProviderContext = createContext(null);
3543
3540
  function SceneProvider({
@@ -3732,7 +3729,15 @@ function ShaderPass({
3732
3729
  context.renderer.setRenderTarget(prevTarget);
3733
3730
  context.renderer.autoClear = prevAutoClear;
3734
3731
  },
3735
- [clear, clearColor, enabled, resolutionUniform, target, timeUniform, uniforms]
3732
+ [
3733
+ clear,
3734
+ clearColor,
3735
+ enabled,
3736
+ resolutionUniform,
3737
+ target,
3738
+ timeUniform,
3739
+ uniforms
3740
+ ]
3736
3741
  );
3737
3742
  const sharedContextRef = sharedScene?.contextRef;
3738
3743
  const shouldCreateOwnScene = !sharedContextRef;
@@ -3762,13 +3767,10 @@ function ShaderPass({
3762
3767
  renderOnce();
3763
3768
  return;
3764
3769
  }
3765
- const unregister = register(
3766
- (ctx, delta, elapsed) => {
3767
- void elapsed;
3768
- handleRender(ctx, delta);
3769
- },
3770
- priority
3771
- );
3770
+ const unregister = register((ctx, delta, elapsed) => {
3771
+ void elapsed;
3772
+ handleRender(ctx, delta);
3773
+ }, priority);
3772
3774
  return () => {
3773
3775
  unregister?.();
3774
3776
  };
@@ -4221,82 +4223,21 @@ function ExpandingRingPass({
4221
4223
  }
4222
4224
 
4223
4225
  // src/components/RenderPipeline.tsx
4224
- import { useEffect as useEffect14, useMemo as useMemo12 } from "react";
4226
+ import { Fragment, useEffect as useEffect14, useMemo as useMemo12 } from "react";
4225
4227
  import * as THREE16 from "three";
4226
4228
 
4227
- // src/components/CombineShaderPass.tsx
4228
- import { useMemo as useMemo11 } from "react";
4229
- import * as THREE14 from "three";
4230
- import { jsx as jsx18 } from "react/jsx-runtime";
4231
- var VERT = `
4232
- out vec2 vUv;
4233
-
4234
- void main() {
4235
- vUv = uv;
4236
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
4237
- }
4238
- `;
4239
- var FRAG = `
4240
- precision highp float;
4241
-
4242
- in vec2 vUv;
4243
-
4244
- uniform sampler2D uTextureA;
4245
- uniform sampler2D uTextureB;
4246
-
4247
- out vec4 fragColor;
4248
-
4249
- void main() {
4250
- vec4 a = texture(uTextureA, vUv);
4251
- vec4 b = texture(uTextureB, vUv);
4252
- vec3 rgb = a.rgb + b.rgb;
4253
- float alpha = max(a.a, b.a);
4254
- fragColor = vec4(rgb, alpha);
4255
- }
4256
- `;
4257
- function CombineShaderPass({
4258
- inputA,
4259
- inputB,
4260
- target = null,
4261
- clear = true,
4262
- priority = 0
4263
- }) {
4264
- const uniforms = useMemo11(
4265
- () => ({
4266
- uTextureA: { value: inputA },
4267
- uTextureB: { value: inputB }
4268
- }),
4269
- // eslint-disable-next-line react-hooks/exhaustive-deps
4270
- []
4271
- );
4272
- uniforms.uTextureA.value = inputA;
4273
- uniforms.uTextureB.value = inputB;
4274
- return /* @__PURE__ */ jsx18(
4275
- ShaderPass,
4276
- {
4277
- vertexShader: VERT,
4278
- fragmentShader: FRAG,
4279
- uniforms,
4280
- target,
4281
- clear,
4282
- priority,
4283
- blending: THREE14.NoBlending
4284
- }
4285
- );
4286
- }
4287
-
4288
4229
  // src/components/TargetPreview.tsx
4289
4230
  import {
4290
4231
  useCallback as useCallback8,
4291
4232
  useEffect as useEffect13,
4292
4233
  useRef as useRef11
4293
4234
  } from "react";
4294
- import * as THREE15 from "three";
4295
- import { jsx as jsx19 } from "react/jsx-runtime";
4235
+ import * as THREE14 from "three";
4236
+ import { jsx as jsx18 } from "react/jsx-runtime";
4296
4237
  function TargetPreview({
4297
4238
  target,
4298
4239
  renderOrder = 0,
4299
- blending = THREE15.NoBlending,
4240
+ blending = THREE14.NoBlending,
4300
4241
  transparent = true,
4301
4242
  toneMapped = false,
4302
4243
  className,
@@ -4309,11 +4250,11 @@ function TargetPreview({
4309
4250
  const sharedScene = useSceneContext();
4310
4251
  const sharedReady = sharedScene?.ready ?? true;
4311
4252
  const handleCreate = useCallback8(() => {
4312
- const scene = new THREE15.Scene();
4313
- const camera = new THREE15.OrthographicCamera(-1, 1, 1, -1, 0, 1);
4253
+ const scene = new THREE14.Scene();
4254
+ const camera = new THREE14.OrthographicCamera(-1, 1, 1, -1, 0, 1);
4314
4255
  camera.position.z = 1;
4315
- const geometry = new THREE15.PlaneGeometry(2, 2);
4316
- const material = new THREE15.MeshBasicMaterial({
4256
+ const geometry = new THREE14.PlaneGeometry(2, 2);
4257
+ const material = new THREE14.MeshBasicMaterial({
4317
4258
  map: target.texture,
4318
4259
  toneMapped,
4319
4260
  transparent,
@@ -4321,7 +4262,7 @@ function TargetPreview({
4321
4262
  depthWrite: false,
4322
4263
  depthTest: false
4323
4264
  });
4324
- const mesh = new THREE15.Mesh(geometry, material);
4265
+ const mesh = new THREE14.Mesh(geometry, material);
4325
4266
  mesh.renderOrder = renderOrder;
4326
4267
  scene.add(mesh);
4327
4268
  assetsRef.current = { scene, camera, mesh, geometry, material };
@@ -4360,12 +4301,9 @@ function TargetPreview({
4360
4301
  handleRender(context);
4361
4302
  return;
4362
4303
  }
4363
- const unregister = register(
4364
- (ctx) => {
4365
- handleRender(ctx);
4366
- },
4367
- renderOrder
4368
- );
4304
+ const unregister = register((ctx) => {
4305
+ handleRender(ctx);
4306
+ }, renderOrder);
4369
4307
  return () => {
4370
4308
  unregister?.();
4371
4309
  };
@@ -4387,7 +4325,7 @@ function TargetPreview({
4387
4325
  if (!shouldCreateOwnScene) {
4388
4326
  return null;
4389
4327
  }
4390
- return /* @__PURE__ */ jsx19(
4328
+ return /* @__PURE__ */ jsx18(
4391
4329
  "div",
4392
4330
  {
4393
4331
  ref: containerRef,
@@ -4402,14 +4340,241 @@ function TargetPreview({
4402
4340
  );
4403
4341
  }
4404
4342
 
4343
+ // src/components/CombineShaderPass.tsx
4344
+ import { useMemo as useMemo11 } from "react";
4345
+ import * as THREE15 from "three";
4346
+ import { jsx as jsx19 } from "react/jsx-runtime";
4347
+ var COMBINE_MODE = {
4348
+ add: 0,
4349
+ screen: 1,
4350
+ multiply: 2,
4351
+ overlay: 3,
4352
+ max: 4,
4353
+ min: 5,
4354
+ difference: 6,
4355
+ alphaOver: 7,
4356
+ premultipliedOver: 8,
4357
+ lerp: 9,
4358
+ mask: 10
4359
+ };
4360
+ var TONEMAP = {
4361
+ none: 0,
4362
+ reinhard: 1,
4363
+ aces: 2
4364
+ };
4365
+ var VERT = `
4366
+ out vec2 vUv;
4367
+
4368
+ void main() {
4369
+ vUv = uv;
4370
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
4371
+ }
4372
+ `;
4373
+ var FRAG = `
4374
+ precision highp float;
4375
+
4376
+ in vec2 vUv;
4377
+
4378
+ uniform sampler2D uTextureA;
4379
+ uniform sampler2D uTextureB;
4380
+ uniform sampler2D uMaskTexture;
4381
+
4382
+ uniform int uMode;
4383
+ uniform float uOpacityA;
4384
+ uniform float uOpacityB;
4385
+ uniform float uMix;
4386
+ uniform int uMaskChannel;
4387
+ uniform float uMaskThreshold;
4388
+ uniform bool uInvertMask;
4389
+ uniform bool uClampOutput;
4390
+ uniform int uTonemap;
4391
+ uniform bool uHasMask;
4392
+
4393
+ out vec4 fragColor;
4394
+
4395
+ float overlayChannel(float base, float blend) {
4396
+ return base < 0.5
4397
+ ? (2.0 * base * blend)
4398
+ : (1.0 - 2.0 * (1.0 - base) * (1.0 - blend));
4399
+ }
4400
+
4401
+ vec3 overlayBlend(vec3 base, vec3 blend) {
4402
+ return vec3(
4403
+ overlayChannel(base.r, blend.r),
4404
+ overlayChannel(base.g, blend.g),
4405
+ overlayChannel(base.b, blend.b)
4406
+ );
4407
+ }
4408
+
4409
+ float sampleMask(vec2 uv) {
4410
+ vec4 maskSample = texture(uMaskTexture, uv);
4411
+ float m = maskSample.r;
4412
+ if (uMaskChannel == 1) {
4413
+ m = maskSample.g;
4414
+ } else if (uMaskChannel == 2) {
4415
+ m = maskSample.b;
4416
+ } else if (uMaskChannel == 3) {
4417
+ m = maskSample.a;
4418
+ }
4419
+ if (uInvertMask) {
4420
+ m = 1.0 - m;
4421
+ }
4422
+ return m;
4423
+ }
4424
+
4425
+ vec3 tonemapReinhard(vec3 color) {
4426
+ return color / (color + vec3(1.0));
4427
+ }
4428
+
4429
+ vec3 tonemapAces(vec3 color) {
4430
+ // ACES approximation by Krzysztof Narkowicz
4431
+ float a = 2.51;
4432
+ float b = 0.03;
4433
+ float c = 2.43;
4434
+ float d = 0.59;
4435
+ float e = 0.14;
4436
+ return clamp((color * (a * color + b)) / (color * (c * color + d) + e), 0.0, 1.0);
4437
+ }
4438
+
4439
+ void main() {
4440
+ vec4 a = texture(uTextureA, vUv);
4441
+ vec4 b = texture(uTextureB, vUv);
4442
+
4443
+ a.rgb *= uOpacityA;
4444
+ a.a *= uOpacityA;
4445
+ b.rgb *= uOpacityB;
4446
+ b.a *= uOpacityB;
4447
+
4448
+ vec3 rgb = vec3(0.0);
4449
+ float alpha = 0.0;
4450
+
4451
+ if (uMode == 0) {
4452
+ rgb = a.rgb + b.rgb;
4453
+ alpha = max(a.a, b.a);
4454
+ } else if (uMode == 1) {
4455
+ rgb = 1.0 - (1.0 - a.rgb) * (1.0 - b.rgb);
4456
+ alpha = max(a.a, b.a);
4457
+ } else if (uMode == 2) {
4458
+ rgb = a.rgb * b.rgb;
4459
+ alpha = max(a.a, b.a);
4460
+ } else if (uMode == 3) {
4461
+ rgb = overlayBlend(a.rgb, b.rgb);
4462
+ alpha = max(a.a, b.a);
4463
+ } else if (uMode == 4) {
4464
+ rgb = max(a.rgb, b.rgb);
4465
+ alpha = max(a.a, b.a);
4466
+ } else if (uMode == 5) {
4467
+ rgb = min(a.rgb, b.rgb);
4468
+ alpha = max(a.a, b.a);
4469
+ } else if (uMode == 6) {
4470
+ rgb = abs(a.rgb - b.rgb);
4471
+ alpha = max(a.a, b.a);
4472
+ } else if (uMode == 7) {
4473
+ float outA = a.a + b.a * (1.0 - a.a);
4474
+ vec3 premult = a.rgb * a.a + b.rgb * b.a * (1.0 - a.a);
4475
+ vec3 outRgb = outA > 1e-6 ? premult / outA : vec3(0.0);
4476
+ rgb = outRgb;
4477
+ alpha = outA;
4478
+ } else if (uMode == 8) {
4479
+ rgb = a.rgb + b.rgb * (1.0 - a.a);
4480
+ alpha = a.a + b.a * (1.0 - a.a);
4481
+ } else if (uMode == 9) {
4482
+ float t = uHasMask ? sampleMask(vUv) : uMix;
4483
+ rgb = mix(b.rgb, a.rgb, t);
4484
+ alpha = mix(b.a, a.a, t);
4485
+ } else if (uMode == 10) {
4486
+ float t = uHasMask ? sampleMask(vUv) : uMix;
4487
+ bool chooseA = t > uMaskThreshold;
4488
+ rgb = chooseA ? a.rgb : b.rgb;
4489
+ alpha = chooseA ? a.a : b.a;
4490
+ }
4491
+
4492
+ if (uTonemap == 1) {
4493
+ rgb = tonemapReinhard(rgb);
4494
+ } else if (uTonemap == 2) {
4495
+ rgb = tonemapAces(rgb);
4496
+ }
4497
+
4498
+ if (uClampOutput) {
4499
+ rgb = clamp(rgb, 0.0, 1.0);
4500
+ alpha = clamp(alpha, 0.0, 1.0);
4501
+ }
4502
+
4503
+ fragColor = vec4(rgb, alpha);
4504
+ }
4505
+ `;
4506
+ function CombineShaderPass({
4507
+ inputA,
4508
+ inputB,
4509
+ mode = "add",
4510
+ opacityA = 1,
4511
+ opacityB = 1,
4512
+ clampOutput = true,
4513
+ mix = 0.5,
4514
+ maskTexture,
4515
+ maskChannel = 0,
4516
+ maskThreshold = 0.5,
4517
+ invertMask = false,
4518
+ tonemap = "none",
4519
+ target = null,
4520
+ clear = true,
4521
+ priority = 0
4522
+ }) {
4523
+ const uniforms = useMemo11(
4524
+ () => ({
4525
+ uTextureA: { value: inputA },
4526
+ uTextureB: { value: inputB },
4527
+ uMaskTexture: { value: maskTexture ?? inputA },
4528
+ uMode: { value: COMBINE_MODE[mode] },
4529
+ uOpacityA: { value: opacityA },
4530
+ uOpacityB: { value: opacityB },
4531
+ uMix: { value: mix },
4532
+ uMaskChannel: { value: maskChannel },
4533
+ uMaskThreshold: { value: maskThreshold },
4534
+ uInvertMask: { value: invertMask },
4535
+ uClampOutput: { value: clampOutput },
4536
+ uTonemap: { value: TONEMAP[tonemap] },
4537
+ uHasMask: { value: Boolean(maskTexture) }
4538
+ }),
4539
+ // eslint-disable-next-line react-hooks/exhaustive-deps
4540
+ []
4541
+ );
4542
+ uniforms.uTextureA.value = inputA;
4543
+ uniforms.uTextureB.value = inputB;
4544
+ uniforms.uMaskTexture.value = maskTexture ?? inputA;
4545
+ uniforms.uMode.value = COMBINE_MODE[mode];
4546
+ uniforms.uOpacityA.value = opacityA;
4547
+ uniforms.uOpacityB.value = opacityB;
4548
+ uniforms.uMix.value = mix;
4549
+ uniforms.uMaskChannel.value = maskChannel;
4550
+ uniforms.uMaskThreshold.value = maskThreshold;
4551
+ uniforms.uInvertMask.value = invertMask;
4552
+ uniforms.uClampOutput.value = clampOutput;
4553
+ uniforms.uTonemap.value = TONEMAP[tonemap];
4554
+ uniforms.uHasMask.value = Boolean(maskTexture);
4555
+ return /* @__PURE__ */ jsx19(
4556
+ ShaderPass,
4557
+ {
4558
+ vertexShader: VERT,
4559
+ fragmentShader: FRAG,
4560
+ uniforms,
4561
+ target,
4562
+ clear,
4563
+ priority,
4564
+ blending: THREE15.NoBlending
4565
+ }
4566
+ );
4567
+ }
4568
+
4405
4569
  // src/components/RenderPipeline.tsx
4406
- import { Fragment, jsx as jsx20, jsxs } from "react/jsx-runtime";
4570
+ import { Fragment as Fragment2, jsx as jsx20, jsxs } from "react/jsx-runtime";
4407
4571
  var DEFAULT_TARGET_OPTIONS = {
4408
4572
  depthBuffer: false,
4409
4573
  stencilBuffer: false
4410
4574
  };
4411
4575
  function RenderPipeline({
4412
4576
  passes,
4577
+ combine,
4413
4578
  preview = true,
4414
4579
  previewProps
4415
4580
  }) {
@@ -4443,7 +4608,9 @@ function RenderPipeline({
4443
4608
  throw new Error("RenderPipeline requires at least 2 passes.");
4444
4609
  }
4445
4610
  const passTargets = targets.filter((entry) => entry.key.startsWith("pass_"));
4446
- const combineTarget = targets.find((entry) => entry.key === "combine")?.target;
4611
+ const combineTarget = targets.find(
4612
+ (entry) => entry.key === "combine"
4613
+ )?.target;
4447
4614
  const firstEnabledIndex = passes.findIndex((pass) => pass.enabled !== false);
4448
4615
  const secondEnabledIndex = passes.findIndex(
4449
4616
  (pass, index) => index > firstEnabledIndex && pass.enabled !== false
@@ -4484,18 +4651,19 @@ function RenderPipeline({
4484
4651
  /* @__PURE__ */ jsx20(
4485
4652
  CombineShaderPass,
4486
4653
  {
4654
+ ...combine,
4487
4655
  inputA,
4488
4656
  inputB,
4489
4657
  target: combineTarget
4490
4658
  }
4491
4659
  )
4492
- ] });
4660
+ ] }, `combine_${index}`);
4493
4661
  }
4494
4662
  previousOutput = target;
4495
4663
  return element;
4496
4664
  });
4497
4665
  const outputTarget = getOutputTarget();
4498
- return /* @__PURE__ */ jsxs(Fragment, { children: [
4666
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
4499
4667
  renderedPasses,
4500
4668
  preview && outputTarget && /* @__PURE__ */ jsx20(
4501
4669
  TargetPreview,
@@ -4753,13 +4921,14 @@ function NoiseWarpPass({
4753
4921
  }
4754
4922
 
4755
4923
  // src/components/DitherPulseRing.tsx
4756
- import { jsx as jsx23 } from "react/jsx-runtime";
4924
+ import { Fragment as Fragment3, jsx as jsx23, jsxs as jsxs2 } from "react/jsx-runtime";
4757
4925
  function DitherPulseRing({
4758
4926
  spriteTextureSrc = DITHER_SPRITE_TEXTURE_SRC,
4759
4927
  glyphDitherEnabled = true,
4760
4928
  diffuseEnabled = false,
4761
4929
  blurEnabled = false,
4762
4930
  noiseWarpEnabled = false,
4931
+ combineMode,
4763
4932
  noiseWarpRadius,
4764
4933
  noiseWarpStrength,
4765
4934
  diffuseRadius,
@@ -4796,6 +4965,7 @@ function DitherPulseRing({
4796
4965
  diffuseEnabled,
4797
4966
  blurEnabled,
4798
4967
  noiseWarpEnabled,
4968
+ combineMode,
4799
4969
  noiseWarpRadius,
4800
4970
  noiseWarpStrength,
4801
4971
  diffuseRadius,
@@ -4821,6 +4991,7 @@ function DitherPulseRingContent({
4821
4991
  diffuseEnabled = false,
4822
4992
  blurEnabled = false,
4823
4993
  noiseWarpEnabled = false,
4994
+ combineMode,
4824
4995
  noiseWarpRadius,
4825
4996
  noiseWarpStrength,
4826
4997
  diffuseRadius,
@@ -4836,6 +5007,22 @@ function DitherPulseRingContent({
4836
5007
  ringPosition,
4837
5008
  ringAlpha
4838
5009
  }) {
5010
+ const normalizeHexColor = (hex) => {
5011
+ if (!hex) return null;
5012
+ const normalized = hex.replace("#", "");
5013
+ if (normalized.length !== 6) return null;
5014
+ const r = parseInt(normalized.slice(0, 2), 16) / 255;
5015
+ const g = parseInt(normalized.slice(2, 4), 16) / 255;
5016
+ const b = parseInt(normalized.slice(4, 6), 16) / 255;
5017
+ return [r, g, b];
5018
+ };
5019
+ const resolveColor = (color) => {
5020
+ if (!color) return null;
5021
+ if (typeof color === "string") {
5022
+ return normalizeHexColor(color);
5023
+ }
5024
+ return color;
5025
+ };
4839
5026
  const glyphUniforms = {};
4840
5027
  glyphUniforms.trackMouse = false;
4841
5028
  const noiseUniforms = {};
@@ -4859,52 +5046,74 @@ function DitherPulseRingContent({
4859
5046
  const borderUniforms = {};
4860
5047
  if (borderThickness !== void 0) borderUniforms.thickness = borderThickness;
4861
5048
  if (borderIntensity !== void 0) borderUniforms.intensity = borderIntensity;
4862
- if (borderColor) borderUniforms.color = borderColor;
5049
+ const resolvedBorderColor = resolveColor(borderColor);
5050
+ if (resolvedBorderColor) borderUniforms.color = resolvedBorderColor;
4863
5051
  if (borderDitherStrength !== void 0) {
4864
5052
  borderUniforms.ditherStrength = borderDitherStrength;
4865
5053
  }
4866
5054
  if (borderTonemap !== void 0) borderUniforms.tonemap = borderTonemap;
4867
5055
  if (borderAlpha !== void 0) borderUniforms.alpha = borderAlpha;
4868
5056
  const ringUniforms = {};
4869
- if (ringColor) ringUniforms.color = ringColor;
5057
+ const resolvedRingColor = resolveColor(ringColor);
5058
+ if (resolvedRingColor) ringUniforms.color = resolvedRingColor;
4870
5059
  if (ringSpeed !== void 0) ringUniforms.speed = ringSpeed;
4871
5060
  if (ringPosition) ringUniforms.position = ringPosition;
4872
5061
  if (ringAlpha !== void 0) ringUniforms.alpha = ringAlpha;
4873
- return /* @__PURE__ */ jsx23(
4874
- RenderPipeline,
4875
- {
4876
- passes: [
4877
- {
4878
- component: BorderBeamPass,
4879
- props: { uniforms: borderUniforms }
4880
- },
4881
- {
4882
- component: ExpandingRingPass,
4883
- props: { clear: true, uniforms: ringUniforms }
4884
- },
4885
- {
4886
- component: NoiseWarpPass,
4887
- enabled: noiseWarpEnabled,
4888
- props: { uniforms: noiseUniforms }
4889
- },
4890
- {
4891
- component: GlyphDitherPass,
4892
- enabled: glyphDitherEnabled,
4893
- props: { spriteTextureSrc, uniforms: glyphUniforms }
4894
- },
4895
- {
4896
- component: DiffusePass,
4897
- enabled: diffuseEnabled,
4898
- props: { uniforms: diffuseUniforms }
4899
- },
4900
- {
4901
- component: BlurPass,
4902
- enabled: blurEnabled,
4903
- props: { uniforms: blurUniforms }
4904
- }
4905
- ]
4906
- }
4907
- );
5062
+ return /* @__PURE__ */ jsxs2(Fragment3, { children: [
5063
+ /* @__PURE__ */ jsx23(
5064
+ RenderPipeline,
5065
+ {
5066
+ passes: [
5067
+ {
5068
+ component: BorderBeamPass,
5069
+ props: { uniforms: borderUniforms }
5070
+ },
5071
+ {
5072
+ component: GlyphDitherPass,
5073
+ enabled: glyphDitherEnabled,
5074
+ props: { spriteTextureSrc, uniforms: glyphUniforms }
5075
+ }
5076
+ ],
5077
+ combine: combineMode ? { mode: combineMode } : void 0
5078
+ }
5079
+ ),
5080
+ /* @__PURE__ */ jsx23(
5081
+ RenderPipeline,
5082
+ {
5083
+ passes: [
5084
+ {
5085
+ component: BorderBeamPass,
5086
+ props: { uniforms: borderUniforms }
5087
+ },
5088
+ {
5089
+ component: ExpandingRingPass,
5090
+ props: { uniforms: ringUniforms }
5091
+ },
5092
+ {
5093
+ component: NoiseWarpPass,
5094
+ enabled: noiseWarpEnabled,
5095
+ props: { uniforms: noiseUniforms }
5096
+ },
5097
+ {
5098
+ component: GlyphDitherPass,
5099
+ enabled: glyphDitherEnabled,
5100
+ props: { spriteTextureSrc, uniforms: glyphUniforms }
5101
+ },
5102
+ {
5103
+ component: DiffusePass,
5104
+ enabled: diffuseEnabled,
5105
+ props: { uniforms: diffuseUniforms }
5106
+ },
5107
+ {
5108
+ component: BlurPass,
5109
+ enabled: blurEnabled,
5110
+ props: { uniforms: blurUniforms }
5111
+ }
5112
+ ],
5113
+ combine: combineMode ? { mode: combineMode } : void 0
5114
+ }
5115
+ )
5116
+ ] });
4908
5117
  }
4909
5118
  export {
4910
5119
  AnimatedDrawingSVG,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toriistudio/shader-ui",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Shader components",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",