babylon.quarks 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +160 -0
  2. package/dist/babylon.quarks.cjs +1 -1
  3. package/dist/babylon.quarks.cjs.map +1 -1
  4. package/dist/babylon.quarks.esm.js +1 -1
  5. package/dist/babylon.quarks.esm.js.map +1 -1
  6. package/dist/babylon.quarks.umd.min.js +1 -1
  7. package/dist/babylon.quarks.umd.min.js.map +1 -1
  8. package/dist/types/index.d.cts +602 -190
  9. package/dist/types/index.d.ts +602 -190
  10. package/package.json +1 -1
  11. package/src/BatchedRenderer.ts +13 -1
  12. package/src/EffectPool.ts +5 -4
  13. package/src/ParticleEmitter.ts +89 -24
  14. package/src/ParticleSystem.ts +213 -30
  15. package/src/QuarksLoader.ts +9 -0
  16. package/src/QuarksSerializer.ts +1 -1
  17. package/src/QuarksUtil.ts +24 -2
  18. package/src/SpriteBatch.ts +3 -0
  19. package/src/TrailBatch.ts +8 -1
  20. package/src/VFXBatch.ts +112 -0
  21. package/src/batchKey.ts +2 -0
  22. package/src/batchSettings.ts +2 -0
  23. package/src/index.ts +2 -0
  24. package/src/shaderProgram.ts +103 -0
  25. package/src/shaderVariants.ts +102 -5
  26. package/src/shaders/chunks/blend.ts +15 -7
  27. package/src/shaders/chunks/imageProcessing.ts +143 -0
  28. package/src/shaders/particle_frag.glsl.ts +2 -0
  29. package/src/shaders/particle_frag.wgsl.ts +2 -0
  30. package/src/shaders/particle_physics_frag.glsl.ts +2 -0
  31. package/src/shaders/particle_physics_frag.wgsl.ts +2 -0
  32. package/src/shaders/trail_frag.glsl.ts +2 -0
  33. package/src/shaders/trail_frag.wgsl.ts +2 -0
  34. package/src/system/EmissionController.ts +21 -16
  35. package/src/system/ParticleSpawner.ts +96 -19
  36. package/src/system/integrate.ts +61 -4
  37. package/src/system/normalize.ts +28 -1
  38. package/src/system/settingsSchema.ts +25 -7
  39. package/src/system/types.ts +83 -14
package/src/QuarksUtil.ts CHANGED
@@ -2,12 +2,20 @@ import {InstancedMesh} from '@babylonjs/core/Meshes/instancedMesh';
2
2
  import {Mesh} from '@babylonjs/core/Meshes/mesh';
3
3
  import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
4
4
  import {Node} from '@babylonjs/core/node';
5
- import {ConstantValue, EmitSubParticleSystem, FunctionValueGenerator, IntervalValue, ValueGenerator} from 'quarks.core';
5
+ import {
6
+ ConstantValue,
7
+ EmitSubParticleSystem,
8
+ FunctionValueGenerator,
9
+ IntervalValue,
10
+ RandomBetweenCurves,
11
+ ValueGenerator,
12
+ } from 'quarks.core';
6
13
  import {BatchedRenderer} from './BatchedRenderer';
7
14
  import {shareEffectResources} from './effectResources';
8
15
  import {MeshSurfaceEmitter} from './MeshSurfaceEmitter';
9
16
  import {ParticleEmitter} from './ParticleEmitter';
10
17
  import {ParticleSystem} from './ParticleSystem';
18
+ import type {StopAction} from './system/types';
11
19
 
12
20
  /**
13
21
  * Helpers that act on a whole effect: every particle system under a root node, as a loaded effect
@@ -71,7 +79,17 @@ export class QuarksUtil {
71
79
  });
72
80
  }
73
81
 
74
- /** Sets {@link ParticleSystem.autoDestroy} on every system under `root`. */
82
+ /** Sets {@link ParticleSystem.stopAction} on every system under `root`. */
83
+ static setStopAction(root: Node, stopAction: StopAction): void {
84
+ QuarksUtil.runOnAllParticleEmitters(root, (emitter) => {
85
+ (emitter.system as unknown as ParticleSystem).stopAction = stopAction;
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Sets {@link ParticleSystem.autoDestroy} on every system under `root`.
91
+ * @deprecated Use {@link QuarksUtil.setStopAction} with `'destroy'`.
92
+ */
75
93
  static setAutoDestroy(root: Node, autoDestroy: boolean): void {
76
94
  QuarksUtil.runOnAllParticleEmitters(root, (emitter) => {
77
95
  (emitter.system as unknown as ParticleSystem).autoDestroy = autoDestroy;
@@ -272,6 +290,10 @@ export class QuarksUtil {
272
290
  if (generator instanceof IntervalValue) {
273
291
  return Math.max(generator.a, generator.b);
274
292
  }
293
+ if (generator instanceof RandomBetweenCurves) {
294
+ // Every mix lies between the two curves.
295
+ return Math.max(QuarksUtil.maxOf(generator.curve1, at), QuarksUtil.maxOf(generator.curve2, at));
296
+ }
275
297
  let max = -Infinity;
276
298
  const samples = 32;
277
299
  for (let i = 0; i <= samples; i++) {
@@ -333,10 +333,12 @@ export class SpriteBatch extends VFXBatch {
333
333
  }
334
334
  }
335
335
  const useEnvAtlas = this.usesEnvAtlas();
336
+ const imageProcessing = this.imageProcessing();
336
337
  const program = shaderProgram(this.settings, {
337
338
  depth: this.depth,
338
339
  envAtlasReady: useEnvAtlas,
339
340
  meshUV2: this.meshUV2(),
341
+ imageProcessing,
340
342
  });
341
343
  const {defines, attributes, uniforms, samplers} = program;
342
344
  const shaderName = program.name;
@@ -407,6 +409,7 @@ export class SpriteBatch extends VFXBatch {
407
409
  }
408
410
 
409
411
  this.bindFog(mat);
412
+ this.bindImageProcessing(mat, imageProcessing);
410
413
  this.bindSurface(mat);
411
414
  // Billboards, beams and the like are flat sheets: both faces, whatever the setting says.
412
415
  mat.backFaceCulling = this.settings.renderFace !== 'both';
package/src/TrailBatch.ts CHANGED
@@ -234,7 +234,13 @@ export class TrailBatch extends VFXBatch {
234
234
 
235
235
  /** @internal */
236
236
  rebuildMaterial(): void {
237
- const program = shaderProgram(this.settings, {depth: this.depth, envAtlasReady: false, meshUV2: false});
237
+ const imageProcessing = this.imageProcessing();
238
+ const program = shaderProgram(this.settings, {
239
+ depth: this.depth,
240
+ envAtlasReady: false,
241
+ meshUV2: false,
242
+ imageProcessing,
243
+ });
238
244
  const {defines, attributes, uniforms, samplers} = program;
239
245
  // Stable name: the trail shader source never varies, so registering it once
240
246
  // keeps Effect.ShadersStore bounded and lets Babylon reuse compiled effects.
@@ -260,6 +266,7 @@ export class TrailBatch extends VFXBatch {
260
266
  mat.setTexture('map', this.settings.texture);
261
267
  }
262
268
  this.bindFog(mat);
269
+ this.bindImageProcessing(mat, imageProcessing);
263
270
  this.bindSoft(mat);
264
271
  this.bindSurface(mat);
265
272
  mat.backFaceCulling = false;
package/src/VFXBatch.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import {BoundingInfo} from '@babylonjs/core/Culling/boundingInfo';
2
2
  import {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
3
3
  import type {Effect} from '@babylonjs/core/Materials/effect';
4
+ import type {ImageProcessingConfiguration} from '@babylonjs/core/Materials/imageProcessingConfiguration';
4
5
  import {ShaderMaterial} from '@babylonjs/core/Materials/shaderMaterial';
5
6
  import {Vector2 as BVector2, Vector3 as BVector3, Vector4 as BVector4, Matrix} from '@babylonjs/core/Maths/math.vector';
6
7
  import {Mesh} from '@babylonjs/core/Meshes/mesh';
8
+ import type {Observer} from '@babylonjs/core/Misc/observable';
7
9
  import type {Node} from '@babylonjs/core/node';
8
10
  import {Scene} from '@babylonjs/core/scene';
9
11
  import {IParticleSystem, Vector3} from 'quarks.core';
@@ -11,6 +13,7 @@ import {VFXBatchSettings} from './BatchedRenderer';
11
13
  import type {ParticleSystem} from './ParticleSystem';
12
14
  import {storedBatchSettings} from './batchKey';
13
15
  import {GRADIENT_MAP_WEIGHTS, MeshLightingSettings, readsUV1, StoredBatchSettings, TextureUV} from './batchSettings';
16
+ import {imageProcessingParts, ShaderImageProcessing} from './shaderProgram';
14
17
  import {DepthBinding, softDepthParams} from './softDepth';
15
18
 
16
19
  export * from './batchSettings';
@@ -199,6 +202,100 @@ export abstract class VFXBatch {
199
202
  });
200
203
  }
201
204
 
205
+ /** The observer of the scene's image processing that rebuilds the material when its defines change. */
206
+ private imageProcessingObserver: Observer<ImageProcessingConfiguration> | null = null;
207
+
208
+ /**
209
+ * The scene's image processing as the material compiles it, see {@link ShaderContext.imageProcessing}:
210
+ * null when the batch does not apply it.
211
+ * @internal
212
+ */
213
+ protected imageProcessing(): ShaderImageProcessing | null {
214
+ if (this.settings.applyImageProcessing === false) {
215
+ return null;
216
+ }
217
+ const defines: ShaderImageProcessing = {
218
+ IMAGEPROCESSING: false,
219
+ WHITEBALANCE: false,
220
+ VIGNETTE: false,
221
+ VIGNETTEBLENDMODEMULTIPLY: false,
222
+ VIGNETTEBLENDMODEOPAQUE: false,
223
+ TONEMAPPING: 0,
224
+ CONTRAST: false,
225
+ EXPOSURE: false,
226
+ COLORCURVES: false,
227
+ COLORGRADING: false,
228
+ COLORGRADING3D: false,
229
+ SAMPLER3DGREENDEPTH: false,
230
+ SAMPLER3DBGRMAP: false,
231
+ DITHER: false,
232
+ IMAGEPROCESSINGPOSTPROCESS: false,
233
+ SKIPFINALCOLORCLAMP: false,
234
+ };
235
+ this.scene.imageProcessingConfiguration.prepareDefines(defines);
236
+ return defines;
237
+ }
238
+
239
+ /**
240
+ * Binds the scene's image processing to a material compiled with `compiled`: its values on every
241
+ * bind, unless a post process applies them, and the material's defines, uniforms and samplers
242
+ * swapped in place whenever the configuration changes what it has to compile (tone mapping
243
+ * switched on, a vignette, a colour grading texture, a post process taking over). The material
244
+ * stays the same one: a configuration that flips back and forth within a frame, as a reflection
245
+ * probe rendering in linear space does, only switches between the effects the engine has
246
+ * cached for each set of defines. A colour grading texture is set on the material too, so
247
+ * that the batch, like Babylon's own materials, is not drawn before it has loaded.
248
+ * @internal
249
+ */
250
+ protected bindImageProcessing(material: ShaderMaterial, compiled: ShaderImageProcessing | null): void {
251
+ this.releaseImageProcessing();
252
+ if (this.settings.applyImageProcessing === false) {
253
+ return;
254
+ }
255
+ const config = this.scene.imageProcessingConfiguration;
256
+ let current = compiled;
257
+ let parts = imageProcessingParts(this.settings, current);
258
+ let key = parts.defines.join('\n');
259
+ const grading = () => {
260
+ if (parts.samplers.includes('txColorTransform') && config.colorGradingTexture) {
261
+ material.setTexture('txColorTransform', config.colorGradingTexture);
262
+ } else {
263
+ material.removeTexture('txColorTransform');
264
+ }
265
+ };
266
+ grading();
267
+ this.imageProcessingObserver = config.onUpdateParameters.add(() => {
268
+ const next = this.imageProcessing();
269
+ const nextParts = imageProcessingParts(this.settings, next);
270
+ const nextKey = nextParts.defines.join('\n');
271
+ if (nextKey !== key) {
272
+ const options = material.options;
273
+ swapEntries(options.defines as string[], parts.defines, nextParts.defines);
274
+ swapEntries(options.uniforms, parts.uniforms, nextParts.uniforms);
275
+ swapEntries(options.samplers, parts.samplers, nextParts.samplers);
276
+ current = next;
277
+ parts = nextParts;
278
+ key = nextKey;
279
+ }
280
+ grading();
281
+ });
282
+ material.onBindObservable.add(() => {
283
+ if (!current?.IMAGEPROCESSING || current.IMAGEPROCESSINGPOSTPROCESS || parts.defines.length === 0) {
284
+ return;
285
+ }
286
+ const effect = material.getEffect();
287
+ if (effect) {
288
+ config.bind(effect);
289
+ }
290
+ });
291
+ }
292
+
293
+ /** Stops following the scene's image processing. @internal */
294
+ protected releaseImageProcessing(): void {
295
+ this.imageProcessingObserver?.remove();
296
+ this.imageProcessingObserver = null;
297
+ }
298
+
202
299
  /**
203
300
  * Seconds of renderer time, set by the renderer before each fill; what UV
204
301
  * and erosion mask scrolling advance by.
@@ -477,6 +574,21 @@ export abstract class VFXBatch {
477
574
 
478
575
  /** @internal */
479
576
  dispose(): void {
577
+ this.releaseImageProcessing();
480
578
  this.mesh.dispose();
481
579
  }
482
580
  }
581
+
582
+ /**
583
+ * Replaces, in a material option list, the entries `previous` added with those of `next`: one
584
+ * occurrence each, so an entry the rest of the program also lists is kept.
585
+ */
586
+ function swapEntries(list: string[], previous: readonly string[], next: readonly string[]): void {
587
+ for (const entry of previous) {
588
+ const at = list.lastIndexOf(entry);
589
+ if (at !== -1) {
590
+ list.splice(at, 1);
591
+ }
592
+ }
593
+ list.push(...next);
594
+ }
package/src/batchKey.ts CHANGED
@@ -104,6 +104,7 @@ export function storedBatchSettings(settings: VFXBatchSettings): StoredBatchSett
104
104
  layerMask: settings.layerMask,
105
105
  renderingGroupId: settings.renderingGroupId ?? 0,
106
106
  applyFog: settings.applyFog ?? true,
107
+ applyImageProcessing: settings.applyImageProcessing ?? true,
107
108
  pivot: quad && pivot && (pivot.x !== 0 || pivot.y !== 0 || pivot.z !== 0) ? pivot.clone() : undefined,
108
109
  sortMode: settings.sortMode ?? 'none',
109
110
  renderFace: isMesh ? validRenderFace(settings.renderFace) : 'both',
@@ -155,6 +156,7 @@ export function shaderKeyEqual(a: StoredBatchSettings, b: StoredBatchSettings):
155
156
  a.renderingGroupId === b.renderingGroupId &&
156
157
  a.renderOrder === b.renderOrder &&
157
158
  a.applyFog === b.applyFog &&
159
+ a.applyImageProcessing === b.applyImageProcessing &&
158
160
  a.sortMode === b.sortMode &&
159
161
  a.renderFace === b.renderFace &&
160
162
  !a.pivot === !b.pivot &&
@@ -521,6 +521,8 @@ export interface StoredBatchSettings {
521
521
  renderingGroupId: number;
522
522
  /** Whether the scene's fog is applied, see {@link VFXBatchSettings.applyFog}. */
523
523
  applyFog: boolean;
524
+ /** Whether the scene's image processing is applied, see {@link VFXBatchSettings.applyImageProcessing}. */
525
+ applyImageProcessing: boolean;
524
526
  /** Billboard pivot in particle-size units, see {@link VFXBatchSettings.pivot}. */
525
527
  pivot?: Vector3;
526
528
  /** Draw order within the batch, see {@link SortMode}. */
package/src/index.ts CHANGED
@@ -48,7 +48,9 @@ export type {
48
48
  CullingMode,
49
49
  ParticleSystemJSONParameters,
50
50
  ParticleSystemParameters,
51
+ RingBufferMode,
51
52
  ScalingMode,
53
+ StopAction,
52
54
  } from './ParticleSystem';
53
55
  export {QuarksFormatError, QuarksLoader} from './QuarksLoader';
54
56
  export type {
@@ -1,4 +1,9 @@
1
1
  import {Constants} from '@babylonjs/core/Engines/constants';
2
+ import type {IImageProcessingConfigurationDefines} from '@babylonjs/core/Materials/imageProcessingConfiguration.defines';
3
+ import {
4
+ PrepareSamplersForImageProcessing,
5
+ PrepareUniformsForImageProcessing,
6
+ } from '@babylonjs/core/Materials/imageProcessingConfiguration.functions';
2
7
  import {hasSizeLimits, takesPivotAndSizeLimits, tilesSheet} from './batchKey';
3
8
  import {readsUV1, RenderMode, StoredBatchSettings} from './batchSettings';
4
9
  import local_particle_physics_vert from './shaders/local_particle_physics_vert.glsl';
@@ -35,6 +40,7 @@ export type ShaderSettings = Pick<
35
40
  | 'materialTransparent'
36
41
  | 'materialBlendMode'
37
42
  | 'applyFog'
43
+ | 'applyImageProcessing'
38
44
  | 'pivot'
39
45
  | 'minParticleSize'
40
46
  | 'maxParticleSize'
@@ -66,8 +72,41 @@ export interface ShaderContext {
66
72
  envAtlasReady: boolean;
67
73
  /** Whether the mesh batch has a second UV set to read for {@link UVChannel} 1. */
68
74
  meshUV2: boolean;
75
+ /**
76
+ * The scene's image processing as `ImageProcessingConfiguration.prepareDefines` writes it;
77
+ * null or left out for none. Compiled in only when the settings apply it, see
78
+ * {@link ParticleSystem.applyImageProcessing}.
79
+ */
80
+ imageProcessing?: ShaderImageProcessing | null;
69
81
  }
70
82
 
83
+ /**
84
+ * The defines an `ImageProcessingConfiguration` prepares for a material
85
+ * (`ImageProcessingConfigurationDefines`): which of its effects are on, and whether a post
86
+ * process applies it instead.
87
+ */
88
+ export type ShaderImageProcessing = Readonly<IImageProcessingConfigurationDefines>;
89
+
90
+ /** The keys of {@link ShaderImageProcessing}, in the order Babylon writes them. @internal */
91
+ export const IMAGE_PROCESSING_DEFINES: readonly (keyof IImageProcessingConfigurationDefines)[] = [
92
+ 'IMAGEPROCESSING',
93
+ 'WHITEBALANCE',
94
+ 'VIGNETTE',
95
+ 'VIGNETTEBLENDMODEMULTIPLY',
96
+ 'VIGNETTEBLENDMODEOPAQUE',
97
+ 'TONEMAPPING',
98
+ 'CONTRAST',
99
+ 'COLORCURVES',
100
+ 'COLORGRADING',
101
+ 'COLORGRADING3D',
102
+ 'SAMPLER3DGREENDEPTH',
103
+ 'SAMPLER3DBGRMAP',
104
+ 'DITHER',
105
+ 'IMAGEPROCESSINGPOSTPROCESS',
106
+ 'EXPOSURE',
107
+ 'SKIPFINALCOLORCLAMP',
108
+ ];
109
+
71
110
  /**
72
111
  * One compiled shader of the library: the sources in both languages and the defines, attributes,
73
112
  * uniforms and samplers the material is built with. Built by {@link shaderProgram} from a
@@ -292,6 +331,7 @@ function spriteShaderProgram(settings: ShaderSettings, context: ShaderContext):
292
331
  uniforms.push('eyePosition');
293
332
  }
294
333
  surfaceDefines(settings, defines, uniforms, samplers);
334
+ imageProcessingDefines(settings, context, defines, uniforms, samplers);
295
335
  if (isMesh) {
296
336
  attributes.push('normal');
297
337
  if (readsUV1(settings) && context.meshUV2) {
@@ -317,6 +357,7 @@ function trailShaderProgram(settings: ShaderSettings, context: ShaderContext): S
317
357
  }
318
358
  surfaceDefines(settings, defines, uniforms, samplers);
319
359
  softDefines(settings, context, defines, uniforms, samplers);
360
+ imageProcessingDefines(settings, context, defines, uniforms, samplers);
320
361
  if (readsUV1(settings)) {
321
362
  // A trail's channel 1 arrives as its own vertex buffer.
322
363
  attributes.push('uv2');
@@ -356,6 +397,68 @@ function blendDefines(settings: ShaderSettings): string[] {
356
397
  return defines;
357
398
  }
358
399
 
400
+ /**
401
+ * Whether a batch with `settings` compiles the scene's image processing `ip` in: when the settings
402
+ * apply it and it does something, either in the material (`IMAGEPROCESSING`) or through a post
403
+ * process the colour is left in linear space for (`IMAGEPROCESSINGPOSTPROCESS`). A material that
404
+ * multiplies the frame takes no in-material processing: its colour is a factor, and tone mapping
405
+ * white would darken the frame where the particle is fully faded. See `chunks/imageProcessing.ts`.
406
+ */
407
+ export function compilesImageProcessing(
408
+ settings: Pick<ShaderSettings, 'applyImageProcessing' | 'materialTransparent' | 'materialBlendMode'>,
409
+ ip: ShaderImageProcessing | null | undefined
410
+ ): boolean {
411
+ if (settings.applyImageProcessing === false || !ip) {
412
+ return false;
413
+ }
414
+ if (ip.IMAGEPROCESSINGPOSTPROCESS) {
415
+ return true;
416
+ }
417
+ return ip.IMAGEPROCESSING && blendCategory(settings).output !== 'multiply';
418
+ }
419
+
420
+ /**
421
+ * The defines, uniforms and samplers the scene's image processing `ip` adds to a material with
422
+ * `settings`: nothing unless {@link compilesImageProcessing}, otherwise its defines as Babylon
423
+ * writes them (a number always, a flag when set) and the uniforms and samplers its effects read.
424
+ * A batch swaps these in place on its material when the configuration changes what compiles.
425
+ * @internal
426
+ */
427
+ export function imageProcessingParts(
428
+ settings: Pick<ShaderSettings, 'applyImageProcessing' | 'materialTransparent' | 'materialBlendMode'>,
429
+ ip: ShaderImageProcessing | null | undefined
430
+ ): {defines: string[]; uniforms: string[]; samplers: string[]} {
431
+ const parts = {defines: [] as string[], uniforms: [] as string[], samplers: [] as string[]};
432
+ if (!ip || !compilesImageProcessing(settings, ip)) {
433
+ return parts;
434
+ }
435
+ for (const key of IMAGE_PROCESSING_DEFINES) {
436
+ const value = ip[key];
437
+ if (typeof value === 'number') {
438
+ parts.defines.push(`${key} ${value}`);
439
+ } else if (value) {
440
+ parts.defines.push(key);
441
+ }
442
+ }
443
+ PrepareUniformsForImageProcessing(parts.uniforms, ip);
444
+ PrepareSamplersForImageProcessing(parts.samplers, ip);
445
+ return parts;
446
+ }
447
+
448
+ /** Appends {@link imageProcessingParts} to a program's lists. */
449
+ function imageProcessingDefines(
450
+ settings: ShaderSettings,
451
+ context: ShaderContext,
452
+ defines: string[],
453
+ uniforms: string[],
454
+ samplers: string[]
455
+ ): void {
456
+ const parts = imageProcessingParts(settings, context.imageProcessing);
457
+ defines.push(...parts.defines);
458
+ uniforms.push(...parts.uniforms);
459
+ samplers.push(...parts.samplers);
460
+ }
461
+
359
462
  /** Soft particles, when on and a depth texture is there to read, and the alpha test. */
360
463
  function softDefines(
361
464
  settings: ShaderSettings,
@@ -1,7 +1,7 @@
1
1
  import {Constants} from '@babylonjs/core/Engines/constants';
2
2
  import type {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
3
3
  import {Vector3} from 'quarks.core';
4
- import {ShaderContext, shaderProgram, ShaderProgram, ShaderSettings} from './shaderProgram';
4
+ import {ShaderContext, ShaderImageProcessing, shaderProgram, ShaderProgram, ShaderSettings} from './shaderProgram';
5
5
  import {
6
6
  normalizeErosion,
7
7
  normalizeGradientMap,
@@ -25,7 +25,10 @@ export interface ShaderVariant {
25
25
  /** A texture reference for settings that only ask whether one is set; it is never sampled. */
26
26
  const TEXTURE = {} as BaseTexture;
27
27
 
28
- /** The settings of a bare batch in `renderMode`: no texture, no effects, fog off, 1×1 sheet. */
28
+ /**
29
+ * The settings of a bare batch in `renderMode`: no texture, no effects, fog off, 1×1 sheet. Image
30
+ * processing is applied, and compiles in only with an image processing axis in the context.
31
+ */
29
32
  function bare(renderMode: RenderMode): ShaderSettings {
30
33
  return {
31
34
  renderMode,
@@ -39,6 +42,7 @@ function bare(renderMode: RenderMode): ShaderSettings {
39
42
  materialTransparent: true,
40
43
  materialBlendMode: Constants.ALPHA_COMBINE,
41
44
  applyFog: false,
45
+ applyImageProcessing: true,
42
46
  pivot: undefined,
43
47
  minParticleSize: 0,
44
48
  maxParticleSize: Infinity,
@@ -184,6 +188,90 @@ const SURFACE_TARGETS = [
184
188
  'trail',
185
189
  ];
186
190
 
191
+ /** A scene configuration's defines with `on` set and the rest off, see {@link ShaderImageProcessing}. */
192
+ function imageProcessing(on: Partial<ShaderImageProcessing>): ShaderImageProcessing {
193
+ return {
194
+ IMAGEPROCESSING: false,
195
+ WHITEBALANCE: false,
196
+ VIGNETTE: false,
197
+ VIGNETTEBLENDMODEMULTIPLY: false,
198
+ VIGNETTEBLENDMODEOPAQUE: false,
199
+ TONEMAPPING: 0,
200
+ CONTRAST: false,
201
+ EXPOSURE: false,
202
+ COLORCURVES: false,
203
+ COLORGRADING: false,
204
+ COLORGRADING3D: false,
205
+ SAMPLER3DGREENDEPTH: false,
206
+ SAMPLER3DBGRMAP: false,
207
+ DITHER: false,
208
+ IMAGEPROCESSINGPOSTPROCESS: false,
209
+ SKIPFINALCOLORCLAMP: false,
210
+ ...on,
211
+ };
212
+ }
213
+
214
+ /**
215
+ * The scene's image processing: applied by a post process, and in the material with each of its
216
+ * effects and tone mappers at least once.
217
+ */
218
+ const IMAGE_PROCESSING: Axis[] = [
219
+ {name: 'post-processed', context: {imageProcessing: imageProcessing({IMAGEPROCESSINGPOSTPROCESS: true})}},
220
+ {name: 'tone mapped', context: {imageProcessing: imageProcessing({IMAGEPROCESSING: true, TONEMAPPING: 1})}},
221
+ {
222
+ name: 'aces exposure contrast',
223
+ context: {
224
+ imageProcessing: imageProcessing({IMAGEPROCESSING: true, TONEMAPPING: 2, EXPOSURE: true, CONTRAST: true}),
225
+ },
226
+ },
227
+ {
228
+ name: 'neutral vignette dither',
229
+ context: {
230
+ imageProcessing: imageProcessing({
231
+ IMAGEPROCESSING: true,
232
+ TONEMAPPING: 3,
233
+ VIGNETTE: true,
234
+ VIGNETTEBLENDMODEMULTIPLY: true,
235
+ DITHER: true,
236
+ }),
237
+ },
238
+ },
239
+ {
240
+ name: 'opaque vignette curves white balance grading',
241
+ context: {
242
+ imageProcessing: imageProcessing({
243
+ IMAGEPROCESSING: true,
244
+ VIGNETTE: true,
245
+ VIGNETTEBLENDMODEOPAQUE: true,
246
+ COLORCURVES: true,
247
+ WHITEBALANCE: true,
248
+ COLORGRADING: true,
249
+ SKIPFINALCOLORCLAMP: true,
250
+ }),
251
+ },
252
+ },
253
+ {
254
+ name: 'grading 3d green depth bgr',
255
+ context: {
256
+ imageProcessing: imageProcessing({
257
+ IMAGEPROCESSING: true,
258
+ COLORGRADING: true,
259
+ COLORGRADING3D: true,
260
+ SAMPLER3DGREENDEPTH: true,
261
+ SAMPLER3DBGRMAP: true,
262
+ }),
263
+ },
264
+ },
265
+ ];
266
+
267
+ /** The blends image processing is tried with: over, premultiplied, additive and multiply. */
268
+ const IMAGE_PROCESSING_BLEND: Axis[] = [
269
+ NONE,
270
+ {name: 'premultiplied', settings: {materialBlendMode: Constants.ALPHA_PREMULTIPLIED}},
271
+ {name: 'additive fog', settings: {materialBlendMode: Constants.ALPHA_ADD, applyFog: true}},
272
+ {name: 'multiply', settings: {materialBlendMode: Constants.ALPHA_MULTIPLY}},
273
+ ];
274
+
187
275
  /** A mesh batch whose geometry has a second UV set. */
188
276
  const MESH_UV2: Axis = {name: 'uv2', context: {meshUV2: true}};
189
277
 
@@ -197,8 +285,8 @@ const EVERYTHING_ELSE: Axis = {
197
285
  /**
198
286
  * Every shader program the library can compile, one per distinct set of defines: every render
199
287
  * mode with every combination of sheet, map, soft particles, alpha test and fog, every blend
200
- * resolve with and without fog and soft particles, and the texture effects alone and together on
201
- * each shader family. What a shader validator compiles, and what
288
+ * resolve with and without fog and soft particles, the texture effects alone and together on
289
+ * each shader family, and the scene's image processing on every render mode. What a shader validator compiles, and what
202
290
  * an application can warm the shader cache with before its first frame (Unity's
203
291
  * ShaderVariantCollection). Built by {@link shaderProgram}, so a variant here is a material a
204
292
  * batch would build.
@@ -208,7 +296,7 @@ export function shaderVariants(): ShaderVariant[] {
208
296
  const out: ShaderVariant[] = [];
209
297
  const add = (axes: Axis[]) => {
210
298
  const settings = bare(RenderMode.BillBoard);
211
- const context: ShaderContext = {depth: null, envAtlasReady: false, meshUV2: false};
299
+ const context: ShaderContext = {depth: null, envAtlasReady: false, meshUV2: false, imageProcessing: null};
212
300
  for (const axis of axes) {
213
301
  Object.assign(settings, axis.settings);
214
302
  Object.assign(context, axis.context);
@@ -241,5 +329,14 @@ export function shaderVariants(): ShaderVariant[] {
241
329
  for (const map of MAP)
242
330
  for (const tile of [NONE, TILE[2]])
243
331
  for (const rest of [NONE, EVERYTHING_ELSE]) add([target, uv2, surface, map, tile, rest]);
332
+
333
+ for (const target of TARGETS)
334
+ for (const ip of IMAGE_PROCESSING)
335
+ for (const blend of IMAGE_PROCESSING_BLEND) for (const map of MAP) add([target, ip, blend, map]);
336
+
337
+ // With the rest of the fragment: soft, alpha-tested, fogged and with every texture effect.
338
+ const everySurface = SURFACE[SURFACE.length - 3];
339
+ for (const target of TARGETS.filter((t) => SURFACE_TARGETS.includes(t.name)))
340
+ for (const ip of IMAGE_PROCESSING) add([target, ip, everySurface, MAP[MAP.length - 1], EVERYTHING_ELSE]);
244
341
  return out;
245
342
  }
@@ -5,27 +5,35 @@
5
5
  * only, so that alpha means coverage. Blend modes that take the colour at full weight
6
6
  * (`BLEND_PREMULTIPLY`: one-one, premultiplied, screen, subtract, …) would ignore it, so the
7
7
  * resolve multiplies the colour by its alpha; modes that multiply the frame (`BLEND_MULTIPLY`)
8
- * move it toward white instead. It runs last, after fog and the soft fade. See `blendCategory`.
8
+ * move it toward white instead. It runs last, after fog and the soft fade, with the scene's image
9
+ * processing (`imageProcessing.ts`) around it. See `blendCategory`.
9
10
  */
10
11
 
11
- /** Resolves the vec4 lvalue `color` for the blend mode, GLSL. */
12
+ import {
13
+ imageProcessingFragmentGLSL,
14
+ imageProcessingFragmentWGSL,
15
+ imageProcessingResolvedGLSL,
16
+ imageProcessingResolvedWGSL,
17
+ } from './imageProcessing';
18
+
19
+ /** Processes and resolves the vec4 lvalue `color` for the blend mode, GLSL. */
12
20
  export function blendFragmentGLSL(color: string): string {
13
- return /* glsl */ `
21
+ return /* glsl */ `${imageProcessingFragmentGLSL(color)}
14
22
  #if defined(BLEND_PREMULTIPLY)
15
23
  ${color}.rgb *= ${color}.a;
16
24
  #elif defined(BLEND_MULTIPLY)
17
25
  ${color}.rgb = mix(vec3(1.0), ${color}.rgb, ${color}.a);
18
26
  #endif
19
- `;
27
+ ${imageProcessingResolvedGLSL(color)}`;
20
28
  }
21
29
 
22
- /** Resolves the `var` vec4 `color` for the blend mode, WGSL. */
30
+ /** Processes and resolves the `var` vec4 `color` for the blend mode, WGSL. */
23
31
  export function blendFragmentWGSL(color: string): string {
24
- return /* wgsl */ `
32
+ return /* wgsl */ `${imageProcessingFragmentWGSL(color)}
25
33
  #if defined(BLEND_PREMULTIPLY)
26
34
  ${color} = vec4f(${color}.rgb * ${color}.a, ${color}.a);
27
35
  #elif defined(BLEND_MULTIPLY)
28
36
  ${color} = vec4f(mix(vec3f(1.0), ${color}.rgb, ${color}.a), ${color}.a);
29
37
  #endif
30
- `;
38
+ ${imageProcessingResolvedWGSL(color)}`;
31
39
  }