@@ -24,7 +24,7 @@
|
|
24
24
|
// edgeDetectionThreshold!: VolumeParameter;
|
25
25
|
|
26
26
|
@serializable(VolumeParameter)
|
27
|
-
preset
|
27
|
+
readonly preset: VolumeParameter = new VolumeParameter();
|
28
28
|
|
29
29
|
|
30
30
|
onCreateEffect(): EffectProviderResult {
|
@@ -1,94 +0,0 @@
|
|
1
|
-
import { BlendFunction, BloomEffect, SelectiveBloomEffect } from "postprocessing";
|
2
|
-
import { MathUtils } from "three";
|
3
|
-
|
4
|
-
import { serializable } from "../../../engine/engine_serialization.js";
|
5
|
-
import { PostProcessingEffect } from "../PostProcessingEffect.js";
|
6
|
-
import { VolumeParameter } from "../VolumeParameter.js";
|
7
|
-
import { registerCustomEffectType } from "../VolumeProfile.js";
|
8
|
-
|
9
|
-
export class Bloom extends PostProcessingEffect {
|
10
|
-
|
11
|
-
/** Whether to use selective bloom by default */
|
12
|
-
static useSelectiveBloom = false;
|
13
|
-
|
14
|
-
get typeName() {
|
15
|
-
return "Bloom";
|
16
|
-
}
|
17
|
-
|
18
|
-
@serializable(VolumeParameter)
|
19
|
-
threshold!: VolumeParameter;
|
20
|
-
@serializable(VolumeParameter)
|
21
|
-
intensity!: VolumeParameter;
|
22
|
-
@serializable(VolumeParameter)
|
23
|
-
scatter!: VolumeParameter;
|
24
|
-
|
25
|
-
selectiveBloom?: boolean;
|
26
|
-
|
27
|
-
init() {
|
28
|
-
this.threshold.defaultValue = 1;
|
29
|
-
this.intensity.defaultValue = 0;
|
30
|
-
this.scatter.defaultValue = .2;
|
31
|
-
|
32
|
-
if (this.selectiveBloom) {
|
33
|
-
this.threshold.valueProcessor = (v: number) => v;
|
34
|
-
this.intensity.valueProcessor = (v: number) => v;
|
35
|
-
this.scatter.valueProcessor = (v: number) => 1 * Math.PI * (1 - v);
|
36
|
-
}
|
37
|
-
else {
|
38
|
-
this.threshold.valueProcessor = (v: number) => v;
|
39
|
-
this.intensity.valueProcessor = (v: number) => v;
|
40
|
-
this.scatter.valueProcessor = (v: number) => 1 * Math.PI * (1 - v);
|
41
|
-
}
|
42
|
-
}
|
43
|
-
|
44
|
-
onCreateEffect() {
|
45
|
-
let bloom: BloomEffect;
|
46
|
-
|
47
|
-
if (this.selectiveBloom == undefined) {
|
48
|
-
this.selectiveBloom = Bloom.useSelectiveBloom;
|
49
|
-
}
|
50
|
-
|
51
|
-
if (this.selectiveBloom) {
|
52
|
-
// https://github.com/pmndrs/postprocessing/blob/64d2829f014cfec97a46bf3c109f3abc55af0715/demo/src/demos/BloomDemo.js#L265
|
53
|
-
const selectiveBloom = bloom = new SelectiveBloomEffect(this.context.scene, this.context.mainCamera!, {
|
54
|
-
blendFunction: BlendFunction.ADD,
|
55
|
-
mipmapBlur: true,
|
56
|
-
luminanceThreshold: this.threshold.value,
|
57
|
-
luminanceSmoothing: this.scatter.value,
|
58
|
-
radius: 0.85, // default value
|
59
|
-
intensity: this.intensity.value,
|
60
|
-
});
|
61
|
-
selectiveBloom.inverted = true;
|
62
|
-
}
|
63
|
-
else {
|
64
|
-
bloom = new BloomEffect({
|
65
|
-
blendFunction: BlendFunction.ADD,
|
66
|
-
mipmapBlur: true,
|
67
|
-
luminanceThreshold: this.threshold.value,
|
68
|
-
luminanceSmoothing: this.scatter.value,
|
69
|
-
radius: 0.85, // default value
|
70
|
-
intensity: this.intensity.value,
|
71
|
-
});
|
72
|
-
}
|
73
|
-
|
74
|
-
this.intensity.onValueChanged = newValue => {
|
75
|
-
bloom!.intensity = newValue;
|
76
|
-
};
|
77
|
-
this.threshold.onValueChanged = newValue => {
|
78
|
-
// for some reason the threshold needs to be gamma-corrected
|
79
|
-
bloom!.luminanceMaterial.threshold = Math.pow(newValue, 2.2);
|
80
|
-
};
|
81
|
-
this.scatter.onValueChanged = newValue => {
|
82
|
-
bloom!.luminancePass.enabled = true;
|
83
|
-
console.log("Scatter", newValue);
|
84
|
-
bloom!.luminanceMaterial.smoothing = newValue;
|
85
|
-
if (bloom["mipmapBlurPass"])
|
86
|
-
// heuristic so it looks similar to "scatter" in other engines
|
87
|
-
bloom!["mipmapBlurPass"].radius = MathUtils.lerp(0.1, 0.9, 1 - newValue / Math.PI);
|
88
|
-
};
|
89
|
-
|
90
|
-
return bloom;
|
91
|
-
}
|
92
|
-
|
93
|
-
}
|
94
|
-
registerCustomEffectType("Bloom", Bloom);
|
@@ -13,11 +13,8 @@
|
|
13
13
|
}
|
14
14
|
|
15
15
|
@serializable(VolumeParameter)
|
16
|
-
intensity
|
16
|
+
readonly intensity: VolumeParameter = new VolumeParameter(0);
|
17
17
|
|
18
|
-
init() {
|
19
|
-
this.intensity.defaultValue = 0;
|
20
|
-
}
|
21
18
|
|
22
19
|
onCreateEffect(): EffectProviderResult {
|
23
20
|
const chromatic = new ChromaticAberrationEffect();
|
@@ -1,15 +1,12 @@
|
|
1
|
-
import { BrightnessContrastEffect, HueSaturationEffect
|
2
|
-
import {
|
1
|
+
import { BrightnessContrastEffect, HueSaturationEffect } from "postprocessing";
|
2
|
+
import { NoToneMapping } from "three";
|
3
3
|
|
4
4
|
import { serializable } from "../../../engine/engine_serialization.js";
|
5
|
-
import { GameObject } from "../../Component.js";
|
6
5
|
import { type EffectProviderResult, PostProcessingEffect } from "../PostProcessingEffect.js";
|
7
|
-
import { Volume } from "../Volume.js";
|
8
6
|
import { VolumeParameter } from "../VolumeParameter.js";
|
9
7
|
import { registerCustomEffectType } from "../VolumeProfile.js";
|
10
|
-
import {
|
8
|
+
import { ToneMappingEffect } from "./Tonemapping.js";
|
11
9
|
|
12
|
-
|
13
10
|
export class ColorAdjustments extends PostProcessingEffect {
|
14
11
|
|
15
12
|
get typeName() {
|
@@ -17,16 +14,16 @@
|
|
17
14
|
}
|
18
15
|
|
19
16
|
@serializable(VolumeParameter)
|
20
|
-
postExposure
|
17
|
+
readonly postExposure: VolumeParameter = new VolumeParameter(0);
|
21
18
|
|
22
19
|
@serializable(VolumeParameter)
|
23
|
-
contrast
|
20
|
+
readonly contrast: VolumeParameter = new VolumeParameter(0);
|
24
21
|
|
25
22
|
@serializable(VolumeParameter)
|
26
|
-
hueShift
|
23
|
+
readonly hueShift: VolumeParameter = new VolumeParameter(0);
|
27
24
|
|
28
25
|
@serializable(VolumeParameter)
|
29
|
-
saturation
|
26
|
+
readonly saturation: VolumeParameter = new VolumeParameter(0);
|
30
27
|
|
31
28
|
init() {
|
32
29
|
this.postExposure!.valueProcessor = v => {
|
@@ -56,55 +53,25 @@
|
|
56
53
|
this.saturation.defaultValue = 0;
|
57
54
|
}
|
58
55
|
|
59
|
-
unapply() {
|
60
|
-
// reset to the current value in the toneMappingEffect if that exists, else Linear
|
61
|
-
const currentMode: any = this.toneMappingEffect?.mode?.value;
|
62
|
-
const newMode = this.toneMappingEffect?.getThreeToneMapping(currentMode)
|
63
|
-
this.context.renderer.toneMapping = newMode ?? LinearToneMapping;
|
64
|
-
}
|
65
|
-
|
66
|
-
private threeToneMappingToEffectMode(mode: number | undefined): ToneMappingMode {
|
67
|
-
switch (mode) {
|
68
|
-
case LinearToneMapping: return ToneMappingMode.LINEAR;
|
69
|
-
case ACESFilmicToneMapping: return ToneMappingMode.ACES_FILMIC;
|
70
|
-
case AgXToneMapping: return ToneMappingMode.AGX;
|
71
|
-
case NeutralToneMapping: return ToneMappingMode.NEUTRAL;
|
72
|
-
case ReinhardToneMapping: return ToneMappingMode.REINHARD;
|
73
|
-
default: return ToneMappingMode.LINEAR;
|
74
|
-
}
|
75
|
-
}
|
76
|
-
|
77
|
-
private toneMappingEffect: ToneMapping | null = null;
|
78
56
|
onCreateEffect(): EffectProviderResult {
|
79
57
|
const effects: EffectProviderResult = [];
|
80
58
|
|
59
|
+
// TODO: do we still need this?
|
81
60
|
if (this.context.renderer.toneMapping !== NoToneMapping && this.postExposure.overrideState)
|
82
61
|
this.context.renderer.toneMapping = NoToneMapping;
|
83
62
|
|
84
63
|
|
85
|
-
//
|
86
|
-
|
87
|
-
|
88
|
-
|
64
|
+
// find the ToneMapping effect because we need it to apply post exposure
|
65
|
+
const hasTonemapping = this.postprocessingContext?.components.find(c => c instanceof ToneMappingEffect) as ToneMappingEffect;
|
66
|
+
if(!hasTonemapping){
|
67
|
+
this.postprocessingContext?.components.push(new ToneMappingEffect());
|
68
|
+
}
|
89
69
|
|
90
70
|
// We need this effect if someone uses ACES or AgX tonemapping;
|
91
71
|
// problem is that we CAN'T use this effect for the "Linear" case, the package expects that in this case you remove the effect
|
92
|
-
const tonemapping = new ToneMappingEffect({
|
93
|
-
mode: this.threeToneMappingToEffectMode(expectedThreeMode),
|
94
|
-
// middleGrey: 0.5,
|
95
|
-
// averageLuminance: 1,
|
96
|
-
});
|
97
|
-
|
98
72
|
this.postExposure!.onValueChanged = (v) => {
|
99
73
|
if (this.postExposure.overrideState)
|
100
74
|
this.context.renderer.toneMappingExposure = v;
|
101
|
-
|
102
|
-
// this is a workaround so that we can apply tonemapping options – no access to the ToneMappingEffect instance from the Tonemapping effect right now...
|
103
|
-
const currentMode = this.toneMappingEffect?.mode?.value;
|
104
|
-
const threeMode = this.toneMappingEffect?.getThreeToneMapping(currentMode);
|
105
|
-
const mappedMode = this.threeToneMappingToEffectMode(threeMode);
|
106
|
-
if (mappedMode !== undefined)
|
107
|
-
tonemapping.mode = mappedMode;
|
108
75
|
};
|
109
76
|
|
110
77
|
const brightnesscontrast = new BrightnessContrastEffect();
|
@@ -114,7 +81,6 @@
|
|
114
81
|
|
115
82
|
effects.push(brightnesscontrast);
|
116
83
|
effects.push(hueSaturationEffect);
|
117
|
-
effects.push(tonemapping);
|
118
84
|
|
119
85
|
this.hueShift!.onValueChanged = v => hueSaturationEffect.hue = v;
|
120
86
|
this.saturation!.onValueChanged = v => hueSaturationEffect.saturation = v;
|
@@ -31,7 +31,7 @@
|
|
31
31
|
export { BasicIKConstraint } from "../BasicIKConstraint.js";
|
32
32
|
export { BehaviorExtension } from "../export/usdz/extensions/behavior/Behaviour.js";
|
33
33
|
export { BehaviorModel } from "../export/usdz/extensions/behavior/BehavioursBuilder.js";
|
34
|
-
export {
|
34
|
+
export { BloomEffect } from "../postprocessing/Effects/BloomEffect.js";
|
35
35
|
export { BoxCollider } from "../Collider.js";
|
36
36
|
export { BoxGizmo } from "../Gizmos.js";
|
37
37
|
export { BoxHelperComponent } from "../BoxHelperComponent.js";
|
@@ -181,7 +181,7 @@
|
|
181
181
|
export { TextExtension } from "../export/usdz/extensions/USDZText.js";
|
182
182
|
export { TextureSheetAnimationModule } from "../ParticleSystemModules.js";
|
183
183
|
export { TiltShiftEffect } from "../postprocessing/Effects/TiltShiftEffect.js";
|
184
|
-
export {
|
184
|
+
export { ToneMappingEffect } from "../postprocessing/Effects/Tonemapping.js";
|
185
185
|
export { TrailModule } from "../ParticleSystemModules.js";
|
186
186
|
export { TransformData } from "../export/usdz/extensions/Animation.js";
|
187
187
|
export { TransformGizmo } from "../TransformGizmo.js";
|
@@ -25,22 +25,22 @@
|
|
25
25
|
mode!: DepthOfFieldMode;
|
26
26
|
|
27
27
|
@serializable(VolumeParameter)
|
28
|
-
focusDistance
|
28
|
+
readonly focusDistance: VolumeParameter = new VolumeParameter();
|
29
29
|
|
30
30
|
@serializable(VolumeParameter)
|
31
|
-
focalLength
|
31
|
+
readonly focalLength: VolumeParameter = new VolumeParameter();
|
32
32
|
|
33
33
|
@serializable(VolumeParameter)
|
34
|
-
aperture
|
34
|
+
readonly aperture: VolumeParameter = new VolumeParameter();
|
35
35
|
|
36
36
|
@serializable(VolumeParameter)
|
37
|
-
gaussianMaxRadius
|
37
|
+
readonly gaussianMaxRadius: VolumeParameter = new VolumeParameter();
|
38
38
|
|
39
39
|
@serializable(VolumeParameter)
|
40
|
-
resolutionScale
|
40
|
+
readonly resolutionScale: VolumeParameter = new VolumeParameter(1 * 1 / window.devicePixelRatio);
|
41
41
|
|
42
42
|
@serializable(VolumeParameter)
|
43
|
-
bokehScale
|
43
|
+
readonly bokehScale: VolumeParameter = new VolumeParameter();
|
44
44
|
|
45
45
|
init() {
|
46
46
|
this.focalLength.valueProcessor = v => {
|
@@ -62,14 +62,13 @@
|
|
62
62
|
return undefined;
|
63
63
|
}
|
64
64
|
|
65
|
-
const factor = 1 / window.devicePixelRatio;
|
65
|
+
// const factor = 1 / window.devicePixelRatio;
|
66
|
+
// if (this.resolutionScale === undefined) {
|
67
|
+
// let defaultValue = 1;
|
68
|
+
// if(isMobileDevice()) defaultValue = .6;
|
69
|
+
// this.resolutionScale = new VolumeParameter(defaultValue * factor);
|
70
|
+
// }
|
66
71
|
|
67
|
-
if (this.resolutionScale === undefined) {
|
68
|
-
let defaultValue = 1;
|
69
|
-
if(isMobileDevice()) defaultValue = .6;
|
70
|
-
this.resolutionScale = new VolumeParameter(defaultValue * factor);
|
71
|
-
}
|
72
|
-
|
73
72
|
// console.log(this.focusDistance.overrideState, this.focusDistance.value);
|
74
73
|
// const depth = new DepthEffect({
|
75
74
|
// inverted: true,
|
@@ -475,7 +475,7 @@
|
|
475
475
|
this.renderer.shadowMap.type = PCFSoftShadowMap;
|
476
476
|
this.renderer.setSize(this.domWidth, this.domHeight);
|
477
477
|
this.renderer.outputColorSpace = SRGBColorSpace;
|
478
|
-
this.renderer.toneMapping = AgXToneMapping;
|
478
|
+
// this.renderer.toneMapping = AgXToneMapping;
|
479
479
|
this.lodsManager.setRenderer(this.renderer);
|
480
480
|
|
481
481
|
this.input.bindEvents();
|
@@ -69,7 +69,7 @@
|
|
69
69
|
vec3 x2 = x * x;
|
70
70
|
vec3 x4 = x2 * x2;
|
71
71
|
|
72
|
-
return
|
72
|
+
return + 15.5 * x4 * x2
|
73
73
|
- 40.14 * x4 * x
|
74
74
|
+ 31.96 * x4
|
75
75
|
- 6.868 * x2 * x
|
@@ -11,7 +11,7 @@
|
|
11
11
|
}
|
12
12
|
|
13
13
|
@serializable(VolumeParameter)
|
14
|
-
granularity
|
14
|
+
readonly granularity: VolumeParameter = new VolumeParameter(10);
|
15
15
|
|
16
16
|
onCreateEffect(): EffectProviderResult {
|
17
17
|
|
@@ -65,9 +65,10 @@
|
|
65
65
|
this._localInstance = this.asset?.instantiateSynced({ parent: this.gameObject }, true) as Promise<IGameObject>;
|
66
66
|
const instance = await this._localInstance;
|
67
67
|
if (instance) {
|
68
|
-
const pl = GameObject.
|
69
|
-
if (pl) {
|
70
|
-
|
68
|
+
const pl = GameObject.getComponentsInChildren(instance, PlayerState);
|
69
|
+
if (pl?.length) {
|
70
|
+
for (const state of pl)
|
71
|
+
state.owner = this.context.connection.connectionId!;
|
71
72
|
this.onPlayerSpawned?.invoke(instance);
|
72
73
|
}
|
73
74
|
else {
|
@@ -2,18 +2,23 @@
|
|
2
2
|
|
3
3
|
import type { EditorModification, IEditorModification } from "../../engine/engine_editor-sync.js";
|
4
4
|
import { serializable } from "../../engine/engine_serialization.js";
|
5
|
-
import type { ISerializable, SerializationContext } from "../../engine/engine_serialization_core.js";
|
6
5
|
import { getParam } from "../../engine/engine_utils.js";
|
7
6
|
import { Component } from "../Component.js";
|
8
|
-
import {
|
7
|
+
import type { PostProcessingHandler } from "./PostProcessingHandler.js";
|
8
|
+
import { getPostProcessingManager, IPostProcessingManager } from "./utils.js";
|
9
9
|
import { VolumeParameter } from "./VolumeParameter.js";
|
10
10
|
|
11
11
|
const debug = getParam("debugpost");
|
12
12
|
|
13
13
|
export declare type EffectProviderResult = Effect | Pass | Array<Effect | Pass>;
|
14
14
|
|
15
|
+
export declare type PostProcessingEffectContext = {
|
16
|
+
handler: PostProcessingHandler;
|
17
|
+
components: PostProcessingEffect[];
|
18
|
+
};
|
19
|
+
|
15
20
|
export interface IEffectProvider {
|
16
|
-
apply(): void | undefined | EffectProviderResult;
|
21
|
+
apply(context: PostProcessingEffectContext): void | undefined | EffectProviderResult;
|
17
22
|
unapply(): void;
|
18
23
|
}
|
19
24
|
|
@@ -43,13 +48,12 @@
|
|
43
48
|
* registerCustomEffectType("Antialiasing", Antialiasing)
|
44
49
|
* ```
|
45
50
|
*/
|
46
|
-
export abstract class PostProcessingEffect extends Component implements IEffectProvider,
|
51
|
+
export abstract class PostProcessingEffect extends Component implements IEffectProvider, IEditorModification {
|
47
52
|
|
48
53
|
get isPostProcessingEffect() { return true; }
|
49
54
|
|
50
55
|
constructor(params: any = undefined) {
|
51
56
|
super();
|
52
|
-
this.ensureVolumeParameters();
|
53
57
|
if (params) {
|
54
58
|
for (const key of Object.keys(params)) {
|
55
59
|
const value = params[key];
|
@@ -59,7 +63,7 @@
|
|
59
63
|
}
|
60
64
|
// allow assigning values to properties that are not VolumeParameters
|
61
65
|
// this is useful when effects are created in code
|
62
|
-
else if(param !== undefined){
|
66
|
+
else if (param !== undefined) {
|
63
67
|
this[key] = value;
|
64
68
|
}
|
65
69
|
}
|
@@ -68,11 +72,13 @@
|
|
68
72
|
|
69
73
|
abstract get typeName(): string;
|
70
74
|
|
75
|
+
@serializable()
|
76
|
+
active: boolean = true;
|
77
|
+
|
71
78
|
private _manager: IPostProcessingManager | null = null;
|
72
79
|
|
73
80
|
onEnable(): void {
|
74
|
-
this.
|
75
|
-
this._manager?.addEffect(this);
|
81
|
+
this.onEffectEnabled();
|
76
82
|
// Dont override the serialized value by enabling (we could also just disable this component / map enabled to active)
|
77
83
|
if (this.__internalDidAwakeAndStart)
|
78
84
|
this.active = true;
|
@@ -83,24 +89,28 @@
|
|
83
89
|
this.active = false;
|
84
90
|
}
|
85
91
|
|
86
|
-
|
87
|
-
|
92
|
+
protected onEffectEnabled(manager?: IPostProcessingManager) {
|
93
|
+
if (manager && manager.isPostProcessingManager === true) this._manager = manager;
|
94
|
+
else if (!this._manager) this._manager = getPostProcessingManager(this);
|
95
|
+
this._manager?.addEffect(this);
|
96
|
+
}
|
88
97
|
|
89
98
|
/** override to initialize bindings on parameters */
|
90
|
-
init() {
|
99
|
+
init() { }
|
91
100
|
|
92
|
-
}
|
93
|
-
|
94
101
|
/** previously created effect (if any) */
|
95
102
|
private _result: void | undefined | EffectProviderResult;
|
103
|
+
private _postprocessingContext: PostProcessingEffectContext | null = null;
|
104
|
+
protected get postprocessingContext() { return this._postprocessingContext; }
|
96
105
|
|
97
|
-
|
98
106
|
/** Apply post settings. Make sure to call super.apply() if you also create an effect */
|
99
|
-
apply(): void | undefined | EffectProviderResult {
|
100
|
-
this.
|
107
|
+
apply(ctx: PostProcessingEffectContext): void | undefined | EffectProviderResult {
|
108
|
+
this._postprocessingContext = ctx;
|
101
109
|
if (!this._result) {
|
110
|
+
this.initParameters();
|
102
111
|
this._result = this.onCreateEffect?.call(this);
|
103
112
|
}
|
113
|
+
// TODO: calling this twice because otherwise the Postprocessing sample doesnt look correct. Need to investigate which effect is causing this (init parameters should be refactored either way https://linear.app/needle/issue/NE-5182)
|
104
114
|
if (this._result) {
|
105
115
|
this.initParameters();
|
106
116
|
}
|
@@ -137,25 +147,6 @@
|
|
137
147
|
}
|
138
148
|
}
|
139
149
|
|
140
|
-
onAfterDeserialize(data: any, _context: SerializationContext): void {
|
141
|
-
// When using additional effects and parameters exported from the editor are not in the volume parameter format
|
142
|
-
if (typeof data === "object") {
|
143
|
-
const types = this["$serializedTypes"];
|
144
|
-
if (types) {
|
145
|
-
for (const fieldName of Object.keys(types)) {
|
146
|
-
const type = types[fieldName];
|
147
|
-
if (type === VolumeParameter) {
|
148
|
-
const value = data[fieldName];
|
149
|
-
if (value !== undefined) {
|
150
|
-
const parameter = this[fieldName];
|
151
|
-
parameter.value = value;
|
152
|
-
}
|
153
|
-
}
|
154
|
-
}
|
155
|
-
}
|
156
|
-
}
|
157
|
-
}
|
158
|
-
|
159
150
|
// TODO this is currently not used for post processing effects that are part of Volume stacks,
|
160
151
|
// since these handle that already.
|
161
152
|
onEditorModification(modification: EditorModification): void | boolean | undefined {
|
@@ -168,23 +159,4 @@
|
|
168
159
|
}
|
169
160
|
}
|
170
161
|
|
171
|
-
private _didCreateVolumeParameters: boolean = false;
|
172
|
-
/** Creates volume parameter fields that have not been initialized yet */
|
173
|
-
protected ensureVolumeParameters() {
|
174
|
-
if (this._didCreateVolumeParameters) return;
|
175
|
-
this._didCreateVolumeParameters = true;
|
176
|
-
|
177
|
-
const types = this["$serializedTypes"];
|
178
|
-
if (types) {
|
179
|
-
for (const fieldName of Object.keys(types)) {
|
180
|
-
const type = types[fieldName];
|
181
|
-
if (type === VolumeParameter) {
|
182
|
-
const parameter = this[fieldName];
|
183
|
-
if (!parameter) {
|
184
|
-
this[fieldName] = new VolumeParameter();
|
185
|
-
}
|
186
|
-
}
|
187
|
-
}
|
188
|
-
}
|
189
|
-
}
|
190
162
|
}
|
@@ -1,6 +1,9 @@
|
|
1
1
|
import { N8AOPostPass } from "n8ao";
|
2
|
-
import {
|
3
|
-
|
2
|
+
import {
|
3
|
+
BloomEffect, BrightnessContrastEffect, ChromaticAberrationEffect, DepthDownsamplingPass, DepthOfFieldEffect, Effect, EffectComposer, EffectPass, HueSaturationEffect, NormalPass, Pass, PixelationEffect, RenderPass, SelectiveBloomEffect, SMAAEffect, SSAOEffect,
|
4
|
+
ToneMappingEffect as _TonemappingEffect, VignetteEffect
|
5
|
+
} from "postprocessing";
|
6
|
+
import { HalfFloatType, NoToneMapping } from "three";
|
4
7
|
|
5
8
|
import { showBalloonWarning } from "../../engine/debug/index.js";
|
6
9
|
import { Context } from "../../engine/engine_setup.js";
|
@@ -8,10 +11,8 @@
|
|
8
11
|
import { getParam, isMobileDevice } from "../../engine/engine_utils.js";
|
9
12
|
import { Camera } from "../Camera.js";
|
10
13
|
import { Antialiasing } from "./Effects/Antialiasing.js";
|
11
|
-
import { ColorAdjustments } from "./Effects/ColorAdjustments.js";
|
12
14
|
import { SharpeningEffect } from "./Effects/Sharpening.js";
|
13
|
-
import {
|
14
|
-
import { PostProcessingEffect } from "./PostProcessingEffect.js";
|
15
|
+
import { PostProcessingEffect, PostProcessingEffectContext } from "./PostProcessingEffect.js";
|
15
16
|
|
16
17
|
const debug = getParam("debugpost");
|
17
18
|
|
@@ -31,7 +32,7 @@
|
|
31
32
|
return this._isActive;
|
32
33
|
}
|
33
34
|
|
34
|
-
get composer() {
|
35
|
+
get composer() {
|
35
36
|
return this._composer;
|
36
37
|
}
|
37
38
|
|
@@ -79,8 +80,6 @@
|
|
79
80
|
this._composer = null;
|
80
81
|
}
|
81
82
|
|
82
|
-
private tempColorAdjustments: ColorAdjustments | null = null;
|
83
|
-
|
84
83
|
private onApply(context: Context, components: PostProcessingEffect[]) {
|
85
84
|
|
86
85
|
if (!components) return;
|
@@ -95,17 +94,14 @@
|
|
95
94
|
// const effects: Array<Effect | Pass> = [];
|
96
95
|
this._effects.length = 0;
|
97
96
|
|
98
|
-
|
99
|
-
//
|
100
|
-
const
|
101
|
-
|
102
|
-
|
103
|
-
if (debug) console.log("Adding a default ColorAdjustments component to apply tonemapping.", ...components);
|
104
|
-
if (this.tempColorAdjustments === null) this.tempColorAdjustments = new ColorAdjustments();
|
105
|
-
this._lastVolumeComponents.push(this.tempColorAdjustments);
|
97
|
+
|
98
|
+
// TODO: if an effect is added or removed during the loop this might not be correct anymore
|
99
|
+
const ctx: PostProcessingEffectContext = {
|
100
|
+
handler: this,
|
101
|
+
components: this._lastVolumeComponents,
|
106
102
|
}
|
107
|
-
|
108
|
-
|
103
|
+
for (let i = 0; i < this._lastVolumeComponents.length; i++) {
|
104
|
+
const component = this._lastVolumeComponents[i];
|
109
105
|
//@ts-ignore
|
110
106
|
component.context = context;
|
111
107
|
if (component.apply) {
|
@@ -115,7 +111,7 @@
|
|
115
111
|
return;
|
116
112
|
}
|
117
113
|
// apply or collect effects
|
118
|
-
const res = component.apply();
|
114
|
+
const res = component.apply(ctx);
|
119
115
|
if (!res) continue;
|
120
116
|
if (Array.isArray(res)) {
|
121
117
|
this._effects.push(...res);
|
@@ -129,6 +125,14 @@
|
|
129
125
|
}
|
130
126
|
}
|
131
127
|
|
128
|
+
// Ensure that we have a tonemapping effect if the renderer is set to use a tone mapping
|
129
|
+
if (this.context.renderer.toneMapping != NoToneMapping) {
|
130
|
+
if (!this._effects.find(e => e instanceof _TonemappingEffect)) {
|
131
|
+
const tonemapping = new _TonemappingEffect();
|
132
|
+
this._effects.push(tonemapping);
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
132
136
|
this.applyEffects(context);
|
133
137
|
}
|
134
138
|
|
@@ -143,7 +147,7 @@
|
|
143
147
|
const renderer = context.renderer;
|
144
148
|
const scene = context.scene;
|
145
149
|
const cam = camera.cam;
|
146
|
-
|
150
|
+
|
147
151
|
// Store the auto clear setting because the postprocessing composer just disables it
|
148
152
|
// and when we disable postprocessing we want to restore the original setting
|
149
153
|
// https://github.com/pmndrs/postprocessing/blob/271944b74b543a5b743a62803a167b60cc6bb4ee/src/core/EffectComposer.js#L230C12-L230C12
|
@@ -159,6 +163,9 @@
|
|
159
163
|
multisampling: Math.min(isMobileDevice() ? 4 : 8, maxSamples),
|
160
164
|
});
|
161
165
|
}
|
166
|
+
if (context.composer && context.composer !== this._composer) {
|
167
|
+
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.");
|
168
|
+
}
|
162
169
|
context.composer = this._composer;
|
163
170
|
const composer = context.composer;
|
164
171
|
composer.setMainCamera(cam);
|
@@ -177,44 +184,44 @@
|
|
177
184
|
|
178
185
|
const automaticEffectsOrdering = true;
|
179
186
|
if (automaticEffectsOrdering) {
|
180
|
-
|
181
|
-
|
187
|
+
try {
|
188
|
+
this.orderEffects();
|
182
189
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
190
|
+
const effects: Array<Effect> = [];
|
191
|
+
|
192
|
+
for (const ef of effectsOrPasses) {
|
193
|
+
if (ef instanceof Effect)
|
194
|
+
effects.push(ef as Effect);
|
195
|
+
else if (ef instanceof Pass) {
|
196
|
+
const pass = new EffectPass(cam, ...effects);
|
197
|
+
pass.mainScene = scene;
|
198
|
+
pass.name = effects.map(e => e.constructor.name).join(", ");
|
199
|
+
pass.enabled = true;
|
200
|
+
// composer.addPass(pass);
|
201
|
+
effects.length = 0;
|
202
|
+
composer.addPass(ef as Pass);
|
203
|
+
}
|
204
|
+
else {
|
205
|
+
// seems some effects are not correctly typed, but three can deal with them,
|
206
|
+
// so we might need to just pass them through
|
207
|
+
// composer.addPass(ef);
|
208
|
+
}
|
209
|
+
}
|
210
|
+
|
211
|
+
// create and apply uber pass
|
212
|
+
if (effects.length > 0) {
|
189
213
|
const pass = new EffectPass(cam, ...effects);
|
214
|
+
pass.name = effects.map(e => e.name).join(" ");
|
190
215
|
pass.mainScene = scene;
|
191
|
-
pass.name = effects.map(e => e.constructor.name).join(", ");
|
192
216
|
pass.enabled = true;
|
193
|
-
|
194
|
-
effects.length = 0;
|
195
|
-
composer.addPass(ef as Pass);
|
217
|
+
composer.addPass(pass);
|
196
218
|
}
|
197
|
-
else {
|
198
|
-
// seems some effects are not correctly typed, but three can deal with them,
|
199
|
-
// so we might need to just pass them through
|
200
|
-
// composer.addPass(ef);
|
201
|
-
}
|
202
219
|
}
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
const pass = new EffectPass(cam, ...effects);
|
207
|
-
pass.name = effects.map(e => e.name).join(" ");
|
208
|
-
pass.mainScene = scene;
|
209
|
-
pass.enabled = true;
|
210
|
-
composer.addPass(pass);
|
220
|
+
catch (e) {
|
221
|
+
console.error("Error while applying postprocessing effects", e);
|
222
|
+
composer.removeAllPasses();
|
211
223
|
}
|
212
224
|
}
|
213
|
-
catch(e) {
|
214
|
-
console.error("Error while applying postprocessing effects", e);
|
215
|
-
composer.removeAllPasses();
|
216
|
-
}
|
217
|
-
}
|
218
225
|
else {
|
219
226
|
for (const ef of effectsOrPasses) {
|
220
227
|
if (ef instanceof Effect)
|
@@ -237,10 +244,19 @@
|
|
237
244
|
// TODO: enforce correct order of effects (e.g. DOF before Bloom)
|
238
245
|
const effects = this._effects;
|
239
246
|
effects.sort((a, b) => {
|
240
|
-
|
241
|
-
|
247
|
+
// we use find index here because sometimes constructor names are prefixed with `_`
|
248
|
+
// 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)
|
249
|
+
const aidx = effectsOrder.findIndex(e => a.constructor.name.endsWith(e.name));
|
250
|
+
const bidx = effectsOrder.findIndex(e => b.constructor.name.endsWith(e.name));
|
242
251
|
// Unknown effects should be rendered first
|
243
|
-
if (aidx < 0
|
252
|
+
if (aidx < 0) {
|
253
|
+
if (debug) console.warn("Unknown effect found: ", a.constructor.name);
|
254
|
+
return -1;
|
255
|
+
}
|
256
|
+
else if (bidx < 0) {
|
257
|
+
if (debug) console.warn("Unknown effect found: ", b.constructor.name);
|
258
|
+
return 1;
|
259
|
+
}
|
244
260
|
if (aidx < 0) return 1;
|
245
261
|
if (bidx < 0) return -1;
|
246
262
|
return aidx - bidx;
|
@@ -268,10 +284,10 @@
|
|
268
284
|
BloomEffect,
|
269
285
|
SelectiveBloomEffect,
|
270
286
|
VignetteEffect,
|
271
|
-
|
287
|
+
PixelationEffect,
|
288
|
+
SharpeningEffect,
|
289
|
+
_TonemappingEffect,
|
272
290
|
HueSaturationEffect,
|
273
291
|
BrightnessContrastEffect,
|
274
|
-
|
275
|
-
SharpeningEffect,
|
276
|
-
Antialiasing
|
292
|
+
Antialiasing,
|
277
293
|
];
|
@@ -33,7 +33,7 @@
|
|
33
33
|
import { BasicIKConstraint } from "../../engine-components/BasicIKConstraint.js";
|
34
34
|
import { BehaviorExtension } from "../../engine-components/export/usdz/extensions/behavior/Behaviour.js";
|
35
35
|
import { BehaviorModel } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js";
|
36
|
-
import {
|
36
|
+
import { BloomEffect } from "../../engine-components/postprocessing/Effects/BloomEffect.js";
|
37
37
|
import { BoxCollider } from "../../engine-components/Collider.js";
|
38
38
|
import { BoxGizmo } from "../../engine-components/Gizmos.js";
|
39
39
|
import { BoxHelperComponent } from "../../engine-components/BoxHelperComponent.js";
|
@@ -186,7 +186,7 @@
|
|
186
186
|
import { TextExtension } from "../../engine-components/export/usdz/extensions/USDZText.js";
|
187
187
|
import { TextureSheetAnimationModule } from "../../engine-components/ParticleSystemModules.js";
|
188
188
|
import { TiltShiftEffect } from "../../engine-components/postprocessing/Effects/TiltShiftEffect.js";
|
189
|
-
import {
|
189
|
+
import { ToneMappingEffect } from "../../engine-components/postprocessing/Effects/Tonemapping.js";
|
190
190
|
import { TrailModule } from "../../engine-components/ParticleSystemModules.js";
|
191
191
|
import { TransformData } from "../../engine-components/export/usdz/extensions/Animation.js";
|
192
192
|
import { TransformGizmo } from "../../engine-components/TransformGizmo.js";
|
@@ -254,7 +254,7 @@
|
|
254
254
|
TypeStore.add("BasicIKConstraint", BasicIKConstraint);
|
255
255
|
TypeStore.add("BehaviorExtension", BehaviorExtension);
|
256
256
|
TypeStore.add("BehaviorModel", BehaviorModel);
|
257
|
-
TypeStore.add("
|
257
|
+
TypeStore.add("BloomEffect", BloomEffect);
|
258
258
|
TypeStore.add("BoxCollider", BoxCollider);
|
259
259
|
TypeStore.add("BoxGizmo", BoxGizmo);
|
260
260
|
TypeStore.add("BoxHelperComponent", BoxHelperComponent);
|
@@ -407,7 +407,7 @@
|
|
407
407
|
TypeStore.add("TextExtension", TextExtension);
|
408
408
|
TypeStore.add("TextureSheetAnimationModule", TextureSheetAnimationModule);
|
409
409
|
TypeStore.add("TiltShiftEffect", TiltShiftEffect);
|
410
|
-
TypeStore.add("
|
410
|
+
TypeStore.add("ToneMappingEffect", ToneMappingEffect);
|
411
411
|
TypeStore.add("TrailModule", TrailModule);
|
412
412
|
TypeStore.add("TransformData", TransformData);
|
413
413
|
TypeStore.add("TransformGizmo", TransformGizmo);
|
@@ -14,19 +14,19 @@
|
|
14
14
|
}
|
15
15
|
|
16
16
|
@serializable(VolumeParameter)
|
17
|
-
intensity
|
17
|
+
readonly intensity: VolumeParameter = new VolumeParameter(2);
|
18
18
|
|
19
19
|
@serializable(VolumeParameter)
|
20
|
-
falloff
|
20
|
+
readonly falloff: VolumeParameter = new VolumeParameter(1);
|
21
21
|
|
22
22
|
@serializable(VolumeParameter)
|
23
|
-
samples
|
23
|
+
readonly samples: VolumeParameter = new VolumeParameter(9);
|
24
24
|
|
25
25
|
@serializable(VolumeParameter)
|
26
|
-
color
|
26
|
+
readonly color: VolumeParameter = new VolumeParameter(new Color(0, 0, 0));
|
27
27
|
|
28
28
|
@serializable(VolumeParameter)
|
29
|
-
luminanceInfluence
|
29
|
+
readonly luminanceInfluence: VolumeParameter = new VolumeParameter(.7);
|
30
30
|
|
31
31
|
onBeforeRender() {
|
32
32
|
if (this._ssao && this.context.mainCamera instanceof PerspectiveCamera) {
|
@@ -56,35 +56,41 @@
|
|
56
56
|
`
|
57
57
|
|
58
58
|
const frag = `
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
uniform float radius;
|
59
|
+
uniform sampler2D tDiffuse;
|
60
|
+
uniform float amount;
|
61
|
+
uniform float radius;
|
63
62
|
|
64
|
-
|
63
|
+
void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {
|
65
64
|
float tx = 1.0 / resolution.x;
|
66
65
|
float ty = 1.0 / resolution.y;
|
67
66
|
vec2 texelSize = vec2(tx, ty);
|
68
67
|
|
69
|
-
vec4 color = texture2D(tDiffuse, uv);
|
70
68
|
vec4 blurred = vec4(0.0);
|
71
69
|
float total = 0.0;
|
70
|
+
|
72
71
|
for (float x = -radius; x <= radius; x++) {
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
72
|
+
for (float y = -radius; y <= radius; y++) {
|
73
|
+
vec2 offset = vec2(x, y) * texelSize;
|
74
|
+
vec4 diffuse = texture2D(tDiffuse, uv + offset);
|
75
|
+
float weight = exp(-length(offset) * amount);
|
76
|
+
blurred += diffuse * weight;
|
77
|
+
total += weight;
|
78
|
+
}
|
80
79
|
}
|
81
|
-
|
80
|
+
|
81
|
+
if (total > 0.0) {
|
82
|
+
blurred /= total;
|
83
|
+
}
|
84
|
+
|
85
|
+
// Calculate the sharpened color using inputColor
|
82
86
|
vec4 sharp = inputColor + (inputColor - blurred) * amount;
|
83
|
-
|
84
|
-
//
|
85
|
-
|
86
|
-
|
87
|
+
|
88
|
+
// Ensure the sharp color does not go below 0 or above 1
|
89
|
+
sharp = clamp(sharp, 0.0, 1.0);
|
90
|
+
|
91
|
+
outputColor = sharp;
|
87
92
|
}
|
93
|
+
|
88
94
|
`
|
89
95
|
|
90
96
|
class _SharpeningEffect extends Effect {
|
@@ -93,8 +99,8 @@
|
|
93
99
|
vertexShader: vert,
|
94
100
|
blendFunction: BlendFunction.NORMAL,
|
95
101
|
uniforms: new Map<string, Uniform<any>>([
|
96
|
-
["amount", new Uniform(
|
97
|
-
["radius", new Uniform(
|
102
|
+
["amount", new Uniform(1)],
|
103
|
+
["radius", new Uniform(1)],
|
98
104
|
// ["threshold", new Uniform(0)],
|
99
105
|
]),
|
100
106
|
});
|
@@ -36,8 +36,6 @@
|
|
36
36
|
|
37
37
|
onCreateEffect(): EffectProviderResult | undefined {
|
38
38
|
|
39
|
-
console.log(this);
|
40
|
-
|
41
39
|
const effect = new TiltShift({
|
42
40
|
kernelSize: KernelSize.VERY_LARGE,
|
43
41
|
});
|
@@ -1,12 +1,17 @@
|
|
1
|
+
import { ToneMappingEffect as _TonemappingEffect, ToneMappingMode } from "postprocessing";
|
1
2
|
import { ACESFilmicToneMapping, AgXToneMapping, LinearToneMapping, NeutralToneMapping, NoToneMapping, ReinhardToneMapping } from "three";
|
2
3
|
|
3
4
|
import { serializable } from "../../../engine/engine_serialization.js";
|
4
|
-
import {
|
5
|
+
import { getParam } from "../../../engine/engine_utils.js";
|
6
|
+
import { EffectProviderResult, PostProcessingEffect } from "../PostProcessingEffect.js";
|
7
|
+
import { findPostProcessingManager } from "../utils.js";
|
5
8
|
import { VolumeParameter } from "../VolumeParameter.js";
|
6
9
|
import { registerCustomEffectType } from "../VolumeProfile.js";
|
7
10
|
|
11
|
+
const debug = getParam("debugpost");
|
8
12
|
|
9
|
-
|
13
|
+
|
14
|
+
enum NEToneMappingMode {
|
10
15
|
None = 0,
|
11
16
|
Neutral = 1, // Neutral tonemapper, close to Reinhard
|
12
17
|
ACES = 2, // ACES Filmic reference tonemapper (custom approximation)
|
@@ -14,52 +19,119 @@
|
|
14
19
|
KhronosNeutral = 4, // PBR Neural tonemapper
|
15
20
|
}
|
16
21
|
|
17
|
-
|
22
|
+
type NEToneMappingModeNames = keyof typeof NEToneMappingMode;
|
18
23
|
|
24
|
+
|
25
|
+
function toThreeToneMapping(mode: NEToneMappingMode | undefined) {
|
26
|
+
switch (mode) {
|
27
|
+
case NEToneMappingMode.None:
|
28
|
+
return LinearToneMapping;
|
29
|
+
case NEToneMappingMode.Neutral:
|
30
|
+
return ReinhardToneMapping;
|
31
|
+
case NEToneMappingMode.ACES:
|
32
|
+
return ACESFilmicToneMapping;
|
33
|
+
case NEToneMappingMode.AgX:
|
34
|
+
return AgXToneMapping;
|
35
|
+
case NEToneMappingMode.KhronosNeutral:
|
36
|
+
return NeutralToneMapping;
|
37
|
+
default:
|
38
|
+
return NeutralToneMapping;
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
function threeToNeToneMapping(mode: number | undefined): NEToneMappingMode {
|
43
|
+
switch (mode) {
|
44
|
+
case LinearToneMapping: return NEToneMappingMode.None;
|
45
|
+
case ACESFilmicToneMapping: return NEToneMappingMode.ACES;
|
46
|
+
case AgXToneMapping: return NEToneMappingMode.AgX;
|
47
|
+
case NeutralToneMapping: return NEToneMappingMode.Neutral;
|
48
|
+
case ReinhardToneMapping: return NEToneMappingMode.Neutral;
|
49
|
+
default: return NEToneMappingMode.None;
|
50
|
+
}
|
51
|
+
|
52
|
+
}
|
53
|
+
|
54
|
+
|
55
|
+
function threeToneMappingToEffectMode(mode: number | undefined): ToneMappingMode {
|
56
|
+
switch (mode) {
|
57
|
+
case LinearToneMapping: return ToneMappingMode.LINEAR;
|
58
|
+
case ACESFilmicToneMapping: return ToneMappingMode.ACES_FILMIC;
|
59
|
+
case AgXToneMapping: return ToneMappingMode.AGX;
|
60
|
+
case NeutralToneMapping: return ToneMappingMode.NEUTRAL;
|
61
|
+
case ReinhardToneMapping: return ToneMappingMode.REINHARD;
|
62
|
+
default: return ToneMappingMode.LINEAR;
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
|
67
|
+
export class ToneMappingEffect extends PostProcessingEffect {
|
68
|
+
|
19
69
|
get typeName() {
|
20
70
|
return "ToneMapping";
|
21
71
|
}
|
22
72
|
|
23
73
|
@serializable(VolumeParameter)
|
24
|
-
mode: VolumeParameter
|
74
|
+
readonly mode: VolumeParameter = new VolumeParameter(undefined);
|
25
75
|
|
76
|
+
setMode(mode: NEToneMappingModeNames) {
|
77
|
+
const enumValue = NEToneMappingMode[mode as NEToneMappingModeNames];
|
78
|
+
if (enumValue === undefined) {
|
79
|
+
console.error("Invalid ToneMapping mode", mode);
|
80
|
+
return this;
|
81
|
+
}
|
82
|
+
this.mode.value = enumValue;
|
83
|
+
return this;
|
84
|
+
}
|
85
|
+
|
26
86
|
get isToneMapping() { return true; }
|
27
87
|
|
28
|
-
|
29
|
-
|
30
|
-
|
88
|
+
onEffectEnabled(): void {
|
89
|
+
// Tonemapping works with and without a postprocessing manager.
|
90
|
+
// If there's no manager already in the scene we don't need to create one because tonemapping can also be applied without a postprocessing pass
|
91
|
+
const ppmanager = findPostProcessingManager(this);
|
92
|
+
if (!ppmanager) return;
|
93
|
+
super.onEffectEnabled(ppmanager);
|
31
94
|
}
|
32
95
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
96
|
+
onCreateEffect(): EffectProviderResult | undefined {
|
97
|
+
// TODO: this should be done in the PostProcessingHandler
|
98
|
+
if (this.postprocessingContext) {
|
99
|
+
for (const other of this.postprocessingContext.components) {
|
100
|
+
// If we're the first tonemapping effect it's all good
|
101
|
+
if (other === this) break;
|
102
|
+
// If another tonemapping effect is found, warn the user
|
103
|
+
if (other != this && other instanceof ToneMappingEffect) {
|
104
|
+
console.warn("Multiple tonemapping effects found in the same postprocessing stack: Please check your scene setup.", { activeEffect: other, ignoredEffect: this });
|
105
|
+
return undefined;
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
38
109
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
110
|
+
// ensure the effect tonemapping value is initialized
|
111
|
+
if (this.mode.isInitialized == false) {
|
112
|
+
const init = threeToNeToneMapping(this.context.renderer.toneMapping);
|
113
|
+
this.mode.initialize(init);
|
114
|
+
}
|
115
|
+
|
116
|
+
const threeMode = toThreeToneMapping(this.mode.value);
|
117
|
+
const tonemapping = new _TonemappingEffect({
|
118
|
+
mode: threeToneMappingToEffectMode(threeMode),
|
119
|
+
});
|
120
|
+
this.mode.onValueChanged = (newValue) => {
|
121
|
+
const threeMode = toThreeToneMapping(newValue);
|
122
|
+
tonemapping.mode = threeToneMappingToEffectMode(threeMode);
|
123
|
+
if (debug) console.log("ToneMapping mode changed to", newValue, NEToneMappingMode[threeMode], ToneMappingMode[tonemapping.mode]);
|
124
|
+
};
|
125
|
+
if (debug) console.log("Use ToneMapping", this.context.renderer.toneMapping, this.mode.value, NEToneMappingMode[threeMode], ToneMappingMode[tonemapping.mode]);
|
126
|
+
return tonemapping;
|
44
127
|
}
|
45
128
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
return LinearToneMapping;
|
50
|
-
case TonemappingMode.Neutral:
|
51
|
-
return ReinhardToneMapping;
|
52
|
-
case TonemappingMode.ACES:
|
53
|
-
return ACESFilmicToneMapping;
|
54
|
-
case TonemappingMode.AgX:
|
55
|
-
return AgXToneMapping;
|
56
|
-
case TonemappingMode.KhronosNeutral:
|
57
|
-
return NeutralToneMapping;
|
58
|
-
default:
|
59
|
-
return NeutralToneMapping;
|
60
|
-
}
|
129
|
+
|
130
|
+
onBeforeRender(): void {
|
131
|
+
this.context.renderer.toneMapping = toThreeToneMapping(this.mode.value);
|
61
132
|
}
|
62
133
|
|
134
|
+
|
63
135
|
}
|
64
136
|
|
65
|
-
registerCustomEffectType("Tonemapping",
|
137
|
+
registerCustomEffectType("Tonemapping", ToneMappingEffect);
|
@@ -22,18 +22,21 @@
|
|
22
22
|
PostprocessingManagerType = type;
|
23
23
|
}
|
24
24
|
|
25
|
-
export function
|
26
|
-
let manager: IPostProcessingManager | null = null;
|
25
|
+
export function findPostProcessingManager(effect: PostProcessingEffect): IPostProcessingManager | null {
|
27
26
|
let obj = effect.gameObject as Object3D | null;
|
28
27
|
while (obj) {
|
29
28
|
for (const comp of foreachComponentEnumerator(obj)) {
|
30
29
|
if ((comp as unknown as IPostProcessingManager).isPostProcessingManager === true) {
|
31
|
-
|
32
|
-
break;
|
30
|
+
return comp as unknown as IPostProcessingManager;
|
33
31
|
}
|
34
32
|
}
|
35
33
|
obj = obj.parent;
|
36
34
|
}
|
35
|
+
return null;
|
36
|
+
}
|
37
|
+
|
38
|
+
export function getPostProcessingManager(effect: PostProcessingEffect): IPostProcessingManager | null {
|
39
|
+
let manager: IPostProcessingManager | null = findPostProcessingManager(effect);
|
37
40
|
if (!manager) {
|
38
41
|
if (PostprocessingManagerType) {
|
39
42
|
if (debug)
|
@@ -99,7 +99,7 @@
|
|
99
99
|
/** @internal */
|
100
100
|
awake() {
|
101
101
|
if (debug) {
|
102
|
-
console.log(this);
|
102
|
+
console.log("PostprocessingManager Awake", this);
|
103
103
|
console.log("Press P to toggle post processing");
|
104
104
|
window.addEventListener("keydown", (e) => {
|
105
105
|
if (e.key === "p") {
|
@@ -159,7 +159,7 @@
|
|
159
159
|
private _isDirty: boolean = false;
|
160
160
|
|
161
161
|
private apply() {
|
162
|
-
if (debug) console.log("Apply PostProcessing"
|
162
|
+
if (debug) console.log("Apply PostProcessing " + this.name);
|
163
163
|
|
164
164
|
if (isDevEnvironment()) {
|
165
165
|
if (this._lastApplyTime !== undefined && Date.now() - this._lastApplyTime < 100) {
|
@@ -198,7 +198,7 @@
|
|
198
198
|
}
|
199
199
|
|
200
200
|
private unapply() {
|
201
|
-
if (debug) console.log("Unapply PostProcessing"
|
201
|
+
if (debug) console.log("Unapply PostProcessing " + this.name);
|
202
202
|
this._postprocessing?.unapply();
|
203
203
|
}
|
204
204
|
|
@@ -1,6 +1,9 @@
|
|
1
|
-
import { serializable } from "../../engine/engine_serialization.js";
|
1
|
+
import { deserializeObject, serializable,SerializationContext } from "../../engine/engine_serialization.js";
|
2
|
+
import { TypeSerializer } from "../../engine/engine_serialization_core.js";
|
2
3
|
import { getParam } from "../../engine/engine_utils.js";
|
3
4
|
|
5
|
+
|
6
|
+
|
4
7
|
export declare type VolumeParameterChangedEvent = (newValue: any, oldValue: any, parameter: VolumeParameter) => void;
|
5
8
|
export declare type VolumeParameterValueProcessor = (value: any) => any;
|
6
9
|
|
@@ -8,12 +11,25 @@
|
|
8
11
|
|
9
12
|
export class VolumeParameter {
|
10
13
|
|
14
|
+
readonly isVolumeParameter = true;
|
15
|
+
|
11
16
|
constructor(value?: any) {
|
12
|
-
|
13
|
-
|
14
|
-
this._valueRaw = value;
|
17
|
+
if (value !== undefined)
|
18
|
+
this.initialize(value);
|
15
19
|
}
|
16
20
|
|
21
|
+
private _isInitialized: boolean = false;
|
22
|
+
get isInitialized() { return this._isInitialized; }
|
23
|
+
|
24
|
+
initialize(value?: any) {
|
25
|
+
if (value !== undefined) {
|
26
|
+
this._value = value;
|
27
|
+
this._defaultValue = value;
|
28
|
+
this._valueRaw = value;
|
29
|
+
this._isInitialized = true;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
17
33
|
@serializable()
|
18
34
|
get overrideState(): boolean {
|
19
35
|
return this._active;
|
@@ -73,8 +89,6 @@
|
|
73
89
|
}
|
74
90
|
else hasChanged = false;
|
75
91
|
}
|
76
|
-
if (hasChanged)
|
77
|
-
console.log("VolumeParameter: value changed from", oldValue, "to", val);
|
78
92
|
}
|
79
93
|
|
80
94
|
if (!this._active && this._defaultValue !== undefined) {
|
@@ -109,3 +123,38 @@
|
|
109
123
|
return true;
|
110
124
|
}
|
111
125
|
}
|
126
|
+
|
127
|
+
|
128
|
+
|
129
|
+
class VolumeParameterSerializer extends TypeSerializer {
|
130
|
+
constructor() {
|
131
|
+
super([VolumeParameter]);
|
132
|
+
}
|
133
|
+
onSerialize(_data: any, _context: SerializationContext) {
|
134
|
+
}
|
135
|
+
onDeserialize(data: { value: any, overrideState: boolean }, context: SerializationContext) {
|
136
|
+
const target = context.target;
|
137
|
+
const name = context.path;
|
138
|
+
|
139
|
+
let parameter: VolumeParameter | undefined;
|
140
|
+
if (target && name) {
|
141
|
+
parameter = target[name];
|
142
|
+
}
|
143
|
+
|
144
|
+
if (!(typeof parameter === "object") || (typeof parameter === "object" && (parameter as VolumeParameter).isVolumeParameter !== true)) {
|
145
|
+
parameter = new VolumeParameter();
|
146
|
+
}
|
147
|
+
|
148
|
+
if (typeof data === "object" && "value" in data) {
|
149
|
+
const value = data.value;
|
150
|
+
parameter.value = value;
|
151
|
+
parameter.overrideState = data.overrideState;
|
152
|
+
}
|
153
|
+
else {
|
154
|
+
parameter.value = data;
|
155
|
+
}
|
156
|
+
|
157
|
+
return parameter;
|
158
|
+
}
|
159
|
+
}
|
160
|
+
new VolumeParameterSerializer();
|
@@ -269,7 +269,8 @@
|
|
269
269
|
private onAvatarSpawned = (instance: GameObject) => {
|
270
270
|
// spawned webxr avatars must have a avatar component
|
271
271
|
if (debug) console.log("WebXR.onAvatarSpawned", instance);
|
272
|
-
GameObject.
|
272
|
+
let avatar = GameObject.getComponentInChildren(instance, Avatar);
|
273
|
+
avatar ??= GameObject.addComponent(instance, Avatar)!;
|
273
274
|
};
|
274
275
|
|
275
276
|
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import { BlendFunction, BloomEffect as _BloomEffect, SelectiveBloomEffect } from "postprocessing";
|
2
|
+
import { MathUtils } from "three";
|
3
|
+
|
4
|
+
import { serializable } from "../../../engine/engine_serialization.js";
|
5
|
+
import { PostProcessingEffect } from "../PostProcessingEffect.js";
|
6
|
+
import { VolumeParameter } from "../VolumeParameter.js";
|
7
|
+
import { registerCustomEffectType } from "../VolumeProfile.js";
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Bloom can be used to make bright areas in the scene glow.
|
11
|
+
* @link Sample https://engine.needle.tools/samples/postprocessing
|
12
|
+
* @example
|
13
|
+
* ```typescript
|
14
|
+
* const bloom = new Bloom();
|
15
|
+
* bloom.intensity.value = 1.5;
|
16
|
+
* bloom.threshold.value = 0.5;
|
17
|
+
* bloom.scatter.value = 0.5;
|
18
|
+
* volume.add(bloom);
|
19
|
+
* ```
|
20
|
+
*/
|
21
|
+
export class BloomEffect extends PostProcessingEffect {
|
22
|
+
|
23
|
+
/** Whether to use selective bloom by default */
|
24
|
+
static useSelectiveBloom = false;
|
25
|
+
|
26
|
+
get typeName() {
|
27
|
+
return "Bloom";
|
28
|
+
}
|
29
|
+
|
30
|
+
/**
|
31
|
+
* The bloom threshold controls at what brightness level the bloom effect will be applied.
|
32
|
+
* A higher value means the bloom will be applied to brighter areas or lights only
|
33
|
+
* @default 0.9
|
34
|
+
*/
|
35
|
+
@serializable(VolumeParameter)
|
36
|
+
readonly threshold: VolumeParameter = new VolumeParameter(.9);
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Intensity of the bloom effect. A higher value will increase the intensity of the bloom effect.
|
40
|
+
* @default 1
|
41
|
+
*/
|
42
|
+
@serializable(VolumeParameter)
|
43
|
+
readonly intensity: VolumeParameter = new VolumeParameter(1);
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Scatter value. The higher the value, the more the bloom will scatter.
|
47
|
+
* @default 0.3
|
48
|
+
*/
|
49
|
+
@serializable(VolumeParameter)
|
50
|
+
readonly scatter: VolumeParameter = new VolumeParameter(.3);
|
51
|
+
|
52
|
+
/**
|
53
|
+
* Set to true to use selective bloom when the effect gets created.
|
54
|
+
* @default false
|
55
|
+
*/
|
56
|
+
selectiveBloom?: boolean;
|
57
|
+
|
58
|
+
init() {
|
59
|
+
this.threshold.valueProcessor = (v: number) => v;
|
60
|
+
this.intensity.valueProcessor = (v: number) => v;
|
61
|
+
this.scatter.valueProcessor = (v: number) => v;
|
62
|
+
}
|
63
|
+
|
64
|
+
onCreateEffect() {
|
65
|
+
let bloom: _BloomEffect;
|
66
|
+
|
67
|
+
if (this.selectiveBloom == undefined) {
|
68
|
+
this.selectiveBloom = BloomEffect.useSelectiveBloom;
|
69
|
+
}
|
70
|
+
|
71
|
+
if (this.selectiveBloom) {
|
72
|
+
// https://github.com/pmndrs/postprocessing/blob/64d2829f014cfec97a46bf3c109f3abc55af0715/demo/src/demos/BloomDemo.js#L265
|
73
|
+
const selectiveBloom = bloom = new SelectiveBloomEffect(this.context.scene, this.context.mainCamera!, {
|
74
|
+
blendFunction: BlendFunction.ADD,
|
75
|
+
mipmapBlur: true,
|
76
|
+
luminanceThreshold: this.threshold.value,
|
77
|
+
luminanceSmoothing: this.scatter.value,
|
78
|
+
radius: 0.85, // default value
|
79
|
+
intensity: this.intensity.value,
|
80
|
+
});
|
81
|
+
selectiveBloom.inverted = true;
|
82
|
+
}
|
83
|
+
else {
|
84
|
+
bloom = new _BloomEffect({
|
85
|
+
blendFunction: BlendFunction.ADD,
|
86
|
+
mipmapBlur: true,
|
87
|
+
luminanceThreshold: this.threshold.value,
|
88
|
+
luminanceSmoothing: this.scatter.value,
|
89
|
+
radius: 0.85, // default value
|
90
|
+
intensity: this.intensity.value,
|
91
|
+
});
|
92
|
+
}
|
93
|
+
|
94
|
+
this.intensity.onValueChanged = newValue => {
|
95
|
+
bloom!.intensity = newValue;
|
96
|
+
};
|
97
|
+
this.threshold.onValueChanged = newValue => {
|
98
|
+
// for some reason the threshold needs to be gamma-corrected
|
99
|
+
bloom!.luminanceMaterial.threshold = Math.pow(newValue, 2.2);
|
100
|
+
};
|
101
|
+
this.scatter.onValueChanged = newValue => {
|
102
|
+
bloom!.luminancePass.enabled = true;
|
103
|
+
bloom!.luminanceMaterial.smoothing = newValue;
|
104
|
+
if (bloom["mipmapBlurPass"])
|
105
|
+
// heuristic so it looks similar to "scatter" in other engines
|
106
|
+
bloom!["mipmapBlurPass"].radius = MathUtils.lerp(0.1, 0.9, newValue);
|
107
|
+
};
|
108
|
+
|
109
|
+
return bloom;
|
110
|
+
}
|
111
|
+
|
112
|
+
}
|
113
|
+
registerCustomEffectType("Bloom", BloomEffect);
|