babylon.quarks 0.27.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 (55) hide show
  1. package/CHANGELOG.md +585 -0
  2. package/README.md +3 -1
  3. package/dist/babylon.quarks.cjs +1 -1
  4. package/dist/babylon.quarks.cjs.map +1 -1
  5. package/dist/babylon.quarks.esm.js +1 -1
  6. package/dist/babylon.quarks.esm.js.map +1 -1
  7. package/dist/babylon.quarks.umd.min.js +1 -1
  8. package/dist/babylon.quarks.umd.min.js.map +1 -1
  9. package/dist/types/index.d.cts +4811 -974
  10. package/dist/types/index.d.ts +4811 -974
  11. package/package.json +7 -2
  12. package/src/BatchedRenderer.ts +106 -79
  13. package/src/EffectPool.ts +96 -16
  14. package/src/MeshSurfaceEmitter.ts +24 -13
  15. package/src/ParticleEmitter.ts +119 -26
  16. package/src/ParticleSystem.ts +305 -55
  17. package/src/QuarksLoader.ts +298 -171
  18. package/src/QuarksPrefab.ts +301 -94
  19. package/src/QuarksSerializer.ts +44 -16
  20. package/src/QuarksUtil.ts +88 -11
  21. package/src/SpriteBatch.ts +24 -6
  22. package/src/TrailBatch.ts +20 -7
  23. package/src/VFXBatch.ts +182 -528
  24. package/src/batchKey.ts +6 -2
  25. package/src/batchSettings.ts +548 -0
  26. package/src/effectDocumentJSON.ts +261 -0
  27. package/src/effectResources.ts +21 -24
  28. package/src/envAtlas.ts +8 -2
  29. package/src/geometryUtil.ts +1 -1
  30. package/src/index.ts +41 -4
  31. package/src/nodeId.ts +25 -2
  32. package/src/shaderProgram.ts +213 -16
  33. package/src/shaderVariants.ts +124 -7
  34. package/src/shaders/chunks/blend.ts +39 -0
  35. package/src/shaders/chunks/fog.ts +8 -3
  36. package/src/shaders/chunks/imageProcessing.ts +143 -0
  37. package/src/shaders/chunks/soft.ts +6 -5
  38. package/src/shaders/chunks/surface.ts +2 -2
  39. package/src/shaders/particle_frag.glsl.ts +5 -1
  40. package/src/shaders/particle_frag.wgsl.ts +4 -0
  41. package/src/shaders/particle_physics_frag.glsl.ts +7 -2
  42. package/src/shaders/particle_physics_frag.wgsl.ts +4 -0
  43. package/src/shaders/trail_frag.glsl.ts +6 -1
  44. package/src/shaders/trail_frag.wgsl.ts +4 -0
  45. package/src/system/EmissionController.ts +23 -20
  46. package/src/system/EventDispatcher.ts +26 -10
  47. package/src/system/ParticleSpawner.ts +96 -19
  48. package/src/system/geometry.ts +22 -29
  49. package/src/system/integrate.ts +61 -4
  50. package/src/system/materialResolve.ts +56 -7
  51. package/src/system/normalize.ts +34 -2
  52. package/src/system/serialization.ts +54 -33
  53. package/src/system/settingsSchema.ts +57 -17
  54. package/src/system/types.ts +150 -42
  55. package/src/trailSettings.ts +9 -6
@@ -1,5 +1,11 @@
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';
8
+ import {readsUV1, RenderMode, StoredBatchSettings} from './batchSettings';
3
9
  import local_particle_physics_vert from './shaders/local_particle_physics_vert.glsl';
4
10
  import local_particle_physics_vert_wgsl from './shaders/local_particle_physics_vert.wgsl';
5
11
  import particle_frag from './shaders/particle_frag.glsl';
@@ -15,7 +21,6 @@ import trail_frag from './shaders/trail_frag.glsl';
15
21
  import trail_frag_wgsl from './shaders/trail_frag.wgsl';
16
22
  import trail_vert from './shaders/trail_vert.glsl';
17
23
  import trail_vert_wgsl from './shaders/trail_vert.wgsl';
18
- import {readsUV1, RenderMode, StoredBatchSettings} from './VFXBatch';
19
24
 
20
25
  /**
21
26
  * The part of a batch's settings that decides its shader: the render mode, which textures and
@@ -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
@@ -111,12 +150,94 @@ export function samplesMap(settings: {renderMode: RenderMode; texture: unknown})
111
150
  return settings.texture !== null || settings.renderMode === RenderMode.Mesh;
112
151
  }
113
152
 
114
- /** Whether the blend mode adds to the frame, in which case fog and fade darken instead of mixing. */
115
- export function blendsAdditively(settings: {materialTransparent: boolean; materialBlendMode: number}): boolean {
116
- return (
117
- settings.materialTransparent &&
118
- (settings.materialBlendMode === Constants.ALPHA_ADD || settings.materialBlendMode === Constants.ALPHA_ONEONE)
119
- );
153
+ /**
154
+ * How a particle's colour has to leave the fragment shader for its blend mode, so that its alpha
155
+ * means coverage: alpha 0 leaves the frame as it was, alpha 1 draws the colour in full, and the
156
+ * fades (soft particles, erosion, a rim fade, alpha over life) only lower alpha. Unity's particle
157
+ * shaders do the same for their Alpha, Premultiply, Additive and Multiply modes. Alpha has no
158
+ * such meaning for an opaque material, nor under `ALPHA_INTERPOLATE` (constant blend colour),
159
+ * `ALPHA_REPLACE_COLOR` (replaces the frame) and `ALPHA_DUAL_SRC0_ADD_SRC1xDST` (reads a second
160
+ * shader output the particle shaders do not write); these leave the colour as it is.
161
+ *
162
+ * - `'straight'`: the blend weighs the colour by its alpha itself (Babylon's `ALPHA_COMBINE`,
163
+ * `ALPHA_ADD`, `ALPHA_LAYER_ACCUMULATE`, …) or does not blend; the colour leaves as it is.
164
+ * - `'premultiply'`: the blend takes the colour at full weight, or the frame by one minus the
165
+ * colour (`ALPHA_PREMULTIPLIED`, `ALPHA_ONEONE`, `ALPHA_SCREENMODE`, `ALPHA_SUBTRACT`,
166
+ * `ALPHA_MAXIMIZED`, `ALPHA_MAX`, …), so the shader multiplies it by its alpha: black, which
167
+ * these modes leave the frame alone with, at alpha 0. Textures stay straight alpha, as in
168
+ * Unity's Premultiply mode. The fade is linear in alpha except under `ALPHA_MAXIMIZED`, whose
169
+ * blend weighs the colour by alpha once more, and `ALPHA_MAX`.
170
+ * - `'multiply'`: the blend multiplies the frame by the colour (`ALPHA_MULTIPLY`, `ALPHA_MIN`), so
171
+ * the shader moves it toward white, which leaves the frame alone, as alpha falls. `ALPHA_MIN`
172
+ * clamps a frame brighter than 1 (an HDR target) to 1 where a particle draws, even at alpha 0.
173
+ */
174
+ export type BlendOutput = 'straight' | 'premultiply' | 'multiply';
175
+
176
+ /**
177
+ * What the scene's fog turns a particle's colour into with distance: the fog colour where the
178
+ * particle covers the frame (`'color'`), black where it adds to it (`'black'`, or the fog would
179
+ * add itself to the whole screen), white where it multiplies it (`'white'`). Unity fogs its
180
+ * additive and multiply particles toward black and white for the same reason.
181
+ */
182
+ export type FogTarget = 'color' | 'black' | 'white';
183
+
184
+ /** The {@link BlendOutput} and {@link FogTarget} of a blend mode. */
185
+ export interface BlendCategory {
186
+ /** How the colour leaves the shader. */
187
+ output: BlendOutput;
188
+ /** Where the fog takes the colour. */
189
+ fogTarget: FogTarget;
190
+ }
191
+
192
+ const PREMULTIPLY_MODES = new Set<number>([
193
+ Constants.ALPHA_SUBTRACT,
194
+ Constants.ALPHA_MAXIMIZED,
195
+ Constants.ALPHA_ONEONE,
196
+ Constants.ALPHA_PREMULTIPLIED,
197
+ Constants.ALPHA_PREMULTIPLIED_PORTERDUFF,
198
+ Constants.ALPHA_SCREENMODE,
199
+ Constants.ALPHA_ONEONE_ONEONE,
200
+ Constants.ALPHA_ALPHATOCOLOR,
201
+ Constants.ALPHA_REVERSEONEMINUS,
202
+ Constants.ALPHA_SRC_DSTONEMINUSSRCALPHA,
203
+ Constants.ALPHA_ONEONE_ONEZERO,
204
+ Constants.ALPHA_EXCLUSION,
205
+ Constants.ALPHA_MAX,
206
+ ]);
207
+ const MULTIPLY_MODES = new Set<number>([Constants.ALPHA_MULTIPLY, Constants.ALPHA_MIN]);
208
+ /** The modes whose fog is the fog colour: those that lay the colour over the frame or replace it. */
209
+ const OVER_MODES = new Set<number>([
210
+ Constants.ALPHA_COMBINE,
211
+ Constants.ALPHA_PREMULTIPLIED,
212
+ Constants.ALPHA_PREMULTIPLIED_PORTERDUFF,
213
+ Constants.ALPHA_INTERPOLATE,
214
+ Constants.ALPHA_SRC_DSTONEMINUSSRCALPHA,
215
+ Constants.ALPHA_LAYER_ACCUMULATE,
216
+ Constants.ALPHA_REPLACE_COLOR,
217
+ ]);
218
+
219
+ /**
220
+ * The {@link BlendCategory} of a material: an opaque one (`materialTransparent` false, or
221
+ * `ALPHA_DISABLE`) is straight and fogs toward the fog colour; a transparent one by its Babylon
222
+ * alpha mode. A mode this table does not know is straight and fogs toward the fog colour.
223
+ */
224
+ export function blendCategory(settings: {materialTransparent: boolean; materialBlendMode: number}): BlendCategory {
225
+ const mode = settings.materialBlendMode;
226
+ if (!settings.materialTransparent || mode === Constants.ALPHA_DISABLE) {
227
+ return {output: 'straight', fogTarget: 'color'};
228
+ }
229
+ const output: BlendOutput = PREMULTIPLY_MODES.has(mode)
230
+ ? 'premultiply'
231
+ : MULTIPLY_MODES.has(mode)
232
+ ? 'multiply'
233
+ : 'straight';
234
+ const fogTarget: FogTarget =
235
+ output === 'multiply'
236
+ ? 'white'
237
+ : OVER_MODES.has(mode) || (output === 'straight' && mode !== Constants.ALPHA_ADD)
238
+ ? 'color'
239
+ : 'black';
240
+ return {output, fogTarget};
120
241
  }
121
242
 
122
243
  /** The shader program a batch with `settings` compiles, given what it knows about its textures. */
@@ -146,7 +267,7 @@ function spriteShaderProgram(settings: ShaderSettings, context: ShaderContext):
146
267
  fragment = {glsl: particle_frag, wgsl: particle_frag_wgsl};
147
268
  }
148
269
 
149
- const defines = fogDefines(settings);
270
+ const defines = blendDefines(settings);
150
271
  const attributes = [...SPRITE_ATTRIBUTES];
151
272
  const uniforms = ['world', 'view', 'projection', ...FOG_UNIFORMS];
152
273
  const samplers: string[] = [];
@@ -210,6 +331,7 @@ function spriteShaderProgram(settings: ShaderSettings, context: ShaderContext):
210
331
  uniforms.push('eyePosition');
211
332
  }
212
333
  surfaceDefines(settings, defines, uniforms, samplers);
334
+ imageProcessingDefines(settings, context, defines, uniforms, samplers);
213
335
  if (isMesh) {
214
336
  attributes.push('normal');
215
337
  if (readsUV1(settings) && context.meshUV2) {
@@ -225,7 +347,7 @@ function spriteShaderProgram(settings: ShaderSettings, context: ShaderContext):
225
347
 
226
348
  /** The program of a trail batch. */
227
349
  function trailShaderProgram(settings: ShaderSettings, context: ShaderContext): ShaderProgram {
228
- const defines = fogDefines(settings);
350
+ const defines = blendDefines(settings);
229
351
  const attributes = [...TRAIL_ATTRIBUTES];
230
352
  const uniforms = ['view', 'projection', ...FOG_UNIFORMS];
231
353
  const samplers: string[] = [];
@@ -235,6 +357,7 @@ function trailShaderProgram(settings: ShaderSettings, context: ShaderContext): S
235
357
  }
236
358
  surfaceDefines(settings, defines, uniforms, samplers);
237
359
  softDefines(settings, context, defines, uniforms, samplers);
360
+ imageProcessingDefines(settings, context, defines, uniforms, samplers);
238
361
  if (readsUV1(settings)) {
239
362
  // A trail's channel 1 arrives as its own vertex buffer.
240
363
  attributes.push('uv2');
@@ -251,15 +374,89 @@ function trailShaderProgram(settings: ShaderSettings, context: ShaderContext): S
251
374
  }
252
375
 
253
376
  /**
254
- * The fog defines: `FOG` when the settings apply the scene's fog, and `FOG_ADDITIVE` when the
255
- * particles add to the frame, so that they fade toward black instead of the fog colour, which
256
- * would fog the whole screen (what Unity does for additive materials).
377
+ * The blend defines: `BLEND_PREMULTIPLY` or `BLEND_MULTIPLY` for how the colour leaves the shader,
378
+ * and with `FOG` (when the settings apply the scene's fog) `FOG_ADDITIVE` or `FOG_MULTIPLY` for a
379
+ * fog toward black or white. See {@link blendCategory}.
257
380
  */
258
- function fogDefines(settings: ShaderSettings): string[] {
259
- if (!settings.applyFog) {
260
- return [];
381
+ function blendDefines(settings: ShaderSettings): string[] {
382
+ const {output, fogTarget} = blendCategory(settings);
383
+ const defines: string[] = [];
384
+ if (output === 'premultiply') {
385
+ defines.push('BLEND_PREMULTIPLY');
386
+ } else if (output === 'multiply') {
387
+ defines.push('BLEND_MULTIPLY');
261
388
  }
262
- return blendsAdditively(settings) ? ['FOG', 'FOG_ADDITIVE'] : ['FOG'];
389
+ if (settings.applyFog) {
390
+ defines.push('FOG');
391
+ if (fogTarget === 'black') {
392
+ defines.push('FOG_ADDITIVE');
393
+ } else if (fogTarget === 'white') {
394
+ defines.push('FOG_MULTIPLY');
395
+ }
396
+ }
397
+ return defines;
398
+ }
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);
263
460
  }
264
461
 
265
462
  /** Soft particles, when on and a depth texture is there to read, and the alpha test. */
@@ -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,
@@ -123,6 +127,21 @@ const FOG: Axis[] = [
123
127
  {name: 'fog additive', settings: {applyFog: true, materialBlendMode: Constants.ALPHA_ADD}},
124
128
  ];
125
129
 
130
+ /** The blend resolves (see `blendCategory`), with and without the fog each one takes. */
131
+ const BLEND: Axis[] = [
132
+ {name: 'premultiplied', settings: {materialBlendMode: Constants.ALPHA_PREMULTIPLIED}},
133
+ {name: 'premultiplied fog', settings: {materialBlendMode: Constants.ALPHA_PREMULTIPLIED, applyFog: true}},
134
+ {name: 'one-one', settings: {materialBlendMode: Constants.ALPHA_ONEONE}},
135
+ {name: 'one-one fog', settings: {materialBlendMode: Constants.ALPHA_ONEONE, applyFog: true}},
136
+ {name: 'multiply', settings: {materialBlendMode: Constants.ALPHA_MULTIPLY}},
137
+ {name: 'multiply fog', settings: {materialBlendMode: Constants.ALPHA_MULTIPLY, applyFog: true}},
138
+ {
139
+ name: 'multiply fog alpha test',
140
+ settings: {materialBlendMode: Constants.ALPHA_MULTIPLY, applyFog: true, materialAlphaTest: 0.5},
141
+ },
142
+ {name: 'opaque', settings: {materialTransparent: false}},
143
+ ];
144
+
126
145
  /** Texture effects (`chunks/surface.ts`), alone and together, on both UV channels. */
127
146
  const SURFACE: Axis[] = (() => {
128
147
  const uv0 = normalizeMapUV({tiling: [2, 2]});
@@ -169,20 +188,105 @@ const SURFACE_TARGETS = [
169
188
  'trail',
170
189
  ];
171
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
+
172
275
  /** A mesh batch whose geometry has a second UV set. */
173
276
  const MESH_UV2: Axis = {name: 'uv2', context: {meshUV2: true}};
174
277
 
175
278
  /** Soft, alpha-tested and fogged at once, to try the effects with the rest of the fragment. */
176
279
  const EVERYTHING_ELSE: Axis = {
177
- name: 'soft packed alpha test fog additive',
178
- settings: {softParticles: true, materialAlphaTest: 0.5, applyFog: true, materialBlendMode: Constants.ALPHA_ADD},
280
+ name: 'soft packed alpha test fog one-one',
281
+ settings: {softParticles: true, materialAlphaTest: 0.5, applyFog: true, materialBlendMode: Constants.ALPHA_ONEONE},
179
282
  context: {depth: {packed: true}},
180
283
  };
181
284
 
182
285
  /**
183
286
  * Every shader program the library can compile, one per distinct set of defines: every render
184
- * mode with every combination of sheet, map, soft particles, alpha test and fog, and the texture
185
- * effects alone and together on each shader family. What a shader validator compiles, and what
287
+ * mode with every combination of sheet, map, soft particles, alpha test and fog, every blend
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
186
290
  * an application can warm the shader cache with before its first frame (Unity's
187
291
  * ShaderVariantCollection). Built by {@link shaderProgram}, so a variant here is a material a
188
292
  * batch would build.
@@ -192,7 +296,7 @@ export function shaderVariants(): ShaderVariant[] {
192
296
  const out: ShaderVariant[] = [];
193
297
  const add = (axes: Axis[]) => {
194
298
  const settings = bare(RenderMode.BillBoard);
195
- const context: ShaderContext = {depth: null, envAtlasReady: false, meshUV2: false};
299
+ const context: ShaderContext = {depth: null, envAtlasReady: false, meshUV2: false, imageProcessing: null};
196
300
  for (const axis of axes) {
197
301
  Object.assign(settings, axis.settings);
198
302
  Object.assign(context, axis.context);
@@ -215,11 +319,24 @@ export function shaderVariants(): ShaderVariant[] {
215
319
  for (const alphaTest of ALPHA_TEST)
216
320
  for (const fog of FOG) add([target, quad, tile, map, soft, alphaTest, fog]);
217
321
 
322
+ for (const target of TARGETS)
323
+ for (const blend of BLEND)
324
+ for (const soft of SOFT.slice(0, 2)) for (const map of MAP) add([target, blend, soft, map]);
325
+
218
326
  for (const target of TARGETS.filter((t) => SURFACE_TARGETS.includes(t.name)))
219
327
  for (const uv2 of target.settings?.renderMode === RenderMode.Mesh ? [NONE, MESH_UV2] : [NONE])
220
328
  for (const surface of SURFACE)
221
329
  for (const map of MAP)
222
330
  for (const tile of [NONE, TILE[2]])
223
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]);
224
341
  return out;
225
342
  }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The blend resolve for the particle shaders, in both shader languages.
3
+ *
4
+ * Every fade in the shaders (soft particles, erosion, a rim fade, alpha over life) lowers alpha
5
+ * only, so that alpha means coverage. Blend modes that take the colour at full weight
6
+ * (`BLEND_PREMULTIPLY`: one-one, premultiplied, screen, subtract, …) would ignore it, so the
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, with the scene's image
9
+ * processing (`imageProcessing.ts`) around it. See `blendCategory`.
10
+ */
11
+
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. */
20
+ export function blendFragmentGLSL(color: string): string {
21
+ return /* glsl */ `${imageProcessingFragmentGLSL(color)}
22
+ #if defined(BLEND_PREMULTIPLY)
23
+ ${color}.rgb *= ${color}.a;
24
+ #elif defined(BLEND_MULTIPLY)
25
+ ${color}.rgb = mix(vec3(1.0), ${color}.rgb, ${color}.a);
26
+ #endif
27
+ ${imageProcessingResolvedGLSL(color)}`;
28
+ }
29
+
30
+ /** Processes and resolves the `var` vec4 `color` for the blend mode, WGSL. */
31
+ export function blendFragmentWGSL(color: string): string {
32
+ return /* wgsl */ `${imageProcessingFragmentWGSL(color)}
33
+ #if defined(BLEND_PREMULTIPLY)
34
+ ${color} = vec4f(${color}.rgb * ${color}.a, ${color}.a);
35
+ #elif defined(BLEND_MULTIPLY)
36
+ ${color} = vec4f(mix(vec3f(1.0), ${color}.rgb, ${color}.a), ${color}.a);
37
+ #endif
38
+ ${imageProcessingResolvedWGSL(color)}`;
39
+ }
@@ -11,7 +11,8 @@
11
11
  *
12
12
  * Additive particles cannot fade toward a fog colour (that would add fog to
13
13
  * the whole screen), so under `FOG_ADDITIVE` they fade toward black instead,
14
- * which is what Unity does for additive materials.
14
+ * and multiplying particles (`FOG_MULTIPLY`) toward white, which leaves the
15
+ * frame as it is: what Unity does for additive and multiply materials.
15
16
  */
16
17
 
17
18
  /** Vertex-side declaration, GLSL. */
@@ -62,8 +63,10 @@ export function fogFragmentGLSL(rgb: string): string {
62
63
  #ifdef FOG
63
64
  {
64
65
  float fogFactor = quarksFogFactor();
65
- #ifdef FOG_ADDITIVE
66
+ #if defined(FOG_ADDITIVE)
66
67
  ${rgb} *= fogFactor;
68
+ #elif defined(FOG_MULTIPLY)
69
+ ${rgb} = mix(vec3(1.0), ${rgb}, fogFactor);
67
70
  #else
68
71
  ${rgb} = mix(vFogColor, ${rgb}, fogFactor);
69
72
  #endif
@@ -110,8 +113,10 @@ fn quarksFog(rgb: vec3f, fogDistanceVector: vec3f) -> vec3f {
110
113
  fogCoeff = exp(-fogDistance * fogDistance * fogDensity * fogDensity);
111
114
  }
112
115
  let fogFactor = clamp(fogCoeff, 0.0, 1.0);
113
- #ifdef FOG_ADDITIVE
116
+ #if defined(FOG_ADDITIVE)
114
117
  return rgb * fogFactor;
118
+ #elif defined(FOG_MULTIPLY)
119
+ return mix(vec3f(1.0), rgb, fogFactor);
115
120
  #else
116
121
  return mix(uniforms.vFogColor, rgb, fogFactor);
117
122
  #endif