@needle-tools/engine 4.6.0 → 4.6.1-next.20d54cd

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 (114) 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-BVrLqIyi.umd.cjs +1575 -0
  7. package/dist/{needle-engine.bundle-DsrPZ9gj.js → needle-engine.bundle-BonYthMO.js} +8850 -8678
  8. package/dist/needle-engine.bundle-CokaG-YG.min.js +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_assetdatabase.js +3 -1
  28. package/lib/engine/engine_assetdatabase.js.map +1 -1
  29. package/lib/engine/engine_context.d.ts +6 -3
  30. package/lib/engine/engine_context.js +20 -12
  31. package/lib/engine/engine_context.js.map +1 -1
  32. package/lib/engine/engine_three_utils.d.ts +17 -14
  33. package/lib/engine/engine_three_utils.js +106 -53
  34. package/lib/engine/engine_three_utils.js.map +1 -1
  35. package/lib/engine/engine_tonemapping.d.ts +4 -0
  36. package/lib/engine/engine_tonemapping.js +21 -18
  37. package/lib/engine/engine_tonemapping.js.map +1 -1
  38. package/lib/engine/engine_utils.js.map +1 -1
  39. package/lib/engine/engine_utils_screenshot.d.ts +1 -1
  40. package/lib/engine/engine_utils_screenshot.js +11 -2
  41. package/lib/engine/engine_utils_screenshot.js.map +1 -1
  42. package/lib/engine/webcomponents/needle-engine.d.ts +4 -1
  43. package/lib/engine/webcomponents/needle-engine.extras.js +3 -3
  44. package/lib/engine/webcomponents/needle-engine.extras.js.map +1 -1
  45. package/lib/engine/webcomponents/needle-engine.js +11 -21
  46. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  47. package/lib/engine-components/CameraUtils.js.map +1 -1
  48. package/lib/engine-components/ReflectionProbe.d.ts +2 -1
  49. package/lib/engine-components/ReflectionProbe.js +4 -1
  50. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  51. package/lib/engine-components/Renderer.js +9 -5
  52. package/lib/engine-components/Renderer.js.map +1 -1
  53. package/lib/engine-components/postprocessing/Effects/Antialiasing.js +3 -1
  54. package/lib/engine-components/postprocessing/Effects/Antialiasing.js.map +1 -1
  55. package/lib/engine-components/postprocessing/Effects/BloomEffect.d.ts +2 -2
  56. package/lib/engine-components/postprocessing/Effects/BloomEffect.js +5 -2
  57. package/lib/engine-components/postprocessing/Effects/BloomEffect.js.map +1 -1
  58. package/lib/engine-components/postprocessing/Effects/ColorAdjustments.d.ts +8 -0
  59. package/lib/engine-components/postprocessing/Effects/ColorAdjustments.js +27 -8
  60. package/lib/engine-components/postprocessing/Effects/ColorAdjustments.js.map +1 -1
  61. package/lib/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.js +1 -0
  62. package/lib/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.js.map +1 -1
  63. package/lib/engine-components/postprocessing/Effects/Sharpening.d.ts +1 -0
  64. package/lib/engine-components/postprocessing/Effects/Sharpening.js +4 -0
  65. package/lib/engine-components/postprocessing/Effects/Sharpening.js.map +1 -1
  66. package/lib/engine-components/postprocessing/Effects/Tonemapping.d.ts +2 -9
  67. package/lib/engine-components/postprocessing/Effects/Tonemapping.js +29 -73
  68. package/lib/engine-components/postprocessing/Effects/Tonemapping.js.map +1 -1
  69. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +13 -0
  70. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.js +52 -0
  71. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.js.map +1 -0
  72. package/lib/engine-components/postprocessing/PostProcessingEffect.d.ts +18 -0
  73. package/lib/engine-components/postprocessing/PostProcessingEffect.js +22 -3
  74. package/lib/engine-components/postprocessing/PostProcessingEffect.js.map +1 -1
  75. package/lib/engine-components/postprocessing/PostProcessingHandler.d.ts +21 -4
  76. package/lib/engine-components/postprocessing/PostProcessingHandler.js +237 -125
  77. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  78. package/lib/engine-components/postprocessing/Volume.d.ts +2 -1
  79. package/lib/engine-components/postprocessing/Volume.js +51 -33
  80. package/lib/engine-components/postprocessing/Volume.js.map +1 -1
  81. package/lib/engine-components/postprocessing/index.d.ts +1 -0
  82. package/lib/engine-components/postprocessing/index.js +1 -0
  83. package/lib/engine-components/postprocessing/index.js.map +1 -1
  84. package/lib/engine-components/postprocessing/utils.d.ts +43 -0
  85. package/lib/engine-components/postprocessing/utils.js +82 -0
  86. package/lib/engine-components/postprocessing/utils.js.map +1 -1
  87. package/package.json +2 -2
  88. package/plugins/vite/dependency-watcher.js +8 -2
  89. package/src/engine/engine_assetdatabase.ts +3 -1
  90. package/src/engine/engine_context.ts +27 -15
  91. package/src/engine/engine_three_utils.ts +134 -58
  92. package/src/engine/engine_tonemapping.ts +23 -24
  93. package/src/engine/engine_utils.ts +2 -2
  94. package/src/engine/engine_utils_screenshot.ts +13 -3
  95. package/src/engine/webcomponents/needle-engine.extras.ts +3 -3
  96. package/src/engine/webcomponents/needle-engine.ts +14 -25
  97. package/src/engine-components/CameraUtils.ts +3 -3
  98. package/src/engine-components/ReflectionProbe.ts +5 -1
  99. package/src/engine-components/Renderer.ts +10 -7
  100. package/src/engine-components/postprocessing/Effects/Antialiasing.ts +3 -1
  101. package/src/engine-components/postprocessing/Effects/BloomEffect.ts +6 -4
  102. package/src/engine-components/postprocessing/Effects/ColorAdjustments.ts +24 -13
  103. package/src/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.ts +1 -0
  104. package/src/engine-components/postprocessing/Effects/Sharpening.ts +5 -0
  105. package/src/engine-components/postprocessing/Effects/Tonemapping.ts +29 -81
  106. package/src/engine-components/postprocessing/Effects/Tonemapping.utils.ts +60 -0
  107. package/src/engine-components/postprocessing/PostProcessingEffect.ts +23 -3
  108. package/src/engine-components/postprocessing/PostProcessingHandler.ts +268 -132
  109. package/src/engine-components/postprocessing/Volume.ts +54 -38
  110. package/src/engine-components/postprocessing/index.ts +2 -1
  111. package/src/engine-components/postprocessing/utils.ts +102 -2
  112. package/dist/generateMeshBVH.worker-Cdfpaq5W.js +0 -25
  113. package/dist/needle-engine.bundle-BDO_N7gN.min.js +0 -1559
  114. package/dist/needle-engine.bundle-CoJqbtmp.umd.cjs +0 -1559
@@ -1,24 +1,26 @@
1
1
  import type { Effect, EffectComposer, Pass, ToneMappingEffect as _TonemappingEffect } from "postprocessing";
2
- import { HalfFloatType, NoToneMapping } 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 { 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,15 @@ 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> = [];
33
+
34
+ /**
35
+ * Returns true if a specific effect is currently active in the post processing stack.
36
+ */
37
+ getEffectIsActive(effect: Effect): boolean {
38
+ if (!effect) return false;
39
+ return this._isActive && this._effects.some(e => e.effect === effect);
40
+ }
31
41
 
32
42
  get isActive() {
33
43
  return this._isActive;
@@ -44,7 +54,7 @@ export class PostProcessingHandler {
44
54
  this.context = context;
45
55
  }
46
56
 
47
- apply(components: PostProcessingEffect[]) : Promise<void> {
57
+ apply(components: PostProcessingEffect[]): Promise<void> {
48
58
  if ("env" in import.meta && import.meta.env.VITE_NEEDLE_USE_POSTPROCESSING === "false") {
49
59
  if (debug) console.warn("Postprocessing is disabled via vite env setting");
50
60
  else console.debug("Postprocessing is disabled via vite env setting");
@@ -60,7 +70,7 @@ export class PostProcessingHandler {
60
70
  return this.onApply(this.context, components);
61
71
  }
62
72
 
63
- unapply() {
73
+ unapply(dispose: boolean = true) {
64
74
  if (debug) console.log("Unapplying postprocessing effects");
65
75
  this._isActive = false;
66
76
  if (this._lastVolumeComponents) {
@@ -73,21 +83,29 @@ export class PostProcessingHandler {
73
83
  const active = context[activeKey] as PostProcessingHandler | null;
74
84
  if (active === this) {
75
85
  delete context[activeKey];
86
+
87
+ // Restore the auto clear setting
88
+ if (typeof context.renderer[autoclearSetting] === "boolean") {
89
+ context.renderer.autoClear = context.renderer[autoclearSetting];
90
+ }
91
+ if (typeof context.renderer[previousToneMapping] === "number") {
92
+ context.renderer.toneMapping = context.renderer[previousToneMapping] as ToneMapping;
93
+ }
76
94
  }
95
+
96
+ this._composer?.removeAllPasses();
97
+ if (dispose) this._composer?.dispose();
98
+
77
99
  if (context.composer === this._composer) {
78
- context.composer?.dispose();
79
100
  context.composer = null;
80
101
  }
81
- if (typeof context.renderer[autoclearSetting] === "boolean") {
82
- context.renderer.autoClear = context.renderer[autoclearSetting];
83
- }
84
102
  }
85
103
 
86
104
  dispose() {
87
- this.unapply();
105
+ this.unapply(true);
88
106
 
89
107
  for (const effect of this._effects) {
90
- effect.dispose();
108
+ effect.effect.dispose();
91
109
  }
92
110
  this._effects.length = 0;
93
111
  this._composer = null;
@@ -105,6 +123,7 @@ export class PostProcessingHandler {
105
123
  // import("./Effects/Sharpening.effect")
106
124
  ]);
107
125
 
126
+
108
127
  // try {
109
128
  // internal_SetSharpeningEffectModule(modules[2]);
110
129
  // }
@@ -141,10 +160,22 @@ export class PostProcessingHandler {
141
160
  // apply or collect effects
142
161
  const res = component.apply(ctx);
143
162
  if (!res) continue;
163
+
164
+
144
165
  if (Array.isArray(res)) {
145
- this._effects.push(...res);
166
+ for (const effect of res) {
167
+ this._effects.push({
168
+ effect,
169
+ priority: component.order
170
+ });
171
+ }
172
+ }
173
+ else {
174
+ this._effects.push({
175
+ effect: res,
176
+ priority: component.order
177
+ });
146
178
  }
147
- else this._effects.push(res);
148
179
  }
149
180
  }
150
181
  else {
@@ -153,24 +184,40 @@ export class PostProcessingHandler {
153
184
  }
154
185
  }
155
186
 
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
187
  this.applyEffects(context);
165
188
  }
166
189
 
190
+ private _anyPassHasDepth = false;
191
+ private _anyPassHasNormal = false;
192
+ private _hasSmaaEffect = false;
193
+
194
+ get anyPassHasDepth() { return this._anyPassHasDepth; }
195
+ get anyPassHasNormal() { return this._anyPassHasNormal; }
196
+ get hasSmaaEffect() { return this._hasSmaaEffect; }
197
+
198
+
199
+
200
+ private _customInputBuffer: WebGLRenderTarget<Texture> | null = null;
201
+ private _customInputBufferId = 0;
202
+ private _multisampling: number = 0;
203
+ set multisampling(value: number) {
204
+ this._multisampling = value;
205
+ }
206
+ get multisampling() {
207
+ return this._multisampling;
208
+ }
209
+
167
210
 
168
211
  /** Build composer passes */
169
212
  private applyEffects(context: Context) {
213
+ // Reset state
214
+ this._anyPassHasDepth = false;
215
+ this._anyPassHasNormal = false;
216
+ this._hasSmaaEffect = false;
170
217
 
171
- const effectsOrPasses = this._effects;
172
-
173
- if (effectsOrPasses.length <= 0) return;
218
+ if (this._effects.length <= 0) {
219
+ return;
220
+ }
174
221
 
175
222
  const camera = context.mainCameraComponent as Camera;
176
223
  const renderer = context.renderer;
@@ -180,8 +227,27 @@ export class PostProcessingHandler {
180
227
  // Store the auto clear setting because the postprocessing composer just disables it
181
228
  // and when we disable postprocessing we want to restore the original setting
182
229
  // https://github.com/pmndrs/postprocessing/blob/271944b74b543a5b743a62803a167b60cc6bb4ee/src/core/EffectComposer.js#L230C12-L230C12
230
+ // First we need to get the previously set autoClear setting, if it exists
231
+ if (typeof renderer[autoclearSetting] === "boolean") {
232
+ renderer.autoClear = renderer[autoclearSetting];
233
+ }
183
234
  renderer[autoclearSetting] = renderer.autoClear;
184
235
 
236
+ if (typeof renderer[previousToneMapping] === "number") {
237
+ renderer.toneMapping = renderer[previousToneMapping] as ToneMapping;
238
+ }
239
+ renderer[previousToneMapping] = renderer.toneMapping;
240
+
241
+ // Ensure that we have a tonemapping effect if the renderer is set to use a tone mapping
242
+ if (renderer.toneMapping != NoToneMapping) {
243
+ if (!this._effects.find(e => e instanceof MODULES.POSTPROCESSING.MODULE.ToneMappingEffect)) {
244
+ const tonemapping = new MODULES.POSTPROCESSING.MODULE.ToneMappingEffect();
245
+ tonemapping.name = `ToneMapping (${renderer.toneMapping})`;
246
+ tonemapping.mode = threeToneMappingToEffectMode(renderer.toneMapping);
247
+ this._effects.push({ effect: tonemapping, priority: PostProcessingEffectOrder.ToneMapping });
248
+ }
249
+ }
250
+
185
251
  // create composer and set active on context
186
252
  if (!this._composer) {
187
253
  // const hdrRenderTarget = new WebGLRenderTarget(window.innerWidth, window.innerHeight, { type: HalfFloatType });
@@ -194,11 +260,14 @@ export class PostProcessingHandler {
194
260
  if (context.composer && context.composer !== this._composer) {
195
261
  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.");
196
262
  }
263
+
197
264
  context.composer = this._composer;
198
265
  const composer = context.composer;
199
266
  composer.setMainCamera(cam);
200
267
  composer.setRenderer(renderer);
201
268
  composer.setMainScene(scene);
269
+ 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)
270
+ composer.multisampling = 0; // Disable multisampling by default
202
271
 
203
272
  for (const prev of composer.passes)
204
273
  prev.dispose();
@@ -206,68 +275,197 @@ export class PostProcessingHandler {
206
275
 
207
276
  // Render to screen pass
208
277
  const screenpass = new MODULES.POSTPROCESSING.MODULE.RenderPass(scene, cam);
209
- screenpass.name = "Render To Screen";
278
+ screenpass.name = "RenderPass";
210
279
  screenpass.mainScene = scene;
211
280
  composer.addPass(screenpass);
212
281
 
213
- const automaticEffectsOrdering = true;
214
- if (automaticEffectsOrdering && !dontMergePasses) {
215
- try {
216
- this.orderEffects();
282
+ const screenPassRender = screenpass.render;
283
+ this._customInputBuffer?.dispose();
284
+ this._customInputBuffer = null;
285
+ screenpass.render = (renderer, inputBuffer, outputBuffer, deltaTime, stencilTest) => {
286
+ if (!inputBuffer) return;
217
287
 
218
- const effects: Array<Effect> = [];
288
+ // screenPassRender.call(screenpass, renderer, inputBuffer, outputBuffer, deltaTime, stencilTest);
289
+ // return;
219
290
 
220
- for (const ef of effectsOrPasses) {
221
- if (ef instanceof MODULES.POSTPROCESSING.MODULE.Effect)
222
- effects.push(ef as Effect);
223
- else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass) {
224
- composer.addPass(ef as Pass);
225
- }
226
- else {
227
- // seems some effects are not correctly typed, but three can deal with them,
228
- // so we might need to just pass them through
229
- composer.addPass(ef);
230
- }
231
- }
291
+ // 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
292
+ inputBuffer.samples = 0;
293
+ if (outputBuffer) {
294
+ outputBuffer.samples = 0;
295
+ }
232
296
 
233
- // create and apply uber pass
234
- if (effects.length > 0) {
235
- const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(cam, ...effects);
236
- pass.name = effects.map(e => e.name).join(" ");
237
- pass.mainScene = scene;
238
- pass.enabled = true;
239
- composer.addPass(pass);
297
+ // Make sure the input buffer is a WebGLRenderTarget with the correct settings
298
+ if (!this._customInputBuffer
299
+ || this._customInputBuffer.width !== inputBuffer.width
300
+ || this._customInputBuffer.height !== inputBuffer.height
301
+ || this._customInputBuffer.samples !== this._multisampling
302
+ || this._customInputBuffer.texture.format !== inputBuffer.texture.format
303
+ || this._customInputBuffer.texture.type !== HalfFloatType
304
+ ) {
305
+ this._customInputBuffer?.dispose();
306
+
307
+ this._customInputBuffer = new WebGLRenderTarget(inputBuffer.width, inputBuffer.height, {
308
+ format: inputBuffer.texture.format,
309
+ type: HalfFloatType,
310
+ depthBuffer: inputBuffer.depthBuffer,
311
+ depthTexture: inputBuffer.depthTexture
312
+ ? new DepthTexture(inputBuffer.width, inputBuffer.height)
313
+ : undefined,
314
+ stencilBuffer: inputBuffer.stencilBuffer,
315
+ samples: Math.max(0, this._multisampling),
316
+ minFilter: inputBuffer.texture.minFilter ?? LinearFilter,
317
+ magFilter: inputBuffer.texture.magFilter ?? LinearFilter,
318
+ generateMipmaps: inputBuffer.texture.generateMipmaps,
319
+ });
320
+ this._customInputBufferId++;
321
+ this._customInputBuffer.texture.name = `CustomInputBuffer-${this._customInputBufferId}`;
322
+ if (this._customInputBuffer.depthTexture && inputBuffer.depthTexture) {
323
+ this._customInputBuffer.depthTexture.format = inputBuffer.depthTexture.format;
324
+ this._customInputBuffer.depthTexture.type = inputBuffer.depthTexture.type;
240
325
  }
326
+ // https://github.com/pmndrs/postprocessing/blob/ad338df710ef41fee4e5d10ad2c2c299030d46ef/src/core/EffectComposer.js#L366
327
+ if (this._customInputBuffer.samples > 0)
328
+ (this._customInputBuffer as any).ignoreDepthForMultisampleCopy = false;
329
+
330
+ if (debug) console.warn(`[PostProcessing] Input buffer created with size ${this._customInputBuffer.width}x${this._customInputBuffer.height} and samples ${this._customInputBuffer.samples}`);
241
331
  }
242
- catch (e) {
243
- console.error("Error while applying postprocessing effects", e);
244
- composer.removeAllPasses();
332
+ // Calling the original render function with the input buffer
333
+ screenPassRender.call(screenpass, renderer, this._customInputBuffer, outputBuffer, deltaTime, stencilTest);
334
+ // Blit the resulting buffer to the input buffer passed in by the composer so it's used for subsequent effects
335
+ Graphics.blit(this._customInputBuffer.texture, inputBuffer, {
336
+ renderer,
337
+ depthTexture: this._customInputBuffer.depthTexture,
338
+ depthWrite: true,
339
+ depthTest: true,
340
+ });
341
+ };
342
+
343
+
344
+ try {
345
+ orderEffects(this._effects);
346
+
347
+ let foundTonemappingEffect = false;
348
+ let activeTonemappingEffect: _TonemappingEffect | null = null;
349
+ for (let i = this._effects.length - 1; i >= 0; i--) {
350
+ const ef = this._effects[i].effect;
351
+ if (ef instanceof MODULES.POSTPROCESSING.MODULE.ToneMappingEffect) {
352
+ // If we already have a tonemapping effect, we can skip this one
353
+ if (foundTonemappingEffect) {
354
+ 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.`);
355
+ this._effects.splice(i, 1);
356
+ continue;
357
+ }
358
+ activeTonemappingEffect = ef;
359
+ foundTonemappingEffect = true;
360
+ }
245
361
  }
246
- }
247
- else {
248
- // we still want to sort passes, but we do not want to merge them for debugging
249
- if (automaticEffectsOrdering)
250
- this.orderEffects();
251
362
 
252
- for (const ef of effectsOrPasses) {
363
+ const effectsToMerge: Array<Effect> = [];
364
+ let hasConvolutionEffectInArray = false;
365
+
366
+ for (const entry of this._effects) {
367
+ const ef = entry.effect;
368
+
369
+ if (ef instanceof MODULES.POSTPROCESSING.MODULE.SMAAEffect) {
370
+ this._hasSmaaEffect = true;
371
+ }
372
+ else if (ef instanceof MODULES.POSTPROCESSING.MODULE.NormalPass) {
373
+ this._anyPassHasNormal = true;
374
+ }
375
+
376
+
377
+ // There can be only one tonemapping effect in the scene, so we can skip all others
378
+ if (ef instanceof MODULES.POSTPROCESSING.MODULE.ToneMappingEffect && activeTonemappingEffect !== ef) {
379
+ // If we already have a tonemapping effect, we can skip this one
380
+ continue;
381
+ }
382
+
383
+ // We can also not merge multiple effects of the same type in one pass
384
+ // So we first need to create a new pass with whatever effects we have so far
385
+ // 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
386
+ // const constructor = ef.constructor as Constructor<Effect | Pass>;
387
+ // if(effectsToMerge.find(e => e.constructor === constructor)) {
388
+ // // this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
389
+ // }
390
+
253
391
  if (ef instanceof MODULES.POSTPROCESSING.MODULE.Effect) {
254
- const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(cam, ef as Effect);
255
- pass.name = ef.name;
256
- composer.addPass(pass);
392
+ const attributes = ef.getAttributes();
393
+ const convolution = MODULES.POSTPROCESSING.MODULE.EffectAttribute.CONVOLUTION;
394
+ if (attributes & convolution) {
395
+ if (debug) console.log("[PostProcessing] Convolution effect: " + ef.name);
396
+ if (hasConvolutionEffectInArray) {
397
+ if (debug) console.log("[PostProcessing] Merging effects with convolution", effectsToMerge.map(e => e.name).join(", "));
398
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
399
+ }
400
+ hasConvolutionEffectInArray = true;
401
+ }
402
+ // Otherwise we can merge it
403
+ effectsToMerge.push(ef as Effect);
257
404
  }
258
- else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass)
405
+ else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass) {
406
+ hasConvolutionEffectInArray = false;
407
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
408
+ ef.renderToScreen = false;
259
409
  composer.addPass(ef as Pass);
260
- else
410
+ }
411
+ else {
261
412
  // seems some effects are not correctly typed, but three can deal with them,
262
- // so we just pass them through
413
+ // so we might need to just pass them through
414
+ hasConvolutionEffectInArray = false;
415
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
263
416
  composer.addPass(ef);
417
+ }
418
+ }
419
+
420
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
421
+ }
422
+ catch (e) {
423
+ console.error("Error while applying postprocessing effects", e);
424
+ composer.removeAllPasses();
425
+ }
426
+
427
+ // 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)
428
+ for (let i = 0; i < composer.passes.length; i++) {
429
+ const pass = composer.passes[i];
430
+ const isLast = i === composer.passes.length - 1;
431
+ if ((pass as any)?.configuration !== undefined) {
432
+ (pass as any).configuration.gammaCorrection = isLast;
264
433
  }
434
+ else if ("autosetGamma" in pass) {
435
+ // Some effects have a autosetGamma property that we can use to set the gamma correction
436
+ pass.autosetGamma = isLast;
437
+ }
438
+
439
+ this._anyPassHasDepth ||= pass.needsDepthTexture;
265
440
  }
266
441
 
267
- if (debug) {
442
+ // DEBUG LAND BELOW
443
+ if (debug) console.log("[PostProcessing] Passes →", [...composer.passes], "\n---------------------------------\n• " + composer.passes.map(i => i.name).join("\n• ") + "\n");
444
+ if (debug) this._onCreateEffectsDebug(this._composer!, cam);
445
+ }
446
+
447
+
268
448
 
269
- console.log("[PostProcessing] Passes →", composer.passes);
449
+ /** Should be called before `composer.addPass()` to create an effect pass with all previously collected effects that can be merged up to that point */
450
+ private createPassForMergeableEffects(effects: Array<Effect>, composer: EffectComposer, camera: Camera3, scene: Scene) {
451
+ if (effects.length > 0) {
452
+ const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(camera, ...effects);
453
+ pass.name = effects.map(e => e.name).join(", ");
454
+ pass.mainScene = scene;
455
+ pass.enabled = true;
456
+ pass.renderToScreen = false;
457
+ composer.addPass(pass);
458
+ effects.length = 0; // Clear effects after adding them to the pass
459
+ }
460
+ }
461
+
462
+
463
+
464
+ private _menuEntry: HTMLSelectElement | null = null;
465
+ private _passIndices: number[] | null = null;
270
466
 
467
+ private _onCreateEffectsDebug(composer: EffectComposer, cam: Camera3) {
468
+ if (debug === "passes") {
271
469
  // DepthEffect for debugging purposes, disabled by default, can be selected in the debug pass select
272
470
  const depthEffect = new MODULES.POSTPROCESSING.MODULE.DepthEffect({
273
471
  blendFunction: MODULES.POSTPROCESSING.MODULE.BlendFunction.NORMAL,
@@ -303,7 +501,7 @@ export class PostProcessingHandler {
303
501
  if (menu && this._passIndices === null) {
304
502
  if (this._menuEntry)
305
503
  this._menuEntry.remove();
306
-
504
+
307
505
  const select = document.createElement("select");
308
506
  select.multiple = true;
309
507
  const defaultOpt = document.createElement("option");
@@ -327,73 +525,11 @@ export class PostProcessingHandler {
327
525
  else {
328
526
  this._passIndices = indices;
329
527
  }
330
-
331
- this.applyEffects(context);
528
+ this.applyEffects(this.context);
332
529
  });
333
530
  }
334
531
  }
335
532
  }
336
533
 
337
- private _menuEntry: HTMLSelectElement | null = null;
338
- private _passIndices: number[] | null = null;
339
-
340
- private orderEffects() {
341
- if (debug) console.log("Before ordering effects", [...this._effects]);
342
-
343
-
344
-
345
- // Order of effects for correct results.
346
- // Aligned with https://github.com/pmndrs/postprocessing/wiki/Effect-Merging#effect-execution-order
347
- // We can not put this into global scope because then the module might not yet be initialized
348
- effectsOrder ??= [
349
- MODULES.POSTPROCESSING.MODULE.NormalPass,
350
- MODULES.POSTPROCESSING.MODULE.DepthDownsamplingPass,
351
- MODULES.POSTPROCESSING.MODULE.SMAAEffect,
352
- MODULES.POSTPROCESSING.MODULE.SSAOEffect,
353
- MODULES.POSTPROCESSING_AO.MODULE.N8AOPostPass,
354
- MODULES.POSTPROCESSING.MODULE.TiltShiftEffect,
355
- MODULES.POSTPROCESSING.MODULE.DepthOfFieldEffect,
356
- MODULES.POSTPROCESSING.MODULE.ChromaticAberrationEffect,
357
- MODULES.POSTPROCESSING.MODULE.BloomEffect,
358
- MODULES.POSTPROCESSING.MODULE.SelectiveBloomEffect,
359
- MODULES.POSTPROCESSING.MODULE.VignetteEffect,
360
- MODULES.POSTPROCESSING.MODULE.PixelationEffect,
361
- MODULES.POSTPROCESSING.MODULE.ToneMappingEffect,
362
- MODULES.POSTPROCESSING.MODULE.HueSaturationEffect,
363
- MODULES.POSTPROCESSING.MODULE.BrightnessContrastEffect,
364
- // __SHARPENING_MODULE._SharpeningEffect,
365
- ];
366
-
367
-
368
- // TODO: enforce correct order of effects (e.g. DOF before Bloom)
369
- const effects = this._effects;
370
- effects.sort((a, b) => {
371
- // we use find index here because sometimes constructor names are prefixed with `_`
372
- // 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)
373
- const aidx = effectsOrder!.findIndex(e => a.constructor.name.endsWith(e.name));
374
- const bidx = effectsOrder!.findIndex(e => b.constructor.name.endsWith(e.name));
375
- // Unknown effects should be rendered first
376
- if (aidx < 0) {
377
- if (debug) console.warn("Unknown effect found: ", a.constructor.name);
378
- return -1;
379
- }
380
- else if (bidx < 0) {
381
- if (debug) console.warn("Unknown effect found: ", b.constructor.name);
382
- return 1;
383
- }
384
- if (aidx < 0) return 1;
385
- if (bidx < 0) return -1;
386
- return aidx - bidx;
387
- });
388
- if (debug) console.log("After ordering effects", [...this._effects]);
389
- for (let i = 0; i < effects.length; i++) {
390
- const effect = effects[i] as any;
391
- if (effect?.configuration?.gammaCorrection !== undefined) {
392
- const isLast = i === effects.length - 1;
393
- effect.configuration.gammaCorrection = isLast;
394
- }
395
- }
396
- }
397
534
  }
398
535
 
399
- let effectsOrder: Array<Constructor<Effect | Pass>> | null = null;