@needle-tools/engine 4.6.1-next.26cf56c → 4.6.1-next.cd98b8c

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.
Files changed (34) hide show
  1. package/dist/{needle-engine.bundle-gJVfKnbJ.js → needle-engine.bundle-BOiTKp9l.js} +8132 -8102
  2. package/dist/{needle-engine.bundle-B0Kku0-E.umd.cjs → needle-engine.bundle-ClaRVK0x.umd.cjs} +146 -146
  3. package/dist/{needle-engine.bundle-B23LP9xh.min.js → needle-engine.bundle-Dq1fvtLi.min.js} +142 -142
  4. package/dist/needle-engine.js +342 -341
  5. package/dist/needle-engine.min.js +1 -1
  6. package/dist/needle-engine.umd.cjs +1 -1
  7. package/dist/{postprocessing-BPhSPjAn.umd.cjs → postprocessing-CjW23fio.umd.cjs} +17 -17
  8. package/dist/{postprocessing-XKNCD6By.js → postprocessing-DYLNOL3W.js} +1 -0
  9. package/dist/{postprocessing-DoBd3XIR.min.js → postprocessing-xYQWCHFu.min.js} +26 -26
  10. package/lib/engine-components/postprocessing/Effects/BloomEffect.d.ts +1 -1
  11. package/lib/engine-components/postprocessing/Effects/BloomEffect.js +3 -0
  12. package/lib/engine-components/postprocessing/Effects/BloomEffect.js.map +1 -1
  13. package/lib/engine-components/postprocessing/PostProcessingEffect.d.ts +18 -0
  14. package/lib/engine-components/postprocessing/PostProcessingEffect.js +18 -0
  15. package/lib/engine-components/postprocessing/PostProcessingEffect.js.map +1 -1
  16. package/lib/engine-components/postprocessing/PostProcessingHandler.d.ts +4 -3
  17. package/lib/engine-components/postprocessing/PostProcessingHandler.js +76 -109
  18. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  19. package/lib/engine-components/postprocessing/Volume.d.ts +0 -1
  20. package/lib/engine-components/postprocessing/Volume.js +4 -5
  21. package/lib/engine-components/postprocessing/Volume.js.map +1 -1
  22. package/lib/engine-components/postprocessing/index.d.ts +1 -0
  23. package/lib/engine-components/postprocessing/index.js +1 -0
  24. package/lib/engine-components/postprocessing/index.js.map +1 -1
  25. package/lib/engine-components/postprocessing/utils.d.ts +36 -0
  26. package/lib/engine-components/postprocessing/utils.js +98 -0
  27. package/lib/engine-components/postprocessing/utils.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/engine-components/postprocessing/Effects/BloomEffect.ts +4 -2
  30. package/src/engine-components/postprocessing/PostProcessingEffect.ts +19 -0
  31. package/src/engine-components/postprocessing/PostProcessingHandler.ts +88 -113
  32. package/src/engine-components/postprocessing/Volume.ts +4 -8
  33. package/src/engine-components/postprocessing/index.ts +1 -0
  34. package/src/engine-components/postprocessing/utils.ts +116 -2
@@ -1,6 +1,7 @@
1
1
  import { isDevEnvironment } from "../../engine/debug/index.js";
2
2
  import { addComponent } from "../../engine/engine_components.js";
3
3
  import { foreachComponentEnumerator } from "../../engine/engine_gameobject.js";
4
+ import { MODULES } from "../../engine/engine_modules.js";
4
5
  import { getParam } from "../../engine/engine_utils.js";
5
6
  export const debug = getParam("debugpost");
6
7
  let PostprocessingManagerType = null;
@@ -35,4 +36,101 @@ export function getPostProcessingManager(effect) {
35
36
  }
36
37
  return manager;
37
38
  }
39
+ /**
40
+ * Default priority for post-processing effects. This can be used to sort effects by their rendering order when creating custom effects.
41
+ * E.g. in your custom effect, you can set `priority: PostProcessingEffectPriority.Bloom + 1;` to ensure it gets rendered after the bloom effect.
42
+ * OR `priority: PostProcessingEffectPriority.Bloom - 1;` to ensure it gets rendered before the bloom effect.
43
+ * @example
44
+ * ```typescript
45
+ * import { PostProcessingEffectPriority } from "@needle-tools/engine"
46
+ *
47
+ * export class MyCustomEffect extends PostProcessingEffect {
48
+ * priority: PostProcessingEffectPriority.Bloom + 1; // render after bloom
49
+ *
50
+ * // ... your effect code
51
+ * }
52
+ * ```
53
+ */
54
+ export const PostProcessingEffectPriority = {
55
+ NormalPass: 0,
56
+ DepthDownsamplingPass: 10,
57
+ SMAA: 20,
58
+ SSAO: 30,
59
+ TiltShift: 40,
60
+ DepthOfField: 50,
61
+ ChromaticAberration: 60,
62
+ Bloom: 70,
63
+ Vignette: 80,
64
+ Pixelation: 90,
65
+ ToneMapping: 100,
66
+ HueSaturation: 110,
67
+ BrightnessContrast: 120,
68
+ // Sharpening: 130,
69
+ };
70
+ // let effectsOrder: Array<Constructor<Effect | Pass>> | null = null;
71
+ let builtinOrder = null;
72
+ export function orderEffects(effects) {
73
+ if (debug === "verbose")
74
+ console.debug("Before ordering effects", [...effects]);
75
+ if (!builtinOrder) {
76
+ builtinOrder = new Map();
77
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.NormalPass, PostProcessingEffectPriority.NormalPass);
78
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.DepthDownsamplingPass, PostProcessingEffectPriority.DepthDownsamplingPass);
79
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.SMAAEffect, PostProcessingEffectPriority.SMAA);
80
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.SSAOEffect, PostProcessingEffectPriority.SSAO);
81
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.TiltShiftEffect, PostProcessingEffectPriority.TiltShift);
82
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.DepthOfFieldEffect, PostProcessingEffectPriority.DepthOfField);
83
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.ChromaticAberrationEffect, PostProcessingEffectPriority.ChromaticAberration);
84
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.BloomEffect, PostProcessingEffectPriority.Bloom);
85
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.VignetteEffect, PostProcessingEffectPriority.Vignette);
86
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.PixelationEffect, PostProcessingEffectPriority.Pixelation);
87
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.ToneMappingEffect, PostProcessingEffectPriority.ToneMapping);
88
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.HueSaturationEffect, PostProcessingEffectPriority.HueSaturation);
89
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.BrightnessContrastEffect, PostProcessingEffectPriority.BrightnessContrast);
90
+ }
91
+ // enforce correct order of effects (e.g. DOF before Bloom)
92
+ effects.sort((a, b) => {
93
+ // we use find index here because sometimes constructor names are prefixed with `_`
94
+ // TODO: find a more robust solution that isnt name based (not sure if that exists tho... maybe we must give effect TYPES some priority/index)
95
+ const aidx = typeof a.priority === "number" ? a.priority : builtinOrder.get(a.effect.constructor) || -1;
96
+ const bidx = typeof b.priority === "number" ? b.priority : builtinOrder.get(b.effect.constructor) || -1;
97
+ // Unknown effects should be rendered first
98
+ if (aidx < 0) {
99
+ if (debug)
100
+ console.warn("Unknown effect found: ", a.constructor.name);
101
+ return -1;
102
+ }
103
+ else if (bidx < 0) {
104
+ if (debug)
105
+ console.warn("Unknown effect found: ", b.constructor.name);
106
+ return 1;
107
+ }
108
+ // if (aidx < 0) return 1;
109
+ // if (bidx < 0) return -1;
110
+ return aidx - bidx;
111
+ });
112
+ // effects.sort((a, b) => {
113
+ // if (a.beforeEffect) {
114
+ // const beforeA = effectsOrder!.findIndex(e => a.beforeEffect!.constructor.name.endsWith(e.name));
115
+ // if (beforeA >= 0) {
116
+ // return -1; // before effect should be rendered first
117
+ // }
118
+ // else {
119
+ // return 1; // no before effect, so we can keep the order
120
+ // }
121
+ // }
122
+ // else if (b.beforeEffect) {
123
+ // const beforeB = effectsOrder!.findIndex(e => b.beforeEffect!.constructor.name.endsWith(e.name));
124
+ // if (beforeB >= 0) {
125
+ // return 1; // before effect should be rendered first
126
+ // }
127
+ // else if (a.beforeEffect) {
128
+ // return -1; // no before effect, so we can keep the order
129
+ // }
130
+ // }
131
+ // return 0; // no before effect, so we can keep the order
132
+ // });
133
+ if (debug === "verbose")
134
+ console.debug("After ordering effects", [...effects]);
135
+ }
38
136
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/engine-components/postprocessing/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAC;AAE/E,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAGxD,MAAM,CAAC,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;AAW3C,IAAI,yBAAyB,GAAuD,IAAI,CAAC;AAEzF,MAAM,UAAU,4BAA4B,CAAC,IAAiD;IAC1F,yBAAyB,GAAG,IAAI,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,MAA4B;IAClE,IAAI,GAAG,GAAG,MAAM,CAAC,UAA6B,CAAC;IAC/C,OAAO,GAAG,EAAE;QACR,KAAK,MAAM,IAAI,IAAI,0BAA0B,CAAC,GAAG,CAAC,EAAE;YAChD,IAAK,IAA0C,CAAC,uBAAuB,KAAK,IAAI,EAAE;gBAC9E,OAAO,IAAyC,CAAC;aACpD;SACJ;QACD,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC;KACpB;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,MAA4B;IACjE,IAAI,OAAO,GAAkC,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAC/E,IAAI,CAAC,OAAO,EAAE;QACV,IAAI,yBAAyB,EAAE;YAC3B,IAAI,KAAK;gBACL,OAAO,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;YAChE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;YAC3B,OAAO,GAAG,YAAY,CAAC,KAAK,EAAE,yBAAyB,CAAC,CAAC;SAC5D;aACI;YACD,IAAI,gBAAgB,EAAE;gBAClB,OAAO,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;SACxD;KACJ;IACD,OAAO,OAAO,CAAC;AACnB,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/engine-components/postprocessing/utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "4.6.1-next.26cf56c",
3
+ "version": "4.6.1-next.cd98b8c",
4
4
  "description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.",
5
5
  "main": "dist/needle-engine.min.js",
6
6
  "exports": {
@@ -1,4 +1,4 @@
1
- import type { BloomEffect as _BloomEffect } from "postprocessing";
1
+ import { BloomEffect as _BloomEffect, EffectAttribute } from "postprocessing";
2
2
  import { MathUtils } from "three";
3
3
 
4
4
  import { MODULES } from "../../../engine/engine_modules.js";
@@ -91,8 +91,10 @@ export class BloomEffect extends PostProcessingEffect {
91
91
  luminanceThreshold: this.threshold.value,
92
92
  luminanceSmoothing: this.scatter.value,
93
93
  radius: 0.85, // default value
94
- intensity: this.intensity.value,
94
+ intensity: this.intensity.value,
95
95
  });
96
+ // @ts-ignore Well... it's protected but bloom needs this :(
97
+ bloom.setAttributes(EffectAttribute.CONVOLUTION);
96
98
  }
97
99
 
98
100
  this.intensity.onValueChanged = newValue => {
@@ -55,6 +55,25 @@ export abstract class PostProcessingEffect extends Component implements IEffectP
55
55
 
56
56
  get isPostProcessingEffect() { return true; }
57
57
 
58
+ /**
59
+ * The priority of this effect. The higher the priority the later the effect will be applied in the post processing stack.
60
+ * This can be used to control the order of effects when multiple effects are applied.
61
+ * It is recommended to use the PostProcessingEffectPriority constant to order your custom effects before or after built-in effects.
62
+ * @default `undefined` (no specific priority set, will be applied in the order of registration)
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * import { PostProcessingEffectPriority } from "@needle-tools/engine"
67
+ *
68
+ * export class MyCustomEffect extends PostProcessingEffect {
69
+ * priority: PostProcessingEffectPriority.Bloom + 1; // render after bloom
70
+ *
71
+ * // ... the rest of your effect code
72
+ * }
73
+ * ```
74
+ */
75
+ priority: number | undefined = undefined;
76
+
58
77
  constructor(params: any = undefined) {
59
78
  super();
60
79
  if (params) {
@@ -1,5 +1,5 @@
1
1
  import type { Effect, EffectComposer, Pass, ToneMappingEffect as _TonemappingEffect } from "postprocessing";
2
- import { Camera as Camera3, HalfFloatType, NoToneMapping } from "three";
2
+ import { Camera as Camera3, HalfFloatType, NoToneMapping, Scene } from "three";
3
3
 
4
4
  import { showBalloonWarning } from "../../engine/debug/index.js";
5
5
  // import { internal_SetSharpeningEffectModule } from "./Effects/Sharpening.js";
@@ -9,13 +9,13 @@ import type { Constructor } from "../../engine/engine_types.js";
9
9
  import { DeviceUtilities, getParam } from "../../engine/engine_utils.js";
10
10
  import { Camera } from "../Camera.js";
11
11
  import { PostProcessingEffect, PostProcessingEffectContext } from "./PostProcessingEffect.js";
12
+ import { orderEffects, PostprocessingEffectData } from "./utils.js";
12
13
 
13
14
  declare const NEEDLE_USE_POSTPROCESSING: boolean;
14
15
  globalThis["NEEDLE_USE_POSTPROCESSING"] = globalThis["NEEDLE_USE_POSTPROCESSING"] !== undefined ? globalThis["NEEDLE_USE_POSTPROCESSING"] : true;
15
16
 
16
17
 
17
18
  const debug = getParam("debugpost");
18
- const dontMergePasses = getParam("debugpostpasses");
19
19
 
20
20
  const activeKey = Symbol("needle:postprocessing-handler");
21
21
  const autoclearSetting = Symbol("needle:previous-autoclear-state");
@@ -27,7 +27,7 @@ export class PostProcessingHandler {
27
27
 
28
28
  private _composer: EffectComposer | null = null;
29
29
  private _lastVolumeComponents?: PostProcessingEffect[];
30
- private _effects: Array<Effect | Pass> = [];
30
+ private readonly _effects: Array<PostprocessingEffectData> = [];
31
31
 
32
32
  get isActive() {
33
33
  return this._isActive;
@@ -60,7 +60,7 @@ export class PostProcessingHandler {
60
60
  return this.onApply(this.context, components);
61
61
  }
62
62
 
63
- unapply() {
63
+ unapply(dispose: boolean = true) {
64
64
  if (debug) console.log("Unapplying postprocessing effects");
65
65
  this._isActive = false;
66
66
  if (this._lastVolumeComponents) {
@@ -73,21 +73,26 @@ export class PostProcessingHandler {
73
73
  const active = context[activeKey] as PostProcessingHandler | null;
74
74
  if (active === this) {
75
75
  delete context[activeKey];
76
+
77
+ // Restore the auto clear setting
78
+ if (typeof context.renderer[autoclearSetting] === "boolean") {
79
+ context.renderer.autoClear = context.renderer[autoclearSetting];
80
+ }
76
81
  }
82
+
83
+ this._composer?.removeAllPasses();
84
+ if (dispose) this._composer?.dispose();
85
+
77
86
  if (context.composer === this._composer) {
78
- context.composer?.dispose();
79
87
  context.composer = null;
80
88
  }
81
- if (typeof context.renderer[autoclearSetting] === "boolean") {
82
- context.renderer.autoClear = context.renderer[autoclearSetting];
83
- }
84
89
  }
85
90
 
86
91
  dispose() {
87
- this.unapply();
92
+ this.unapply(true);
88
93
 
89
94
  for (const effect of this._effects) {
90
- effect.dispose();
95
+ effect.effect.dispose();
91
96
  }
92
97
  this._effects.length = 0;
93
98
  this._composer = null;
@@ -105,6 +110,7 @@ export class PostProcessingHandler {
105
110
  // import("./Effects/Sharpening.effect")
106
111
  ]);
107
112
 
113
+
108
114
  // try {
109
115
  // internal_SetSharpeningEffectModule(modules[2]);
110
116
  // }
@@ -141,10 +147,22 @@ export class PostProcessingHandler {
141
147
  // apply or collect effects
142
148
  const res = component.apply(ctx);
143
149
  if (!res) continue;
150
+
151
+
144
152
  if (Array.isArray(res)) {
145
- this._effects.push(...res);
153
+ for (const effect of res) {
154
+ this._effects.push({
155
+ effect,
156
+ priority: component.priority
157
+ });
158
+ }
159
+ }
160
+ else {
161
+ this._effects.push({
162
+ effect: res,
163
+ priority: component.priority
164
+ });
146
165
  }
147
- else this._effects.push(res);
148
166
  }
149
167
  }
150
168
  else {
@@ -157,7 +175,7 @@ export class PostProcessingHandler {
157
175
  if (this.context.renderer.toneMapping != NoToneMapping) {
158
176
  if (!this._effects.find(e => e instanceof MODULES.POSTPROCESSING.MODULE.ToneMappingEffect)) {
159
177
  const tonemapping = new MODULES.POSTPROCESSING.MODULE.ToneMappingEffect();
160
- this._effects.push(tonemapping);
178
+ this._effects.push({ effect: tonemapping });
161
179
  }
162
180
  }
163
181
 
@@ -175,9 +193,16 @@ export class PostProcessingHandler {
175
193
 
176
194
  /** Build composer passes */
177
195
  private applyEffects(context: Context) {
196
+ // Reset state
197
+ this._anyPassHasDepth = false;
198
+ this._anyPassHasNormal = false;
199
+ this._hasSmaaEffect = false;
178
200
 
179
201
  const effectsOrPasses = this._effects;
180
- if (effectsOrPasses.length <= 0) return;
202
+ if (effectsOrPasses.length <= 0) {
203
+ return;
204
+ }
205
+
181
206
  const camera = context.mainCameraComponent as Camera;
182
207
  const renderer = context.renderer;
183
208
  const scene = context.scene;
@@ -186,13 +211,12 @@ export class PostProcessingHandler {
186
211
  // Store the auto clear setting because the postprocessing composer just disables it
187
212
  // and when we disable postprocessing we want to restore the original setting
188
213
  // https://github.com/pmndrs/postprocessing/blob/271944b74b543a5b743a62803a167b60cc6bb4ee/src/core/EffectComposer.js#L230C12-L230C12
214
+ // First we need to get the previously set autoClear setting, if it exists
215
+ if (typeof renderer[autoclearSetting] === "boolean") {
216
+ renderer.autoClear = renderer[autoclearSetting];
217
+ }
189
218
  renderer[autoclearSetting] = renderer.autoClear;
190
219
 
191
- // Reset state
192
- this._anyPassHasDepth = false;
193
- this._anyPassHasNormal = false;
194
- this._hasSmaaEffect = false;
195
-
196
220
  for (const ef of effectsOrPasses) {
197
221
  if (ef instanceof MODULES.POSTPROCESSING.MODULE.SMAAEffect) {
198
222
  this._hasSmaaEffect = true;
@@ -229,63 +253,50 @@ export class PostProcessingHandler {
229
253
  screenpass.mainScene = scene;
230
254
  composer.addPass(screenpass);
231
255
 
232
- const automaticEffectsOrdering = true;
233
- if (automaticEffectsOrdering && !dontMergePasses) {
234
- try {
235
- this.orderEffects();
256
+ try {
257
+ orderEffects(this._effects);
236
258
 
237
- const effects: Array<Effect> = [];
259
+ const effectsToMerge: Array<Effect> = [];
260
+ let hasConvolutionEffectInArray = false;
238
261
 
239
- for (const ef of effectsOrPasses) {
240
- if (ef instanceof MODULES.POSTPROCESSING.MODULE.Effect) {
241
- effects.push(ef as Effect);
242
- }
243
- else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass) {
244
- ef.renderToScreen = false;
245
- composer.addPass(ef as Pass);
246
- }
247
- else {
248
- // seems some effects are not correctly typed, but three can deal with them,
249
- // so we might need to just pass them through
250
- composer.addPass(ef);
251
- }
252
- }
253
-
254
- // create and apply uber pass
255
- if (effects.length > 0) {
256
- const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(cam, ...effects);
257
- pass.name = effects.map(e => e.name).join(", ");
258
- pass.mainScene = scene;
259
- pass.enabled = true;
260
- pass.renderToScreen = false;
261
- composer.addPass(pass);
262
- }
263
- }
264
- catch (e) {
265
- console.error("Error while applying postprocessing effects", e);
266
- composer.removeAllPasses();
267
- }
268
- }
269
- else {
270
- // we still want to sort passes, but we do not want to merge them for debugging
271
- if (automaticEffectsOrdering)
272
- this.orderEffects();
273
-
274
- for (const ef of effectsOrPasses) {
262
+ for (const entry of effectsOrPasses) {
263
+ const ef = entry.effect;
275
264
  if (ef instanceof MODULES.POSTPROCESSING.MODULE.Effect) {
276
- const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(cam, ef as Effect);
277
- pass.name = ef.name;
278
- composer.addPass(pass);
265
+ const attributes = ef.getAttributes();
266
+ const convolution = MODULES.POSTPROCESSING.MODULE.EffectAttribute.CONVOLUTION;
267
+ if (attributes & convolution) {
268
+ console.log("[PostProcessing] Convolution effect detected: " + ef.name, hasConvolutionEffectInArray);
269
+ if (hasConvolutionEffectInArray) {
270
+ hasConvolutionEffectInArray = false;
271
+ console.log("[PostProcessing] Merging effects with convolution effect in array", effectsToMerge.map(e => e.name).join(", "));
272
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
273
+
274
+ }
275
+ hasConvolutionEffectInArray = true;
276
+ }
277
+ // Otherwise we can merge it
278
+ effectsToMerge.push(ef as Effect);
279
279
  }
280
280
  else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass) {
281
+ hasConvolutionEffectInArray = false;
282
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
281
283
  ef.renderToScreen = false;
282
284
  composer.addPass(ef as Pass);
283
285
  }
284
- else
286
+ else {
285
287
  // seems some effects are not correctly typed, but three can deal with them,
286
- // so we just pass them through
288
+ // so we might need to just pass them through
289
+ hasConvolutionEffectInArray = false;
290
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
287
291
  composer.addPass(ef);
292
+ }
288
293
  }
294
+
295
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
296
+ }
297
+ catch (e) {
298
+ console.error("Error while applying postprocessing effects", e);
299
+ composer.removeAllPasses();
289
300
  }
290
301
 
291
302
  // The last pass is the one that renders to the screen, so we need to set the gamma correction for it (and enable it for all others)
@@ -314,56 +325,20 @@ export class PostProcessingHandler {
314
325
  if (debug) this._onCreateEffectsDebug(this._composer!, cam);
315
326
  }
316
327
 
317
- private orderEffects() {
318
- if (debug === "verbose") console.debug("Before ordering effects", [...this._effects]);
319
-
320
- // Order of effects for correct results.
321
- // Aligned with https://github.com/pmndrs/postprocessing/wiki/Effect-Merging#effect-execution-order
322
- // We can not put this into global scope because then the module might not yet be initialized
323
- effectsOrder ??= [
324
- MODULES.POSTPROCESSING.MODULE.NormalPass,
325
- MODULES.POSTPROCESSING.MODULE.DepthDownsamplingPass,
326
- MODULES.POSTPROCESSING.MODULE.SMAAEffect,
327
- MODULES.POSTPROCESSING.MODULE.SSAOEffect,
328
- MODULES.POSTPROCESSING_AO.MODULE.N8AOPostPass,
329
- MODULES.POSTPROCESSING.MODULE.TiltShiftEffect,
330
- MODULES.POSTPROCESSING.MODULE.DepthOfFieldEffect,
331
- MODULES.POSTPROCESSING.MODULE.ChromaticAberrationEffect,
332
- MODULES.POSTPROCESSING.MODULE.BloomEffect,
333
- MODULES.POSTPROCESSING.MODULE.SelectiveBloomEffect,
334
- MODULES.POSTPROCESSING.MODULE.VignetteEffect,
335
- MODULES.POSTPROCESSING.MODULE.PixelationEffect,
336
- MODULES.POSTPROCESSING.MODULE.ToneMappingEffect,
337
- MODULES.POSTPROCESSING.MODULE.HueSaturationEffect,
338
- MODULES.POSTPROCESSING.MODULE.BrightnessContrastEffect,
339
- // __SHARPENING_MODULE._SharpeningEffect,
340
- ];
341
-
342
-
343
- // TODO: enforce correct order of effects (e.g. DOF before Bloom)
344
- const effects = this._effects;
345
- effects.sort((a, b) => {
346
- // we use find index here because sometimes constructor names are prefixed with `_`
347
- // TODO: find a more robust solution that isnt name based (not sure if that exists tho... maybe we must give effect TYPES some priority/index)
348
- const aidx = effectsOrder!.findIndex(e => a.constructor.name.endsWith(e.name));
349
- const bidx = effectsOrder!.findIndex(e => b.constructor.name.endsWith(e.name));
350
- // Unknown effects should be rendered first
351
- if (aidx < 0) {
352
- if (debug) console.warn("Unknown effect found: ", a.constructor.name);
353
- return -1;
354
- }
355
- else if (bidx < 0) {
356
- if (debug) console.warn("Unknown effect found: ", b.constructor.name);
357
- return 1;
358
- }
359
- // if (aidx < 0) return 1;
360
- // if (bidx < 0) return -1;
361
- return aidx - bidx;
362
- });
363
328
 
364
- if (debug === "verbose") console.debug("After ordering effects", [...this._effects]);
365
- }
366
329
 
330
+ /** Should be called before `composer.addPass()` to create an effect pass with all previously collected effects that can be merged up to that point */
331
+ private createPassForMergeableEffects(effects: Array<Effect>, composer: EffectComposer, camera: Camera3, scene: Scene) {
332
+ if (effects.length > 0) {
333
+ const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(camera, ...effects);
334
+ pass.name = effects.map(e => e.name).join(", ");
335
+ pass.mainScene = scene;
336
+ pass.enabled = true;
337
+ pass.renderToScreen = false;
338
+ composer.addPass(pass);
339
+ effects.length = 0; // Clear effects after adding them to the pass
340
+ }
341
+ }
367
342
 
368
343
 
369
344
 
@@ -257,7 +257,7 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
257
257
  private _isDirty: boolean = false;
258
258
 
259
259
  private apply() {
260
- if (debug) console.log(`Apply PostProcessing "${this.name}"`);
260
+ if (debug) console.log(`Apply PostProcessing "${this.name || "unnamed"}"`);
261
261
 
262
262
  if (isDevEnvironment()) {
263
263
  if (this._lastApplyTime !== undefined && Date.now() - this._lastApplyTime < 100) {
@@ -269,7 +269,6 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
269
269
  }
270
270
 
271
271
  this._isDirty = false;
272
- this.unapply();
273
272
 
274
273
  this._activeEffects.length = 0;
275
274
  // get from profile
@@ -312,16 +311,13 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
312
311
  console.warn(`[PostProcessing] No composer found`);
313
312
  }
314
313
  })
315
-
314
+ }
315
+ else {
316
+ this._postprocessing?.unapply(false);
316
317
  }
317
318
 
318
319
  }
319
320
 
320
- private unapply() {
321
- this._postprocessing?.unapply();
322
- }
323
-
324
-
325
321
  private _applyPostQueue() {
326
322
  if (this._modificationQueue) {
327
323
  for (const entry of this._modificationQueue.values()) this.onEditorModification(entry);
@@ -3,3 +3,4 @@ export * from "./PostProcessingHandler.js"
3
3
  export { PostProcessingManager } from "./Volume.js"
4
4
  export * from "./VolumeParameter.js"
5
5
  export * from "./VolumeProfile.js";
6
+ export { PostProcessingEffectPriority } from "./utils.js";
@@ -1,9 +1,11 @@
1
+ import type { Effect, Pass } from "postprocessing";
1
2
  import { Object3D } from "three";
2
3
 
3
4
  import { isDevEnvironment } from "../../engine/debug/index.js";
4
5
  import { addComponent } from "../../engine/engine_components.js";
5
6
  import { foreachComponentEnumerator } from "../../engine/engine_gameobject.js";
6
- import { ConstructorConcrete, IComponent } from "../../engine/engine_types.js";
7
+ import { MODULES } from "../../engine/engine_modules.js";
8
+ import { Constructor, ConstructorConcrete, IComponent } from "../../engine/engine_types.js";
7
9
  import { getParam } from "../../engine/engine_utils.js";
8
10
  import { type PostProcessingEffect } from "./PostProcessingEffect.js";
9
11
 
@@ -52,4 +54,116 @@ export function getPostProcessingManager(effect: PostProcessingEffect): IPostPro
52
54
  }
53
55
  }
54
56
  return manager;
55
- }
57
+ }
58
+
59
+
60
+
61
+ export type PostprocessingEffectData = {
62
+ effect: Effect | Pass;
63
+ priority?: number,
64
+ }
65
+
66
+ /**
67
+ * Default priority for post-processing effects. This can be used to sort effects by their rendering order when creating custom effects.
68
+ * E.g. in your custom effect, you can set `priority: PostProcessingEffectPriority.Bloom + 1;` to ensure it gets rendered after the bloom effect.
69
+ * OR `priority: PostProcessingEffectPriority.Bloom - 1;` to ensure it gets rendered before the bloom effect.
70
+ * @example
71
+ * ```typescript
72
+ * import { PostProcessingEffectPriority } from "@needle-tools/engine"
73
+ *
74
+ * export class MyCustomEffect extends PostProcessingEffect {
75
+ * priority: PostProcessingEffectPriority.Bloom + 1; // render after bloom
76
+ *
77
+ * // ... your effect code
78
+ * }
79
+ * ```
80
+ */
81
+ export const PostProcessingEffectPriority = {
82
+ NormalPass: 0,
83
+ DepthDownsamplingPass: 10,
84
+ SMAA: 20,
85
+ SSAO: 30,
86
+ TiltShift: 40,
87
+ DepthOfField: 50,
88
+ ChromaticAberration: 60,
89
+ Bloom: 70,
90
+ Vignette: 80,
91
+ Pixelation: 90,
92
+ ToneMapping: 100,
93
+ HueSaturation: 110,
94
+ BrightnessContrast: 120,
95
+ // Sharpening: 130,
96
+ }
97
+ // let effectsOrder: Array<Constructor<Effect | Pass>> | null = null;
98
+
99
+ let builtinOrder: Map<Constructor<Effect | Pass>, number> | null = null;
100
+
101
+ export function orderEffects(effects: Array<PostprocessingEffectData>) {
102
+ if (debug === "verbose") console.debug("Before ordering effects", [...effects]);
103
+
104
+ if (!builtinOrder) {
105
+ builtinOrder = new Map<Constructor<Effect | Pass>, number>();
106
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.NormalPass, PostProcessingEffectPriority.NormalPass);
107
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.DepthDownsamplingPass, PostProcessingEffectPriority.DepthDownsamplingPass);
108
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.SMAAEffect, PostProcessingEffectPriority.SMAA);
109
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.SSAOEffect, PostProcessingEffectPriority.SSAO);
110
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.TiltShiftEffect, PostProcessingEffectPriority.TiltShift);
111
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.DepthOfFieldEffect, PostProcessingEffectPriority.DepthOfField);
112
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.ChromaticAberrationEffect, PostProcessingEffectPriority.ChromaticAberration);
113
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.BloomEffect, PostProcessingEffectPriority.Bloom);
114
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.VignetteEffect, PostProcessingEffectPriority.Vignette);
115
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.PixelationEffect, PostProcessingEffectPriority.Pixelation);
116
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.ToneMappingEffect, PostProcessingEffectPriority.ToneMapping);
117
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.HueSaturationEffect, PostProcessingEffectPriority.HueSaturation);
118
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.BrightnessContrastEffect, PostProcessingEffectPriority.BrightnessContrast);
119
+ }
120
+
121
+ // enforce correct order of effects (e.g. DOF before Bloom)
122
+ effects.sort((a, b) => {
123
+ // we use find index here because sometimes constructor names are prefixed with `_`
124
+ // TODO: find a more robust solution that isnt name based (not sure if that exists tho... maybe we must give effect TYPES some priority/index)
125
+ const aidx = typeof a.priority === "number" ? a.priority : builtinOrder!.get(a.effect.constructor as Constructor<Effect | Pass>) || -1;
126
+ const bidx = typeof b.priority === "number" ? b.priority : builtinOrder!.get(b.effect.constructor as Constructor<Effect | Pass>) || -1;
127
+
128
+ // Unknown effects should be rendered first
129
+ if (aidx < 0) {
130
+ if (debug) console.warn("Unknown effect found: ", a.constructor.name);
131
+ return -1;
132
+ }
133
+ else if (bidx < 0) {
134
+ if (debug) console.warn("Unknown effect found: ", b.constructor.name);
135
+ return 1;
136
+ }
137
+ // if (aidx < 0) return 1;
138
+ // if (bidx < 0) return -1;
139
+ return aidx - bidx;
140
+ });
141
+
142
+ // effects.sort((a, b) => {
143
+ // if (a.beforeEffect) {
144
+ // const beforeA = effectsOrder!.findIndex(e => a.beforeEffect!.constructor.name.endsWith(e.name));
145
+ // if (beforeA >= 0) {
146
+ // return -1; // before effect should be rendered first
147
+ // }
148
+ // else {
149
+ // return 1; // no before effect, so we can keep the order
150
+ // }
151
+ // }
152
+ // else if (b.beforeEffect) {
153
+ // const beforeB = effectsOrder!.findIndex(e => b.beforeEffect!.constructor.name.endsWith(e.name));
154
+ // if (beforeB >= 0) {
155
+ // return 1; // before effect should be rendered first
156
+ // }
157
+ // else if (a.beforeEffect) {
158
+ // return -1; // no before effect, so we can keep the order
159
+ // }
160
+
161
+ // }
162
+ // return 0; // no before effect, so we can keep the order
163
+ // });
164
+
165
+
166
+ if (debug === "verbose") console.debug("After ordering effects", [...effects]);
167
+ }
168
+
169
+