@triforge/compositor-core 0.1.1 → 0.1.2

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.
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Compile-time GLSL loop-bound sanitizer.
3
+ *
4
+ * Several passes bake a JS number directly into a GLSL `for` loop bound, e.g.
5
+ * `for (int i = 0; i < ${n}; i++)`. Per the root ecosystem rule ("ALWAYS cap
6
+ * GLSL loop iterations to a compile-time constant"), that number must be a
7
+ * finite integer within a sane range before it reaches the shader source:
8
+ *
9
+ * - `NaN`/`Infinity` would produce a shader that fails to compile.
10
+ * - An absurdly large value (e.g. a mistyped `samples: 1e6`) would compile a
11
+ * loop that hangs the GPU.
12
+ *
13
+ * This clamps to [min, max] and rounds to an integer, so the emitted bound is
14
+ * always a small compile-time constant.
15
+ */
16
+ export declare function glslLoopBound(value: number, min: number, max: number): number;
17
+ //# sourceMappingURL=glslLoopBound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glslLoopBound.d.ts","sourceRoot":"","sources":["../../src/core/glslLoopBound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAM7E"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Compile-time GLSL loop-bound sanitizer.
3
+ *
4
+ * Several passes bake a JS number directly into a GLSL `for` loop bound, e.g.
5
+ * `for (int i = 0; i < ${n}; i++)`. Per the root ecosystem rule ("ALWAYS cap
6
+ * GLSL loop iterations to a compile-time constant"), that number must be a
7
+ * finite integer within a sane range before it reaches the shader source:
8
+ *
9
+ * - `NaN`/`Infinity` would produce a shader that fails to compile.
10
+ * - An absurdly large value (e.g. a mistyped `samples: 1e6`) would compile a
11
+ * loop that hangs the GPU.
12
+ *
13
+ * This clamps to [min, max] and rounds to an integer, so the emitted bound is
14
+ * always a small compile-time constant.
15
+ */
16
+ export function glslLoopBound(value, min, max) {
17
+ if (!Number.isFinite(value))
18
+ return min;
19
+ const rounded = Math.round(value);
20
+ if (rounded < min)
21
+ return min;
22
+ if (rounded > max)
23
+ return max;
24
+ return rounded;
25
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"GlareStreaks.d.ts","sourceRoot":"","sources":["../../src/passes/GlareStreaks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEvD,MAAM,WAAW,mBAAmB;IAClC,qGAAqG;IACrG,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;;;;GAMG;AACH,qBAAa,YAAa,SAAQ,QAAQ;IACxC,QAAQ,CAAC,QAAQ,kBAAiB;IAClC,UAAU,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;gBAErG,IAAI,GAAE,mBAAwB;IAY1C,cAAc;IAEd,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO;CA8DhE"}
1
+ {"version":3,"file":"GlareStreaks.d.ts","sourceRoot":"","sources":["../../src/passes/GlareStreaks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAGvD,MAAM,WAAW,mBAAmB;IAClC,qGAAqG;IACrG,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;;;;GAMG;AACH,qBAAa,YAAa,SAAQ,QAAQ;IACxC,QAAQ,CAAC,QAAQ,kBAAiB;IAClC,UAAU,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;gBAErG,IAAI,GAAE,mBAAwB;IAY1C,cAAc;IAEd,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO;CAmEhE"}
@@ -1,4 +1,5 @@
1
1
  import { BasePass } from '../core/BasePass.js';
2
+ import { glslLoopBound } from '../core/glslLoopBound.js';
2
3
  /**
3
4
  * GlareStreaks — anamorphic lens streak effect on bright areas.
4
5
  * Blender Compositor: Glare node (Streaks type).
@@ -24,7 +25,12 @@ export class GlareStreaks extends BasePass {
24
25
  const ShaderPass = reg['ShaderPass'];
25
26
  if (!ShaderPass)
26
27
  throw new Error('GlareStreaks: ShaderPass not found.');
27
- const { threshold, strength, streaks, length, angle, fade } = this.parameters;
28
+ const { threshold, strength, length, angle, fade } = this.parameters;
29
+ // Cap both baked loop bounds: streak count (1..16) and per-streak sample
30
+ // length (1..128) — both are interpolated into generated GLSL / unrolled
31
+ // JS below, so both must be finite compile-time-bounded integers.
32
+ const streaks = glslLoopBound(this.parameters.streaks, 1, 16);
33
+ const sampleLength = glslLoopBound(length, 1, 128);
28
34
  // Build direction vectors for each streak
29
35
  const dirs = [];
30
36
  for (let i = 0; i < streaks; i++) {
@@ -40,41 +46,41 @@ export class GlareStreaks extends BasePass {
40
46
  fade: { value: fade },
41
47
  length: { value: length },
42
48
  },
43
- vertexShader: `
44
- varying vec2 vUv;
45
- void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }
49
+ vertexShader: `
50
+ varying vec2 vUv;
51
+ void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }
46
52
  `,
47
- fragmentShader: `
48
- uniform sampler2D tDiffuse;
49
- uniform vec2 resolution;
50
- uniform float threshold;
51
- uniform float strength;
52
- uniform float fade;
53
- uniform float length;
54
- varying vec2 vUv;
55
-
56
- float luminance(vec3 c) { return dot(c, vec3(0.299, 0.587, 0.114)); }
57
-
58
- vec4 sampleStreak(vec2 dir) {
59
- vec4 acc = vec4(0.0);
60
- float att = 1.0;
61
- vec2 step = dir / resolution;
62
- for (int i = 1; i <= ${Math.max(1, Math.round(length))}; i++) {
63
- vec2 uv = vUv + step * float(i) * strength;
64
- vec4 samp = texture2D(tDiffuse, clamp(uv, 0.0, 1.0));
65
- float lum = luminance(samp.rgb);
66
- if (lum > threshold) acc += samp * att;
67
- att *= fade;
68
- }
69
- return acc;
70
- }
71
-
72
- void main() {
73
- vec4 base = texture2D(tDiffuse, vUv);
74
- vec4 glare = vec4(0.0);
75
- ${dirs.map(d => `glare += sampleStreak(${d});`).join('\n ')}
76
- gl_FragColor = base + glare * (1.0 / ${Math.max(1, streaks)}.0);
77
- }
53
+ fragmentShader: `
54
+ uniform sampler2D tDiffuse;
55
+ uniform vec2 resolution;
56
+ uniform float threshold;
57
+ uniform float strength;
58
+ uniform float fade;
59
+ uniform float length;
60
+ varying vec2 vUv;
61
+
62
+ float luminance(vec3 c) { return dot(c, vec3(0.299, 0.587, 0.114)); }
63
+
64
+ vec4 sampleStreak(vec2 dir) {
65
+ vec4 acc = vec4(0.0);
66
+ float att = 1.0;
67
+ vec2 step = dir / resolution;
68
+ for (int i = 1; i <= ${sampleLength}; i++) {
69
+ vec2 uv = vUv + step * float(i) * strength;
70
+ vec4 samp = texture2D(tDiffuse, clamp(uv, 0.0, 1.0));
71
+ float lum = luminance(samp.rgb);
72
+ if (lum > threshold) acc += samp * att;
73
+ att *= fade;
74
+ }
75
+ return acc;
76
+ }
77
+
78
+ void main() {
79
+ vec4 base = texture2D(tDiffuse, vUv);
80
+ vec4 glare = vec4(0.0);
81
+ ${dirs.map(d => `glare += sampleStreak(${d});`).join('\n ')}
82
+ gl_FragColor = base + glare * (1.0 / ${Math.max(1, streaks)}.0);
83
+ }
78
84
  `,
79
85
  };
80
86
  return new ShaderPass(shader);
@@ -1 +1 @@
1
- {"version":3,"file":"LensFlare.d.ts","sourceRoot":"","sources":["../../src/passes/LensFlare.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,qBAAa,SAAU,SAAQ,QAAQ;IACrC,QAAQ,CAAC,QAAQ,eAAc;IAC/B,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;gBAE7F,IAAI,GAAE,gBAAqB;IAWvC,cAAc;IAEd,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO;CAyEhE"}
1
+ {"version":3,"file":"LensFlare.d.ts","sourceRoot":"","sources":["../../src/passes/LensFlare.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAGvD,MAAM,WAAW,gBAAgB;IAC/B,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,qBAAa,SAAU,SAAQ,QAAQ;IACrC,QAAQ,CAAC,QAAQ,eAAc;IAC/B,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;gBAE7F,IAAI,GAAE,gBAAqB;IAWvC,cAAc;IAEd,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO;CA0EhE"}
@@ -1,4 +1,5 @@
1
1
  import { BasePass } from '../core/BasePass.js';
2
+ import { glslLoopBound } from '../core/glslLoopBound.js';
2
3
  /**
3
4
  * LensFlare — Blender Compositor: Lens Flare / Glare node (lens reflections type).
4
5
  *
@@ -23,7 +24,8 @@ export class LensFlare extends BasePass {
23
24
  if (!ShaderPass)
24
25
  throw new Error('LensFlare: ShaderPass not found.');
25
26
  const { ghosts, strength, threshold, haloWidth, chromatic } = this.parameters;
26
- const n = Math.max(1, Math.round(ghosts));
27
+ // Cap the baked loop bound: finite, integer, 1..32 ghost reflections.
28
+ const n = glslLoopBound(ghosts, 1, 32);
27
29
  const shader = {
28
30
  uniforms: {
29
31
  tDiffuse: { value: null },
@@ -32,59 +34,59 @@ export class LensFlare extends BasePass {
32
34
  haloWidth: { value: haloWidth },
33
35
  chromatic: { value: chromatic },
34
36
  },
35
- vertexShader: `
36
- varying vec2 vUv;
37
- void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }
37
+ vertexShader: `
38
+ varying vec2 vUv;
39
+ void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }
38
40
  `,
39
- fragmentShader: `
40
- uniform sampler2D tDiffuse;
41
- uniform float strength;
42
- uniform float threshold;
43
- uniform float haloWidth;
44
- uniform float chromatic;
45
- varying vec2 vUv;
46
-
47
- float luminance(vec3 c) { return dot(c, vec3(0.299, 0.587, 0.114)); }
48
-
49
- vec3 sampleChromatic(vec2 uv, vec2 offset) {
50
- vec2 r = clamp(uv + offset * (1.0 + chromatic * 0.1), 0.0, 1.0);
51
- vec2 b = clamp(uv + offset * (1.0 - chromatic * 0.1), 0.0, 1.0);
52
- return vec3(
53
- texture2D(tDiffuse, r).r,
54
- texture2D(tDiffuse, clamp(uv + offset, 0.0, 1.0)).g,
55
- texture2D(tDiffuse, b).b
56
- );
57
- }
58
-
59
- void main() {
60
- vec4 base = texture2D(tDiffuse, vUv);
61
- vec3 flare = vec3(0.0);
62
- vec2 ghost = vec2(0.5) - vUv; // vector to screen centre
63
-
64
- // Ghost reflections along the screen diagonal
65
- for (int i = 1; i <= ${n}; i++) {
66
- float fi = float(i);
67
- float disp = 0.3 + fi * 0.25;
68
- vec2 guv = vUv + ghost * disp;
69
- vec3 samp = sampleChromatic(guv, ghost * 0.01 * fi);
70
- float lum = luminance(samp);
71
- if (lum > threshold) {
72
- vec3 tint = mix(vec3(1.0, 0.8, 0.6), vec3(0.6, 0.8, 1.0), fract(fi * 0.37));
73
- flare += samp * tint * (lum - threshold) * strength / fi;
74
- }
75
- }
76
-
77
- // Halo ring
78
- float dist = length(ghost);
79
- float haloBias = abs(dist - haloWidth);
80
- float halo = smoothstep(0.05, 0.0, haloBias) * strength * 0.5;
81
- vec4 haloCentre = texture2D(tDiffuse, clamp(vec2(0.5) + normalize(-ghost) * haloWidth, 0.0, 1.0));
82
- if (luminance(haloCentre.rgb) > threshold) {
83
- flare += haloCentre.rgb * halo;
84
- }
85
-
86
- gl_FragColor = vec4(base.rgb + flare, base.a);
87
- }
41
+ fragmentShader: `
42
+ uniform sampler2D tDiffuse;
43
+ uniform float strength;
44
+ uniform float threshold;
45
+ uniform float haloWidth;
46
+ uniform float chromatic;
47
+ varying vec2 vUv;
48
+
49
+ float luminance(vec3 c) { return dot(c, vec3(0.299, 0.587, 0.114)); }
50
+
51
+ vec3 sampleChromatic(vec2 uv, vec2 offset) {
52
+ vec2 r = clamp(uv + offset * (1.0 + chromatic * 0.1), 0.0, 1.0);
53
+ vec2 b = clamp(uv + offset * (1.0 - chromatic * 0.1), 0.0, 1.0);
54
+ return vec3(
55
+ texture2D(tDiffuse, r).r,
56
+ texture2D(tDiffuse, clamp(uv + offset, 0.0, 1.0)).g,
57
+ texture2D(tDiffuse, b).b
58
+ );
59
+ }
60
+
61
+ void main() {
62
+ vec4 base = texture2D(tDiffuse, vUv);
63
+ vec3 flare = vec3(0.0);
64
+ vec2 ghost = vec2(0.5) - vUv; // vector to screen centre
65
+
66
+ // Ghost reflections along the screen diagonal
67
+ for (int i = 1; i <= ${n}; i++) {
68
+ float fi = float(i);
69
+ float disp = 0.3 + fi * 0.25;
70
+ vec2 guv = vUv + ghost * disp;
71
+ vec3 samp = sampleChromatic(guv, ghost * 0.01 * fi);
72
+ float lum = luminance(samp);
73
+ if (lum > threshold) {
74
+ vec3 tint = mix(vec3(1.0, 0.8, 0.6), vec3(0.6, 0.8, 1.0), fract(fi * 0.37));
75
+ flare += samp * tint * (lum - threshold) * strength / fi;
76
+ }
77
+ }
78
+
79
+ // Halo ring
80
+ float dist = length(ghost);
81
+ float haloBias = abs(dist - haloWidth);
82
+ float halo = smoothstep(0.05, 0.0, haloBias) * strength * 0.5;
83
+ vec4 haloCentre = texture2D(tDiffuse, clamp(vec2(0.5) + normalize(-ghost) * haloWidth, 0.0, 1.0));
84
+ if (luminance(haloCentre.rgb) > threshold) {
85
+ flare += haloCentre.rgb * halo;
86
+ }
87
+
88
+ gl_FragColor = vec4(base.rgb + flare, base.a);
89
+ }
88
90
  `,
89
91
  };
90
92
  return new ShaderPass(shader);
@@ -1 +1 @@
1
- {"version":3,"file":"MotionBlur.d.ts","sourceRoot":"","sources":["../../src/passes/MotionBlur.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEvD,MAAM,WAAW,iBAAiB;IAChC,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;GAQG;AACH,qBAAa,UAAW,SAAQ,QAAQ;IACtC,QAAQ,CAAC,QAAQ,gBAAe;IAChC,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;gBAE7D,IAAI,GAAE,iBAAsB;IASxC,cAAc;IAEd,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO;CA6ChE"}
1
+ {"version":3,"file":"MotionBlur.d.ts","sourceRoot":"","sources":["../../src/passes/MotionBlur.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAGvD,MAAM,WAAW,iBAAiB;IAChC,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;GAQG;AACH,qBAAa,UAAW,SAAQ,QAAQ;IACtC,QAAQ,CAAC,QAAQ,gBAAe;IAChC,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;gBAE7D,IAAI,GAAE,iBAAsB;IASxC,cAAc;IAEd,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO;CA+ChE"}
@@ -1,4 +1,5 @@
1
1
  import { BasePass } from '../core/BasePass.js';
2
+ import { glslLoopBound } from '../core/glslLoopBound.js';
2
3
  /**
3
4
  * MotionBlur — screen-space radial/directional motion blur.
4
5
  * Blender Compositor: Vector Blur node.
@@ -24,7 +25,9 @@ export class MotionBlur extends BasePass {
24
25
  if (!ShaderPass)
25
26
  throw new Error('MotionBlur: ShaderPass not found.');
26
27
  const { samples, velocityScale, maxRadius } = this.parameters;
27
- const n = Math.max(1, Math.round(samples));
28
+ // Cap the baked loop bound: finite, integer, 1..64 (matches typical
29
+ // real-time motion-blur sample budgets; guards against NaN / huge values).
30
+ const n = glslLoopBound(samples, 1, 64);
28
31
  const shader = {
29
32
  uniforms: {
30
33
  tDiffuse: { value: null },
@@ -32,32 +35,32 @@ export class MotionBlur extends BasePass {
32
35
  velocityScale: { value: velocityScale },
33
36
  maxRadius: { value: maxRadius },
34
37
  },
35
- vertexShader: `
36
- varying vec2 vUv;
37
- void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }
38
+ vertexShader: `
39
+ varying vec2 vUv;
40
+ void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }
38
41
  `,
39
- fragmentShader: `
40
- uniform sampler2D tDiffuse;
41
- uniform vec2 resolution;
42
- uniform float velocityScale;
43
- uniform float maxRadius;
44
- varying vec2 vUv;
45
-
46
- void main() {
47
- // Screen-space radial blur toward centre
48
- vec2 centre = vec2(0.5);
49
- vec2 vel = (centre - vUv) * velocityScale;
50
- float len = length(vel * resolution);
51
- if (len > maxRadius) vel = vel * (maxRadius / max(len, 0.001));
52
-
53
- vec4 acc = vec4(0.0);
54
- for (int i = 0; i < ${n}; i++) {
55
- float t = float(i) / float(${n - 1});
56
- vec2 uv = clamp(vUv + vel * t / resolution, 0.0, 1.0);
57
- acc += texture2D(tDiffuse, uv);
58
- }
59
- gl_FragColor = acc / float(${n});
60
- }
42
+ fragmentShader: `
43
+ uniform sampler2D tDiffuse;
44
+ uniform vec2 resolution;
45
+ uniform float velocityScale;
46
+ uniform float maxRadius;
47
+ varying vec2 vUv;
48
+
49
+ void main() {
50
+ // Screen-space radial blur toward centre
51
+ vec2 centre = vec2(0.5);
52
+ vec2 vel = (centre - vUv) * velocityScale;
53
+ float len = length(vel * resolution);
54
+ if (len > maxRadius) vel = vel * (maxRadius / max(len, 0.001));
55
+
56
+ vec4 acc = vec4(0.0);
57
+ for (int i = 0; i < ${n}; i++) {
58
+ float t = float(i) / float(${n - 1});
59
+ vec2 uv = clamp(vUv + vel * t / resolution, 0.0, 1.0);
60
+ acc += texture2D(tDiffuse, uv);
61
+ }
62
+ gl_FragColor = acc / float(${n});
63
+ }
61
64
  `,
62
65
  };
63
66
  return new ShaderPass(shader);
package/package.json CHANGED
@@ -1,54 +1,54 @@
1
- {
2
- "name": "@triforge/compositor-core",
3
- "version": "0.1.1",
4
- "description": "Blender-matched compositor node graph for Three.js — backend-agnostic (three/addons or pmndrs/postprocessing)",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "files": [
9
- "dist",
10
- "package.json"
11
- ],
12
- "scripts": {
13
- "build": "tsc",
14
- "dev": "tsc --watch",
15
- "test": "node test/run-tests.js"
16
- },
17
- "keywords": [
18
- "three.js",
19
- "post-processing",
20
- "compositor",
21
- "blender",
22
- "effects"
23
- ],
24
- "author": "Aftab Ibrahim Kazi",
25
- "license": "MIT",
26
- "peerDependencies": {
27
- "three": ">=0.128.0"
28
- },
29
- "peerDependenciesMeta": {
30
- "postprocessing": {
31
- "optional": true
32
- }
33
- },
34
- "devDependencies": {
35
- "@types/three": "^0.156.0",
36
- "three": "^0.156.0",
37
- "typescript": "^5.3.0"
38
- },
39
- "repository": {
40
- "type": "git",
41
- "url": "https://github.com/AftabIbrahimKazi/triforge.git",
42
- "directory": "st-compositor-core"
43
- },
44
- "homepage": "https://github.com/AftabIbrahimKazi/triforge#readme",
45
- "publishConfig": {
46
- "access": "public"
47
- },
48
- "exports": {
49
- ".": {
50
- "import": "./dist/index.js",
51
- "types": "./dist/index.d.ts"
52
- }
53
- }
54
- }
1
+ {
2
+ "name": "@triforge/compositor-core",
3
+ "version": "0.1.2",
4
+ "description": "Blender-matched compositor node graph for Three.js — backend-agnostic (three/addons or pmndrs/postprocessing)",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "package.json"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsc --watch",
15
+ "test": "node test/run-tests.js"
16
+ },
17
+ "keywords": [
18
+ "three.js",
19
+ "post-processing",
20
+ "compositor",
21
+ "blender",
22
+ "effects"
23
+ ],
24
+ "author": "Aftab Ibrahim Kazi",
25
+ "license": "MIT",
26
+ "peerDependencies": {
27
+ "three": ">=0.128.0"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "postprocessing": {
31
+ "optional": true
32
+ }
33
+ },
34
+ "devDependencies": {
35
+ "@types/three": "^0.156.0",
36
+ "three": "^0.156.0",
37
+ "typescript": "^5.3.0"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/AftabIbrahimKazi/triforge.git",
42
+ "directory": "st-compositor-core"
43
+ },
44
+ "homepage": "https://github.com/AftabIbrahimKazi/triforge#readme",
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "exports": {
49
+ ".": {
50
+ "import": "./dist/index.js",
51
+ "types": "./dist/index.d.ts"
52
+ }
53
+ }
54
+ }