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

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 (40) hide show
  1. package/dist/{needle-engine.bundle-B23LP9xh.min.js → needle-engine.bundle-OFyr3b1P.min.js} +170 -170
  2. package/dist/{needle-engine.bundle-B0Kku0-E.umd.cjs → needle-engine.bundle-PlhhqNqg.umd.cjs} +146 -146
  3. package/dist/{needle-engine.bundle-gJVfKnbJ.js → needle-engine.bundle-xzFdwTHR.js} +8161 -8114
  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/engine_context.js +4 -1
  11. package/lib/engine/engine_context.js.map +1 -1
  12. package/lib/engine-components/postprocessing/Effects/Antialiasing.js +2 -1
  13. package/lib/engine-components/postprocessing/Effects/Antialiasing.js.map +1 -1
  14. package/lib/engine-components/postprocessing/Effects/BloomEffect.d.ts +1 -1
  15. package/lib/engine-components/postprocessing/Effects/BloomEffect.js +3 -0
  16. package/lib/engine-components/postprocessing/Effects/BloomEffect.js.map +1 -1
  17. package/lib/engine-components/postprocessing/PostProcessingEffect.d.ts +18 -0
  18. package/lib/engine-components/postprocessing/PostProcessingEffect.js +18 -0
  19. package/lib/engine-components/postprocessing/PostProcessingEffect.js.map +1 -1
  20. package/lib/engine-components/postprocessing/PostProcessingHandler.d.ts +4 -3
  21. package/lib/engine-components/postprocessing/PostProcessingHandler.js +83 -119
  22. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  23. package/lib/engine-components/postprocessing/Volume.d.ts +0 -1
  24. package/lib/engine-components/postprocessing/Volume.js +48 -36
  25. package/lib/engine-components/postprocessing/Volume.js.map +1 -1
  26. package/lib/engine-components/postprocessing/index.d.ts +1 -0
  27. package/lib/engine-components/postprocessing/index.js +1 -0
  28. package/lib/engine-components/postprocessing/index.js.map +1 -1
  29. package/lib/engine-components/postprocessing/utils.d.ts +36 -0
  30. package/lib/engine-components/postprocessing/utils.js +98 -0
  31. package/lib/engine-components/postprocessing/utils.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/engine/engine_context.ts +3 -1
  34. package/src/engine-components/postprocessing/Effects/Antialiasing.ts +2 -1
  35. package/src/engine-components/postprocessing/Effects/BloomEffect.ts +4 -2
  36. package/src/engine-components/postprocessing/PostProcessingEffect.ts +19 -0
  37. package/src/engine-components/postprocessing/PostProcessingHandler.ts +94 -123
  38. package/src/engine-components/postprocessing/Volume.ts +52 -42
  39. package/src/engine-components/postprocessing/index.ts +1 -0
  40. 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.5971d67",
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": {
@@ -33,7 +33,7 @@ import { NetworkConnection } from './engine_networking.js';
33
33
  import { Physics } from './engine_physics.js';
34
34
  import { PlayerViewManager } from './engine_playerview.js';
35
35
  import { RendererData as SceneLighting } from './engine_scenelighting.js';
36
- import { logHierarchy } from './engine_three_utils.js';
36
+ import { getTempColor, logHierarchy } from './engine_three_utils.js';
37
37
  import { Time } from './engine_time.js';
38
38
  import { patchTonemapping } from './engine_tonemapping.js';
39
39
  import type { CoroutineData, ICamera, IComponent, IContext, ILight, LoadedModel, Model, Vec2 } from "./engine_types.js";
@@ -1612,6 +1612,8 @@ export class Context implements IContext {
1612
1612
  if (currentPassesCamera != camera)
1613
1613
  this.composer.setMainCamera(camera);
1614
1614
  }
1615
+ const backgroundColor = this.renderer.getClearColor(getTempColor());
1616
+ if(backgroundColor) this.renderer.setClearColor(backgroundColor.convertSRGBToLinear());
1615
1617
  this.composer.render(this.time.deltaTime);
1616
1618
  }
1617
1619
  else if (camera) {
@@ -34,7 +34,8 @@ export class Antialiasing extends PostProcessingEffect {
34
34
  const effect = new MODULES.POSTPROCESSING.MODULE.SMAAEffect({
35
35
  preset: MODULES.POSTPROCESSING.MODULE.SMAAPreset.HIGH,
36
36
  edgeDetectionMode: MODULES.POSTPROCESSING.MODULE.EdgeDetectionMode.LUMA,
37
- predicationMode: MODULES.POSTPROCESSING.MODULE.PredicationMode.DEPTH,
37
+ // Keep predication mode disabled (default) since it looks better
38
+ // predicationMode: MODULES.POSTPROCESSING.MODULE.PredicationMode.DEPTH,
38
39
  });
39
40
 
40
41
  this.preset.onValueChanged = (newValue) => {
@@ -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,18 +211,11 @@ 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
189
- renderer[autoclearSetting] = renderer.autoClear;
190
-
191
- // Reset state
192
- this._anyPassHasDepth = false;
193
- this._anyPassHasNormal = false;
194
- this._hasSmaaEffect = false;
195
-
196
- for (const ef of effectsOrPasses) {
197
- if (ef instanceof MODULES.POSTPROCESSING.MODULE.SMAAEffect) {
198
- this._hasSmaaEffect = true;
199
- }
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];
200
217
  }
218
+ renderer[autoclearSetting] = renderer.autoClear;
201
219
 
202
220
  // create composer and set active on context
203
221
  if (!this._composer) {
@@ -218,6 +236,7 @@ export class PostProcessingHandler {
218
236
  composer.setRenderer(renderer);
219
237
  composer.setMainScene(scene);
220
238
  composer.autoRenderToScreen = true; // Must be enabled because it might be disabled during addPass by the composer itself (depending on the effect's settings or order)
239
+ composer.multisampling = -1;
221
240
 
222
241
  for (const prev of composer.passes)
223
242
  prev.dispose();
@@ -229,63 +248,58 @@ export class PostProcessingHandler {
229
248
  screenpass.mainScene = scene;
230
249
  composer.addPass(screenpass);
231
250
 
232
- const automaticEffectsOrdering = true;
233
- if (automaticEffectsOrdering && !dontMergePasses) {
234
- try {
235
- this.orderEffects();
251
+ try {
252
+ orderEffects(this._effects);
236
253
 
237
- const effects: Array<Effect> = [];
254
+ const effectsToMerge: Array<Effect> = [];
255
+ let hasConvolutionEffectInArray = false;
238
256
 
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
- }
257
+ for (const entry of effectsOrPasses) {
258
+ const ef = entry.effect;
253
259
 
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);
260
+ if (ef instanceof MODULES.POSTPROCESSING.MODULE.SMAAEffect) {
261
+ this._hasSmaaEffect = true;
262
+ }
263
+ else if (ef instanceof MODULES.POSTPROCESSING.MODULE.NormalPass) {
264
+ this._anyPassHasNormal = true;
262
265
  }
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
266
 
274
- for (const ef of effectsOrPasses) {
275
267
  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);
268
+ const attributes = ef.getAttributes();
269
+ const convolution = MODULES.POSTPROCESSING.MODULE.EffectAttribute.CONVOLUTION;
270
+ if (attributes & convolution) {
271
+ if(debug) console.log("[PostProcessing] Convolution effect detected: " + ef.name, hasConvolutionEffectInArray);
272
+ if (hasConvolutionEffectInArray) {
273
+ hasConvolutionEffectInArray = false;
274
+ if(debug) console.log("[PostProcessing] Merging effects with convolution effect in array", effectsToMerge.map(e => e.name).join(", "));
275
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
276
+
277
+ }
278
+ hasConvolutionEffectInArray = true;
279
+ }
280
+ // Otherwise we can merge it
281
+ effectsToMerge.push(ef as Effect);
279
282
  }
280
283
  else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass) {
284
+ hasConvolutionEffectInArray = false;
285
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
281
286
  ef.renderToScreen = false;
282
287
  composer.addPass(ef as Pass);
283
288
  }
284
- else
289
+ else {
285
290
  // seems some effects are not correctly typed, but three can deal with them,
286
- // so we just pass them through
291
+ // so we might need to just pass them through
292
+ hasConvolutionEffectInArray = false;
293
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
287
294
  composer.addPass(ef);
295
+ }
288
296
  }
297
+
298
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
299
+ }
300
+ catch (e) {
301
+ console.error("Error while applying postprocessing effects", e);
302
+ composer.removeAllPasses();
289
303
  }
290
304
 
291
305
  // 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)
@@ -301,12 +315,6 @@ export class PostProcessingHandler {
301
315
  }
302
316
 
303
317
  this._anyPassHasDepth ||= pass.needsDepthTexture;
304
- if (pass instanceof MODULES.POSTPROCESSING.MODULE.NormalPass) {
305
- this._anyPassHasNormal = true;
306
- }
307
- if (pass instanceof MODULES.POSTPROCESSING.MODULE.SMAAEffect) {
308
- this._hasSmaaEffect = true;
309
- }
310
318
  }
311
319
 
312
320
  // DEBUG LAND BELOW
@@ -314,56 +322,20 @@ export class PostProcessingHandler {
314
322
  if (debug) this._onCreateEffectsDebug(this._composer!, cam);
315
323
  }
316
324
 
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
325
 
364
- if (debug === "verbose") console.debug("After ordering effects", [...this._effects]);
365
- }
366
326
 
327
+ /** Should be called before `composer.addPass()` to create an effect pass with all previously collected effects that can be merged up to that point */
328
+ private createPassForMergeableEffects(effects: Array<Effect>, composer: EffectComposer, camera: Camera3, scene: Scene) {
329
+ if (effects.length > 0) {
330
+ const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(camera, ...effects);
331
+ pass.name = effects.map(e => e.name).join(", ");
332
+ pass.mainScene = scene;
333
+ pass.enabled = true;
334
+ pass.renderToScreen = false;
335
+ composer.addPass(pass);
336
+ effects.length = 0; // Clear effects after adding them to the pass
337
+ }
338
+ }
367
339
 
368
340
 
369
341
 
@@ -439,4 +411,3 @@ export class PostProcessingHandler {
439
411
 
440
412
  }
441
413
 
442
- let effectsOrder: Array<Constructor<Effect | Pass>> | null = null;