@needle-tools/engine 4.6.0 → 4.6.1-next.cd98b8c
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.
- package/CHANGELOG.md +6 -0
- package/dist/generateMeshBVH.worker-BaNp_Xtp.js +25 -0
- package/dist/{gltf-progressive-Bm9eEfgu.min.js → gltf-progressive-Bl4okF1b.min.js} +1 -1
- package/dist/{gltf-progressive-GjIqwSG3.js → gltf-progressive-DSpdn0QT.js} +2 -2
- package/dist/{gltf-progressive-Dn6o99rH.umd.cjs → gltf-progressive-P8b8a0qY.umd.cjs} +1 -1
- package/dist/{needle-engine.bundle-DsrPZ9gj.js → needle-engine.bundle-BOiTKp9l.js} +8027 -7961
- package/dist/{needle-engine.bundle-CoJqbtmp.umd.cjs → needle-engine.bundle-ClaRVK0x.umd.cjs} +152 -148
- package/dist/needle-engine.bundle-Dq1fvtLi.min.js +1563 -0
- package/dist/needle-engine.js +343 -342
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-CRQa6Qxn.umd.cjs → postprocessing-CjW23fio.umd.cjs} +18 -18
- package/dist/{postprocessing-D6W1EyZ-.js → postprocessing-DYLNOL3W.js} +4 -3
- package/dist/{postprocessing-DF8AlRgW.min.js → postprocessing-xYQWCHFu.min.js} +26 -26
- package/dist/{three-DMrv-4ar.umd.cjs → three-B_hneGZr.umd.cjs} +4 -4
- package/dist/{three-Bz6X1mrw.js → three-DrqIzZTH.js} +4198 -4198
- package/dist/{three-Boa-jOq-.min.js → three-DuDKwKB8.min.js} +33 -33
- package/dist/{three-examples-GggCDHv0.js → three-examples-B50TT3Iu.js} +5 -5
- package/dist/{three-examples-DuVhxqft.min.js → three-examples-DaDLBuy6.min.js} +14 -14
- package/dist/{three-examples-C7ryg8vN.umd.cjs → three-examples-X3OadjXB.umd.cjs} +3 -3
- package/dist/{three-mesh-ui-CY6Izc7C.min.js → three-mesh-ui-B3p3gyUz.min.js} +1 -1
- package/dist/{three-mesh-ui-CwlN0FUC.umd.cjs → three-mesh-ui-CQiIQIlA.umd.cjs} +1 -1
- package/dist/{three-mesh-ui-CLNOfsWn.js → three-mesh-ui-CxuWt7m-.js} +1 -1
- package/dist/{vendor-zxXa3Dmr.min.js → vendor-BlSxe9JJ.min.js} +3 -3
- package/dist/{vendor-BSD1RQIh.js → vendor-BmYIgaS1.js} +3 -3
- package/dist/{vendor-DHr4aqIZ.umd.cjs → vendor-Cavtu3CP.umd.cjs} +3 -3
- package/lib/engine/engine_assetdatabase.js +3 -1
- package/lib/engine/engine_assetdatabase.js.map +1 -1
- package/lib/engine/engine_context.d.ts +2 -2
- package/lib/engine/engine_context.js +11 -10
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_utils_screenshot.d.ts +1 -1
- package/lib/engine/engine_utils_screenshot.js +11 -2
- package/lib/engine/engine_utils_screenshot.js.map +1 -1
- package/lib/engine-components/ReflectionProbe.d.ts +2 -1
- package/lib/engine-components/ReflectionProbe.js +4 -1
- package/lib/engine-components/ReflectionProbe.js.map +1 -1
- package/lib/engine-components/Renderer.js +9 -5
- package/lib/engine-components/Renderer.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Antialiasing.js +2 -1
- package/lib/engine-components/postprocessing/Effects/Antialiasing.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/BloomEffect.d.ts +1 -1
- package/lib/engine-components/postprocessing/Effects/BloomEffect.js +3 -0
- package/lib/engine-components/postprocessing/Effects/BloomEffect.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.js +10 -6
- package/lib/engine-components/postprocessing/Effects/Tonemapping.js.map +1 -1
- package/lib/engine-components/postprocessing/PostProcessingEffect.d.ts +18 -0
- package/lib/engine-components/postprocessing/PostProcessingEffect.js +20 -1
- package/lib/engine-components/postprocessing/PostProcessingEffect.js.map +1 -1
- package/lib/engine-components/postprocessing/PostProcessingHandler.d.ts +11 -3
- package/lib/engine-components/postprocessing/PostProcessingHandler.js +122 -118
- package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
- package/lib/engine-components/postprocessing/Volume.d.ts +2 -1
- package/lib/engine-components/postprocessing/Volume.js +16 -6
- package/lib/engine-components/postprocessing/Volume.js.map +1 -1
- package/lib/engine-components/postprocessing/index.d.ts +1 -0
- package/lib/engine-components/postprocessing/index.js +1 -0
- package/lib/engine-components/postprocessing/index.js.map +1 -1
- package/lib/engine-components/postprocessing/utils.d.ts +38 -0
- package/lib/engine-components/postprocessing/utils.js +98 -0
- package/lib/engine-components/postprocessing/utils.js.map +1 -1
- package/package.json +2 -2
- package/plugins/vite/dependency-watcher.js +8 -2
- package/src/engine/engine_assetdatabase.ts +3 -1
- package/src/engine/engine_context.ts +14 -11
- package/src/engine/engine_utils_screenshot.ts +13 -3
- package/src/engine-components/ReflectionProbe.ts +5 -1
- package/src/engine-components/Renderer.ts +10 -7
- package/src/engine-components/postprocessing/Effects/Antialiasing.ts +2 -1
- package/src/engine-components/postprocessing/Effects/BloomEffect.ts +4 -2
- package/src/engine-components/postprocessing/Effects/Tonemapping.ts +9 -7
- package/src/engine-components/postprocessing/PostProcessingEffect.ts +21 -1
- package/src/engine-components/postprocessing/PostProcessingHandler.ts +142 -124
- package/src/engine-components/postprocessing/Volume.ts +18 -9
- package/src/engine-components/postprocessing/index.ts +1 -0
- package/src/engine-components/postprocessing/utils.ts +118 -2
- package/dist/generateMeshBVH.worker-Cdfpaq5W.js +0 -25
- package/dist/needle-engine.bundle-BDO_N7gN.min.js +0 -1559
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Effect, EffectComposer, Pass, ToneMappingEffect as _TonemappingEffect } from "postprocessing";
|
|
2
|
-
import { HalfFloatType, NoToneMapping } from "three";
|
|
2
|
+
import { Camera as Camera3, HalfFloatType, NoToneMapping, Scene } from "three";
|
|
3
3
|
|
|
4
4
|
import { showBalloonWarning } from "../../engine/debug/index.js";
|
|
5
5
|
// import { internal_SetSharpeningEffectModule } from "./Effects/Sharpening.js";
|
|
@@ -9,13 +9,13 @@ import type { Constructor } from "../../engine/engine_types.js";
|
|
|
9
9
|
import { DeviceUtilities, getParam } from "../../engine/engine_utils.js";
|
|
10
10
|
import { Camera } from "../Camera.js";
|
|
11
11
|
import { PostProcessingEffect, PostProcessingEffectContext } from "./PostProcessingEffect.js";
|
|
12
|
+
import { orderEffects, PostprocessingEffectData } from "./utils.js";
|
|
12
13
|
|
|
13
14
|
declare const NEEDLE_USE_POSTPROCESSING: boolean;
|
|
14
15
|
globalThis["NEEDLE_USE_POSTPROCESSING"] = globalThis["NEEDLE_USE_POSTPROCESSING"] !== undefined ? globalThis["NEEDLE_USE_POSTPROCESSING"] : true;
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
const debug = getParam("debugpost");
|
|
18
|
-
const dontMergePasses = getParam("debugpostpasses");
|
|
19
19
|
|
|
20
20
|
const activeKey = Symbol("needle:postprocessing-handler");
|
|
21
21
|
const autoclearSetting = Symbol("needle:previous-autoclear-state");
|
|
@@ -27,7 +27,7 @@ export class PostProcessingHandler {
|
|
|
27
27
|
|
|
28
28
|
private _composer: EffectComposer | null = null;
|
|
29
29
|
private _lastVolumeComponents?: PostProcessingEffect[];
|
|
30
|
-
private _effects: Array<
|
|
30
|
+
private readonly _effects: Array<PostprocessingEffectData> = [];
|
|
31
31
|
|
|
32
32
|
get isActive() {
|
|
33
33
|
return this._isActive;
|
|
@@ -44,7 +44,7 @@ export class PostProcessingHandler {
|
|
|
44
44
|
this.context = context;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
apply(components: PostProcessingEffect[])
|
|
47
|
+
apply(components: PostProcessingEffect[]): Promise<void> {
|
|
48
48
|
if ("env" in import.meta && import.meta.env.VITE_NEEDLE_USE_POSTPROCESSING === "false") {
|
|
49
49
|
if (debug) console.warn("Postprocessing is disabled via vite env setting");
|
|
50
50
|
else console.debug("Postprocessing is disabled via vite env setting");
|
|
@@ -60,7 +60,7 @@ export class PostProcessingHandler {
|
|
|
60
60
|
return this.onApply(this.context, components);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
unapply() {
|
|
63
|
+
unapply(dispose: boolean = true) {
|
|
64
64
|
if (debug) console.log("Unapplying postprocessing effects");
|
|
65
65
|
this._isActive = false;
|
|
66
66
|
if (this._lastVolumeComponents) {
|
|
@@ -73,21 +73,26 @@ export class PostProcessingHandler {
|
|
|
73
73
|
const active = context[activeKey] as PostProcessingHandler | null;
|
|
74
74
|
if (active === this) {
|
|
75
75
|
delete context[activeKey];
|
|
76
|
+
|
|
77
|
+
// Restore the auto clear setting
|
|
78
|
+
if (typeof context.renderer[autoclearSetting] === "boolean") {
|
|
79
|
+
context.renderer.autoClear = context.renderer[autoclearSetting];
|
|
80
|
+
}
|
|
76
81
|
}
|
|
82
|
+
|
|
83
|
+
this._composer?.removeAllPasses();
|
|
84
|
+
if (dispose) this._composer?.dispose();
|
|
85
|
+
|
|
77
86
|
if (context.composer === this._composer) {
|
|
78
|
-
context.composer?.dispose();
|
|
79
87
|
context.composer = null;
|
|
80
88
|
}
|
|
81
|
-
if (typeof context.renderer[autoclearSetting] === "boolean") {
|
|
82
|
-
context.renderer.autoClear = context.renderer[autoclearSetting];
|
|
83
|
-
}
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
dispose() {
|
|
87
|
-
this.unapply();
|
|
92
|
+
this.unapply(true);
|
|
88
93
|
|
|
89
94
|
for (const effect of this._effects) {
|
|
90
|
-
effect.dispose();
|
|
95
|
+
effect.effect.dispose();
|
|
91
96
|
}
|
|
92
97
|
this._effects.length = 0;
|
|
93
98
|
this._composer = null;
|
|
@@ -105,6 +110,7 @@ export class PostProcessingHandler {
|
|
|
105
110
|
// import("./Effects/Sharpening.effect")
|
|
106
111
|
]);
|
|
107
112
|
|
|
113
|
+
|
|
108
114
|
// try {
|
|
109
115
|
// internal_SetSharpeningEffectModule(modules[2]);
|
|
110
116
|
// }
|
|
@@ -141,10 +147,22 @@ export class PostProcessingHandler {
|
|
|
141
147
|
// apply or collect effects
|
|
142
148
|
const res = component.apply(ctx);
|
|
143
149
|
if (!res) continue;
|
|
150
|
+
|
|
151
|
+
|
|
144
152
|
if (Array.isArray(res)) {
|
|
145
|
-
|
|
153
|
+
for (const effect of res) {
|
|
154
|
+
this._effects.push({
|
|
155
|
+
effect,
|
|
156
|
+
priority: component.priority
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
this._effects.push({
|
|
162
|
+
effect: res,
|
|
163
|
+
priority: component.priority
|
|
164
|
+
});
|
|
146
165
|
}
|
|
147
|
-
else this._effects.push(res);
|
|
148
166
|
}
|
|
149
167
|
}
|
|
150
168
|
else {
|
|
@@ -157,20 +175,33 @@ export class PostProcessingHandler {
|
|
|
157
175
|
if (this.context.renderer.toneMapping != NoToneMapping) {
|
|
158
176
|
if (!this._effects.find(e => e instanceof MODULES.POSTPROCESSING.MODULE.ToneMappingEffect)) {
|
|
159
177
|
const tonemapping = new MODULES.POSTPROCESSING.MODULE.ToneMappingEffect();
|
|
160
|
-
this._effects.push(tonemapping);
|
|
178
|
+
this._effects.push({ effect: tonemapping });
|
|
161
179
|
}
|
|
162
180
|
}
|
|
163
181
|
|
|
164
182
|
this.applyEffects(context);
|
|
165
183
|
}
|
|
166
184
|
|
|
185
|
+
private _anyPassHasDepth = false;
|
|
186
|
+
private _anyPassHasNormal = false;
|
|
187
|
+
private _hasSmaaEffect = false;
|
|
188
|
+
|
|
189
|
+
get anyPassHasDepth() { return this._anyPassHasDepth; }
|
|
190
|
+
get anyPassHasNormal() { return this._anyPassHasNormal; }
|
|
191
|
+
get hasSmaaEffect() { return this._hasSmaaEffect; }
|
|
192
|
+
|
|
167
193
|
|
|
168
194
|
/** Build composer passes */
|
|
169
195
|
private applyEffects(context: Context) {
|
|
196
|
+
// Reset state
|
|
197
|
+
this._anyPassHasDepth = false;
|
|
198
|
+
this._anyPassHasNormal = false;
|
|
199
|
+
this._hasSmaaEffect = false;
|
|
170
200
|
|
|
171
201
|
const effectsOrPasses = this._effects;
|
|
172
|
-
|
|
173
|
-
|
|
202
|
+
if (effectsOrPasses.length <= 0) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
174
205
|
|
|
175
206
|
const camera = context.mainCameraComponent as Camera;
|
|
176
207
|
const renderer = context.renderer;
|
|
@@ -180,8 +211,18 @@ export class PostProcessingHandler {
|
|
|
180
211
|
// Store the auto clear setting because the postprocessing composer just disables it
|
|
181
212
|
// and when we disable postprocessing we want to restore the original setting
|
|
182
213
|
// https://github.com/pmndrs/postprocessing/blob/271944b74b543a5b743a62803a167b60cc6bb4ee/src/core/EffectComposer.js#L230C12-L230C12
|
|
214
|
+
// First we need to get the previously set autoClear setting, if it exists
|
|
215
|
+
if (typeof renderer[autoclearSetting] === "boolean") {
|
|
216
|
+
renderer.autoClear = renderer[autoclearSetting];
|
|
217
|
+
}
|
|
183
218
|
renderer[autoclearSetting] = renderer.autoClear;
|
|
184
219
|
|
|
220
|
+
for (const ef of effectsOrPasses) {
|
|
221
|
+
if (ef instanceof MODULES.POSTPROCESSING.MODULE.SMAAEffect) {
|
|
222
|
+
this._hasSmaaEffect = true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
185
226
|
// create composer and set active on context
|
|
186
227
|
if (!this._composer) {
|
|
187
228
|
// const hdrRenderTarget = new WebGLRenderTarget(window.innerWidth, window.innerHeight, { type: HalfFloatType });
|
|
@@ -194,11 +235,13 @@ export class PostProcessingHandler {
|
|
|
194
235
|
if (context.composer && context.composer !== this._composer) {
|
|
195
236
|
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
237
|
}
|
|
238
|
+
|
|
197
239
|
context.composer = this._composer;
|
|
198
240
|
const composer = context.composer;
|
|
199
241
|
composer.setMainCamera(cam);
|
|
200
242
|
composer.setRenderer(renderer);
|
|
201
243
|
composer.setMainScene(scene);
|
|
244
|
+
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)
|
|
202
245
|
|
|
203
246
|
for (const prev of composer.passes)
|
|
204
247
|
prev.dispose();
|
|
@@ -210,64 +253,100 @@ export class PostProcessingHandler {
|
|
|
210
253
|
screenpass.mainScene = scene;
|
|
211
254
|
composer.addPass(screenpass);
|
|
212
255
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
this.orderEffects();
|
|
256
|
+
try {
|
|
257
|
+
orderEffects(this._effects);
|
|
217
258
|
|
|
218
|
-
|
|
259
|
+
const effectsToMerge: Array<Effect> = [];
|
|
260
|
+
let hasConvolutionEffectInArray = false;
|
|
219
261
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
262
|
+
for (const entry of effectsOrPasses) {
|
|
263
|
+
const ef = entry.effect;
|
|
264
|
+
if (ef instanceof MODULES.POSTPROCESSING.MODULE.Effect) {
|
|
265
|
+
const attributes = ef.getAttributes();
|
|
266
|
+
const convolution = MODULES.POSTPROCESSING.MODULE.EffectAttribute.CONVOLUTION;
|
|
267
|
+
if (attributes & convolution) {
|
|
268
|
+
console.log("[PostProcessing] Convolution effect detected: " + ef.name, hasConvolutionEffectInArray);
|
|
269
|
+
if (hasConvolutionEffectInArray) {
|
|
270
|
+
hasConvolutionEffectInArray = false;
|
|
271
|
+
console.log("[PostProcessing] Merging effects with convolution effect in array", effectsToMerge.map(e => e.name).join(", "));
|
|
272
|
+
this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
|
|
273
|
+
|
|
274
|
+
}
|
|
275
|
+
hasConvolutionEffectInArray = true;
|
|
230
276
|
}
|
|
277
|
+
// Otherwise we can merge it
|
|
278
|
+
effectsToMerge.push(ef as Effect);
|
|
231
279
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
280
|
+
else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass) {
|
|
281
|
+
hasConvolutionEffectInArray = false;
|
|
282
|
+
this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
|
|
283
|
+
ef.renderToScreen = false;
|
|
284
|
+
composer.addPass(ef as Pass);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
// seems some effects are not correctly typed, but three can deal with them,
|
|
288
|
+
// so we might need to just pass them through
|
|
289
|
+
hasConvolutionEffectInArray = false;
|
|
290
|
+
this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
|
|
291
|
+
composer.addPass(ef);
|
|
240
292
|
}
|
|
241
293
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
294
|
+
|
|
295
|
+
this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
|
|
296
|
+
}
|
|
297
|
+
catch (e) {
|
|
298
|
+
console.error("Error while applying postprocessing effects", e);
|
|
299
|
+
composer.removeAllPasses();
|
|
246
300
|
}
|
|
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
301
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass)
|
|
259
|
-
composer.addPass(ef as Pass);
|
|
260
|
-
else
|
|
261
|
-
// seems some effects are not correctly typed, but three can deal with them,
|
|
262
|
-
// so we just pass them through
|
|
263
|
-
composer.addPass(ef);
|
|
302
|
+
// 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)
|
|
303
|
+
for (let i = 0; i < composer.passes.length; i++) {
|
|
304
|
+
const pass = composer.passes[i];
|
|
305
|
+
const isLast = i === composer.passes.length - 1;
|
|
306
|
+
if ((pass as any)?.configuration !== undefined) {
|
|
307
|
+
(pass as any).configuration.gammaCorrection = isLast;
|
|
264
308
|
}
|
|
309
|
+
else if ("autosetGamma" in pass) {
|
|
310
|
+
// Some effects have a autosetGamma property that we can use to set the gamma correction
|
|
311
|
+
pass.autosetGamma = isLast;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
this._anyPassHasDepth ||= pass.needsDepthTexture;
|
|
315
|
+
if (pass instanceof MODULES.POSTPROCESSING.MODULE.NormalPass) {
|
|
316
|
+
this._anyPassHasNormal = true;
|
|
317
|
+
}
|
|
318
|
+
if (pass instanceof MODULES.POSTPROCESSING.MODULE.SMAAEffect) {
|
|
319
|
+
this._hasSmaaEffect = true;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// DEBUG LAND BELOW
|
|
324
|
+
if (debug) console.log("[PostProcessing] Passes →", [...composer.passes], "\n---------------------------------\n• " + composer.passes.map(i => i.name).join("\n• ") + "\n");
|
|
325
|
+
if (debug) this._onCreateEffectsDebug(this._composer!, cam);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
/** Should be called before `composer.addPass()` to create an effect pass with all previously collected effects that can be merged up to that point */
|
|
331
|
+
private createPassForMergeableEffects(effects: Array<Effect>, composer: EffectComposer, camera: Camera3, scene: Scene) {
|
|
332
|
+
if (effects.length > 0) {
|
|
333
|
+
const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(camera, ...effects);
|
|
334
|
+
pass.name = effects.map(e => e.name).join(", ");
|
|
335
|
+
pass.mainScene = scene;
|
|
336
|
+
pass.enabled = true;
|
|
337
|
+
pass.renderToScreen = false;
|
|
338
|
+
composer.addPass(pass);
|
|
339
|
+
effects.length = 0; // Clear effects after adding them to the pass
|
|
265
340
|
}
|
|
341
|
+
}
|
|
266
342
|
|
|
267
|
-
if (debug) {
|
|
268
343
|
|
|
269
|
-
console.log("[PostProcessing] Passes →", composer.passes);
|
|
270
344
|
|
|
345
|
+
private _menuEntry: HTMLSelectElement | null = null;
|
|
346
|
+
private _passIndices: number[] | null = null;
|
|
347
|
+
|
|
348
|
+
private _onCreateEffectsDebug(composer: EffectComposer, cam: Camera3) {
|
|
349
|
+
if (debug === "passes") {
|
|
271
350
|
// DepthEffect for debugging purposes, disabled by default, can be selected in the debug pass select
|
|
272
351
|
const depthEffect = new MODULES.POSTPROCESSING.MODULE.DepthEffect({
|
|
273
352
|
blendFunction: MODULES.POSTPROCESSING.MODULE.BlendFunction.NORMAL,
|
|
@@ -303,7 +382,7 @@ export class PostProcessingHandler {
|
|
|
303
382
|
if (menu && this._passIndices === null) {
|
|
304
383
|
if (this._menuEntry)
|
|
305
384
|
this._menuEntry.remove();
|
|
306
|
-
|
|
385
|
+
|
|
307
386
|
const select = document.createElement("select");
|
|
308
387
|
select.multiple = true;
|
|
309
388
|
const defaultOpt = document.createElement("option");
|
|
@@ -327,73 +406,12 @@ export class PostProcessingHandler {
|
|
|
327
406
|
else {
|
|
328
407
|
this._passIndices = indices;
|
|
329
408
|
}
|
|
330
|
-
|
|
331
|
-
this.applyEffects(context);
|
|
409
|
+
this.applyEffects(this.context);
|
|
332
410
|
});
|
|
333
411
|
}
|
|
334
412
|
}
|
|
335
413
|
}
|
|
336
414
|
|
|
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
415
|
}
|
|
398
416
|
|
|
399
417
|
let effectsOrder: Array<Constructor<Effect | Pass>> | null = null;
|
|
@@ -56,6 +56,9 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
|
|
|
56
56
|
return this._activeEffects;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
get dirty() { return this._isDirty; }
|
|
60
|
+
set dirty(value: boolean) { this._isDirty = value; }
|
|
61
|
+
|
|
59
62
|
@serializeable(VolumeProfile)
|
|
60
63
|
sharedProfile?: VolumeProfile;
|
|
61
64
|
|
|
@@ -185,7 +188,17 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
|
|
|
185
188
|
this.context.composer.setMainScene(this.context.scene);
|
|
186
189
|
|
|
187
190
|
const composer = this.context.composer;
|
|
188
|
-
|
|
191
|
+
|
|
192
|
+
// If the postprocessing handler is using depth+normals (e.g. with SMAA) we ALWAYS disable multisampling to avoid ugly edges
|
|
193
|
+
if (this._postprocessing && (this._postprocessing.hasSmaaEffect)) {
|
|
194
|
+
if (composer.multisampling !== 0) {
|
|
195
|
+
composer.multisampling = 0;
|
|
196
|
+
if (debug || isDevEnvironment()) {
|
|
197
|
+
console.warn(`[PostProcessing] Disabling multisampling because depth or normals are used. If you need anti-aliasing, consider adding an Antialiasing component or SMAA effect to the PostprocessingManager.`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (this.multisampling === "auto") {
|
|
189
202
|
|
|
190
203
|
const timeSinceLastChange = this.context.time.realtimeSinceStartup - this._multisampleAutoChangeTime;
|
|
191
204
|
|
|
@@ -244,7 +257,7 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
|
|
|
244
257
|
private _isDirty: boolean = false;
|
|
245
258
|
|
|
246
259
|
private apply() {
|
|
247
|
-
if (debug) console.log(`Apply PostProcessing "${this.name}"`);
|
|
260
|
+
if (debug) console.log(`Apply PostProcessing "${this.name || "unnamed"}"`);
|
|
248
261
|
|
|
249
262
|
if (isDevEnvironment()) {
|
|
250
263
|
if (this._lastApplyTime !== undefined && Date.now() - this._lastApplyTime < 100) {
|
|
@@ -256,7 +269,6 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
|
|
|
256
269
|
}
|
|
257
270
|
|
|
258
271
|
this._isDirty = false;
|
|
259
|
-
this.unapply();
|
|
260
272
|
|
|
261
273
|
this._activeEffects.length = 0;
|
|
262
274
|
// get from profile
|
|
@@ -299,16 +311,13 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
|
|
|
299
311
|
console.warn(`[PostProcessing] No composer found`);
|
|
300
312
|
}
|
|
301
313
|
})
|
|
302
|
-
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
this._postprocessing?.unapply(false);
|
|
303
317
|
}
|
|
304
318
|
|
|
305
319
|
}
|
|
306
320
|
|
|
307
|
-
private unapply() {
|
|
308
|
-
this._postprocessing?.unapply();
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
321
|
private _applyPostQueue() {
|
|
313
322
|
if (this._modificationQueue) {
|
|
314
323
|
for (const entry of this._modificationQueue.values()) this.onEditorModification(entry);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { Effect, Pass } from "postprocessing";
|
|
1
2
|
import { Object3D } from "three";
|
|
2
3
|
|
|
3
4
|
import { isDevEnvironment } from "../../engine/debug/index.js";
|
|
4
5
|
import { addComponent } from "../../engine/engine_components.js";
|
|
5
6
|
import { foreachComponentEnumerator } from "../../engine/engine_gameobject.js";
|
|
6
|
-
import {
|
|
7
|
+
import { MODULES } from "../../engine/engine_modules.js";
|
|
8
|
+
import { Constructor, ConstructorConcrete, IComponent } from "../../engine/engine_types.js";
|
|
7
9
|
import { getParam } from "../../engine/engine_utils.js";
|
|
8
10
|
import { type PostProcessingEffect } from "./PostProcessingEffect.js";
|
|
9
11
|
|
|
@@ -11,6 +13,8 @@ export const debug = getParam("debugpost");
|
|
|
11
13
|
|
|
12
14
|
export type IPostProcessingManager = IComponent & {
|
|
13
15
|
get isPostProcessingManager(): boolean;
|
|
16
|
+
get dirty(): boolean;
|
|
17
|
+
set dirty(value: boolean);
|
|
14
18
|
addEffect(effect: PostProcessingEffect): void;
|
|
15
19
|
removeEffect(effect: PostProcessingEffect): void;
|
|
16
20
|
}
|
|
@@ -50,4 +54,116 @@ export function getPostProcessingManager(effect: PostProcessingEffect): IPostPro
|
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
return manager;
|
|
53
|
-
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
export type PostprocessingEffectData = {
|
|
62
|
+
effect: Effect | Pass;
|
|
63
|
+
priority?: number,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
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.
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* import { PostProcessingEffectPriority } from "@needle-tools/engine"
|
|
73
|
+
*
|
|
74
|
+
* export class MyCustomEffect extends PostProcessingEffect {
|
|
75
|
+
* priority: PostProcessingEffectPriority.Bloom + 1; // render after bloom
|
|
76
|
+
*
|
|
77
|
+
* // ... your effect code
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export const PostProcessingEffectPriority = {
|
|
82
|
+
NormalPass: 0,
|
|
83
|
+
DepthDownsamplingPass: 10,
|
|
84
|
+
SMAA: 20,
|
|
85
|
+
SSAO: 30,
|
|
86
|
+
TiltShift: 40,
|
|
87
|
+
DepthOfField: 50,
|
|
88
|
+
ChromaticAberration: 60,
|
|
89
|
+
Bloom: 70,
|
|
90
|
+
Vignette: 80,
|
|
91
|
+
Pixelation: 90,
|
|
92
|
+
ToneMapping: 100,
|
|
93
|
+
HueSaturation: 110,
|
|
94
|
+
BrightnessContrast: 120,
|
|
95
|
+
// Sharpening: 130,
|
|
96
|
+
}
|
|
97
|
+
// let effectsOrder: Array<Constructor<Effect | Pass>> | null = null;
|
|
98
|
+
|
|
99
|
+
let builtinOrder: Map<Constructor<Effect | Pass>, number> | null = null;
|
|
100
|
+
|
|
101
|
+
export function orderEffects(effects: Array<PostprocessingEffectData>) {
|
|
102
|
+
if (debug === "verbose") console.debug("Before ordering effects", [...effects]);
|
|
103
|
+
|
|
104
|
+
if (!builtinOrder) {
|
|
105
|
+
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);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// enforce correct order of effects (e.g. DOF before Bloom)
|
|
122
|
+
effects.sort((a, b) => {
|
|
123
|
+
// we use find index here because sometimes constructor names are prefixed with `_`
|
|
124
|
+
// 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;
|
|
127
|
+
|
|
128
|
+
// 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);
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
// if (aidx < 0) return 1;
|
|
138
|
+
// if (bidx < 0) return -1;
|
|
139
|
+
return aidx - bidx;
|
|
140
|
+
});
|
|
141
|
+
|
|
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
|
+
|
|
166
|
+
if (debug === "verbose") console.debug("After ordering effects", [...effects]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|