@needle-tools/engine 4.6.1 → 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 (102) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/generateMeshBVH.worker-BaNp_Xtp.js +25 -0
  3. package/dist/{gltf-progressive-Bm9eEfgu.min.js → gltf-progressive-Bl4okF1b.min.js} +1 -1
  4. package/dist/{gltf-progressive-GjIqwSG3.js → gltf-progressive-DSpdn0QT.js} +2 -2
  5. package/dist/{gltf-progressive-Dn6o99rH.umd.cjs → gltf-progressive-P8b8a0qY.umd.cjs} +1 -1
  6. package/dist/needle-engine.bundle-CJ4jhuta.min.js +1575 -0
  7. package/dist/{needle-engine.bundle-BVg46UWZ.js → needle-engine.bundle-CQzZighj.js} +9722 -9554
  8. package/dist/needle-engine.bundle-CdAK5p8o.umd.cjs +1575 -0
  9. package/dist/needle-engine.js +352 -351
  10. package/dist/needle-engine.min.js +1 -1
  11. package/dist/needle-engine.umd.cjs +1 -1
  12. package/dist/{postprocessing-CRQa6Qxn.umd.cjs → postprocessing-CjW23fio.umd.cjs} +18 -18
  13. package/dist/{postprocessing-D6W1EyZ-.js → postprocessing-DYLNOL3W.js} +4 -3
  14. package/dist/{postprocessing-DF8AlRgW.min.js → postprocessing-xYQWCHFu.min.js} +26 -26
  15. package/dist/{three-DMrv-4ar.umd.cjs → three-B_hneGZr.umd.cjs} +4 -4
  16. package/dist/{three-Bz6X1mrw.js → three-DrqIzZTH.js} +4198 -4198
  17. package/dist/{three-Boa-jOq-.min.js → three-DuDKwKB8.min.js} +33 -33
  18. package/dist/{three-examples-GggCDHv0.js → three-examples-B50TT3Iu.js} +5 -5
  19. package/dist/{three-examples-DuVhxqft.min.js → three-examples-DaDLBuy6.min.js} +14 -14
  20. package/dist/{three-examples-C7ryg8vN.umd.cjs → three-examples-X3OadjXB.umd.cjs} +3 -3
  21. package/dist/{three-mesh-ui-CY6Izc7C.min.js → three-mesh-ui-B3p3gyUz.min.js} +1 -1
  22. package/dist/{three-mesh-ui-CwlN0FUC.umd.cjs → three-mesh-ui-CQiIQIlA.umd.cjs} +1 -1
  23. package/dist/{three-mesh-ui-CLNOfsWn.js → three-mesh-ui-CxuWt7m-.js} +1 -1
  24. package/dist/{vendor-zxXa3Dmr.min.js → vendor-BlSxe9JJ.min.js} +3 -3
  25. package/dist/{vendor-BSD1RQIh.js → vendor-BmYIgaS1.js} +3 -3
  26. package/dist/{vendor-DHr4aqIZ.umd.cjs → vendor-Cavtu3CP.umd.cjs} +3 -3
  27. package/lib/engine/engine_context.d.ts +4 -1
  28. package/lib/engine/engine_context.js +9 -2
  29. package/lib/engine/engine_context.js.map +1 -1
  30. package/lib/engine/engine_three_utils.d.ts +17 -14
  31. package/lib/engine/engine_three_utils.js +106 -53
  32. package/lib/engine/engine_three_utils.js.map +1 -1
  33. package/lib/engine/engine_tonemapping.d.ts +4 -0
  34. package/lib/engine/engine_tonemapping.js +21 -18
  35. package/lib/engine/engine_tonemapping.js.map +1 -1
  36. package/lib/engine/engine_utils.js.map +1 -1
  37. package/lib/engine/engine_utils_screenshot.d.ts +1 -1
  38. package/lib/engine/engine_utils_screenshot.js +11 -2
  39. package/lib/engine/engine_utils_screenshot.js.map +1 -1
  40. package/lib/engine/webcomponents/needle-engine.d.ts +4 -1
  41. package/lib/engine/webcomponents/needle-engine.extras.js +3 -3
  42. package/lib/engine/webcomponents/needle-engine.extras.js.map +1 -1
  43. package/lib/engine/webcomponents/needle-engine.js +11 -21
  44. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  45. package/lib/engine-components/CameraUtils.js.map +1 -1
  46. package/lib/engine-components/postprocessing/Effects/Antialiasing.js +3 -1
  47. package/lib/engine-components/postprocessing/Effects/Antialiasing.js.map +1 -1
  48. package/lib/engine-components/postprocessing/Effects/BloomEffect.d.ts +2 -2
  49. package/lib/engine-components/postprocessing/Effects/BloomEffect.js +2 -2
  50. package/lib/engine-components/postprocessing/Effects/ColorAdjustments.d.ts +8 -0
  51. package/lib/engine-components/postprocessing/Effects/ColorAdjustments.js +27 -8
  52. package/lib/engine-components/postprocessing/Effects/ColorAdjustments.js.map +1 -1
  53. package/lib/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.js +1 -0
  54. package/lib/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.js.map +1 -1
  55. package/lib/engine-components/postprocessing/Effects/Sharpening.d.ts +1 -0
  56. package/lib/engine-components/postprocessing/Effects/Sharpening.js +4 -0
  57. package/lib/engine-components/postprocessing/Effects/Sharpening.js.map +1 -1
  58. package/lib/engine-components/postprocessing/Effects/Tonemapping.d.ts +2 -9
  59. package/lib/engine-components/postprocessing/Effects/Tonemapping.js +23 -71
  60. package/lib/engine-components/postprocessing/Effects/Tonemapping.js.map +1 -1
  61. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +13 -0
  62. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.js +52 -0
  63. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.js.map +1 -0
  64. package/lib/engine-components/postprocessing/PostProcessingEffect.d.ts +18 -0
  65. package/lib/engine-components/postprocessing/PostProcessingEffect.js +22 -3
  66. package/lib/engine-components/postprocessing/PostProcessingEffect.js.map +1 -1
  67. package/lib/engine-components/postprocessing/PostProcessingHandler.d.ts +20 -4
  68. package/lib/engine-components/postprocessing/PostProcessingHandler.js +231 -116
  69. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  70. package/lib/engine-components/postprocessing/Volume.d.ts +2 -1
  71. package/lib/engine-components/postprocessing/Volume.js +51 -33
  72. package/lib/engine-components/postprocessing/Volume.js.map +1 -1
  73. package/lib/engine-components/postprocessing/index.d.ts +1 -0
  74. package/lib/engine-components/postprocessing/index.js +1 -0
  75. package/lib/engine-components/postprocessing/index.js.map +1 -1
  76. package/lib/engine-components/postprocessing/utils.d.ts +44 -0
  77. package/lib/engine-components/postprocessing/utils.js +82 -0
  78. package/lib/engine-components/postprocessing/utils.js.map +1 -1
  79. package/package.json +2 -2
  80. package/src/engine/engine_context.ts +13 -4
  81. package/src/engine/engine_three_utils.ts +134 -58
  82. package/src/engine/engine_tonemapping.ts +23 -24
  83. package/src/engine/engine_utils.ts +2 -2
  84. package/src/engine/engine_utils_screenshot.ts +13 -3
  85. package/src/engine/webcomponents/needle-engine.extras.ts +3 -3
  86. package/src/engine/webcomponents/needle-engine.ts +14 -25
  87. package/src/engine-components/CameraUtils.ts +3 -3
  88. package/src/engine-components/postprocessing/Effects/Antialiasing.ts +3 -1
  89. package/src/engine-components/postprocessing/Effects/BloomEffect.ts +4 -4
  90. package/src/engine-components/postprocessing/Effects/ColorAdjustments.ts +24 -13
  91. package/src/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.ts +1 -0
  92. package/src/engine-components/postprocessing/Effects/Sharpening.ts +5 -0
  93. package/src/engine-components/postprocessing/Effects/Tonemapping.ts +26 -80
  94. package/src/engine-components/postprocessing/Effects/Tonemapping.utils.ts +60 -0
  95. package/src/engine-components/postprocessing/PostProcessingEffect.ts +23 -3
  96. package/src/engine-components/postprocessing/PostProcessingHandler.ts +267 -122
  97. package/src/engine-components/postprocessing/Volume.ts +54 -38
  98. package/src/engine-components/postprocessing/index.ts +2 -1
  99. package/src/engine-components/postprocessing/utils.ts +103 -2
  100. package/dist/generateMeshBVH.worker-Cdfpaq5W.js +0 -25
  101. package/dist/needle-engine.bundle-AOXFIsYk.umd.cjs +0 -1563
  102. package/dist/needle-engine.bundle-Dt52m2jf.min.js +0 -1563
@@ -1,24 +1,27 @@
1
1
  import type { Effect, EffectComposer, Pass, ToneMappingEffect as _TonemappingEffect } from "postprocessing";
2
- import { HalfFloatType, NoToneMapping, Camera as Camera3 } 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";
14
+ import { orderEffects, PostprocessingEffectData, PostProcessingEffectOrder } from "./utils.js";
12
15
 
13
16
  declare const NEEDLE_USE_POSTPROCESSING: boolean;
14
17
  globalThis["NEEDLE_USE_POSTPROCESSING"] = globalThis["NEEDLE_USE_POSTPROCESSING"] !== undefined ? globalThis["NEEDLE_USE_POSTPROCESSING"] : true;
15
18
 
16
19
 
17
20
  const debug = getParam("debugpost");
18
- const dontMergePasses = getParam("debugpostpasses");
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
@@ -27,7 +30,15 @@ export class PostProcessingHandler {
27
30
 
28
31
  private _composer: EffectComposer | null = null;
29
32
  private _lastVolumeComponents?: PostProcessingEffect[];
30
- private _effects: Array<Effect | Pass> = [];
33
+ private readonly _effects: Array<PostprocessingEffectData> = [];
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
+ }
31
42
 
32
43
  get isActive() {
33
44
  return this._isActive;
@@ -60,7 +71,7 @@ export class PostProcessingHandler {
60
71
  return this.onApply(this.context, components);
61
72
  }
62
73
 
63
- unapply() {
74
+ unapply(dispose: boolean = true) {
64
75
  if (debug) console.log("Unapplying postprocessing effects");
65
76
  this._isActive = false;
66
77
  if (this._lastVolumeComponents) {
@@ -73,21 +84,29 @@ export class PostProcessingHandler {
73
84
  const active = context[activeKey] as PostProcessingHandler | null;
74
85
  if (active === this) {
75
86
  delete context[activeKey];
87
+
88
+ // Restore the auto clear setting
89
+ if (typeof context.renderer[autoclearSetting] === "boolean") {
90
+ context.renderer.autoClear = context.renderer[autoclearSetting];
91
+ }
92
+ if (typeof context.renderer[previousToneMapping] === "number") {
93
+ context.renderer.toneMapping = context.renderer[previousToneMapping] as ToneMapping;
94
+ }
76
95
  }
96
+
97
+ this._composer?.removeAllPasses();
98
+ if (dispose) this._composer?.dispose();
99
+
77
100
  if (context.composer === this._composer) {
78
- context.composer?.dispose();
79
101
  context.composer = null;
80
102
  }
81
- if (typeof context.renderer[autoclearSetting] === "boolean") {
82
- context.renderer.autoClear = context.renderer[autoclearSetting];
83
- }
84
103
  }
85
104
 
86
105
  dispose() {
87
- this.unapply();
106
+ this.unapply(true);
88
107
 
89
108
  for (const effect of this._effects) {
90
- effect.dispose();
109
+ effect.effect.dispose();
91
110
  }
92
111
  this._effects.length = 0;
93
112
  this._composer = null;
@@ -105,6 +124,7 @@ export class PostProcessingHandler {
105
124
  // import("./Effects/Sharpening.effect")
106
125
  ]);
107
126
 
127
+
108
128
  // try {
109
129
  // internal_SetSharpeningEffectModule(modules[2]);
110
130
  // }
@@ -141,10 +161,24 @@ export class PostProcessingHandler {
141
161
  // apply or collect effects
142
162
  const res = component.apply(ctx);
143
163
  if (!res) continue;
164
+
165
+
144
166
  if (Array.isArray(res)) {
145
- this._effects.push(...res);
167
+ for (const effect of res) {
168
+ this._effects.push({
169
+ effect,
170
+ typeName: component.typeName,
171
+ priority: component.order
172
+ });
173
+ }
174
+ }
175
+ else {
176
+ this._effects.push({
177
+ effect: res,
178
+ typeName: component.typeName,
179
+ priority: component.order
180
+ });
146
181
  }
147
- else this._effects.push(res);
148
182
  }
149
183
  }
150
184
  else {
@@ -153,23 +187,41 @@ export class PostProcessingHandler {
153
187
  }
154
188
  }
155
189
 
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
190
  this.applyEffects(context);
165
191
  }
166
192
 
193
+ private _anyPassHasDepth = false;
194
+ private _anyPassHasNormal = false;
195
+ private _hasSmaaEffect = false;
196
+
197
+ get anyPassHasDepth() { return this._anyPassHasDepth; }
198
+ get anyPassHasNormal() { return this._anyPassHasNormal; }
199
+ get hasSmaaEffect() { return this._hasSmaaEffect; }
200
+
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
+
167
213
 
168
214
  /** Build composer passes */
169
215
  private applyEffects(context: Context) {
216
+ // Reset state
217
+ this._anyPassHasDepth = false;
218
+ this._anyPassHasNormal = false;
219
+ this._hasSmaaEffect = false;
220
+
221
+ if (this._effects.length <= 0) {
222
+ return;
223
+ }
170
224
 
171
- const effectsOrPasses = this._effects;
172
- if (effectsOrPasses.length <= 0) return;
173
225
  const camera = context.mainCameraComponent as Camera;
174
226
  const renderer = context.renderer;
175
227
  const scene = context.scene;
@@ -178,8 +230,31 @@ export class PostProcessingHandler {
178
230
  // Store the auto clear setting because the postprocessing composer just disables it
179
231
  // and when we disable postprocessing we want to restore the original setting
180
232
  // https://github.com/pmndrs/postprocessing/blob/271944b74b543a5b743a62803a167b60cc6bb4ee/src/core/EffectComposer.js#L230C12-L230C12
233
+ // First we need to get the previously set autoClear setting, if it exists
234
+ if (typeof renderer[autoclearSetting] === "boolean") {
235
+ renderer.autoClear = renderer[autoclearSetting];
236
+ }
181
237
  renderer[autoclearSetting] = renderer.autoClear;
182
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
+
183
258
  // create composer and set active on context
184
259
  if (!this._composer) {
185
260
  // const hdrRenderTarget = new WebGLRenderTarget(window.innerWidth, window.innerHeight, { type: HalfFloatType });
@@ -192,12 +267,14 @@ export class PostProcessingHandler {
192
267
  if (context.composer && context.composer !== this._composer) {
193
268
  console.warn("There's already an active EffectComposer in your scene: replacing it with a new one. This might cause unexpected behaviour. Make sure to only use one PostprocessingManager/Volume in your scene.");
194
269
  }
270
+
195
271
  context.composer = this._composer;
196
272
  const composer = context.composer;
197
273
  composer.setMainCamera(cam);
198
274
  composer.setRenderer(renderer);
199
275
  composer.setMainScene(scene);
200
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)
277
+ composer.multisampling = 0; // Disable multisampling by default
201
278
 
202
279
  for (const prev of composer.passes)
203
280
  prev.dispose();
@@ -205,81 +282,186 @@ export class PostProcessingHandler {
205
282
 
206
283
  // Render to screen pass
207
284
  const screenpass = new MODULES.POSTPROCESSING.MODULE.RenderPass(scene, cam);
208
- screenpass.name = "Render To Screen";
285
+ screenpass.name = "RenderPass";
209
286
  screenpass.mainScene = scene;
210
287
  composer.addPass(screenpass);
211
288
 
212
- const automaticEffectsOrdering = true;
213
- if (automaticEffectsOrdering && !dontMergePasses) {
214
- try {
215
- this.orderEffects();
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;
216
294
 
217
- const effects: Array<Effect> = [];
295
+ // screenPassRender.call(screenpass, renderer, inputBuffer, outputBuffer, deltaTime, stencilTest);
296
+ // return;
218
297
 
219
- for (const ef of effectsOrPasses) {
220
- if (ef instanceof MODULES.POSTPROCESSING.MODULE.Effect) {
221
- effects.push(ef as Effect);
222
- }
223
- else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass) {
224
- ef.renderToScreen = false;
225
- composer.addPass(ef as Pass);
226
- }
227
- else {
228
- // seems some effects are not correctly typed, but three can deal with them,
229
- // so we might need to just pass them through
230
- composer.addPass(ef);
231
- }
232
- }
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
+ }
233
303
 
234
- // create and apply uber pass
235
- if (effects.length > 0) {
236
- const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(cam, ...effects);
237
- pass.name = effects.map(e => e.name).join(", ");
238
- pass.mainScene = scene;
239
- pass.enabled = true;
240
- pass.renderToScreen = false;
241
- composer.addPass(pass);
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;
242
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}`);
243
338
  }
244
- catch (e) {
245
- console.error("Error while applying postprocessing effects", e);
246
- composer.removeAllPasses();
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
+
351
+ try {
352
+ orderEffects(this._effects);
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
+ }
247
368
  }
248
- }
249
- else {
250
- // we still want to sort passes, but we do not want to merge them for debugging
251
- if (automaticEffectsOrdering)
252
- this.orderEffects();
253
369
 
254
- for (const ef of effectsOrPasses) {
370
+ const effectsToMerge: Array<Effect> = [];
371
+ let hasConvolutionEffectInArray = false;
372
+
373
+ for (let i = 0; i < this._effects.length; i++) {
374
+ const entry = this._effects[i];
375
+ const ef = entry.effect;
376
+
377
+ if (ef instanceof MODULES.POSTPROCESSING.MODULE.SMAAEffect) {
378
+ this._hasSmaaEffect = true;
379
+ }
380
+ else if (ef instanceof MODULES.POSTPROCESSING.MODULE.NormalPass) {
381
+ this._anyPassHasNormal = true;
382
+ }
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
+
255
400
  if (ef instanceof MODULES.POSTPROCESSING.MODULE.Effect) {
256
- const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(cam, ef as Effect);
257
- pass.name = ef.name;
258
- composer.addPass(pass);
401
+ const attributes = ef.getAttributes();
402
+ const convolution = MODULES.POSTPROCESSING.MODULE.EffectAttribute.CONVOLUTION;
403
+ if (attributes & convolution) {
404
+ if (debug) console.log("[PostProcessing] Convolution effect: " + ef.name);
405
+ if (hasConvolutionEffectInArray) {
406
+ if (debug) console.log("[PostProcessing] Merging effects with convolution", effectsToMerge.map(e => e.name).join(", "));
407
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
408
+ }
409
+ hasConvolutionEffectInArray = true;
410
+ }
411
+ // Otherwise we can merge it
412
+ effectsToMerge.push(ef as Effect);
259
413
  }
260
414
  else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass) {
415
+ hasConvolutionEffectInArray = false;
416
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
261
417
  ef.renderToScreen = false;
262
418
  composer.addPass(ef as Pass);
263
419
  }
264
- else
420
+ else {
265
421
  // seems some effects are not correctly typed, but three can deal with them,
266
- // so we just pass them through
422
+ // so we might need to just pass them through
423
+ hasConvolutionEffectInArray = false;
424
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
267
425
  composer.addPass(ef);
426
+ }
427
+
268
428
  }
269
- }
270
429
 
430
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
431
+ }
432
+ catch (e) {
433
+ console.error("Error while applying postprocessing effects", e);
434
+ composer.passes.forEach(p => p.dispose());
435
+ composer.removeAllPasses();
436
+ }
271
437
 
272
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)
273
- for (let i = 0; i < composer.passes.length; i++) {
274
- const pass = composer.passes[i] as any;
275
- const isLast = i === composer.passes.length - 1;
276
- if (pass?.configuration !== undefined) {
277
- pass.configuration.gammaCorrection = isLast;
439
+ let foundEnabled = false;
440
+ for (let i = composer.passes.length - 1; i >= 0; i--) {
441
+ const pass = composer.passes[i];
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
+
454
+ if ((pass as any)?.configuration !== undefined) {
455
+ (pass as any).configuration.gammaCorrection = gammaCorrect;
278
456
  }
279
457
  else if ("autosetGamma" in pass) {
280
458
  // Some effects have a autosetGamma property that we can use to set the gamma correction
281
- pass.autosetGamma = isLast;
459
+ pass.autosetGamma = gammaCorrect;
282
460
  }
461
+
462
+
463
+ this._anyPassHasDepth ||= pass.needsDepthTexture;
464
+
283
465
  }
284
466
 
285
467
  // DEBUG LAND BELOW
@@ -287,56 +469,20 @@ export class PostProcessingHandler {
287
469
  if (debug) this._onCreateEffectsDebug(this._composer!, cam);
288
470
  }
289
471
 
290
- private orderEffects() {
291
- if (debug === "verbose") console.debug("Before ordering effects", [...this._effects]);
292
-
293
- // Order of effects for correct results.
294
- // Aligned with https://github.com/pmndrs/postprocessing/wiki/Effect-Merging#effect-execution-order
295
- // We can not put this into global scope because then the module might not yet be initialized
296
- effectsOrder ??= [
297
- MODULES.POSTPROCESSING.MODULE.NormalPass,
298
- MODULES.POSTPROCESSING.MODULE.DepthDownsamplingPass,
299
- MODULES.POSTPROCESSING.MODULE.SMAAEffect,
300
- MODULES.POSTPROCESSING.MODULE.SSAOEffect,
301
- MODULES.POSTPROCESSING_AO.MODULE.N8AOPostPass,
302
- MODULES.POSTPROCESSING.MODULE.TiltShiftEffect,
303
- MODULES.POSTPROCESSING.MODULE.DepthOfFieldEffect,
304
- MODULES.POSTPROCESSING.MODULE.ChromaticAberrationEffect,
305
- MODULES.POSTPROCESSING.MODULE.BloomEffect,
306
- MODULES.POSTPROCESSING.MODULE.SelectiveBloomEffect,
307
- MODULES.POSTPROCESSING.MODULE.VignetteEffect,
308
- MODULES.POSTPROCESSING.MODULE.PixelationEffect,
309
- MODULES.POSTPROCESSING.MODULE.ToneMappingEffect,
310
- MODULES.POSTPROCESSING.MODULE.HueSaturationEffect,
311
- MODULES.POSTPROCESSING.MODULE.BrightnessContrastEffect,
312
- // __SHARPENING_MODULE._SharpeningEffect,
313
- ];
314
-
315
-
316
- // TODO: enforce correct order of effects (e.g. DOF before Bloom)
317
- const effects = this._effects;
318
- effects.sort((a, b) => {
319
- // we use find index here because sometimes constructor names are prefixed with `_`
320
- // 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)
321
- const aidx = effectsOrder!.findIndex(e => a.constructor.name.endsWith(e.name));
322
- const bidx = effectsOrder!.findIndex(e => b.constructor.name.endsWith(e.name));
323
- // Unknown effects should be rendered first
324
- if (aidx < 0) {
325
- if (debug) console.warn("Unknown effect found: ", a.constructor.name);
326
- return -1;
327
- }
328
- else if (bidx < 0) {
329
- if (debug) console.warn("Unknown effect found: ", b.constructor.name);
330
- return 1;
331
- }
332
- // if (aidx < 0) return 1;
333
- // if (bidx < 0) return -1;
334
- return aidx - bidx;
335
- });
336
472
 
337
- if (debug === "verbose") console.debug("After ordering effects", [...this._effects]);
338
- }
339
473
 
474
+ /** Should be called before `composer.addPass()` to create an effect pass with all previously collected effects that can be merged up to that point */
475
+ private createPassForMergeableEffects(effects: Array<Effect>, composer: EffectComposer, camera: Camera3, scene: Scene) {
476
+ if (effects.length > 0) {
477
+ const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(camera, ...effects);
478
+ pass.name = effects.map(e => e.name).join(", ");
479
+ pass.mainScene = scene;
480
+ pass.enabled = true;
481
+ pass.renderToScreen = false;
482
+ composer.addPass(pass);
483
+ effects.length = 0; // Clear effects after adding them to the pass
484
+ }
485
+ }
340
486
 
341
487
 
342
488
 
@@ -412,4 +558,3 @@ export class PostProcessingHandler {
412
558
 
413
559
  }
414
560
 
415
- let effectsOrder: Array<Constructor<Effect | Pass>> | null = null;