@needle-tools/engine 4.6.1-next.f9f2e7d → 4.6.2-next.fb486b2

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 (73) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/{needle-engine.bundle-DoQP-VX2.min.js → needle-engine.bundle-CJ4jhuta.min.js} +173 -161
  3. package/dist/{needle-engine.bundle-ntX9QTqT.js → needle-engine.bundle-CQzZighj.js} +7390 -7295
  4. package/dist/{needle-engine.bundle-CbE5i73R.umd.cjs → needle-engine.bundle-CdAK5p8o.umd.cjs} +177 -165
  5. package/dist/needle-engine.js +48 -48
  6. package/dist/needle-engine.min.js +1 -1
  7. package/dist/needle-engine.umd.cjs +1 -1
  8. package/lib/engine/engine_context.d.ts +2 -1
  9. package/lib/engine/engine_context.js +3 -2
  10. package/lib/engine/engine_context.js.map +1 -1
  11. package/lib/engine/engine_three_utils.d.ts +17 -14
  12. package/lib/engine/engine_three_utils.js +106 -53
  13. package/lib/engine/engine_three_utils.js.map +1 -1
  14. package/lib/engine/engine_tonemapping.d.ts +4 -0
  15. package/lib/engine/engine_tonemapping.js +21 -18
  16. package/lib/engine/engine_tonemapping.js.map +1 -1
  17. package/lib/engine/engine_utils.js.map +1 -1
  18. package/lib/engine/webcomponents/needle-engine.d.ts +4 -1
  19. package/lib/engine/webcomponents/needle-engine.extras.js +3 -3
  20. package/lib/engine/webcomponents/needle-engine.extras.js.map +1 -1
  21. package/lib/engine/webcomponents/needle-engine.js +11 -21
  22. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  23. package/lib/engine-components/CameraUtils.js.map +1 -1
  24. package/lib/engine-components/postprocessing/Effects/BloomEffect.d.ts +1 -1
  25. package/lib/engine-components/postprocessing/Effects/BloomEffect.js +2 -5
  26. package/lib/engine-components/postprocessing/Effects/BloomEffect.js.map +1 -1
  27. package/lib/engine-components/postprocessing/Effects/ColorAdjustments.d.ts +8 -0
  28. package/lib/engine-components/postprocessing/Effects/ColorAdjustments.js +27 -8
  29. package/lib/engine-components/postprocessing/Effects/ColorAdjustments.js.map +1 -1
  30. package/lib/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.js +1 -0
  31. package/lib/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.js.map +1 -1
  32. package/lib/engine-components/postprocessing/Effects/Sharpening.d.ts +1 -0
  33. package/lib/engine-components/postprocessing/Effects/Sharpening.js +4 -0
  34. package/lib/engine-components/postprocessing/Effects/Sharpening.js.map +1 -1
  35. package/lib/engine-components/postprocessing/Effects/Tonemapping.d.ts +2 -9
  36. package/lib/engine-components/postprocessing/Effects/Tonemapping.js +23 -71
  37. package/lib/engine-components/postprocessing/Effects/Tonemapping.js.map +1 -1
  38. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +13 -0
  39. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.js +52 -0
  40. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.js.map +1 -0
  41. package/lib/engine-components/postprocessing/PostProcessingEffect.d.ts +7 -7
  42. package/lib/engine-components/postprocessing/PostProcessingEffect.js +9 -9
  43. package/lib/engine-components/postprocessing/PostProcessingEffect.js.map +1 -1
  44. package/lib/engine-components/postprocessing/PostProcessingHandler.d.ts +10 -1
  45. package/lib/engine-components/postprocessing/PostProcessingHandler.js +152 -23
  46. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  47. package/lib/engine-components/postprocessing/Volume.js +19 -24
  48. package/lib/engine-components/postprocessing/Volume.js.map +1 -1
  49. package/lib/engine-components/postprocessing/index.d.ts +1 -1
  50. package/lib/engine-components/postprocessing/index.js +1 -1
  51. package/lib/engine-components/postprocessing/index.js.map +1 -1
  52. package/lib/engine-components/postprocessing/utils.d.ts +13 -7
  53. package/lib/engine-components/postprocessing/utils.js +37 -53
  54. package/lib/engine-components/postprocessing/utils.js.map +1 -1
  55. package/package.json +1 -1
  56. package/src/engine/engine_context.ts +5 -3
  57. package/src/engine/engine_three_utils.ts +134 -58
  58. package/src/engine/engine_tonemapping.ts +23 -24
  59. package/src/engine/engine_utils.ts +2 -2
  60. package/src/engine/webcomponents/needle-engine.extras.ts +3 -3
  61. package/src/engine/webcomponents/needle-engine.ts +14 -25
  62. package/src/engine-components/CameraUtils.ts +3 -3
  63. package/src/engine-components/postprocessing/Effects/BloomEffect.ts +2 -4
  64. package/src/engine-components/postprocessing/Effects/ColorAdjustments.ts +24 -13
  65. package/src/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.ts +1 -0
  66. package/src/engine-components/postprocessing/Effects/Sharpening.ts +5 -0
  67. package/src/engine-components/postprocessing/Effects/Tonemapping.ts +26 -80
  68. package/src/engine-components/postprocessing/Effects/Tonemapping.utils.ts +60 -0
  69. package/src/engine-components/postprocessing/PostProcessingEffect.ts +9 -9
  70. package/src/engine-components/postprocessing/PostProcessingHandler.ts +174 -27
  71. package/src/engine-components/postprocessing/Volume.ts +19 -26
  72. package/src/engine-components/postprocessing/index.ts +2 -2
  73. package/src/engine-components/postprocessing/utils.ts +41 -56
@@ -1,15 +1,17 @@
1
1
  import type { Effect, EffectComposer, Pass, ToneMappingEffect as _TonemappingEffect } from "postprocessing";
2
- import { Camera as Camera3, HalfFloatType, NoToneMapping, Scene } from "three";
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 { Constructor } from "../../engine/engine_types.js";
10
+ import { getParam } from "../../engine/engine_utils.js";
10
11
  import { Camera } from "../Camera.js";
12
+ import { threeToneMappingToEffectMode } from "./Effects/Tonemapping.utils.js";
11
13
  import { PostProcessingEffect, PostProcessingEffectContext } from "./PostProcessingEffect.js";
12
- import { orderEffects, PostprocessingEffectData } from "./utils.js";
14
+ import { orderEffects, PostprocessingEffectData, PostProcessingEffectOrder } from "./utils.js";
13
15
 
14
16
  declare const NEEDLE_USE_POSTPROCESSING: boolean;
15
17
  globalThis["NEEDLE_USE_POSTPROCESSING"] = globalThis["NEEDLE_USE_POSTPROCESSING"] !== undefined ? globalThis["NEEDLE_USE_POSTPROCESSING"] : true;
@@ -19,6 +21,7 @@ const debug = getParam("debugpost");
19
21
 
20
22
  const activeKey = Symbol("needle:postprocessing-handler");
21
23
  const autoclearSetting = Symbol("needle:previous-autoclear-state");
24
+ const previousToneMapping = Symbol("needle:previous-tone-mapping");
22
25
 
23
26
  /**
24
27
  * PostProcessingHandler is responsible for applying post processing effects to the scene. It is internally used by the {@link Volume} component
@@ -29,6 +32,14 @@ export class PostProcessingHandler {
29
32
  private _lastVolumeComponents?: PostProcessingEffect[];
30
33
  private readonly _effects: Array<PostprocessingEffectData> = [];
31
34
 
35
+ /**
36
+ * Returns true if a specific effect is currently active in the post processing stack.
37
+ */
38
+ getEffectIsActive(effect: Effect): boolean {
39
+ if (!effect) return false;
40
+ return this._isActive && this._effects.some(e => e.effect === effect);
41
+ }
42
+
32
43
  get isActive() {
33
44
  return this._isActive;
34
45
  }
@@ -78,6 +89,9 @@ export class PostProcessingHandler {
78
89
  if (typeof context.renderer[autoclearSetting] === "boolean") {
79
90
  context.renderer.autoClear = context.renderer[autoclearSetting];
80
91
  }
92
+ if (typeof context.renderer[previousToneMapping] === "number") {
93
+ context.renderer.toneMapping = context.renderer[previousToneMapping] as ToneMapping;
94
+ }
81
95
  }
82
96
 
83
97
  this._composer?.removeAllPasses();
@@ -153,14 +167,16 @@ export class PostProcessingHandler {
153
167
  for (const effect of res) {
154
168
  this._effects.push({
155
169
  effect,
156
- priority: component.priority
170
+ typeName: component.typeName,
171
+ priority: component.order
157
172
  });
158
173
  }
159
174
  }
160
175
  else {
161
176
  this._effects.push({
162
177
  effect: res,
163
- priority: component.priority
178
+ typeName: component.typeName,
179
+ priority: component.order
164
180
  });
165
181
  }
166
182
  }
@@ -171,14 +187,6 @@ export class PostProcessingHandler {
171
187
  }
172
188
  }
173
189
 
174
- // Ensure that we have a tonemapping effect if the renderer is set to use a tone mapping
175
- if (this.context.renderer.toneMapping != NoToneMapping) {
176
- if (!this._effects.find(e => e instanceof MODULES.POSTPROCESSING.MODULE.ToneMappingEffect)) {
177
- const tonemapping = new MODULES.POSTPROCESSING.MODULE.ToneMappingEffect();
178
- this._effects.push({ effect: tonemapping });
179
- }
180
- }
181
-
182
190
  this.applyEffects(context);
183
191
  }
184
192
 
@@ -191,6 +199,18 @@ export class PostProcessingHandler {
191
199
  get hasSmaaEffect() { return this._hasSmaaEffect; }
192
200
 
193
201
 
202
+
203
+ private _customInputBuffer: WebGLRenderTarget<Texture> | null = null;
204
+ private _customInputBufferId = 0;
205
+ private _multisampling: number = 0;
206
+ set multisampling(value: number) {
207
+ this._multisampling = value;
208
+ }
209
+ get multisampling() {
210
+ return this._multisampling;
211
+ }
212
+
213
+
194
214
  /** Build composer passes */
195
215
  private applyEffects(context: Context) {
196
216
  // Reset state
@@ -198,8 +218,7 @@ export class PostProcessingHandler {
198
218
  this._anyPassHasNormal = false;
199
219
  this._hasSmaaEffect = false;
200
220
 
201
- const effectsOrPasses = this._effects;
202
- if (effectsOrPasses.length <= 0) {
221
+ if (this._effects.length <= 0) {
203
222
  return;
204
223
  }
205
224
 
@@ -217,6 +236,25 @@ export class PostProcessingHandler {
217
236
  }
218
237
  renderer[autoclearSetting] = renderer.autoClear;
219
238
 
239
+ if (typeof renderer[previousToneMapping] === "number") {
240
+ renderer.toneMapping = renderer[previousToneMapping] as ToneMapping;
241
+ }
242
+ renderer[previousToneMapping] = renderer.toneMapping;
243
+
244
+ // Ensure that we have a tonemapping effect if the renderer is set to use a tone mapping
245
+ if (renderer.toneMapping != NoToneMapping) {
246
+ if (!this._effects.find(e => e instanceof MODULES.POSTPROCESSING.MODULE.ToneMappingEffect)) {
247
+ const tonemapping = new MODULES.POSTPROCESSING.MODULE.ToneMappingEffect();
248
+ tonemapping.name = `ToneMapping (${renderer.toneMapping})`;
249
+ tonemapping.mode = threeToneMappingToEffectMode(renderer.toneMapping);
250
+ this._effects.push({
251
+ typeName: "ToneMapping",
252
+ effect: tonemapping,
253
+ priority: PostProcessingEffectOrder.ToneMapping
254
+ });
255
+ }
256
+ }
257
+
220
258
  // create composer and set active on context
221
259
  if (!this._composer) {
222
260
  // const hdrRenderTarget = new WebGLRenderTarget(window.innerWidth, window.innerHeight, { type: HalfFloatType });
@@ -236,7 +274,7 @@ export class PostProcessingHandler {
236
274
  composer.setRenderer(renderer);
237
275
  composer.setMainScene(scene);
238
276
  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 = 0;
277
+ composer.multisampling = 0; // Disable multisampling by default
240
278
 
241
279
  for (const prev of composer.passes)
242
280
  prev.dispose();
@@ -244,17 +282,96 @@ export class PostProcessingHandler {
244
282
 
245
283
  // Render to screen pass
246
284
  const screenpass = new MODULES.POSTPROCESSING.MODULE.RenderPass(scene, cam);
247
- screenpass.name = "Render To Screen";
285
+ screenpass.name = "RenderPass";
248
286
  screenpass.mainScene = scene;
249
287
  composer.addPass(screenpass);
250
288
 
289
+ const screenPassRender = screenpass.render;
290
+ this._customInputBuffer?.dispose();
291
+ this._customInputBuffer = null;
292
+ screenpass.render = (renderer, inputBuffer, outputBuffer, deltaTime, stencilTest) => {
293
+ if (!inputBuffer) return;
294
+
295
+ // screenPassRender.call(screenpass, renderer, inputBuffer, outputBuffer, deltaTime, stencilTest);
296
+ // return;
297
+
298
+ // 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
299
+ inputBuffer.samples = 0;
300
+ if (outputBuffer) {
301
+ outputBuffer.samples = 0;
302
+ }
303
+
304
+ // Make sure the input buffer is a WebGLRenderTarget with the correct settings
305
+ if (!this._customInputBuffer
306
+ || this._customInputBuffer.width !== inputBuffer.width
307
+ || this._customInputBuffer.height !== inputBuffer.height
308
+ || this._customInputBuffer.samples !== this._multisampling
309
+ || this._customInputBuffer.texture.format !== inputBuffer.texture.format
310
+ || this._customInputBuffer.texture.type !== HalfFloatType
311
+ ) {
312
+ this._customInputBuffer?.dispose();
313
+
314
+ this._customInputBuffer = new WebGLRenderTarget(inputBuffer.width, inputBuffer.height, {
315
+ format: inputBuffer.texture.format,
316
+ type: HalfFloatType,
317
+ depthBuffer: inputBuffer.depthBuffer,
318
+ depthTexture: inputBuffer.depthTexture
319
+ ? new DepthTexture(inputBuffer.width, inputBuffer.height)
320
+ : undefined,
321
+ stencilBuffer: inputBuffer.stencilBuffer,
322
+ samples: Math.max(0, this._multisampling),
323
+ minFilter: inputBuffer.texture.minFilter ?? LinearFilter,
324
+ magFilter: inputBuffer.texture.magFilter ?? LinearFilter,
325
+ generateMipmaps: inputBuffer.texture.generateMipmaps,
326
+ });
327
+ this._customInputBufferId++;
328
+ this._customInputBuffer.texture.name = `CustomInputBuffer-${this._customInputBufferId}`;
329
+ if (this._customInputBuffer.depthTexture && inputBuffer.depthTexture) {
330
+ this._customInputBuffer.depthTexture.format = inputBuffer.depthTexture.format;
331
+ this._customInputBuffer.depthTexture.type = inputBuffer.depthTexture.type;
332
+ }
333
+ // https://github.com/pmndrs/postprocessing/blob/ad338df710ef41fee4e5d10ad2c2c299030d46ef/src/core/EffectComposer.js#L366
334
+ if (this._customInputBuffer.samples > 0)
335
+ (this._customInputBuffer as any).ignoreDepthForMultisampleCopy = false;
336
+
337
+ if (debug) console.warn(`[PostProcessing] Input buffer created with size ${this._customInputBuffer.width}x${this._customInputBuffer.height} and samples ${this._customInputBuffer.samples}`);
338
+ }
339
+ // Calling the original render function with the input buffer
340
+ screenPassRender.call(screenpass, renderer, this._customInputBuffer, outputBuffer, deltaTime, stencilTest);
341
+ // Blit the resulting buffer to the input buffer passed in by the composer so it's used for subsequent effects
342
+ Graphics.blit(this._customInputBuffer.texture, inputBuffer, {
343
+ renderer,
344
+ depthTexture: this._customInputBuffer.depthTexture,
345
+ depthWrite: true,
346
+ depthTest: true,
347
+ });
348
+ };
349
+
350
+
251
351
  try {
252
352
  orderEffects(this._effects);
253
353
 
354
+ let foundTonemappingEffect = false;
355
+ let activeTonemappingEffect: _TonemappingEffect | null = null;
356
+ for (let i = this._effects.length - 1; i >= 0; i--) {
357
+ const ef = this._effects[i].effect;
358
+ if (ef instanceof MODULES.POSTPROCESSING.MODULE.ToneMappingEffect) {
359
+ // If we already have a tonemapping effect, we can skip this one
360
+ if (foundTonemappingEffect) {
361
+ if (debug) console.warn(`[PostProcessing] Found multiple tonemapping effects in the scene: ${ef.name} and ${activeTonemappingEffect?.name}. Only the last one added will be used.`);
362
+ this._effects.splice(i, 1);
363
+ continue;
364
+ }
365
+ activeTonemappingEffect = ef;
366
+ foundTonemappingEffect = true;
367
+ }
368
+ }
369
+
254
370
  const effectsToMerge: Array<Effect> = [];
255
371
  let hasConvolutionEffectInArray = false;
256
372
 
257
- for (const entry of effectsOrPasses) {
373
+ for (let i = 0; i < this._effects.length; i++) {
374
+ const entry = this._effects[i];
258
375
  const ef = entry.effect;
259
376
 
260
377
  if (ef instanceof MODULES.POSTPROCESSING.MODULE.SMAAEffect) {
@@ -264,16 +381,30 @@ export class PostProcessingHandler {
264
381
  this._anyPassHasNormal = true;
265
382
  }
266
383
 
384
+ // There can be only one tonemapping effect in the scene, so we skip all others
385
+ if (ef instanceof MODULES.POSTPROCESSING.MODULE.ToneMappingEffect && activeTonemappingEffect !== ef) {
386
+ // If we already have a tonemapping effect, we can skip this one
387
+ continue;
388
+ }
389
+
390
+ // We can also not merge multiple effects of the same type in one pass
391
+ // So we first need to create a new pass with whatever effects we have so far
392
+ // 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
393
+ // const constructor = ef.constructor;
394
+ // if (effectsToMerge.find(e => e.constructor === constructor)) {
395
+ // this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
396
+ // }
397
+
398
+
399
+
267
400
  if (ef instanceof MODULES.POSTPROCESSING.MODULE.Effect) {
268
401
  const attributes = ef.getAttributes();
269
402
  const convolution = MODULES.POSTPROCESSING.MODULE.EffectAttribute.CONVOLUTION;
270
403
  if (attributes & convolution) {
271
- if(debug) console.log("[PostProcessing] Convolution effect detected: " + ef.name, hasConvolutionEffectInArray);
404
+ if (debug) console.log("[PostProcessing] Convolution effect: " + ef.name);
272
405
  if (hasConvolutionEffectInArray) {
273
- hasConvolutionEffectInArray = false;
274
- if(debug) console.log("[PostProcessing] Merging effects with convolution effect in array", effectsToMerge.map(e => e.name).join(", "));
406
+ if (debug) console.log("[PostProcessing] Merging effects with convolution", effectsToMerge.map(e => e.name).join(", "));
275
407
  this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
276
-
277
408
  }
278
409
  hasConvolutionEffectInArray = true;
279
410
  }
@@ -293,28 +424,44 @@ export class PostProcessingHandler {
293
424
  this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
294
425
  composer.addPass(ef);
295
426
  }
427
+
296
428
  }
297
429
 
298
430
  this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
299
431
  }
300
432
  catch (e) {
301
433
  console.error("Error while applying postprocessing effects", e);
434
+ composer.passes.forEach(p => p.dispose());
302
435
  composer.removeAllPasses();
303
436
  }
304
437
 
305
438
  // 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)
306
- for (let i = 0; i < composer.passes.length; i++) {
439
+ let foundEnabled = false;
440
+ for (let i = composer.passes.length - 1; i >= 0; i--) {
307
441
  const pass = composer.passes[i];
308
- const isLast = i === composer.passes.length - 1;
442
+ let gammaCorrect = false;
443
+ let renderToScreen = false;
444
+ if (pass.enabled) {
445
+ if (!foundEnabled) {
446
+ gammaCorrect = true;
447
+ renderToScreen = true;
448
+ }
449
+ foundEnabled = true;
450
+ }
451
+
452
+ pass.renderToScreen = renderToScreen;
453
+
309
454
  if ((pass as any)?.configuration !== undefined) {
310
- (pass as any).configuration.gammaCorrection = isLast;
455
+ (pass as any).configuration.gammaCorrection = gammaCorrect;
311
456
  }
312
457
  else if ("autosetGamma" in pass) {
313
458
  // Some effects have a autosetGamma property that we can use to set the gamma correction
314
- pass.autosetGamma = isLast;
459
+ pass.autosetGamma = gammaCorrect;
315
460
  }
316
461
 
462
+
317
463
  this._anyPassHasDepth ||= pass.needsDepthTexture;
464
+
318
465
  }
319
466
 
320
467
  // DEBUG LAND BELOW
@@ -187,14 +187,12 @@ 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 as EffectComposer;
191
-
192
190
  if (this.multisampling === "auto") {
193
191
 
194
192
  // If the postprocessing handler is using depth+normals (e.g. with SMAA) we ALWAYS disable multisampling to avoid ugly edges
195
193
  if (this._postprocessing && (this._postprocessing.hasSmaaEffect)) {
196
- if (composer.multisampling !== 0) {
197
- composer.multisampling = 0;
194
+ if (this._postprocessing.multisampling !== 0) {
195
+ this._postprocessing.multisampling = 0;
198
196
  if (debug || isDevEnvironment()) {
199
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.`);
200
198
  }
@@ -206,45 +204,41 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
206
204
  if (this.context.time.realtimeSinceStartup - this._componentEnabledTime > 2
207
205
  && timeSinceLastChange > .5
208
206
  ) {
209
- const prev = composer.multisampling;
207
+ const prev = this._postprocessing.multisampling;
210
208
 
211
- if (composer.multisampling > 0 && this.context.time.smoothedFps <= 50) {
209
+ if (this._postprocessing.multisampling > 0 && this.context.time.smoothedFps <= 50) {
212
210
  this._multisampleAutoChangeTime = this.context.time.realtimeSinceStartup;
213
211
  this._multisampleAutoDecreaseTime = this.context.time.realtimeSinceStartup;
214
- let newMultiSample = composer.multisampling * .5;
212
+ let newMultiSample = this._postprocessing.multisampling * .5;
215
213
  newMultiSample = Math.floor(newMultiSample);
216
- if (newMultiSample != composer.multisampling) {
217
- composer.multisampling = newMultiSample;
214
+ if (newMultiSample != this._postprocessing.multisampling) {
215
+ this._postprocessing.multisampling = newMultiSample;
218
216
  }
219
- if (debug) console.debug(`[PostProcessing] Reduced multisampling from ${prev} to ${composer.multisampling}`);
217
+ if (debug) console.debug(`[PostProcessing] Reduced multisampling from ${prev} to ${this._postprocessing.multisampling}`);
220
218
  }
221
219
  // if performance is good for a while try increasing multisampling again
222
220
  else if (timeSinceLastChange > 1
223
221
  && this.context.time.smoothedFps >= 59
224
- && composer.multisampling < this.context.renderer.capabilities.maxSamples
222
+ && this._postprocessing.multisampling < this.context.renderer.capabilities.maxSamples
225
223
  && this.context.time.realtimeSinceStartup - this._multisampleAutoDecreaseTime > 10
226
224
  ) {
227
225
  this._multisampleAutoChangeTime = this.context.time.realtimeSinceStartup;
228
- let newMultiSample = composer.multisampling <= 0 ? 1 : composer.multisampling * 2;
226
+ let newMultiSample = this._postprocessing.multisampling <= 0 ? 1 : this._postprocessing.multisampling * 2;
229
227
  newMultiSample = Math.floor(newMultiSample);
230
- if (newMultiSample !== composer.multisampling) {
231
- composer.multisampling = newMultiSample;
228
+ if (newMultiSample !== this._postprocessing.multisampling) {
229
+ this._postprocessing.multisampling = newMultiSample;
232
230
  }
233
- if (debug) console.debug(`[PostProcessing] Increased multisampling from ${prev} to ${composer.multisampling}`);
231
+ if (debug) console.debug(`[PostProcessing] Increased multisampling from ${prev} to ${this._postprocessing.multisampling}`);
234
232
  }
235
233
  }
236
234
  }
237
235
  }
238
236
  else {
239
237
  const newMultiSample = Math.max(0, Math.min(this.multisampling, this.context.renderer.capabilities.maxSamples));
240
- if (newMultiSample !== composer.multisampling)
241
- composer.multisampling = newMultiSample;
238
+ if (newMultiSample !== this._postprocessing.multisampling)
239
+ this._postprocessing.multisampling = newMultiSample;
242
240
  }
243
241
 
244
- // Fix multisampling for the composer, it ONLY needs to be done for the input buffer
245
- // this is super important for performance a negative visual impact
246
- composer.outputBuffer.samples = 0;
247
-
248
242
  // only set the main camera if any pass has a different camera
249
243
  // trying to avoid doing this regularly since it involves doing potentially unnecessary work
250
244
  // https://github.com/pmndrs/postprocessing/blob/3d3df0576b6d49aec9e763262d5a1ff7429fd91a/src/core/EffectComposer.js#L406
@@ -309,17 +303,16 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
309
303
 
310
304
  this._applyPostQueue();
311
305
 
312
- const composer = this._postprocessing?.composer;
313
- if (composer) {
306
+ if (this._postprocessing) {
314
307
  if (this.multisampling === "auto") {
315
- composer.multisampling = DeviceUtilities.isMobileDevice()
308
+ this._postprocessing.multisampling = DeviceUtilities.isMobileDevice()
316
309
  ? 2
317
310
  : 4;
318
311
  }
319
312
  else {
320
- 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));
321
314
  }
322
- 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()})`);
323
316
  }
324
317
  else if (debug) {
325
318
  console.warn(`[PostProcessing] No composer found`);
@@ -1,6 +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 { PostProcessingEffectPriority } from "./utils.js";
6
+ export * from "./VolumeProfile.js";
@@ -61,28 +61,32 @@ export function getPostProcessingManager(effect: PostProcessingEffect): IPostPro
61
61
  export type PostprocessingEffectData = {
62
62
  effect: Effect | Pass;
63
63
  priority?: number,
64
+ typeName?: string | null | undefined,
64
65
  }
65
66
 
66
67
  /**
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.
68
+ * Default order for post-processing effects. This can be used to sort effects by their rendering order when creating custom effects.
69
+ * E.g. in your custom effect, you can set `order: PostProcessingEffectOrder.Bloom + 1;` to ensure it gets rendered after the bloom effect.
70
+ * OR `order: PostProcessingEffectOrder.Bloom - 1;` to ensure it gets rendered before the bloom effect.
70
71
  * @example
71
72
  * ```typescript
72
- * import { PostProcessingEffectPriority } from "@needle-tools/engine"
73
- *
73
+ * import { PostProcessingEffectOrder } from "@needle-tools/engine"
74
+ *
74
75
  * export class MyCustomEffect extends PostProcessingEffect {
75
- * priority: PostProcessingEffectPriority.Bloom + 1; // render after bloom
76
+ * order: PostProcessingEffectPriority.Bloom + 1; // render after bloom
76
77
  *
77
78
  * // ... your effect code
78
79
  * }
79
80
  * ```
80
81
  */
81
- export const PostProcessingEffectPriority = {
82
+ export const PostProcessingEffectOrder = {
83
+ /** Used to render effects at the start of the post-processing chain */
84
+ AT_START: -10_000,
85
+
82
86
  NormalPass: 0,
83
87
  DepthDownsamplingPass: 10,
84
- SMAA: 20,
85
- SSAO: 30,
88
+ SSAO: 20,
89
+ SMAA: 30,
86
90
  TiltShift: 40,
87
91
  DepthOfField: 50,
88
92
  ChromaticAberration: 60,
@@ -92,7 +96,10 @@ export const PostProcessingEffectPriority = {
92
96
  ToneMapping: 100,
93
97
  HueSaturation: 110,
94
98
  BrightnessContrast: 120,
95
- // Sharpening: 130,
99
+ Sharpening: 130,
100
+
101
+ /** Used to render effects at the end of the post-processing chain, e.g. for final adjustments or overlays. */
102
+ AT_END: 10_000,
96
103
  }
97
104
  // let effectsOrder: Array<Constructor<Effect | Pass>> | null = null;
98
105
 
@@ -103,65 +110,43 @@ export function orderEffects(effects: Array<PostprocessingEffectData>) {
103
110
 
104
111
  if (!builtinOrder) {
105
112
  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);
113
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.NormalPass, PostProcessingEffectOrder.NormalPass);
114
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.DepthDownsamplingPass, PostProcessingEffectOrder.DepthDownsamplingPass);
115
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.SMAAEffect, PostProcessingEffectOrder.SMAA);
116
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.SSAOEffect, PostProcessingEffectOrder.SSAO);
117
+ builtinOrder.set(MODULES.POSTPROCESSING_AO.MODULE.N8AOPostPass, PostProcessingEffectOrder.SSAO);
118
+ builtinOrder.set(MODULES.POSTPROCESSING_AO.MODULE.N8AOPass, PostProcessingEffectOrder.SSAO);
119
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.TiltShiftEffect, PostProcessingEffectOrder.TiltShift);
120
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.DepthOfFieldEffect, PostProcessingEffectOrder.DepthOfField);
121
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.ChromaticAberrationEffect, PostProcessingEffectOrder.ChromaticAberration);
122
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.BloomEffect, PostProcessingEffectOrder.Bloom);
123
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.SelectiveBloomEffect, PostProcessingEffectOrder.Bloom);
124
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.VignetteEffect, PostProcessingEffectOrder.Vignette);
125
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.PixelationEffect, PostProcessingEffectOrder.Pixelation);
126
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.ToneMappingEffect, PostProcessingEffectOrder.ToneMapping);
127
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.HueSaturationEffect, PostProcessingEffectOrder.HueSaturation);
128
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.BrightnessContrastEffect, PostProcessingEffectOrder.BrightnessContrast);
119
129
  }
120
130
 
121
131
  // enforce correct order of effects (e.g. DOF before Bloom)
122
132
  effects.sort((a, b) => {
123
133
  // we use find index here because sometimes constructor names are prefixed with `_`
124
134
  // 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;
135
+ const aidx = typeof a.priority === "number" ? a.priority : builtinOrder!.get(a.effect.constructor as Constructor<Effect | Pass>) ?? Number.NEGATIVE_INFINITY;
136
+ const bidx = typeof b.priority === "number" ? b.priority : builtinOrder!.get(b.effect.constructor as Constructor<Effect | Pass>) ?? Number.NEGATIVE_INFINITY;
127
137
 
128
138
  // 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);
139
+ if (aidx === Number.NEGATIVE_INFINITY) {
140
+ if (debug) console.warn("Unknown effect found: ", a.constructor.name, a);
135
141
  return 1;
136
142
  }
137
- // if (aidx < 0) return 1;
138
- // if (bidx < 0) return -1;
143
+ else if (bidx === Number.NEGATIVE_INFINITY) {
144
+ if (debug) console.warn("Unknown effect found: ", b.constructor.name, b);
145
+ return -1;
146
+ }
139
147
  return aidx - bidx;
140
148
  });
141
149
 
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
150
 
166
151
  if (debug === "verbose") console.debug("After ordering effects", [...effects]);
167
152
  }