@triforge/compositor-core 0.1.0 → 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.
- package/dist/core/CompositorOutput.d.ts +0 -1
- package/dist/core/CompositorOutput.d.ts.map +1 -1
- package/dist/core/CompositorOutput.js +25 -49
- package/dist/core/glslLoopBound.d.ts +17 -0
- package/dist/core/glslLoopBound.d.ts.map +1 -0
- package/dist/core/glslLoopBound.js +25 -0
- package/dist/passes/GlareStreaks.d.ts.map +1 -1
- package/dist/passes/GlareStreaks.js +41 -35
- package/dist/passes/LensFlare.d.ts.map +1 -1
- package/dist/passes/LensFlare.js +55 -53
- package/dist/passes/MotionBlur.d.ts.map +1 -1
- package/dist/passes/MotionBlur.js +29 -26
- package/package.json +54 -48
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompositorOutput.d.ts","sourceRoot":"","sources":["../../src/core/CompositorOutput.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAAE,KAAK,EAAE,MAAM,EAE7B,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"CompositorOutput.d.ts","sourceRoot":"","sources":["../../src/core/CompositorOutput.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAAE,KAAK,EAAE,MAAM,EAE7B,MAAM,OAAO,CAAA;AAUd,OAAO,KAAK,EAAE,QAAQ,EAAgB,MAAM,eAAe,CAAA;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAW,wBAAwB,CAAA;AAEpE,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,OAAO,CAAC,EAAG,iBAAiB,CAAA;IAC5B,QAAQ,EAAG,aAAa,CAAA;IACxB,KAAK,EAAM,KAAK,CAAA;IAChB,MAAM,EAAK,MAAM,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,gBAAgB;IAC3B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;IAEnC,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,SAAS,CAAS;gBAEd,IAAI,EAAE,uBAAuB;IAOzC,yDAAyD;IACzD,GAAG,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI;IAMzB,4DAA4D;IAC5D,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI;IAM5B,wDAAwD;IACxD,IAAI,MAAM,IAAI,SAAS,QAAQ,EAAE,CAAwB;IAEzD,sDAAsD;IACtD,IAAI,UAAU,IAAI,OAAO,CAA0B;IAEnD;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAc9B;;;;OAIG;IACH,MAAM,IAAI,IAAI;IAQd,+DAA+D;IAC/D,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAK5C,OAAO,IAAI,IAAI;IAQf,OAAO,CAAC,aAAa;YA+CP,cAAc;CAiD7B"}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { Vector2, WebGLRenderTarget, LinearFilter, RGBAFormat, HalfFloatType, } from 'three';
|
|
2
|
+
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
|
|
3
|
+
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
|
|
4
|
+
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js';
|
|
5
|
+
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
|
|
6
|
+
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
|
|
7
|
+
import { FilmPass } from 'three/examples/jsm/postprocessing/FilmPass.js';
|
|
8
|
+
import { BokehPass } from 'three/examples/jsm/postprocessing/BokehPass.js';
|
|
9
|
+
import { SSAOPass } from 'three/examples/jsm/postprocessing/SSAOPass.js';
|
|
10
|
+
import { SSRPass } from 'three/examples/jsm/postprocessing/SSRPass.js';
|
|
2
11
|
/**
|
|
3
12
|
* CompositorOutput — terminal node of the compositor graph.
|
|
4
13
|
*
|
|
@@ -60,7 +69,7 @@ export class CompositorOutput {
|
|
|
60
69
|
this._composer = await this._compilePmndrs();
|
|
61
70
|
}
|
|
62
71
|
else {
|
|
63
|
-
this._composer =
|
|
72
|
+
this._composer = this._compileThree();
|
|
64
73
|
}
|
|
65
74
|
this._compiled = true;
|
|
66
75
|
}
|
|
@@ -88,31 +97,18 @@ export class CompositorOutput {
|
|
|
88
97
|
this._compiled = false;
|
|
89
98
|
}
|
|
90
99
|
// ── Three.js backend ──────────────────────────────────────────────────────
|
|
91
|
-
|
|
92
|
-
// Dynamically import three/addons so this package has no hard dependency
|
|
93
|
-
// on a specific addons path — works with CDN importmap and npm alike.
|
|
94
|
-
let EC;
|
|
95
|
-
let RenderPass;
|
|
96
|
-
let OutputPass;
|
|
97
|
-
try {
|
|
98
|
-
const [ecMod, rpMod, opMod] = await Promise.all([
|
|
99
|
-
import('three/addons/postprocessing/EffectComposer.js'),
|
|
100
|
-
import('three/addons/postprocessing/RenderPass.js'),
|
|
101
|
-
import('three/addons/postprocessing/OutputPass.js'),
|
|
102
|
-
]);
|
|
103
|
-
EC = ecMod['EffectComposer'];
|
|
104
|
-
RenderPass = rpMod['RenderPass'];
|
|
105
|
-
OutputPass = opMod['OutputPass'];
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
throw new Error('@st-compositor-core: Could not load three/addons postprocessing modules.\n' +
|
|
109
|
-
'Make sure "three/addons/" is in your importmap or install three with examples/jsm.\n' +
|
|
110
|
-
'Example importmap entry:\n' +
|
|
111
|
-
' "three/addons/": "https://unpkg.com/three@0.165.0/examples/jsm/"');
|
|
112
|
-
}
|
|
100
|
+
_compileThree() {
|
|
113
101
|
const activePasses = this._passes.filter(p => p.enabled);
|
|
114
|
-
//
|
|
115
|
-
const registry =
|
|
102
|
+
// Registry built from static imports — compatible with Vite, Webpack, Rollup, esbuild
|
|
103
|
+
const registry = {
|
|
104
|
+
ShaderPass,
|
|
105
|
+
UnrealBloomPass,
|
|
106
|
+
FilmPass,
|
|
107
|
+
BokehPass,
|
|
108
|
+
SSAOPass,
|
|
109
|
+
SSRPass,
|
|
110
|
+
RenderPass,
|
|
111
|
+
};
|
|
116
112
|
const size = new Vector2();
|
|
117
113
|
this._renderer.getSize(size);
|
|
118
114
|
const target = new WebGLRenderTarget(size.x, size.y, {
|
|
@@ -121,47 +117,27 @@ export class CompositorOutput {
|
|
|
121
117
|
format: RGBAFormat,
|
|
122
118
|
type: HalfFloatType,
|
|
123
119
|
});
|
|
124
|
-
const composer = new
|
|
120
|
+
const composer = new EffectComposer(this._renderer, target);
|
|
125
121
|
const addPass = (p) => composer.addPass(p);
|
|
126
|
-
// RenderPass first — renders the scene into the first buffer
|
|
127
122
|
addPass(new RenderPass(this._scene, this._camera));
|
|
128
123
|
registry['_renderer'] = this._renderer;
|
|
129
124
|
registry['_scene'] = this._scene;
|
|
130
125
|
registry['_camera'] = this._camera;
|
|
131
|
-
// User passes
|
|
132
126
|
for (const pass of activePasses) {
|
|
133
127
|
const built = pass._buildThree(size.x, size.y, registry);
|
|
134
128
|
if (built !== null)
|
|
135
129
|
addPass(built);
|
|
136
130
|
}
|
|
137
|
-
//
|
|
131
|
+
// Required since Three.js r152 to write to screen correctly
|
|
138
132
|
addPass(new OutputPass());
|
|
139
133
|
return composer;
|
|
140
134
|
}
|
|
141
|
-
async _loadThreePassModules(passes) {
|
|
142
|
-
const registry = {};
|
|
143
|
-
const needed = new Set();
|
|
144
|
-
for (const pass of passes) {
|
|
145
|
-
for (const dep of pass._threePassDeps()) {
|
|
146
|
-
needed.add(dep);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
await Promise.all([...needed].map(async (dep) => {
|
|
150
|
-
try {
|
|
151
|
-
const mod = await import(`three/addons/postprocessing/${dep}.js`);
|
|
152
|
-
Object.assign(registry, mod);
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
// Pass will throw its own clear error in _buildThree() if dep is missing
|
|
156
|
-
}
|
|
157
|
-
}));
|
|
158
|
-
return registry;
|
|
159
|
-
}
|
|
160
135
|
// ── pmndrs backend ────────────────────────────────────────────────────────
|
|
161
136
|
async _compilePmndrs() {
|
|
162
137
|
let pkg;
|
|
163
138
|
try {
|
|
164
|
-
|
|
139
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
140
|
+
pkg = await Function('m', 'return import(m)')('postprocessing');
|
|
165
141
|
}
|
|
166
142
|
catch {
|
|
167
143
|
throw new Error('@st-compositor-core: backend "pmndrs" requires the "postprocessing" package.\n' +
|
|
@@ -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;
|
|
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,
|
|
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 <= ${
|
|
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;
|
|
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"}
|
package/dist/passes/LensFlare.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
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
|
-
|
|
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,48 +1,54 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@triforge/compositor-core",
|
|
3
|
-
"version": "0.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
|
-
|
|
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
|
+
}
|