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

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 (79) hide show
  1. package/dist/{needle-engine.bundle-gJVfKnbJ.js → needle-engine.bundle-BBxAzv6J.js} +9228 -9100
  2. package/dist/{needle-engine.bundle-B23LP9xh.min.js → needle-engine.bundle-BwVJc8bG.min.js} +213 -201
  3. package/dist/{needle-engine.bundle-B0Kku0-E.umd.cjs → needle-engine.bundle-cvRBTPLl.umd.cjs} +190 -178
  4. package/dist/needle-engine.js +351 -350
  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.d.ts +4 -1
  11. package/lib/engine/engine_context.js +9 -2
  12. package/lib/engine/engine_context.js.map +1 -1
  13. package/lib/engine/engine_three_utils.d.ts +17 -14
  14. package/lib/engine/engine_three_utils.js +106 -53
  15. package/lib/engine/engine_three_utils.js.map +1 -1
  16. package/lib/engine/engine_tonemapping.d.ts +4 -0
  17. package/lib/engine/engine_tonemapping.js +21 -18
  18. package/lib/engine/engine_tonemapping.js.map +1 -1
  19. package/lib/engine/engine_utils.js.map +1 -1
  20. package/lib/engine/webcomponents/needle-engine.d.ts +4 -1
  21. package/lib/engine/webcomponents/needle-engine.extras.js +3 -3
  22. package/lib/engine/webcomponents/needle-engine.extras.js.map +1 -1
  23. package/lib/engine/webcomponents/needle-engine.js +11 -21
  24. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  25. package/lib/engine-components/CameraUtils.js.map +1 -1
  26. package/lib/engine-components/postprocessing/Effects/Antialiasing.js +2 -1
  27. package/lib/engine-components/postprocessing/Effects/Antialiasing.js.map +1 -1
  28. package/lib/engine-components/postprocessing/Effects/BloomEffect.d.ts +2 -2
  29. package/lib/engine-components/postprocessing/Effects/BloomEffect.js +5 -2
  30. package/lib/engine-components/postprocessing/Effects/BloomEffect.js.map +1 -1
  31. package/lib/engine-components/postprocessing/Effects/ColorAdjustments.d.ts +8 -0
  32. package/lib/engine-components/postprocessing/Effects/ColorAdjustments.js +27 -8
  33. package/lib/engine-components/postprocessing/Effects/ColorAdjustments.js.map +1 -1
  34. package/lib/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.js +1 -0
  35. package/lib/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.js.map +1 -1
  36. package/lib/engine-components/postprocessing/Effects/Sharpening.d.ts +1 -0
  37. package/lib/engine-components/postprocessing/Effects/Sharpening.js +4 -0
  38. package/lib/engine-components/postprocessing/Effects/Sharpening.js.map +1 -1
  39. package/lib/engine-components/postprocessing/Effects/Tonemapping.d.ts +2 -9
  40. package/lib/engine-components/postprocessing/Effects/Tonemapping.js +22 -57
  41. package/lib/engine-components/postprocessing/Effects/Tonemapping.js.map +1 -1
  42. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +13 -0
  43. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.js +52 -0
  44. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.js.map +1 -0
  45. package/lib/engine-components/postprocessing/PostProcessingEffect.d.ts +18 -0
  46. package/lib/engine-components/postprocessing/PostProcessingEffect.js +20 -2
  47. package/lib/engine-components/postprocessing/PostProcessingEffect.js.map +1 -1
  48. package/lib/engine-components/postprocessing/PostProcessingHandler.d.ts +10 -4
  49. package/lib/engine-components/postprocessing/PostProcessingHandler.js +180 -126
  50. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  51. package/lib/engine-components/postprocessing/Volume.d.ts +0 -1
  52. package/lib/engine-components/postprocessing/Volume.js +49 -42
  53. package/lib/engine-components/postprocessing/Volume.js.map +1 -1
  54. package/lib/engine-components/postprocessing/index.d.ts +1 -0
  55. package/lib/engine-components/postprocessing/index.js +1 -0
  56. package/lib/engine-components/postprocessing/index.js.map +1 -1
  57. package/lib/engine-components/postprocessing/utils.d.ts +41 -0
  58. package/lib/engine-components/postprocessing/utils.js +82 -0
  59. package/lib/engine-components/postprocessing/utils.js.map +1 -1
  60. package/package.json +1 -1
  61. package/src/engine/engine_context.ts +13 -4
  62. package/src/engine/engine_three_utils.ts +134 -58
  63. package/src/engine/engine_tonemapping.ts +23 -24
  64. package/src/engine/engine_utils.ts +2 -2
  65. package/src/engine/webcomponents/needle-engine.extras.ts +3 -3
  66. package/src/engine/webcomponents/needle-engine.ts +14 -25
  67. package/src/engine-components/CameraUtils.ts +3 -3
  68. package/src/engine-components/postprocessing/Effects/Antialiasing.ts +2 -1
  69. package/src/engine-components/postprocessing/Effects/BloomEffect.ts +6 -4
  70. package/src/engine-components/postprocessing/Effects/ColorAdjustments.ts +24 -13
  71. package/src/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.ts +1 -0
  72. package/src/engine-components/postprocessing/Effects/Sharpening.ts +5 -0
  73. package/src/engine-components/postprocessing/Effects/Tonemapping.ts +24 -66
  74. package/src/engine-components/postprocessing/Effects/Tonemapping.utils.ts +60 -0
  75. package/src/engine-components/postprocessing/PostProcessingEffect.ts +21 -2
  76. package/src/engine-components/postprocessing/PostProcessingHandler.ts +201 -130
  77. package/src/engine-components/postprocessing/Volume.ts +50 -47
  78. package/src/engine-components/postprocessing/index.ts +2 -1
  79. package/src/engine-components/postprocessing/utils.ts +100 -2
@@ -1,24 +1,26 @@
1
- import type { Effect, EffectComposer, Pass, ToneMappingEffect as _TonemappingEffect } from "postprocessing";
2
- import { Camera as Camera3, HalfFloatType, NoToneMapping } from "three";
1
+ import { Effect, EffectComposer, Pass, ToneMappingEffect as _TonemappingEffect } from "postprocessing";
2
+ import { Camera as Camera3, DepthTexture, HalfFloatType, LinearFilter, NoToneMapping, Scene, Texture, ToneMapping, WebGLRenderTarget } from "three";
3
3
 
4
4
  import { showBalloonWarning } from "../../engine/debug/index.js";
5
5
  // import { internal_SetSharpeningEffectModule } from "./Effects/Sharpening.js";
6
6
  import { MODULES } from "../../engine/engine_modules.js";
7
7
  import { Context } from "../../engine/engine_setup.js";
8
- import type { Constructor } from "../../engine/engine_types.js";
9
- import { DeviceUtilities, getParam } from "../../engine/engine_utils.js";
8
+ import { Graphics } from "../../engine/engine_three_utils.js";
9
+ import { getParam } from "../../engine/engine_utils.js";
10
10
  import { Camera } from "../Camera.js";
11
+ import { threeToneMappingToEffectMode } from "./Effects/Tonemapping.utils.js";
11
12
  import { PostProcessingEffect, PostProcessingEffectContext } from "./PostProcessingEffect.js";
13
+ import { orderEffects, PostprocessingEffectData, PostProcessingEffectOrder } from "./utils.js";
12
14
 
13
15
  declare const NEEDLE_USE_POSTPROCESSING: boolean;
14
16
  globalThis["NEEDLE_USE_POSTPROCESSING"] = globalThis["NEEDLE_USE_POSTPROCESSING"] !== undefined ? globalThis["NEEDLE_USE_POSTPROCESSING"] : true;
15
17
 
16
18
 
17
19
  const debug = getParam("debugpost");
18
- const dontMergePasses = getParam("debugpostpasses");
19
20
 
20
21
  const activeKey = Symbol("needle:postprocessing-handler");
21
22
  const autoclearSetting = Symbol("needle:previous-autoclear-state");
23
+ const previousToneMapping = Symbol("needle:previous-tone-mapping");
22
24
 
23
25
  /**
24
26
  * PostProcessingHandler is responsible for applying post processing effects to the scene. It is internally used by the {@link Volume} component
@@ -27,7 +29,7 @@ export class PostProcessingHandler {
27
29
 
28
30
  private _composer: EffectComposer | null = null;
29
31
  private _lastVolumeComponents?: PostProcessingEffect[];
30
- private _effects: Array<Effect | Pass> = [];
32
+ private readonly _effects: Array<PostprocessingEffectData> = [];
31
33
 
32
34
  get isActive() {
33
35
  return this._isActive;
@@ -60,7 +62,7 @@ export class PostProcessingHandler {
60
62
  return this.onApply(this.context, components);
61
63
  }
62
64
 
63
- unapply() {
65
+ unapply(dispose: boolean = true) {
64
66
  if (debug) console.log("Unapplying postprocessing effects");
65
67
  this._isActive = false;
66
68
  if (this._lastVolumeComponents) {
@@ -73,21 +75,29 @@ export class PostProcessingHandler {
73
75
  const active = context[activeKey] as PostProcessingHandler | null;
74
76
  if (active === this) {
75
77
  delete context[activeKey];
78
+
79
+ // Restore the auto clear setting
80
+ if (typeof context.renderer[autoclearSetting] === "boolean") {
81
+ context.renderer.autoClear = context.renderer[autoclearSetting];
82
+ }
83
+ if (typeof context.renderer[previousToneMapping] === "number") {
84
+ context.renderer.toneMapping = context.renderer[previousToneMapping] as ToneMapping;
85
+ }
76
86
  }
87
+
88
+ this._composer?.removeAllPasses();
89
+ if (dispose) this._composer?.dispose();
90
+
77
91
  if (context.composer === this._composer) {
78
- context.composer?.dispose();
79
92
  context.composer = null;
80
93
  }
81
- if (typeof context.renderer[autoclearSetting] === "boolean") {
82
- context.renderer.autoClear = context.renderer[autoclearSetting];
83
- }
84
94
  }
85
95
 
86
96
  dispose() {
87
- this.unapply();
97
+ this.unapply(true);
88
98
 
89
99
  for (const effect of this._effects) {
90
- effect.dispose();
100
+ effect.effect.dispose();
91
101
  }
92
102
  this._effects.length = 0;
93
103
  this._composer = null;
@@ -105,6 +115,7 @@ export class PostProcessingHandler {
105
115
  // import("./Effects/Sharpening.effect")
106
116
  ]);
107
117
 
118
+
108
119
  // try {
109
120
  // internal_SetSharpeningEffectModule(modules[2]);
110
121
  // }
@@ -141,10 +152,22 @@ export class PostProcessingHandler {
141
152
  // apply or collect effects
142
153
  const res = component.apply(ctx);
143
154
  if (!res) continue;
155
+
156
+
144
157
  if (Array.isArray(res)) {
145
- this._effects.push(...res);
158
+ for (const effect of res) {
159
+ this._effects.push({
160
+ effect,
161
+ priority: component.order
162
+ });
163
+ }
164
+ }
165
+ else {
166
+ this._effects.push({
167
+ effect: res,
168
+ priority: component.order
169
+ });
146
170
  }
147
- else this._effects.push(res);
148
171
  }
149
172
  }
150
173
  else {
@@ -153,14 +176,6 @@ export class PostProcessingHandler {
153
176
  }
154
177
  }
155
178
 
156
- // Ensure that we have a tonemapping effect if the renderer is set to use a tone mapping
157
- if (this.context.renderer.toneMapping != NoToneMapping) {
158
- if (!this._effects.find(e => e instanceof MODULES.POSTPROCESSING.MODULE.ToneMappingEffect)) {
159
- const tonemapping = new MODULES.POSTPROCESSING.MODULE.ToneMappingEffect();
160
- this._effects.push(tonemapping);
161
- }
162
- }
163
-
164
179
  this.applyEffects(context);
165
180
  }
166
181
 
@@ -173,11 +188,29 @@ export class PostProcessingHandler {
173
188
  get hasSmaaEffect() { return this._hasSmaaEffect; }
174
189
 
175
190
 
191
+
192
+ private _customInputBuffer: WebGLRenderTarget<Texture> | null = null;
193
+ private _customInputBufferId = 0;
194
+ private _multisampling: number = 0;
195
+ set multisampling(value: number) {
196
+ this._multisampling = value;
197
+ }
198
+ get multisampling() {
199
+ return this._multisampling;
200
+ }
201
+
202
+
176
203
  /** Build composer passes */
177
204
  private applyEffects(context: Context) {
205
+ // Reset state
206
+ this._anyPassHasDepth = false;
207
+ this._anyPassHasNormal = false;
208
+ this._hasSmaaEffect = false;
209
+
210
+ if (this._effects.length <= 0) {
211
+ return;
212
+ }
178
213
 
179
- const effectsOrPasses = this._effects;
180
- if (effectsOrPasses.length <= 0) return;
181
214
  const camera = context.mainCameraComponent as Camera;
182
215
  const renderer = context.renderer;
183
216
  const scene = context.scene;
@@ -186,16 +219,24 @@ export class PostProcessingHandler {
186
219
  // Store the auto clear setting because the postprocessing composer just disables it
187
220
  // and when we disable postprocessing we want to restore the original setting
188
221
  // https://github.com/pmndrs/postprocessing/blob/271944b74b543a5b743a62803a167b60cc6bb4ee/src/core/EffectComposer.js#L230C12-L230C12
222
+ // First we need to get the previously set autoClear setting, if it exists
223
+ if (typeof renderer[autoclearSetting] === "boolean") {
224
+ renderer.autoClear = renderer[autoclearSetting];
225
+ }
189
226
  renderer[autoclearSetting] = renderer.autoClear;
190
227
 
191
- // Reset state
192
- this._anyPassHasDepth = false;
193
- this._anyPassHasNormal = false;
194
- this._hasSmaaEffect = false;
228
+ if (typeof renderer[previousToneMapping] === "number") {
229
+ renderer.toneMapping = renderer[previousToneMapping] as ToneMapping;
230
+ }
231
+ renderer[previousToneMapping] = renderer.toneMapping;
195
232
 
196
- for (const ef of effectsOrPasses) {
197
- if (ef instanceof MODULES.POSTPROCESSING.MODULE.SMAAEffect) {
198
- this._hasSmaaEffect = true;
233
+ // Ensure that we have a tonemapping effect if the renderer is set to use a tone mapping
234
+ if (renderer.toneMapping != NoToneMapping) {
235
+ if (!this._effects.find(e => e instanceof MODULES.POSTPROCESSING.MODULE.ToneMappingEffect)) {
236
+ const tonemapping = new MODULES.POSTPROCESSING.MODULE.ToneMappingEffect();
237
+ tonemapping.name = `ToneMapping (${renderer.toneMapping})`;
238
+ tonemapping.mode = threeToneMappingToEffectMode(renderer.toneMapping);
239
+ this._effects.push({ effect: tonemapping, priority: PostProcessingEffectOrder.ToneMapping });
199
240
  }
200
241
  }
201
242
 
@@ -218,6 +259,7 @@ export class PostProcessingHandler {
218
259
  composer.setRenderer(renderer);
219
260
  composer.setMainScene(scene);
220
261
  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)
262
+ composer.multisampling = 0; // Disable multisampling by default
221
263
 
222
264
  for (const prev of composer.passes)
223
265
  prev.dispose();
@@ -225,67 +267,139 @@ export class PostProcessingHandler {
225
267
 
226
268
  // Render to screen pass
227
269
  const screenpass = new MODULES.POSTPROCESSING.MODULE.RenderPass(scene, cam);
228
- screenpass.name = "Render To Screen";
270
+ screenpass.name = "RenderPass";
229
271
  screenpass.mainScene = scene;
230
272
  composer.addPass(screenpass);
231
273
 
232
- const automaticEffectsOrdering = true;
233
- if (automaticEffectsOrdering && !dontMergePasses) {
234
- try {
235
- this.orderEffects();
274
+ const screenPassRender = screenpass.render;
275
+ this._customInputBuffer?.dispose();
276
+ this._customInputBuffer = null;
277
+ screenpass.render = (renderer, inputBuffer, outputBuffer, deltaTime, stencilTest) => {
278
+ if (!inputBuffer) return;
236
279
 
237
- const effects: Array<Effect> = [];
280
+ // screenPassRender.call(screenpass, renderer, inputBuffer, outputBuffer, deltaTime, stencilTest);
281
+ // return;
238
282
 
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
- }
283
+ // Make sure multisampling is disabled on the composer buffers. Technically a user could still set multisampling directly on the composer so this is to override that and make sure these textures do NOT use multisampling
284
+ inputBuffer.samples = 0;
285
+ if (outputBuffer) {
286
+ outputBuffer.samples = 0;
287
+ }
253
288
 
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);
289
+ // Make sure the input buffer is a WebGLRenderTarget with the correct settings
290
+ if (!this._customInputBuffer
291
+ || this._customInputBuffer.width !== inputBuffer.width
292
+ || this._customInputBuffer.height !== inputBuffer.height
293
+ || this._customInputBuffer.samples !== this._multisampling
294
+ || this._customInputBuffer.texture.format !== inputBuffer.texture.format
295
+ || this._customInputBuffer.texture.type !== HalfFloatType
296
+ ) {
297
+ this._customInputBuffer?.dispose();
298
+
299
+ this._customInputBuffer = new WebGLRenderTarget(inputBuffer.width, inputBuffer.height, {
300
+ format: inputBuffer.texture.format,
301
+ type: HalfFloatType,
302
+ depthBuffer: inputBuffer.depthBuffer,
303
+ depthTexture: inputBuffer.depthTexture
304
+ ? new DepthTexture(inputBuffer.width, inputBuffer.height)
305
+ : undefined,
306
+ stencilBuffer: inputBuffer.stencilBuffer,
307
+ samples: Math.max(0, this._multisampling),
308
+ minFilter: inputBuffer.texture.minFilter ?? LinearFilter,
309
+ magFilter: inputBuffer.texture.magFilter ?? LinearFilter,
310
+ generateMipmaps: inputBuffer.texture.generateMipmaps,
311
+ });
312
+ this._customInputBufferId++;
313
+ this._customInputBuffer.texture.name = `CustomInputBuffer-${this._customInputBufferId}`;
314
+ if (this._customInputBuffer.depthTexture && inputBuffer.depthTexture) {
315
+ this._customInputBuffer.depthTexture.format = inputBuffer.depthTexture.format;
316
+ this._customInputBuffer.depthTexture.type = inputBuffer.depthTexture.type;
262
317
  }
318
+ // https://github.com/pmndrs/postprocessing/blob/ad338df710ef41fee4e5d10ad2c2c299030d46ef/src/core/EffectComposer.js#L366
319
+ if (this._customInputBuffer.samples > 0)
320
+ (this._customInputBuffer as any).ignoreDepthForMultisampleCopy = false;
321
+
322
+ if (debug) console.warn(`[PostProcessing] Input buffer created with size ${this._customInputBuffer.width}x${this._customInputBuffer.height} and samples ${this._customInputBuffer.samples}`);
263
323
  }
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();
324
+ // Calling the original render function with the input buffer
325
+ screenPassRender.call(screenpass, renderer, this._customInputBuffer, outputBuffer, deltaTime, stencilTest);
326
+ // Blit the resulting buffer to the input buffer passed in by the composer so it's used for subsequent effects
327
+ Graphics.blit(this._customInputBuffer.texture, inputBuffer, {
328
+ renderer,
329
+ depthTexture: this._customInputBuffer.depthTexture,
330
+ depthWrite: true,
331
+ depthTest: true,
332
+ });
333
+ };
334
+
335
+
336
+ let foundTonemappingEffect = false;
337
+ try {
338
+ orderEffects(this._effects);
339
+
340
+ const effectsToMerge: Array<Effect> = [];
341
+ let hasConvolutionEffectInArray = false;
342
+
343
+ for (const entry of this._effects) {
344
+ const ef = entry.effect;
345
+
346
+ if (ef instanceof MODULES.POSTPROCESSING.MODULE.SMAAEffect) {
347
+ this._hasSmaaEffect = true;
348
+ }
349
+ else if (ef instanceof MODULES.POSTPROCESSING.MODULE.NormalPass) {
350
+ this._anyPassHasNormal = true;
351
+ }
352
+
353
+
354
+ // There can be only one tonemapping effect in the scene, so we can skip all others
355
+ if (ef instanceof MODULES.POSTPROCESSING.MODULE.ToneMappingEffect) {
356
+ // If we already have a tonemapping effect, we can skip this one
357
+ if (foundTonemappingEffect) continue;
358
+ foundTonemappingEffect = true;
359
+ }
360
+
361
+ // We can also not merge multiple effects of the same type in one pass
362
+ // So we first need to create a new pass with whatever effects we have so far
363
+ // TODO: this seems to work fine for some effects (like ColorAdjustments) and only caused issues with multiple Tonemapping effects so far which is handled above
364
+ // const constructor = ef.constructor as Constructor<Effect | Pass>;
365
+ // if(effectsToMerge.find(e => e.constructor === constructor)) {
366
+ // // this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
367
+ // }
273
368
 
274
- for (const ef of effectsOrPasses) {
275
369
  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);
370
+ const attributes = ef.getAttributes();
371
+ const convolution = MODULES.POSTPROCESSING.MODULE.EffectAttribute.CONVOLUTION;
372
+ if (attributes & convolution) {
373
+ if (debug) console.log("[PostProcessing] Convolution effect: " + ef.name);
374
+ if (hasConvolutionEffectInArray) {
375
+ if (debug) console.log("[PostProcessing] Merging effects with convolution", effectsToMerge.map(e => e.name).join(", "));
376
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
377
+ }
378
+ hasConvolutionEffectInArray = true;
379
+ }
380
+ // Otherwise we can merge it
381
+ effectsToMerge.push(ef as Effect);
279
382
  }
280
383
  else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass) {
384
+ hasConvolutionEffectInArray = false;
385
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
281
386
  ef.renderToScreen = false;
282
387
  composer.addPass(ef as Pass);
283
388
  }
284
- else
389
+ else {
285
390
  // seems some effects are not correctly typed, but three can deal with them,
286
- // so we just pass them through
391
+ // so we might need to just pass them through
392
+ hasConvolutionEffectInArray = false;
393
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
287
394
  composer.addPass(ef);
395
+ }
288
396
  }
397
+
398
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
399
+ }
400
+ catch (e) {
401
+ console.error("Error while applying postprocessing effects", e);
402
+ composer.removeAllPasses();
289
403
  }
290
404
 
291
405
  // 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 +415,6 @@ export class PostProcessingHandler {
301
415
  }
302
416
 
303
417
  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
418
  }
311
419
 
312
420
  // DEBUG LAND BELOW
@@ -314,56 +422,20 @@ export class PostProcessingHandler {
314
422
  if (debug) this._onCreateEffectsDebug(this._composer!, cam);
315
423
  }
316
424
 
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
425
 
364
- if (debug === "verbose") console.debug("After ordering effects", [...this._effects]);
365
- }
366
426
 
427
+ /** Should be called before `composer.addPass()` to create an effect pass with all previously collected effects that can be merged up to that point */
428
+ private createPassForMergeableEffects(effects: Array<Effect>, composer: EffectComposer, camera: Camera3, scene: Scene) {
429
+ if (effects.length > 0) {
430
+ const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(camera, ...effects);
431
+ pass.name = effects.map(e => e.name).join(", ");
432
+ pass.mainScene = scene;
433
+ pass.enabled = true;
434
+ pass.renderToScreen = false;
435
+ composer.addPass(pass);
436
+ effects.length = 0; // Clear effects after adding them to the pass
437
+ }
438
+ }
367
439
 
368
440
 
369
441
 
@@ -439,4 +511,3 @@ export class PostProcessingHandler {
439
511
 
440
512
  }
441
513
 
442
- let effectsOrder: Array<Constructor<Effect | Pass>> | null = null;
@@ -1,4 +1,4 @@
1
- import type { Effect } from "postprocessing";
1
+ import type { Effect, EffectComposer } from "postprocessing";
2
2
 
3
3
  import { isDevEnvironment, showBalloonMessage } from "../../engine/debug/index.js";
4
4
  import { Context } from "../../engine/engine_context.js";
@@ -187,48 +187,56 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
187
187
 
188
188
  this.context.composer.setMainScene(this.context.scene);
189
189
 
190
- const composer = this.context.composer;
190
+ if (this.multisampling === "auto") {
191
191
 
192
- // If the postprocessing handler is using depth+normals (e.g. with SMAA) we ALWAYS disable multisampling to avoid ugly edges
193
- if (this._postprocessing && (this._postprocessing.hasSmaaEffect)) {
194
- if (composer.multisampling !== 0) {
195
- composer.multisampling = 0;
196
- if (debug || isDevEnvironment()) {
197
- console.warn(`[PostProcessing] Disabling multisampling because depth or normals are used. If you need anti-aliasing, consider adding an Antialiasing component or SMAA effect to the PostprocessingManager.`);
192
+ // If the postprocessing handler is using depth+normals (e.g. with SMAA) we ALWAYS disable multisampling to avoid ugly edges
193
+ if (this._postprocessing && (this._postprocessing.hasSmaaEffect)) {
194
+ if (this._postprocessing.multisampling !== 0) {
195
+ this._postprocessing.multisampling = 0;
196
+ if (debug || isDevEnvironment()) {
197
+ console.warn(`[PostProcessing] Disabling multisampling you're using SMAA and have set the 'multisampling: auto' on your PostprocessingManager/Volume component. If you need anti-aliasing, consider adding an Antialiasing component or SMAA effect to the PostprocessingManager.`);
198
+ }
198
199
  }
199
200
  }
200
- }
201
- else if (this.multisampling === "auto") {
202
-
203
- const timeSinceLastChange = this.context.time.realtimeSinceStartup - this._multisampleAutoChangeTime;
204
-
205
- if (this.context.time.realtimeSinceStartup - this._componentEnabledTime > 2
206
- && timeSinceLastChange > .5
207
- ) {
208
- const prev = composer.multisampling;
201
+ else {
202
+ const timeSinceLastChange = this.context.time.realtimeSinceStartup - this._multisampleAutoChangeTime;
209
203
 
210
- if (composer.multisampling > 0 && this.context.time.smoothedFps <= 50) {
211
- this._multisampleAutoChangeTime = this.context.time.realtimeSinceStartup;
212
- this._multisampleAutoDecreaseTime = this.context.time.realtimeSinceStartup;
213
- composer.multisampling *= .5;
214
- composer.multisampling = Math.floor(composer.multisampling);
215
- if (debug) console.debug(`[PostProcessing] Reduced multisampling from ${prev} to ${composer.multisampling}`);
216
- }
217
- // if performance is good for a while try increasing multisampling again
218
- else if (timeSinceLastChange > 1
219
- && this.context.time.smoothedFps >= 59
220
- && composer.multisampling < this.context.renderer.capabilities.maxSamples
221
- && this.context.time.realtimeSinceStartup - this._multisampleAutoDecreaseTime > 10
204
+ if (this.context.time.realtimeSinceStartup - this._componentEnabledTime > 2
205
+ && timeSinceLastChange > .5
222
206
  ) {
223
- this._multisampleAutoChangeTime = this.context.time.realtimeSinceStartup;
224
- composer.multisampling = composer.multisampling <= 0 ? 1 : composer.multisampling * 2;
225
- composer.multisampling = Math.floor(composer.multisampling);
226
- if (debug) console.debug(`[PostProcessing] Increased multisampling from ${prev} to ${composer.multisampling}`);
207
+ const prev = this._postprocessing.multisampling;
208
+
209
+ if (this._postprocessing.multisampling > 0 && this.context.time.smoothedFps <= 50) {
210
+ this._multisampleAutoChangeTime = this.context.time.realtimeSinceStartup;
211
+ this._multisampleAutoDecreaseTime = this.context.time.realtimeSinceStartup;
212
+ let newMultiSample = this._postprocessing.multisampling * .5;
213
+ newMultiSample = Math.floor(newMultiSample);
214
+ if (newMultiSample != this._postprocessing.multisampling) {
215
+ this._postprocessing.multisampling = newMultiSample;
216
+ }
217
+ if (debug) console.debug(`[PostProcessing] Reduced multisampling from ${prev} to ${this._postprocessing.multisampling}`);
218
+ }
219
+ // if performance is good for a while try increasing multisampling again
220
+ else if (timeSinceLastChange > 1
221
+ && this.context.time.smoothedFps >= 59
222
+ && this._postprocessing.multisampling < this.context.renderer.capabilities.maxSamples
223
+ && this.context.time.realtimeSinceStartup - this._multisampleAutoDecreaseTime > 10
224
+ ) {
225
+ this._multisampleAutoChangeTime = this.context.time.realtimeSinceStartup;
226
+ let newMultiSample = this._postprocessing.multisampling <= 0 ? 1 : this._postprocessing.multisampling * 2;
227
+ newMultiSample = Math.floor(newMultiSample);
228
+ if (newMultiSample !== this._postprocessing.multisampling) {
229
+ this._postprocessing.multisampling = newMultiSample;
230
+ }
231
+ if (debug) console.debug(`[PostProcessing] Increased multisampling from ${prev} to ${this._postprocessing.multisampling}`);
232
+ }
227
233
  }
228
234
  }
229
235
  }
230
236
  else {
231
- composer.multisampling = Math.max(0, Math.min(this.multisampling, this.context.renderer.capabilities.maxSamples));
237
+ const newMultiSample = Math.max(0, Math.min(this.multisampling, this.context.renderer.capabilities.maxSamples));
238
+ if (newMultiSample !== this._postprocessing.multisampling)
239
+ this._postprocessing.multisampling = newMultiSample;
232
240
  }
233
241
 
234
242
  // only set the main camera if any pass has a different camera
@@ -257,7 +265,7 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
257
265
  private _isDirty: boolean = false;
258
266
 
259
267
  private apply() {
260
- if (debug) console.log(`Apply PostProcessing "${this.name}"`);
268
+ if (debug) console.log(`Apply PostProcessing "${this.name || "unnamed"}"`);
261
269
 
262
270
  if (isDevEnvironment()) {
263
271
  if (this._lastApplyTime !== undefined && Date.now() - this._lastApplyTime < 100) {
@@ -269,7 +277,6 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
269
277
  }
270
278
 
271
279
  this._isDirty = false;
272
- this.unapply();
273
280
 
274
281
  this._activeEffects.length = 0;
275
282
  // get from profile
@@ -296,32 +303,28 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
296
303
 
297
304
  this._applyPostQueue();
298
305
 
299
- const composer = this._postprocessing?.composer;
300
- if (composer) {
306
+ if (this._postprocessing) {
301
307
  if (this.multisampling === "auto") {
302
- composer.multisampling = DeviceUtilities.isMobileDevice()
308
+ this._postprocessing.multisampling = DeviceUtilities.isMobileDevice()
303
309
  ? 2
304
310
  : 4;
305
311
  }
306
312
  else {
307
- composer.multisampling = Math.max(0, Math.min(this.multisampling, this.context.renderer.capabilities.maxSamples));
313
+ this._postprocessing.multisampling = Math.max(0, Math.min(this.multisampling, this.context.renderer.capabilities.maxSamples));
308
314
  }
309
- if (debug) console.debug(`[PostProcessing] Set multisampling to ${composer.multisampling} (Is Mobile: ${DeviceUtilities.isMobileDevice()})`);
315
+ if (debug) console.debug(`[PostProcessing] Set multisampling to ${this._postprocessing.multisampling} (Is Mobile: ${DeviceUtilities.isMobileDevice()})`);
310
316
  }
311
317
  else if (debug) {
312
318
  console.warn(`[PostProcessing] No composer found`);
313
319
  }
314
320
  })
315
-
321
+ }
322
+ else {
323
+ this._postprocessing?.unapply(false);
316
324
  }
317
325
 
318
326
  }
319
327
 
320
- private unapply() {
321
- this._postprocessing?.unapply();
322
- }
323
-
324
-
325
328
  private _applyPostQueue() {
326
329
  if (this._modificationQueue) {
327
330
  for (const entry of this._modificationQueue.values()) this.onEditorModification(entry);
@@ -1,5 +1,6 @@
1
1
  export * from "./PostProcessingEffect.js";
2
2
  export * from "./PostProcessingHandler.js"
3
+ export { PostProcessingEffectOrder } from "./utils.js";
3
4
  export { PostProcessingManager } from "./Volume.js"
4
5
  export * from "./VolumeParameter.js"
5
- export * from "./VolumeProfile.js";
6
+ export * from "./VolumeProfile.js";