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.
- package/CHANGELOG.md +160 -0
- package/dist/babylon.quarks.cjs +1 -1
- package/dist/babylon.quarks.cjs.map +1 -1
- package/dist/babylon.quarks.esm.js +1 -1
- package/dist/babylon.quarks.esm.js.map +1 -1
- package/dist/babylon.quarks.umd.min.js +1 -1
- package/dist/babylon.quarks.umd.min.js.map +1 -1
- package/dist/types/index.d.cts +602 -190
- package/dist/types/index.d.ts +602 -190
- package/package.json +1 -1
- package/src/BatchedRenderer.ts +13 -1
- package/src/EffectPool.ts +5 -4
- package/src/ParticleEmitter.ts +89 -24
- package/src/ParticleSystem.ts +213 -30
- package/src/QuarksLoader.ts +9 -0
- package/src/QuarksSerializer.ts +1 -1
- package/src/QuarksUtil.ts +24 -2
- package/src/SpriteBatch.ts +3 -0
- package/src/TrailBatch.ts +8 -1
- package/src/VFXBatch.ts +112 -0
- package/src/batchKey.ts +2 -0
- package/src/batchSettings.ts +2 -0
- package/src/index.ts +2 -0
- package/src/shaderProgram.ts +103 -0
- package/src/shaderVariants.ts +102 -5
- package/src/shaders/chunks/blend.ts +15 -7
- package/src/shaders/chunks/imageProcessing.ts +143 -0
- package/src/shaders/particle_frag.glsl.ts +2 -0
- package/src/shaders/particle_frag.wgsl.ts +2 -0
- package/src/shaders/particle_physics_frag.glsl.ts +2 -0
- package/src/shaders/particle_physics_frag.wgsl.ts +2 -0
- package/src/shaders/trail_frag.glsl.ts +2 -0
- package/src/shaders/trail_frag.wgsl.ts +2 -0
- package/src/system/EmissionController.ts +21 -16
- package/src/system/ParticleSpawner.ts +96 -19
- package/src/system/integrate.ts +61 -4
- package/src/system/normalize.ts +28 -1
- package/src/system/settingsSchema.ts +25 -7
- 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 {
|
|
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.
|
|
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++) {
|
package/src/SpriteBatch.ts
CHANGED
|
@@ -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
|
|
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 &&
|
package/src/batchSettings.ts
CHANGED
|
@@ -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
package/src/shaderProgram.ts
CHANGED
|
@@ -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,
|
package/src/shaderVariants.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
}
|