@call-me-sensei/toonlab 0.1.1
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/ATTRIBUTION.md +40 -0
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/package.json +97 -0
- package/src/character/characterRig.js +353 -0
- package/src/character/freestyleSwimClip.js +541 -0
- package/src/character/index.js +4 -0
- package/src/character/modelLoader.js +433 -0
- package/src/core/materialRoles.js +438 -0
- package/src/core/presetDocuments.js +110 -0
- package/src/core/shaderBackend.js +24 -0
- package/src/debrisgen/debrisFields.js +544 -0
- package/src/debrisgen/debrisGenerator.js +1929 -0
- package/src/debrisgen/debrisPalettes.js +71 -0
- package/src/debrisgen/debrisPhysics.js +198 -0
- package/src/debrisgen/debrisPresets.js +155 -0
- package/src/debrisgen/debrisSettings.js +333 -0
- package/src/debrisgen/debrisTextures.js +380 -0
- package/src/debrisgen/index.js +5 -0
- package/src/debug/fieldValues.js +95 -0
- package/src/debug/index.js +7 -0
- package/src/debug/settingsPanel.js +170 -0
- package/src/environment/environmentAmbientProbe.js +160 -0
- package/src/environment/environmentMaterialAdapter.js +336 -0
- package/src/environment/environmentMaterialClassifier.js +178 -0
- package/src/environment/environmentPlanarReflection.js +201 -0
- package/src/environment/environmentPresets.js +443 -0
- package/src/environment/environmentRigs.js +684 -0
- package/src/environment/environmentSettings.js +504 -0
- package/src/environment/environmentShaderMaterials.js +318 -0
- package/src/environment/environmentSunShadowPass.js +268 -0
- package/src/environment/environmentTextureResolver.js +161 -0
- package/src/environment/environmentTimeOfDay.js +167 -0
- package/src/environment/environmentVertexAo.js +188 -0
- package/src/environment/index.js +8 -0
- package/src/environment/scanAssetStylize.js +103 -0
- package/src/index.js +21 -0
- package/src/loaders/index.js +1 -0
- package/src/post/index.js +2 -0
- package/src/post/postProcessing.js +1087 -0
- package/src/rockgen/export/glbExport.js +177 -0
- package/src/rockgen/heightfield/heightfieldErosion.js +7 -0
- package/src/rockgen/heightfield/heightfieldPatch.js +183 -0
- package/src/rockgen/heightfield/stylizedErosionSim.js +356 -0
- package/src/rockgen/index.js +14 -0
- package/src/rockgen/mesh/meshAttributes.js +374 -0
- package/src/rockgen/mesh/meshDocument.js +158 -0
- package/src/rockgen/mesh/surfaceNets.js +329 -0
- package/src/rockgen/noise/cellularNoise3.js +92 -0
- package/src/rockgen/noise/prng.js +62 -0
- package/src/rockgen/noise/simplexNoise3.js +89 -0
- package/src/rockgen/noise/valueNoise3.js +87 -0
- package/src/rockgen/rockDocument.js +255 -0
- package/src/rockgen/rockHelpers.js +8 -0
- package/src/rockgen/rockgenPresets.js +559 -0
- package/src/rockgen/rockgenSettings.js +756 -0
- package/src/rockgen/sdf/fieldCompiler.js +544 -0
- package/src/rockgen/sdf/sculptEdits.js +66 -0
- package/src/rockgen/sdf/sdfModifiers.js +45 -0
- package/src/rockgen/sdf/sdfOps.js +37 -0
- package/src/rockgen/sdf/sdfPrimitives.js +88 -0
- package/src/shaders-tsl/anime.js +963 -0
- package/src/shaders-tsl/chunks/character-color.js +64 -0
- package/src/shaders-tsl/chunks/character-highlights.js +174 -0
- package/src/shaders-tsl/chunks/character-lighting.js +309 -0
- package/src/shaders-tsl/chunks/character-material-maps.js +146 -0
- package/src/shaders-tsl/chunks/character-roles.js +52 -0
- package/src/shaders-tsl/chunks/character-scene-lights.js +270 -0
- package/src/shaders-tsl/chunks/character-shadow-color.js +61 -0
- package/src/shaders-tsl/chunks/character-skinning.js +140 -0
- package/src/shaders-tsl/chunks/environment-color.js +47 -0
- package/src/shaders-tsl/chunks/environment-debug.js +79 -0
- package/src/shaders-tsl/chunks/environment-lighting.js +260 -0
- package/src/shaders-tsl/chunks/environment-sun-shadow.js +87 -0
- package/src/shaders-tsl/chunks/foliage-fog.js +60 -0
- package/src/shaders-tsl/chunks/pass-depth-color.js +89 -0
- package/src/shaders-tsl/chunks/stylized-cloud-shadow.js +70 -0
- package/src/shaders-tsl/chunks/water-color.js +190 -0
- package/src/shaders-tsl/chunks/water-common.js +124 -0
- package/src/shaders-tsl/chunks/water-foam.js +93 -0
- package/src/shaders-tsl/chunks/water-lighting.js +116 -0
- package/src/shaders-tsl/chunks/water-ripple.js +64 -0
- package/src/shaders-tsl/chunks/water-waves.js +100 -0
- package/src/shaders-tsl/environment-ao-overlay.js +60 -0
- package/src/shaders-tsl/environment.js +680 -0
- package/src/shaders-tsl/flower.js +267 -0
- package/src/shaders-tsl/grass.js +193 -0
- package/src/shaders-tsl/post-composite.js +461 -0
- package/src/shaders-tsl/sky.js +175 -0
- package/src/shaders-tsl/tree-leaf.js +264 -0
- package/src/shaders-tsl/water-breaker.js +423 -0
- package/src/shaders-tsl/water-kelp.js +171 -0
- package/src/shaders-tsl/water-rain.js +107 -0
- package/src/shaders-tsl/water-simulation.js +140 -0
- package/src/shaders-tsl/water-splash.js +298 -0
- package/src/shaders-tsl/water.js +509 -0
- package/src/sky/index.js +2 -0
- package/src/sky/stylizedSky.js +341 -0
- package/src/toon/characterRenderPasses.js +646 -0
- package/src/toon/index.js +4 -0
- package/src/toon/settings/alphaSettings.js +170 -0
- package/src/toon/settings/averageShadowSettings.js +181 -0
- package/src/toon/settings/baseTextureSettings.js +133 -0
- package/src/toon/settings/celShadeSettings.js +96 -0
- package/src/toon/settings/contactShadowSettings.js +114 -0
- package/src/toon/settings/eyeHighlightSettings.js +187 -0
- package/src/toon/settings/faceLightingSettings.js +135 -0
- package/src/toon/settings/furSettings.js +112 -0
- package/src/toon/settings/glitterSettings.js +102 -0
- package/src/toon/settings/hairHighlightSettings.js +325 -0
- package/src/toon/settings/indirectLightSettings.js +244 -0
- package/src/toon/settings/localLightSettings.js +176 -0
- package/src/toon/settings/materialMapSettings.js +299 -0
- package/src/toon/settings/outlineSettings.js +263 -0
- package/src/toon/settings/perspectiveRemovalSettings.js +60 -0
- package/src/toon/settings/rimLightSettings.js +268 -0
- package/src/toon/settings/sceneShadowSettings.js +147 -0
- package/src/toon/settings/selfShadowSettings.js +221 -0
- package/src/toon/settings/shadowColorSettings.js +238 -0
- package/src/toon/settings/skinToneSettings.js +150 -0
- package/src/toon/settings/specularSettings.js +314 -0
- package/src/toon/settings/stickerSettings.js +102 -0
- package/src/toon/toonMaterialAdapter.js +2235 -0
- package/src/toon/toonSettings.js +860 -0
- package/src/vegetation/flowerSpecies.js +260 -0
- package/src/vegetation/index.js +10 -0
- package/src/vegetation/stylizedBush.js +136 -0
- package/src/vegetation/stylizedFlower.js +213 -0
- package/src/vegetation/stylizedFlowers.js +297 -0
- package/src/vegetation/stylizedGrass.js +491 -0
- package/src/vegetation/stylizedTree.js +2449 -0
- package/src/vegetation/stylizedTreeFoliage.js +691 -0
- package/src/vegetation/treeExport.js +300 -0
- package/src/vegetation/treeRecipe.js +647 -0
- package/src/water/index.js +2 -0
- package/src/water/water.js +20 -0
- package/src/water/waterBreakerSystem.js +591 -0
- package/src/water/waterInteraction.js +193 -0
- package/src/water/waterMaterial.js +263 -0
- package/src/water/waterRain.js +136 -0
- package/src/water/waterRippleSimulation.js +227 -0
- package/src/water/waterScenePasses.js +415 -0
- package/src/water/waterSettings.js +1138 -0
- package/src/water/waterSplashSystem.js +251 -0
- package/src/water/waterSurface.js +475 -0
- package/src/water/waterVegetation.js +83 -0
|
@@ -0,0 +1,1087 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
import { createPassDepthColorMaterial } from '../shaders-tsl/chunks/pass-depth-color.js';
|
|
4
|
+
import {
|
|
5
|
+
createBloomDownsampleNodeMaterial,
|
|
6
|
+
createBloomPrefilterNodeMaterial,
|
|
7
|
+
createBloomUpsampleNodeMaterial,
|
|
8
|
+
createPostCompositeNodeMaterial,
|
|
9
|
+
fallbackPostTexture,
|
|
10
|
+
} from '../shaders-tsl/post-composite.js';
|
|
11
|
+
import {
|
|
12
|
+
createSettingsPresetDocument,
|
|
13
|
+
validateSettingsPresetDocument,
|
|
14
|
+
} from '../core/presetDocuments.js';
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_POST_PROCESSING_FEATURES = Object.freeze({
|
|
17
|
+
bloom: false,
|
|
18
|
+
colorGrade: false,
|
|
19
|
+
depthCue: false,
|
|
20
|
+
enabled: false,
|
|
21
|
+
// Camera-reprojection motion blur (reconstructs velocity from the depth
|
|
22
|
+
// buffer and the previous frame's view-projection). Blurs camera movement
|
|
23
|
+
// only — per-bone character motion has no velocity buffer in three.js.
|
|
24
|
+
motionBlur: false,
|
|
25
|
+
screenOutline: false,
|
|
26
|
+
vignette: false,
|
|
27
|
+
verticalGrade: false,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const DEFAULT_POST_PROCESSING_PARAMETERS = Object.freeze({
|
|
31
|
+
// Character-aware bloom: with a character mask connected (see
|
|
32
|
+
// characterRenderPasses), bloom gathered from character pixels is scaled by
|
|
33
|
+
// bloomCharacterBoost and everything else by bloomBackgroundSuppress.
|
|
34
|
+
// Defaults of 1/1 reproduce plain bloom.
|
|
35
|
+
bloomBackgroundSuppress: 1.0,
|
|
36
|
+
bloomCharacterBoost: 1.0,
|
|
37
|
+
// 'single' = the original one-pass 9-tap bloom. 'pyramid' = multi-pass
|
|
38
|
+
// dual-filter mip chain: wider, softer glow for roughly the same cost at
|
|
39
|
+
// high radii, at the price of extra render targets.
|
|
40
|
+
bloomLevels: 5,
|
|
41
|
+
bloomMode: 'single',
|
|
42
|
+
bloomRadius: 0.16,
|
|
43
|
+
bloomStrength: 0.0,
|
|
44
|
+
bloomThreshold: 0.995,
|
|
45
|
+
bottomDark: 0.0,
|
|
46
|
+
contrast: 1.0,
|
|
47
|
+
depthCueColor: new THREE.Color(0x9db7d8),
|
|
48
|
+
depthCueFar: 24.0,
|
|
49
|
+
depthCueNear: 1.0,
|
|
50
|
+
depthCueStrength: 0.0,
|
|
51
|
+
exposure: 1.0,
|
|
52
|
+
// Optional 2D-strip color LUT (horizontal slices, height = slice size, e.g.
|
|
53
|
+
// a 1024x32 strip). Runtime texture object — not serializable to JSON.
|
|
54
|
+
lutMap: null,
|
|
55
|
+
lutSize: 0, // 0 = derive from lutMap.image.height
|
|
56
|
+
lutStrength: 0.0,
|
|
57
|
+
motionBlurStrength: 0.55,
|
|
58
|
+
outlineColor: new THREE.Color(0x10131a),
|
|
59
|
+
outlineDepthStrength: 0.16,
|
|
60
|
+
outlineLumaStrength: 0.04,
|
|
61
|
+
outlineStrength: 0.0,
|
|
62
|
+
saturation: 1.0,
|
|
63
|
+
strength: 1.0,
|
|
64
|
+
topLight: 0.0,
|
|
65
|
+
vignetteRadius: 0.72,
|
|
66
|
+
vignetteSoftness: 0.34,
|
|
67
|
+
vignetteStrength: 0.0,
|
|
68
|
+
warmth: 0.0,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export const POST_PROCESSING_PRESETS = Object.freeze({
|
|
72
|
+
off: {
|
|
73
|
+
features: {
|
|
74
|
+
...DEFAULT_POST_PROCESSING_FEATURES,
|
|
75
|
+
},
|
|
76
|
+
parameters: {
|
|
77
|
+
strength: 0.0,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
custom: {
|
|
81
|
+
features: {
|
|
82
|
+
...DEFAULT_POST_PROCESSING_FEATURES,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
softAnime: {
|
|
86
|
+
features: {
|
|
87
|
+
bloom: false,
|
|
88
|
+
colorGrade: false,
|
|
89
|
+
enabled: true,
|
|
90
|
+
vignette: true,
|
|
91
|
+
verticalGrade: false,
|
|
92
|
+
},
|
|
93
|
+
parameters: {
|
|
94
|
+
bloomRadius: 0.08,
|
|
95
|
+
bloomStrength: 0.0,
|
|
96
|
+
bloomThreshold: 0.992,
|
|
97
|
+
bottomDark: 0.0,
|
|
98
|
+
contrast: 1.0,
|
|
99
|
+
exposure: 1.0,
|
|
100
|
+
saturation: 1.0,
|
|
101
|
+
strength: 0.45,
|
|
102
|
+
topLight: 0.0,
|
|
103
|
+
vignetteStrength: 0.018,
|
|
104
|
+
warmth: 0.0,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
debugEdges: {
|
|
108
|
+
features: {
|
|
109
|
+
depthCue: true,
|
|
110
|
+
enabled: true,
|
|
111
|
+
screenOutline: true,
|
|
112
|
+
},
|
|
113
|
+
parameters: {
|
|
114
|
+
depthCueStrength: 0.08,
|
|
115
|
+
outlineDepthStrength: 0.85,
|
|
116
|
+
outlineLumaStrength: 0.55,
|
|
117
|
+
outlineStrength: 1.0,
|
|
118
|
+
strength: 1.0,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
showcase: {
|
|
122
|
+
features: {
|
|
123
|
+
bloom: true,
|
|
124
|
+
enabled: true,
|
|
125
|
+
motionBlur: true,
|
|
126
|
+
vignette: true,
|
|
127
|
+
},
|
|
128
|
+
parameters: {
|
|
129
|
+
bloomCharacterBoost: 1.6,
|
|
130
|
+
bloomLevels: 5,
|
|
131
|
+
bloomMode: 'pyramid',
|
|
132
|
+
bloomRadius: 0.5,
|
|
133
|
+
bloomStrength: 0.3,
|
|
134
|
+
bloomThreshold: 0.85,
|
|
135
|
+
motionBlurStrength: 0.55,
|
|
136
|
+
strength: 1.0,
|
|
137
|
+
vignetteStrength: 0.018,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
function cleanObject(value) {
|
|
143
|
+
return value && typeof value === 'object' ? value : {};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Settings schema metadata, preset registry, and preset documents. Mirrors
|
|
148
|
+
// the pattern in src/toon/toonSettings.js so the schema-driven debug panel
|
|
149
|
+
// and serialized preset documents work for post-processing too. Everything in
|
|
150
|
+
// this section is additive; the pipeline code further down is unchanged.
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Document `type` string written into serialized post-processing presets.
|
|
155
|
+
* @type {string}
|
|
156
|
+
*/
|
|
157
|
+
export const POST_PROCESSING_PRESET_DOCUMENT_TYPE = 'toonlab/post-processing-preset';
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Current schema version written into post-processing preset documents.
|
|
161
|
+
* @type {number}
|
|
162
|
+
*/
|
|
163
|
+
export const POST_PROCESSING_PRESET_SCHEMA_VERSION = 1;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Setting groups for the schema-driven settings panel. Same entry shape as
|
|
167
|
+
* `TOON_SETTING_GROUPS`: `{ id, label, description }`.
|
|
168
|
+
* @type {ReadonlyArray<{description: string, id: string, label: string}>}
|
|
169
|
+
*/
|
|
170
|
+
export const POST_PROCESSING_SETTING_GROUPS = Object.freeze([
|
|
171
|
+
Object.freeze({
|
|
172
|
+
description: 'Toggles for each optional screen-space effect in the final composite pass.',
|
|
173
|
+
id: 'features',
|
|
174
|
+
label: 'Features',
|
|
175
|
+
}),
|
|
176
|
+
Object.freeze({
|
|
177
|
+
description: 'Tuning values used by the post-processing effects when their feature toggles are on.',
|
|
178
|
+
id: 'parameters',
|
|
179
|
+
label: 'Parameters',
|
|
180
|
+
}),
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
const POST_PROCESSING_SETTING_GROUP_METADATA = Object.freeze(
|
|
184
|
+
Object.fromEntries(POST_PROCESSING_SETTING_GROUPS.map((group) => [group.id, group])),
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const POST_PROCESSING_SETTING_DEFAULTS = Object.freeze({
|
|
188
|
+
features: DEFAULT_POST_PROCESSING_FEATURES,
|
|
189
|
+
parameters: DEFAULT_POST_PROCESSING_PARAMETERS,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Hand-written labels, descriptions, ranges, and select options per field.
|
|
193
|
+
// Types are derived from the default values; ranges reflect how each uniform
|
|
194
|
+
// is actually used in the composite shader / bloom chain below.
|
|
195
|
+
const POST_PROCESSING_FIELD_DEFINITIONS = Object.freeze({
|
|
196
|
+
features: Object.freeze({
|
|
197
|
+
bloom: {
|
|
198
|
+
description: 'Adds glow around pixels brighter than the bloom threshold.',
|
|
199
|
+
label: 'Bloom',
|
|
200
|
+
},
|
|
201
|
+
colorGrade: {
|
|
202
|
+
description: 'Applies exposure, contrast, saturation, and warmth grading.',
|
|
203
|
+
label: 'Color Grade',
|
|
204
|
+
},
|
|
205
|
+
depthCue: {
|
|
206
|
+
description: 'Fades distant pixels toward the depth cue color for atmospheric depth.',
|
|
207
|
+
label: 'Depth Cue',
|
|
208
|
+
},
|
|
209
|
+
enabled: {
|
|
210
|
+
description: 'Forces the post-processing composite pass on, even with no individual effect active.',
|
|
211
|
+
label: 'Enabled',
|
|
212
|
+
},
|
|
213
|
+
motionBlur: {
|
|
214
|
+
description: 'Blurs camera movement by reprojecting the previous frame (camera motion only).',
|
|
215
|
+
label: 'Motion Blur',
|
|
216
|
+
},
|
|
217
|
+
screenOutline: {
|
|
218
|
+
description: 'Draws screen-space outlines from depth and luminance edges.',
|
|
219
|
+
label: 'Screen Outline',
|
|
220
|
+
},
|
|
221
|
+
verticalGrade: {
|
|
222
|
+
description: 'Adds warm light at the top of the frame and darkening at the bottom.',
|
|
223
|
+
label: 'Vertical Grade',
|
|
224
|
+
},
|
|
225
|
+
vignette: {
|
|
226
|
+
description: 'Darkens the frame toward the corners.',
|
|
227
|
+
label: 'Vignette',
|
|
228
|
+
},
|
|
229
|
+
}),
|
|
230
|
+
parameters: Object.freeze({
|
|
231
|
+
bloomBackgroundSuppress: {
|
|
232
|
+
description: 'Scales bloom gathered from non-character pixels when a character mask is connected.',
|
|
233
|
+
label: 'Bloom Background Suppress',
|
|
234
|
+
range: { max: 2, min: 0, step: 0.01 },
|
|
235
|
+
},
|
|
236
|
+
bloomCharacterBoost: {
|
|
237
|
+
description: 'Scales bloom gathered from character pixels when a character mask is connected.',
|
|
238
|
+
label: 'Bloom Character Boost',
|
|
239
|
+
range: { max: 4, min: 0, step: 0.05 },
|
|
240
|
+
},
|
|
241
|
+
bloomLevels: {
|
|
242
|
+
description: 'Number of mip levels in the pyramid bloom chain (pyramid mode only).',
|
|
243
|
+
label: 'Bloom Levels',
|
|
244
|
+
range: { max: 8, min: 2, step: 1 },
|
|
245
|
+
},
|
|
246
|
+
bloomMode: {
|
|
247
|
+
description: 'Selects the one-pass 9-tap bloom or the wider multi-pass pyramid bloom.',
|
|
248
|
+
label: 'Bloom Mode',
|
|
249
|
+
optionLabels: { pyramid: 'Pyramid (Mip Chain)', single: 'Single Pass' },
|
|
250
|
+
options: ['single', 'pyramid'],
|
|
251
|
+
},
|
|
252
|
+
bloomRadius: {
|
|
253
|
+
description: 'Controls how far the bloom glow spreads from bright pixels.',
|
|
254
|
+
label: 'Bloom Radius',
|
|
255
|
+
range: { max: 1, min: 0, step: 0.01 },
|
|
256
|
+
},
|
|
257
|
+
bloomStrength: {
|
|
258
|
+
description: 'Controls how strongly bloom is added to the image.',
|
|
259
|
+
label: 'Bloom Strength',
|
|
260
|
+
range: { max: 2, min: 0, step: 0.01 },
|
|
261
|
+
},
|
|
262
|
+
bloomThreshold: {
|
|
263
|
+
description: 'Sets the luminance above which pixels start to bloom.',
|
|
264
|
+
label: 'Bloom Threshold',
|
|
265
|
+
range: { max: 1, min: 0, step: 0.001 },
|
|
266
|
+
},
|
|
267
|
+
bottomDark: {
|
|
268
|
+
description: 'Darkens the lower part of the frame in the vertical grade.',
|
|
269
|
+
label: 'Bottom Dark',
|
|
270
|
+
range: { max: 1, min: 0, step: 0.01 },
|
|
271
|
+
},
|
|
272
|
+
contrast: {
|
|
273
|
+
description: 'Scales contrast around mid gray in the color grade.',
|
|
274
|
+
label: 'Contrast',
|
|
275
|
+
range: { max: 2, min: 0, step: 0.01 },
|
|
276
|
+
},
|
|
277
|
+
depthCueColor: {
|
|
278
|
+
description: 'Sets the color distant pixels fade toward.',
|
|
279
|
+
label: 'Depth Cue Color',
|
|
280
|
+
},
|
|
281
|
+
depthCueFar: {
|
|
282
|
+
description: 'Sets the depth at which the depth cue reaches full strength.',
|
|
283
|
+
label: 'Depth Cue Far',
|
|
284
|
+
range: { max: 200, min: 0, step: 0.5 },
|
|
285
|
+
},
|
|
286
|
+
depthCueNear: {
|
|
287
|
+
description: 'Sets the depth at which the depth cue starts to appear.',
|
|
288
|
+
label: 'Depth Cue Near',
|
|
289
|
+
range: { max: 50, min: 0, step: 0.1 },
|
|
290
|
+
},
|
|
291
|
+
depthCueStrength: {
|
|
292
|
+
description: 'Controls how strongly distant pixels blend toward the depth cue color.',
|
|
293
|
+
label: 'Depth Cue Strength',
|
|
294
|
+
range: { max: 1, min: 0, step: 0.01 },
|
|
295
|
+
},
|
|
296
|
+
exposure: {
|
|
297
|
+
description: 'Multiplies overall image brightness in the color grade.',
|
|
298
|
+
label: 'Exposure',
|
|
299
|
+
range: { max: 4, min: 0, step: 0.01 },
|
|
300
|
+
},
|
|
301
|
+
lutMap: {
|
|
302
|
+
description: 'Optional 2D-strip color LUT texture (runtime only, not serialized).',
|
|
303
|
+
label: 'LUT Map',
|
|
304
|
+
},
|
|
305
|
+
lutSize: {
|
|
306
|
+
description: 'Slice size of the LUT strip; 0 derives it from the texture height.',
|
|
307
|
+
label: 'LUT Size',
|
|
308
|
+
range: { max: 64, min: 0, step: 1 },
|
|
309
|
+
},
|
|
310
|
+
lutStrength: {
|
|
311
|
+
description: 'Controls how strongly the LUT recolors the graded image.',
|
|
312
|
+
label: 'LUT Strength',
|
|
313
|
+
range: { max: 1, min: 0, step: 0.01 },
|
|
314
|
+
},
|
|
315
|
+
motionBlurStrength: {
|
|
316
|
+
description: 'Scales the camera-reprojection blur distance along the motion vector.',
|
|
317
|
+
label: 'Motion Blur Strength',
|
|
318
|
+
range: { max: 2, min: 0, step: 0.01 },
|
|
319
|
+
},
|
|
320
|
+
outlineColor: {
|
|
321
|
+
description: 'Sets the color drawn on detected screen-space edges.',
|
|
322
|
+
label: 'Outline Color',
|
|
323
|
+
},
|
|
324
|
+
outlineDepthStrength: {
|
|
325
|
+
description: 'Controls how strongly depth discontinuities contribute to outlines.',
|
|
326
|
+
label: 'Outline Depth Strength',
|
|
327
|
+
range: { max: 2, min: 0, step: 0.01 },
|
|
328
|
+
},
|
|
329
|
+
outlineLumaStrength: {
|
|
330
|
+
description: 'Controls how strongly luminance edges contribute to outlines.',
|
|
331
|
+
label: 'Outline Luma Strength',
|
|
332
|
+
range: { max: 2, min: 0, step: 0.01 },
|
|
333
|
+
},
|
|
334
|
+
outlineStrength: {
|
|
335
|
+
description: 'Controls the overall opacity of screen-space outlines.',
|
|
336
|
+
label: 'Outline Strength',
|
|
337
|
+
range: { max: 2, min: 0, step: 0.01 },
|
|
338
|
+
},
|
|
339
|
+
saturation: {
|
|
340
|
+
description: 'Scales color saturation in the color grade.',
|
|
341
|
+
label: 'Saturation',
|
|
342
|
+
range: { max: 2, min: 0, step: 0.01 },
|
|
343
|
+
},
|
|
344
|
+
strength: {
|
|
345
|
+
description: 'Blends between the raw render and the full post-processing result.',
|
|
346
|
+
label: 'Strength',
|
|
347
|
+
range: { max: 1, min: 0, step: 0.01 },
|
|
348
|
+
},
|
|
349
|
+
topLight: {
|
|
350
|
+
description: 'Adds warm light to the upper part of the frame in the vertical grade.',
|
|
351
|
+
label: 'Top Light',
|
|
352
|
+
range: { max: 1, min: 0, step: 0.01 },
|
|
353
|
+
},
|
|
354
|
+
vignetteRadius: {
|
|
355
|
+
description: 'Sets the distance from the frame center where the vignette starts.',
|
|
356
|
+
label: 'Vignette Radius',
|
|
357
|
+
range: { max: 1, min: 0, step: 0.01 },
|
|
358
|
+
},
|
|
359
|
+
vignetteSoftness: {
|
|
360
|
+
description: 'Controls the falloff width of the vignette edge.',
|
|
361
|
+
label: 'Vignette Softness',
|
|
362
|
+
range: { max: 1, min: 0, step: 0.01 },
|
|
363
|
+
},
|
|
364
|
+
vignetteStrength: {
|
|
365
|
+
description: 'Controls how strongly the vignette darkens the frame edges.',
|
|
366
|
+
label: 'Vignette Strength',
|
|
367
|
+
range: { max: 1, min: 0, step: 0.01 },
|
|
368
|
+
},
|
|
369
|
+
warmth: {
|
|
370
|
+
description: 'Shifts the color grade warmer (positive) or cooler (negative).',
|
|
371
|
+
label: 'Warmth',
|
|
372
|
+
range: { max: 1, min: -1, step: 0.01 },
|
|
373
|
+
},
|
|
374
|
+
}),
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
function labelFromPostProcessingKey(key) {
|
|
378
|
+
return key
|
|
379
|
+
.replace(/([A-Z])/g, ' $1')
|
|
380
|
+
.replace(/^./, (char) => char.toUpperCase());
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function postProcessingFieldTypeFor(definition, value) {
|
|
384
|
+
if (definition.options) return 'select';
|
|
385
|
+
if (typeof value === 'boolean') return 'boolean';
|
|
386
|
+
if (typeof value === 'number') return 'number';
|
|
387
|
+
if (value?.isColor) return 'color';
|
|
388
|
+
if (value === null) return 'texture';
|
|
389
|
+
return 'text';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function serializablePostProcessingValue(value) {
|
|
393
|
+
if (value?.isColor) return [value.r, value.g, value.b];
|
|
394
|
+
return value;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function createPostProcessingFieldMetadata(group, key, value) {
|
|
398
|
+
const definition = POST_PROCESSING_FIELD_DEFINITIONS[group.id]?.[key] ?? {};
|
|
399
|
+
const type = postProcessingFieldTypeFor(definition, value);
|
|
400
|
+
return Object.freeze({
|
|
401
|
+
defaultValue: serializablePostProcessingValue(value),
|
|
402
|
+
description: definition.description ?? `Configures ${labelFromPostProcessingKey(key).toLowerCase()} for ${group.label.toLowerCase()}.`,
|
|
403
|
+
group: group.id,
|
|
404
|
+
id: `${group.id}.${key}`,
|
|
405
|
+
key,
|
|
406
|
+
label: definition.label ?? labelFromPostProcessingKey(key),
|
|
407
|
+
optionLabels: definition.optionLabels ? Object.freeze(definition.optionLabels) : null,
|
|
408
|
+
options: definition.options ? Object.freeze(definition.options) : null,
|
|
409
|
+
range: type === 'number' ? Object.freeze(definition.range ?? { max: 1, min: 0, step: 0.01 }) : null,
|
|
410
|
+
serializable: type !== 'texture',
|
|
411
|
+
type,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Field metadata for every post-processing feature and parameter, keyed by
|
|
417
|
+
* group id and then field key. Same field shape as
|
|
418
|
+
* `TOON_SETTING_FIELD_SCHEMA` (`id`, `group`, `key`, `label`, `description`,
|
|
419
|
+
* `type`, `range`, `options`, `optionLabels`, `defaultValue`,
|
|
420
|
+
* `serializable`), consumable by the schema-driven debug settings panel.
|
|
421
|
+
* @type {Readonly<Record<string, Readonly<Record<string, object>>>>}
|
|
422
|
+
*/
|
|
423
|
+
export const POST_PROCESSING_SETTING_FIELD_SCHEMA = Object.freeze(
|
|
424
|
+
Object.fromEntries(
|
|
425
|
+
POST_PROCESSING_SETTING_GROUPS.map((group) => [
|
|
426
|
+
group.id,
|
|
427
|
+
Object.freeze(
|
|
428
|
+
Object.fromEntries(
|
|
429
|
+
Object.entries(POST_PROCESSING_SETTING_DEFAULTS[group.id] ?? {})
|
|
430
|
+
.map(([key, value]) => [key, createPostProcessingFieldMetadata(group, key, value)]),
|
|
431
|
+
),
|
|
432
|
+
),
|
|
433
|
+
]),
|
|
434
|
+
),
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
function isPlainObject(value) {
|
|
438
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function numberFromValue(value, fallback) {
|
|
442
|
+
const number = Number(value);
|
|
443
|
+
return Number.isFinite(number) ? number : fallback;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function booleanFromValue(value, fallback) {
|
|
447
|
+
if (typeof value === 'boolean') return value;
|
|
448
|
+
if (typeof value === 'string') {
|
|
449
|
+
const normalized = value.trim().toLowerCase();
|
|
450
|
+
if (['1', 'true', 'on', 'yes'].includes(normalized)) return true;
|
|
451
|
+
if (['0', 'false', 'off', 'no'].includes(normalized)) return false;
|
|
452
|
+
}
|
|
453
|
+
if (Number.isFinite(value)) return value !== 0;
|
|
454
|
+
return fallback;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function selectFromValue(value, fallback, options) {
|
|
458
|
+
if (!options?.length) return value;
|
|
459
|
+
const normalized = String(value ?? '').trim().toLowerCase();
|
|
460
|
+
const match = options.find((option) => String(option).toLowerCase() === normalized);
|
|
461
|
+
if (match !== undefined) return match;
|
|
462
|
+
return fallback;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function colorArrayFromValue(value, fallback) {
|
|
466
|
+
const fallbackColor = new THREE.Color(fallback?.[0] ?? 1, fallback?.[1] ?? 1, fallback?.[2] ?? 1);
|
|
467
|
+
const color = colorFromParameter(value, fallbackColor);
|
|
468
|
+
return [color.r, color.g, color.b];
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function coercePostProcessingFieldValue(value, field) {
|
|
472
|
+
switch (field.type) {
|
|
473
|
+
case 'boolean':
|
|
474
|
+
return booleanFromValue(value, field.defaultValue);
|
|
475
|
+
case 'number':
|
|
476
|
+
return numberFromValue(value, field.defaultValue);
|
|
477
|
+
case 'select':
|
|
478
|
+
return selectFromValue(value, field.defaultValue, field.options);
|
|
479
|
+
case 'color':
|
|
480
|
+
return colorArrayFromValue(value, field.defaultValue);
|
|
481
|
+
default:
|
|
482
|
+
return value;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Reduces a `{ features, parameters }` settings object to serializable,
|
|
488
|
+
* schema-known values: booleans/numbers are coerced, select values are
|
|
489
|
+
* matched against their options, colors become `[r, g, b]` arrays, and
|
|
490
|
+
* runtime-only fields (like `lutMap`) plus unknown keys are dropped. Only
|
|
491
|
+
* keys present in the input are kept, so partial presets stay partial.
|
|
492
|
+
* @param {object} [settings] Raw `{ features, parameters }` input.
|
|
493
|
+
* @returns {object} Sanitized settings safe for `JSON.stringify`.
|
|
494
|
+
*/
|
|
495
|
+
export function sanitizePostProcessingPresetSettings(settings = {}) {
|
|
496
|
+
const source = cleanObject(settings);
|
|
497
|
+
const sanitized = {};
|
|
498
|
+
|
|
499
|
+
for (const group of POST_PROCESSING_SETTING_GROUPS) {
|
|
500
|
+
const groupSource = cleanObject(source[group.id]);
|
|
501
|
+
if (Object.keys(groupSource).length === 0) continue;
|
|
502
|
+
|
|
503
|
+
const fields = POST_PROCESSING_SETTING_FIELD_SCHEMA[group.id] ?? {};
|
|
504
|
+
const groupSettings = {};
|
|
505
|
+
for (const [key, value] of Object.entries(groupSource)) {
|
|
506
|
+
const field = fields[key];
|
|
507
|
+
if (!field?.serializable) continue;
|
|
508
|
+
groupSettings[key] = coercePostProcessingFieldValue(value, field);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (Object.keys(groupSettings).length > 0) sanitized[group.id] = groupSettings;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return sanitized;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function collectUnknownPostProcessingSettingKeys(settings = {}) {
|
|
518
|
+
const source = cleanObject(settings);
|
|
519
|
+
const warnings = [];
|
|
520
|
+
for (const [groupId, groupValue] of Object.entries(source)) {
|
|
521
|
+
if (!(groupId in POST_PROCESSING_SETTING_GROUP_METADATA)) {
|
|
522
|
+
warnings.push(`Unknown settings group "${groupId}" was ignored.`);
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
const fields = POST_PROCESSING_SETTING_FIELD_SCHEMA[groupId] ?? {};
|
|
526
|
+
for (const key of Object.keys(cleanObject(groupValue))) {
|
|
527
|
+
if (!fields[key]) warnings.push(`Unknown setting "${groupId}.${key}" was ignored.`);
|
|
528
|
+
else if (!fields[key].serializable) warnings.push(`Setting "${groupId}.${key}" is runtime-only and was ignored.`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return warnings;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function normalizePostProcessingPresetId(value) {
|
|
535
|
+
return String(value ?? '').trim();
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function migratePostProcessingPresetDocument(input) {
|
|
539
|
+
const source = cleanObject(input);
|
|
540
|
+
const version = Number.isFinite(source.version) ? Math.round(source.version) : 0;
|
|
541
|
+
const settings = cleanObject(source.settings);
|
|
542
|
+
|
|
543
|
+
if (version <= 1) {
|
|
544
|
+
return {
|
|
545
|
+
description: source.description ?? '',
|
|
546
|
+
id: source.id ?? source.name ?? source.preset ?? '',
|
|
547
|
+
label: source.label ?? source.title ?? source.name ?? source.id ?? '',
|
|
548
|
+
settings: Object.keys(settings).length > 0
|
|
549
|
+
? settings
|
|
550
|
+
: {
|
|
551
|
+
...(isPlainObject(source.features) ? { features: source.features } : {}),
|
|
552
|
+
...(isPlainObject(source.parameters) ? { parameters: source.parameters } : {}),
|
|
553
|
+
},
|
|
554
|
+
type: source.type ?? POST_PROCESSING_PRESET_DOCUMENT_TYPE,
|
|
555
|
+
version: POST_PROCESSING_PRESET_SCHEMA_VERSION,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return source;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Validates (and migrates) a parsed post-processing preset document.
|
|
564
|
+
* Mirrors `validateToonPresetDocument`: never throws, collects all problems.
|
|
565
|
+
* @param {object} input Parsed JSON document (or document-like object).
|
|
566
|
+
* @returns {{ok: boolean, errors: string[], warnings: string[], value: object|null}}
|
|
567
|
+
* `value` is the normalized document (`type`, `version`, `id`, `label`,
|
|
568
|
+
* `description`, sanitized `settings`) when `ok`, otherwise `null`.
|
|
569
|
+
*/
|
|
570
|
+
export function validatePostProcessingPresetDocument(input) {
|
|
571
|
+
return validateSettingsPresetDocument(input, {
|
|
572
|
+
collectWarnings: collectUnknownPostProcessingSettingKeys,
|
|
573
|
+
documentType: POST_PROCESSING_PRESET_DOCUMENT_TYPE,
|
|
574
|
+
migrateDocument: migratePostProcessingPresetDocument,
|
|
575
|
+
normalizeId: normalizePostProcessingPresetId,
|
|
576
|
+
sanitizeSettings: sanitizePostProcessingPresetSettings,
|
|
577
|
+
schemaVersion: POST_PROCESSING_PRESET_SCHEMA_VERSION,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Builds a validated, serializable post-processing preset document. The
|
|
583
|
+
* definition may carry `{ settings: { features, parameters } }` or top-level
|
|
584
|
+
* `features` / `parameters`, plus optional `label` and `description`.
|
|
585
|
+
* @param {string} id Preset id the document is stored under.
|
|
586
|
+
* @param {object} [definition] Preset definition.
|
|
587
|
+
* @returns {object} Normalized document (see `validatePostProcessingPresetDocument`).
|
|
588
|
+
* @throws {Error} When validation fails (e.g. missing id).
|
|
589
|
+
*/
|
|
590
|
+
export function createPostProcessingPresetDocument(id, definition = {}) {
|
|
591
|
+
return createSettingsPresetDocument(id, definition, {
|
|
592
|
+
collectSettings: (source) => (isPlainObject(source.settings)
|
|
593
|
+
? source.settings
|
|
594
|
+
: {
|
|
595
|
+
...(isPlainObject(source.features) ? { features: source.features } : {}),
|
|
596
|
+
...(isPlainObject(source.parameters) ? { parameters: source.parameters } : {}),
|
|
597
|
+
}),
|
|
598
|
+
documentType: POST_PROCESSING_PRESET_DOCUMENT_TYPE,
|
|
599
|
+
schemaVersion: POST_PROCESSING_PRESET_SCHEMA_VERSION,
|
|
600
|
+
validateDocument: validatePostProcessingPresetDocument,
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const BUILT_IN_POST_PROCESSING_PRESET_METADATA = Object.freeze({
|
|
605
|
+
custom: Object.freeze({
|
|
606
|
+
description: 'Neutral starting point that expects host-supplied feature and parameter overrides.',
|
|
607
|
+
label: 'Custom',
|
|
608
|
+
}),
|
|
609
|
+
debugEdges: Object.freeze({
|
|
610
|
+
description: 'Depth cue plus strong screen outlines for inspecting edge detection.',
|
|
611
|
+
label: 'Debug Edges',
|
|
612
|
+
}),
|
|
613
|
+
off: Object.freeze({
|
|
614
|
+
description: 'Post-processing composite disabled; the scene renders directly.',
|
|
615
|
+
label: 'Off',
|
|
616
|
+
}),
|
|
617
|
+
showcase: Object.freeze({
|
|
618
|
+
description: 'Pyramid bloom, motion blur, and vignette turned on with demo values.',
|
|
619
|
+
label: 'Showcase',
|
|
620
|
+
}),
|
|
621
|
+
softAnime: Object.freeze({
|
|
622
|
+
description: 'Gentle vignette-led grade for soft anime framing.',
|
|
623
|
+
label: 'Soft Anime',
|
|
624
|
+
}),
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
const postProcessingPresetRegistry = new Map(
|
|
628
|
+
Object.entries(POST_PROCESSING_PRESETS).map(([id, preset]) => [id, {
|
|
629
|
+
description: BUILT_IN_POST_PROCESSING_PRESET_METADATA[id]?.description ?? '',
|
|
630
|
+
features: preset.features,
|
|
631
|
+
label: BUILT_IN_POST_PROCESSING_PRESET_METADATA[id]?.label ?? labelFromPostProcessingKey(id),
|
|
632
|
+
parameters: preset.parameters,
|
|
633
|
+
}]),
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Registers a post-processing preset so it can be used anywhere built-in
|
|
638
|
+
* presets are (e.g. `createPostProcessingSettings({ preset: id })`). The
|
|
639
|
+
* definition is validated and sanitized through the preset document pipeline.
|
|
640
|
+
* @param {string} id Preset id (case-sensitive, like the built-in ids).
|
|
641
|
+
* @param {object} [definition] Preset definition (see `createPostProcessingPresetDocument`).
|
|
642
|
+
* @param {{overwrite?: boolean}} [options] Set `overwrite: true` to replace an existing preset.
|
|
643
|
+
* @returns {{id: string, label: string, description: string}} Registered preset metadata.
|
|
644
|
+
* @throws {Error} When validation fails or the id already exists without `overwrite`.
|
|
645
|
+
*/
|
|
646
|
+
export function registerPostProcessingPreset(id, definition = {}, { overwrite = false } = {}) {
|
|
647
|
+
const document = createPostProcessingPresetDocument(id, definition);
|
|
648
|
+
if (!overwrite && postProcessingPresetRegistry.has(document.id)) {
|
|
649
|
+
throw new Error(`Post-processing preset "${document.id}" already exists.`);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
postProcessingPresetRegistry.set(document.id, {
|
|
653
|
+
description: document.description,
|
|
654
|
+
features: document.settings.features ?? {},
|
|
655
|
+
label: document.label,
|
|
656
|
+
parameters: document.settings.parameters ?? {},
|
|
657
|
+
});
|
|
658
|
+
return {
|
|
659
|
+
description: document.description,
|
|
660
|
+
id: document.id,
|
|
661
|
+
label: document.label,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Lists every known post-processing preset (built-in and registered) as
|
|
667
|
+
* `{ id, label, description }` options for preset pickers.
|
|
668
|
+
* @returns {Array<{id: string, label: string, description: string}>}
|
|
669
|
+
*/
|
|
670
|
+
export function getPostProcessingPresetOptions() {
|
|
671
|
+
return Array.from(postProcessingPresetRegistry.entries()).map(([id, preset]) => ({
|
|
672
|
+
description: preset.description ?? '',
|
|
673
|
+
id,
|
|
674
|
+
label: preset.label ?? id,
|
|
675
|
+
}));
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function colorFromParameter(value, fallback) {
|
|
679
|
+
if (value?.isColor) return value.clone();
|
|
680
|
+
if (Array.isArray(value) && value.length >= 3) {
|
|
681
|
+
const [r, g, b] = value.map(Number);
|
|
682
|
+
if ([r, g, b].every(Number.isFinite)) return new THREE.Color(r, g, b);
|
|
683
|
+
}
|
|
684
|
+
if (typeof value === 'number' || typeof value === 'string') {
|
|
685
|
+
try {
|
|
686
|
+
return new THREE.Color(value);
|
|
687
|
+
} catch {
|
|
688
|
+
return fallback.clone();
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (value && typeof value === 'object') {
|
|
692
|
+
const r = Number(value.r);
|
|
693
|
+
const g = Number(value.g);
|
|
694
|
+
const b = Number(value.b);
|
|
695
|
+
if ([r, g, b].every(Number.isFinite)) return new THREE.Color(r, g, b);
|
|
696
|
+
}
|
|
697
|
+
return fallback.clone();
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function createMergedParameters(...parameterSets) {
|
|
701
|
+
const merged = {
|
|
702
|
+
...DEFAULT_POST_PROCESSING_PARAMETERS,
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
for (const source of parameterSets) {
|
|
706
|
+
Object.assign(merged, cleanObject(source));
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
merged.depthCueColor = colorFromParameter(
|
|
710
|
+
merged.depthCueColor,
|
|
711
|
+
DEFAULT_POST_PROCESSING_PARAMETERS.depthCueColor,
|
|
712
|
+
);
|
|
713
|
+
merged.outlineColor = colorFromParameter(
|
|
714
|
+
merged.outlineColor,
|
|
715
|
+
DEFAULT_POST_PROCESSING_PARAMETERS.outlineColor,
|
|
716
|
+
);
|
|
717
|
+
return merged;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export function createPostProcessingSettings(options = {}) {
|
|
721
|
+
const source = cleanObject(options);
|
|
722
|
+
const presetName = source.preset || 'off';
|
|
723
|
+
const preset = postProcessingPresetRegistry.get(presetName) ??
|
|
724
|
+
POST_PROCESSING_PRESETS[presetName] ??
|
|
725
|
+
POST_PROCESSING_PRESETS.off;
|
|
726
|
+
const features = {
|
|
727
|
+
...DEFAULT_POST_PROCESSING_FEATURES,
|
|
728
|
+
...cleanObject(preset.features),
|
|
729
|
+
...cleanObject(source.features),
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
return {
|
|
733
|
+
features,
|
|
734
|
+
parameters: createMergedParameters(preset.parameters, source.parameters),
|
|
735
|
+
preset: presetName,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
export function isPostProcessingEnabled(settingsInput = {}) {
|
|
740
|
+
const settings = createPostProcessingSettings(settingsInput);
|
|
741
|
+
const { features } = settings;
|
|
742
|
+
return Boolean(
|
|
743
|
+
features.enabled ||
|
|
744
|
+
features.bloom ||
|
|
745
|
+
features.colorGrade ||
|
|
746
|
+
features.depthCue ||
|
|
747
|
+
features.motionBlur ||
|
|
748
|
+
features.screenOutline ||
|
|
749
|
+
features.vignette ||
|
|
750
|
+
features.verticalGrade,
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function createPostRenderTarget(width, height) {
|
|
755
|
+
const renderTarget = new THREE.WebGLRenderTarget(width, height, {
|
|
756
|
+
depthBuffer: true,
|
|
757
|
+
stencilBuffer: false,
|
|
758
|
+
type: THREE.HalfFloatType,
|
|
759
|
+
// The canvas's MSAA does not apply to intermediate targets; without this
|
|
760
|
+
// the post path silently loses antialiasing versus the raw render.
|
|
761
|
+
samples: 4,
|
|
762
|
+
});
|
|
763
|
+
renderTarget.texture.name = 'PostProcessing.Color';
|
|
764
|
+
renderTarget.texture.colorSpace = THREE.NoColorSpace;
|
|
765
|
+
// Scene depth is read from the dedicated depth-color prepass, never from
|
|
766
|
+
// this attachment; the forced-GL node backend rejects MSAA depth resolves.
|
|
767
|
+
return renderTarget;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Node shaders can't sample depth attachments as a plain float consistently
|
|
771
|
+
// across both builders, so a second override-material scene pass writes linear
|
|
772
|
+
// window depth into a float color target.
|
|
773
|
+
function createScenePostDepthTarget(width, height) {
|
|
774
|
+
const renderTarget = new THREE.WebGLRenderTarget(width, height, {
|
|
775
|
+
depthBuffer: true,
|
|
776
|
+
magFilter: THREE.NearestFilter,
|
|
777
|
+
minFilter: THREE.NearestFilter,
|
|
778
|
+
stencilBuffer: false,
|
|
779
|
+
type: THREE.FloatType,
|
|
780
|
+
});
|
|
781
|
+
renderTarget.texture.name = 'PostProcessing.SceneDepthColor';
|
|
782
|
+
return renderTarget;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function needsScenePostDepth(settings) {
|
|
786
|
+
const { features, parameters } = settings;
|
|
787
|
+
return Boolean(
|
|
788
|
+
features.depthCue ||
|
|
789
|
+
features.screenOutline ||
|
|
790
|
+
(features.motionBlur && parameters.motionBlurStrength > 0),
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// ---------------------------------------------------------------------------
|
|
795
|
+
// Pyramid bloom (dual-filter mip chain): prefilter -> N downsamples -> N
|
|
796
|
+
// upsamples that accumulate each level back in. Wider, softer glow than the
|
|
797
|
+
// single-pass bloom, activated with parameters.bloomMode = 'pyramid'.
|
|
798
|
+
// ---------------------------------------------------------------------------
|
|
799
|
+
|
|
800
|
+
function createFinalCompositeMaterial() {
|
|
801
|
+
return createPostCompositeNodeMaterial();
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function applyCompositeSettings(material, settings, camera, depthTexture, width, height, pixelRatio) {
|
|
805
|
+
if (!material) return;
|
|
806
|
+
const { features, parameters } = settings;
|
|
807
|
+
const uniforms = material.uniforms;
|
|
808
|
+
uniforms.bloomBackgroundSuppress.value = parameters.bloomBackgroundSuppress;
|
|
809
|
+
uniforms.bloomCharacterBoost.value = parameters.bloomCharacterBoost;
|
|
810
|
+
uniforms.bloomRadius.value = parameters.bloomRadius;
|
|
811
|
+
uniforms.bloomStrength.value = features.bloom ? parameters.bloomStrength : 0.0;
|
|
812
|
+
uniforms.bloomThreshold.value = parameters.bloomThreshold;
|
|
813
|
+
uniforms.bottomDark.value = parameters.bottomDark;
|
|
814
|
+
uniforms.cameraFar.value = camera.far;
|
|
815
|
+
uniforms.cameraNear.value = camera.near;
|
|
816
|
+
uniforms.contrast.value = parameters.contrast;
|
|
817
|
+
uniforms.depthCueColor.value.copy(parameters.depthCueColor);
|
|
818
|
+
uniforms.depthCueFar.value = parameters.depthCueFar;
|
|
819
|
+
uniforms.depthCueNear.value = parameters.depthCueNear;
|
|
820
|
+
uniforms.depthCueStrength.value = parameters.depthCueStrength;
|
|
821
|
+
uniforms.exposure.value = parameters.exposure;
|
|
822
|
+
const lutMap = parameters.lutMap?.isTexture ? parameters.lutMap : null;
|
|
823
|
+
// Classic tolerates a `null` sampler uniform (it's only ever read behind an
|
|
824
|
+
// `if (lutStrength > 0.0 && lutSize > 1.5)` guard that stays false whenever
|
|
825
|
+
// lutMap is unset). The node backends bind every declared texture
|
|
826
|
+
// regardless of runtime branching, so `null` is unsafe there even inside a
|
|
827
|
+
// runtime-gated If() — fall back to the shared 1x1 placeholder instead.
|
|
828
|
+
// Harmless on classic: lutSize stays 0 below, so tLut is still never
|
|
829
|
+
// observably sampled.
|
|
830
|
+
uniforms.tLut.value = lutMap || fallbackPostTexture();
|
|
831
|
+
uniforms.lutSize.value = lutMap ? (parameters.lutSize || lutMap.image?.height || 0) : 0;
|
|
832
|
+
uniforms.lutStrength.value = lutMap ? parameters.lutStrength : 0.0;
|
|
833
|
+
uniforms.outlineColor.value.copy(parameters.outlineColor);
|
|
834
|
+
uniforms.outlineDepthStrength.value = parameters.outlineDepthStrength;
|
|
835
|
+
uniforms.outlineLumaStrength.value = parameters.outlineLumaStrength;
|
|
836
|
+
uniforms.outlineStrength.value = parameters.outlineStrength;
|
|
837
|
+
uniforms.resolution.value.set(width * pixelRatio, height * pixelRatio);
|
|
838
|
+
uniforms.saturation.value = parameters.saturation;
|
|
839
|
+
uniforms.strength.value = parameters.strength;
|
|
840
|
+
uniforms.tDepth.value = depthTexture;
|
|
841
|
+
uniforms.tDiffuse.value = material.userData.sourceTexture || fallbackPostTexture();
|
|
842
|
+
uniforms.topLight.value = parameters.topLight;
|
|
843
|
+
uniforms.useColorGrade.value = features.colorGrade ? 1.0 : 0.0;
|
|
844
|
+
uniforms.useDepthCue.value = features.depthCue ? 1.0 : 0.0;
|
|
845
|
+
uniforms.useScreenOutline.value = features.screenOutline ? 1.0 : 0.0;
|
|
846
|
+
uniforms.useVignette.value = features.vignette ? 1.0 : 0.0;
|
|
847
|
+
uniforms.useVerticalGrade.value = features.verticalGrade ? 1.0 : 0.0;
|
|
848
|
+
uniforms.vignetteRadius.value = parameters.vignetteRadius;
|
|
849
|
+
uniforms.vignetteSoftness.value = parameters.vignetteSoftness;
|
|
850
|
+
uniforms.vignetteStrength.value = parameters.vignetteStrength;
|
|
851
|
+
uniforms.warmth.value = parameters.warmth;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
export function createPostProcessingPipeline({
|
|
855
|
+
camera,
|
|
856
|
+
renderer,
|
|
857
|
+
scene,
|
|
858
|
+
settings: settingsInput = {},
|
|
859
|
+
height = window.innerHeight,
|
|
860
|
+
pixelRatio = window.devicePixelRatio,
|
|
861
|
+
width = window.innerWidth,
|
|
862
|
+
} = {}) {
|
|
863
|
+
let settings = createPostProcessingSettings(settingsInput);
|
|
864
|
+
let enabled = isPostProcessingEnabled(settings);
|
|
865
|
+
let currentWidth = width;
|
|
866
|
+
let currentHeight = height;
|
|
867
|
+
let currentPixelRatio = pixelRatio;
|
|
868
|
+
|
|
869
|
+
const target = createPostRenderTarget(width * pixelRatio, height * pixelRatio);
|
|
870
|
+
|
|
871
|
+
// Allocated unconditionally, but only rendered into when a depth-consuming
|
|
872
|
+
// feature is active.
|
|
873
|
+
const sceneDepthColorMaterial = createPassDepthColorMaterial();
|
|
874
|
+
const sceneDepthColorTarget = createScenePostDepthTarget(width * pixelRatio, height * pixelRatio);
|
|
875
|
+
function compositeDepthTexture() {
|
|
876
|
+
return sceneDepthColorTarget.texture;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
const compositeMaterial = createFinalCompositeMaterial();
|
|
880
|
+
compositeMaterial.userData.sourceTexture = target.texture;
|
|
881
|
+
const compositeScene = new THREE.Scene();
|
|
882
|
+
const compositeCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
|
|
883
|
+
const compositeMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), compositeMaterial);
|
|
884
|
+
compositeMesh.frustumCulled = false;
|
|
885
|
+
compositeScene.add(compositeMesh);
|
|
886
|
+
applyCompositeSettings(compositeMaterial, settings, camera, compositeDepthTexture(), width, height, pixelRatio);
|
|
887
|
+
|
|
888
|
+
// ---- Pyramid bloom chain (lazy; only allocated when the mode is used) ----
|
|
889
|
+
const bloomQuadScene = new THREE.Scene();
|
|
890
|
+
const bloomQuadMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), null);
|
|
891
|
+
bloomQuadMesh.frustumCulled = false;
|
|
892
|
+
bloomQuadScene.add(bloomQuadMesh);
|
|
893
|
+
const bloomPrefilterMaterial = createBloomPrefilterNodeMaterial();
|
|
894
|
+
const bloomDownsampleMaterial = createBloomDownsampleNodeMaterial();
|
|
895
|
+
const bloomUpsampleMaterial = createBloomUpsampleNodeMaterial();
|
|
896
|
+
let bloomDownChain = [];
|
|
897
|
+
let bloomUpChain = [];
|
|
898
|
+
let bloomChainWidth = 0;
|
|
899
|
+
let bloomChainHeight = 0;
|
|
900
|
+
|
|
901
|
+
function disposeBloomChain() {
|
|
902
|
+
for (const rt of [...bloomDownChain, ...bloomUpChain]) rt.dispose();
|
|
903
|
+
bloomDownChain = [];
|
|
904
|
+
bloomUpChain = [];
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function ensureBloomChain(levels) {
|
|
908
|
+
const baseWidth = Math.max(1, Math.floor((currentWidth * currentPixelRatio) / 2));
|
|
909
|
+
const baseHeight = Math.max(1, Math.floor((currentHeight * currentPixelRatio) / 2));
|
|
910
|
+
if (bloomChainWidth === baseWidth && bloomChainHeight === baseHeight && bloomDownChain.length === levels) return;
|
|
911
|
+
disposeBloomChain();
|
|
912
|
+
bloomChainWidth = baseWidth;
|
|
913
|
+
bloomChainHeight = baseHeight;
|
|
914
|
+
let levelWidth = baseWidth;
|
|
915
|
+
let levelHeight = baseHeight;
|
|
916
|
+
for (let i = 0; i < levels; i++) {
|
|
917
|
+
const options = { depthBuffer: false, stencilBuffer: false, type: THREE.HalfFloatType };
|
|
918
|
+
const down = new THREE.WebGLRenderTarget(levelWidth, levelHeight, options);
|
|
919
|
+
down.texture.name = `PostProcessing.BloomDown${i}`;
|
|
920
|
+
bloomDownChain.push(down);
|
|
921
|
+
if (i < levels - 1) {
|
|
922
|
+
const up = new THREE.WebGLRenderTarget(levelWidth, levelHeight, options);
|
|
923
|
+
up.texture.name = `PostProcessing.BloomUp${i}`;
|
|
924
|
+
bloomUpChain.push(up);
|
|
925
|
+
}
|
|
926
|
+
levelWidth = Math.max(1, levelWidth >> 1);
|
|
927
|
+
levelHeight = Math.max(1, levelHeight >> 1);
|
|
928
|
+
if (levelWidth < 4 || levelHeight < 4) {
|
|
929
|
+
levels = i + 1;
|
|
930
|
+
break;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function renderBloomPass(material, renderTargetOut) {
|
|
936
|
+
bloomQuadMesh.material = material;
|
|
937
|
+
renderer.setRenderTarget(renderTargetOut);
|
|
938
|
+
renderer.render(bloomQuadScene, compositeCamera);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Runs the mip chain and returns the texture the composite should add.
|
|
942
|
+
function renderBloomPyramid() {
|
|
943
|
+
const levels = Math.max(2, Math.min(8, Math.round(settings.parameters.bloomLevels)));
|
|
944
|
+
ensureBloomChain(levels);
|
|
945
|
+
const activeLevels = bloomDownChain.length;
|
|
946
|
+
|
|
947
|
+
bloomPrefilterMaterial.uniforms.tInput.value = target.texture;
|
|
948
|
+
bloomPrefilterMaterial.uniforms.bloomThreshold.value = settings.parameters.bloomThreshold;
|
|
949
|
+
bloomPrefilterMaterial.uniforms.bloomCharacterBoost.value = settings.parameters.bloomCharacterBoost;
|
|
950
|
+
bloomPrefilterMaterial.uniforms.bloomBackgroundSuppress.value = settings.parameters.bloomBackgroundSuppress;
|
|
951
|
+
bloomPrefilterMaterial.uniforms.tCharacterMask.value = compositeMaterial.uniforms.tCharacterMask.value;
|
|
952
|
+
bloomPrefilterMaterial.uniforms.useCharacterMask.value = compositeMaterial.uniforms.useCharacterMask.value;
|
|
953
|
+
renderBloomPass(bloomPrefilterMaterial, bloomDownChain[0]);
|
|
954
|
+
|
|
955
|
+
for (let i = 1; i < activeLevels; i++) {
|
|
956
|
+
bloomDownsampleMaterial.uniforms.tInput.value = bloomDownChain[i - 1].texture;
|
|
957
|
+
bloomDownsampleMaterial.uniforms.texelSize.value.set(1 / bloomDownChain[i - 1].width, 1 / bloomDownChain[i - 1].height);
|
|
958
|
+
renderBloomPass(bloomDownsampleMaterial, bloomDownChain[i]);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const spread = 1 + settings.parameters.bloomRadius * 4;
|
|
962
|
+
let previousTexture = bloomDownChain[activeLevels - 1].texture;
|
|
963
|
+
for (let i = activeLevels - 2; i >= 0; i--) {
|
|
964
|
+
bloomUpsampleMaterial.uniforms.tInput.value = previousTexture;
|
|
965
|
+
bloomUpsampleMaterial.uniforms.tAccumulate.value = bloomDownChain[i].texture;
|
|
966
|
+
bloomUpsampleMaterial.uniforms.texelSize.value.set(1 / bloomUpChain[i].width, 1 / bloomUpChain[i].height);
|
|
967
|
+
bloomUpsampleMaterial.uniforms.bloomSpread.value = spread;
|
|
968
|
+
renderBloomPass(bloomUpsampleMaterial, bloomUpChain[i]);
|
|
969
|
+
previousTexture = bloomUpChain[i].texture;
|
|
970
|
+
}
|
|
971
|
+
return previousTexture;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// ---- Motion blur previous-frame matrices ----
|
|
975
|
+
const currentViewProjection = new THREE.Matrix4();
|
|
976
|
+
const previousViewProjection = new THREE.Matrix4();
|
|
977
|
+
let hasPreviousViewProjection = false;
|
|
978
|
+
|
|
979
|
+
return {
|
|
980
|
+
compositeMaterial,
|
|
981
|
+
compositeScene,
|
|
982
|
+
get enabled() {
|
|
983
|
+
return enabled;
|
|
984
|
+
},
|
|
985
|
+
get settings() {
|
|
986
|
+
return settings;
|
|
987
|
+
},
|
|
988
|
+
render(delta) {
|
|
989
|
+
void delta;
|
|
990
|
+
if (!enabled) {
|
|
991
|
+
renderer.render(scene, camera);
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
const previousRenderTarget = renderer.getRenderTarget();
|
|
996
|
+
|
|
997
|
+
// Scene depth-as-color prepass feeding tDepth. Skipped whenever nothing
|
|
998
|
+
// this frame actually reads depth, exactly like characterRenderPasses'
|
|
999
|
+
// "only renders when a registered material consumes its output".
|
|
1000
|
+
if (needsScenePostDepth(settings)) {
|
|
1001
|
+
const previousOverrideMaterial = scene.overrideMaterial;
|
|
1002
|
+
renderer.setRenderTarget(sceneDepthColorTarget);
|
|
1003
|
+
renderer.clear();
|
|
1004
|
+
scene.overrideMaterial = sceneDepthColorMaterial;
|
|
1005
|
+
renderer.render(scene, camera);
|
|
1006
|
+
scene.overrideMaterial = previousOverrideMaterial;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
renderer.setRenderTarget(target);
|
|
1010
|
+
renderer.clear();
|
|
1011
|
+
renderer.render(scene, camera);
|
|
1012
|
+
|
|
1013
|
+
const uniforms = compositeMaterial.uniforms;
|
|
1014
|
+
|
|
1015
|
+
if (settings.features.motionBlur && settings.parameters.motionBlurStrength > 0) {
|
|
1016
|
+
currentViewProjection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
1017
|
+
if (!hasPreviousViewProjection) {
|
|
1018
|
+
previousViewProjection.copy(currentViewProjection);
|
|
1019
|
+
hasPreviousViewProjection = true;
|
|
1020
|
+
}
|
|
1021
|
+
uniforms.motionBlurInverseViewProjection.value.copy(currentViewProjection).invert();
|
|
1022
|
+
uniforms.motionBlurPreviousViewProjection.value.copy(previousViewProjection);
|
|
1023
|
+
uniforms.motionBlurStrength.value = settings.parameters.motionBlurStrength;
|
|
1024
|
+
uniforms.useMotionBlur.value = 1;
|
|
1025
|
+
previousViewProjection.copy(currentViewProjection);
|
|
1026
|
+
} else {
|
|
1027
|
+
uniforms.useMotionBlur.value = 0;
|
|
1028
|
+
hasPreviousViewProjection = false;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
if (settings.features.bloom &&
|
|
1032
|
+
settings.parameters.bloomMode === 'pyramid' &&
|
|
1033
|
+
settings.parameters.bloomStrength > 0) {
|
|
1034
|
+
uniforms.tBloom.value = renderBloomPyramid();
|
|
1035
|
+
uniforms.useBloomTexture.value = 1;
|
|
1036
|
+
} else {
|
|
1037
|
+
uniforms.useBloomTexture.value = 0;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
renderer.setRenderTarget(null);
|
|
1041
|
+
renderer.render(compositeScene, compositeCamera);
|
|
1042
|
+
renderer.setRenderTarget(previousRenderTarget);
|
|
1043
|
+
},
|
|
1044
|
+
setSize(nextWidth, nextHeight, nextPixelRatio = pixelRatio) {
|
|
1045
|
+
currentWidth = nextWidth;
|
|
1046
|
+
currentHeight = nextHeight;
|
|
1047
|
+
currentPixelRatio = nextPixelRatio;
|
|
1048
|
+
renderer.setPixelRatio(nextPixelRatio);
|
|
1049
|
+
renderer.setSize(nextWidth, nextHeight);
|
|
1050
|
+
target.setSize(nextWidth * nextPixelRatio, nextHeight * nextPixelRatio);
|
|
1051
|
+
sceneDepthColorTarget.setSize(nextWidth * nextPixelRatio, nextHeight * nextPixelRatio);
|
|
1052
|
+
disposeBloomChain();
|
|
1053
|
+
bloomChainWidth = 0;
|
|
1054
|
+
bloomChainHeight = 0;
|
|
1055
|
+
applyCompositeSettings(
|
|
1056
|
+
compositeMaterial,
|
|
1057
|
+
settings,
|
|
1058
|
+
camera,
|
|
1059
|
+
compositeDepthTexture(),
|
|
1060
|
+
nextWidth,
|
|
1061
|
+
nextHeight,
|
|
1062
|
+
nextPixelRatio,
|
|
1063
|
+
);
|
|
1064
|
+
},
|
|
1065
|
+
setCharacterMask(texture) {
|
|
1066
|
+
// See applyCompositeSettings' tLut comment: the node backends need a
|
|
1067
|
+
// real bound texture at all times, never `null`; harmless on classic
|
|
1068
|
+
// since tCharacterMask is only read behind the useCharacterMask guard.
|
|
1069
|
+
compositeMaterial.uniforms.tCharacterMask.value = texture ?? fallbackPostTexture();
|
|
1070
|
+
compositeMaterial.uniforms.useCharacterMask.value = texture ? 1.0 : 0.0;
|
|
1071
|
+
},
|
|
1072
|
+
setSettings(nextSettingsInput = {}) {
|
|
1073
|
+
settings = createPostProcessingSettings(nextSettingsInput);
|
|
1074
|
+
enabled = isPostProcessingEnabled(settings);
|
|
1075
|
+
applyCompositeSettings(
|
|
1076
|
+
compositeMaterial,
|
|
1077
|
+
settings,
|
|
1078
|
+
camera,
|
|
1079
|
+
compositeDepthTexture(),
|
|
1080
|
+
currentWidth,
|
|
1081
|
+
currentHeight,
|
|
1082
|
+
currentPixelRatio,
|
|
1083
|
+
);
|
|
1084
|
+
},
|
|
1085
|
+
target,
|
|
1086
|
+
};
|
|
1087
|
+
}
|