babylon.quarks 0.26.0 → 0.27.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 (79) hide show
  1. package/CHANGELOG.md +769 -8
  2. package/README.md +10 -8
  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 +6683 -0
  10. package/dist/types/index.d.ts +1959 -588
  11. package/package.json +12 -6
  12. package/src/BatchedRenderer.ts +204 -101
  13. package/src/EffectPool.ts +4 -1
  14. package/src/MeshSurfaceEmitter.ts +29 -4
  15. package/src/ParticleEmitter.ts +45 -15
  16. package/src/ParticleSystem.ts +909 -2061
  17. package/src/QuarksLoader.ts +421 -67
  18. package/src/QuarksPrefab.ts +91 -4
  19. package/src/QuarksSerializer.ts +484 -0
  20. package/src/QuarksUtil.ts +15 -0
  21. package/src/SpriteBatch.ts +106 -246
  22. package/src/TrailBatch.ts +533 -342
  23. package/src/VFXBatch.ts +236 -136
  24. package/src/batchKey.ts +237 -0
  25. package/src/effectResources.ts +79 -0
  26. package/src/envAtlas.ts +119 -17
  27. package/src/index.ts +32 -33
  28. package/src/nodeId.ts +13 -0
  29. package/src/shaderProgram.ts +325 -0
  30. package/src/shaderVariants.ts +225 -0
  31. package/src/shaders/chunks/fog.ts +4 -2
  32. package/src/shaders/chunks/sheet.ts +111 -0
  33. package/src/shaders/chunks/soft.ts +84 -0
  34. package/src/shaders/chunks/surface.ts +10 -0
  35. package/src/shaders/local_particle_physics_vert.glsl.ts +14 -28
  36. package/src/shaders/local_particle_physics_vert.wgsl.ts +16 -29
  37. package/src/shaders/particle_frag.glsl.ts +3 -22
  38. package/src/shaders/particle_frag.wgsl.ts +3 -23
  39. package/src/shaders/particle_physics_frag.glsl.ts +12 -25
  40. package/src/shaders/particle_physics_frag.wgsl.ts +12 -26
  41. package/src/shaders/particle_vert.glsl.ts +5 -27
  42. package/src/shaders/particle_vert.wgsl.ts +5 -26
  43. package/src/shaders/stretched_bb_particle_vert.glsl.ts +7 -33
  44. package/src/shaders/stretched_bb_particle_vert.wgsl.ts +7 -33
  45. package/src/shaders/trail_frag.glsl.ts +11 -1
  46. package/src/shaders/trail_frag.wgsl.ts +11 -0
  47. package/src/shaders/trail_vert.glsl.ts +45 -36
  48. package/src/shaders/trail_vert.wgsl.ts +46 -40
  49. package/src/softDepth.ts +136 -0
  50. package/src/system/BehaviorPlan.ts +85 -0
  51. package/src/system/BoundsTracker.ts +181 -0
  52. package/src/system/EmissionController.ts +360 -0
  53. package/src/system/EventDispatcher.ts +44 -0
  54. package/src/system/ParticleSpawner.ts +274 -0
  55. package/src/system/SimulationClock.ts +130 -0
  56. package/src/system/geometry.ts +111 -0
  57. package/src/system/integrate.ts +428 -0
  58. package/src/system/materialResolve.ts +116 -0
  59. package/src/system/normalize.ts +95 -0
  60. package/src/system/serialization.ts +401 -0
  61. package/src/system/settingsSchema.ts +317 -0
  62. package/src/system/types.ts +410 -0
  63. package/src/textureSource.ts +30 -0
  64. package/src/trailSettings.ts +51 -0
  65. package/src/BatchedParticleRenderer.ts +0 -3
  66. package/src/materials/ParticleMaterials.ts +0 -23
  67. package/src/materials/index.ts +0 -1
  68. package/src/shaders/chunks/index.ts +0 -1
  69. package/src/shaders/chunks/register-shader-chunks.ts +0 -32
  70. package/src/shaders/chunks/soft_fragment.glsl.ts +0 -13
  71. package/src/shaders/chunks/soft_pars_fragment.glsl.ts +0 -20
  72. package/src/shaders/chunks/soft_pars_vertex.glsl.ts +0 -6
  73. package/src/shaders/chunks/soft_vertex.glsl.ts +0 -6
  74. package/src/shaders/chunks/tile_fragment.glsl.ts +0 -9
  75. package/src/shaders/chunks/tile_pars_fragment.glsl.ts +0 -9
  76. package/src/shaders/chunks/tile_pars_vertex.glsl.ts +0 -28
  77. package/src/shaders/chunks/tile_vertex.glsl.ts +0 -18
  78. package/src/shaders/index.ts +0 -9
  79. package/src/shaders/local_particle_vert.glsl.ts +0 -50
package/src/VFXBatch.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import {BoundingInfo} from '@babylonjs/core/Culling/boundingInfo';
2
- import {Constants} from '@babylonjs/core/Engines/constants';
3
2
  import {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
3
+ import type {Effect} from '@babylonjs/core/Materials/effect';
4
4
  import {ShaderMaterial} from '@babylonjs/core/Materials/shaderMaterial';
5
5
  import {Color3} from '@babylonjs/core/Maths/math.color';
6
6
  import {Vector2 as BVector2, Vector3 as BVector3, Vector4 as BVector4} from '@babylonjs/core/Maths/math.vector';
@@ -9,6 +9,8 @@ import {Scene} from '@babylonjs/core/scene';
9
9
  import {IParticleSystem, Vector3} from 'quarks.core';
10
10
  import {VFXBatchSettings} from './BatchedRenderer';
11
11
  import type {ParticleSystem} from './ParticleSystem';
12
+ import {storedBatchSettings} from './batchKey';
13
+ import {DepthBinding, softDepthParams} from './softDepth';
12
14
 
13
15
  /**
14
16
  * The order the particles of a batch are drawn in (Unity's Sort Mode). Alpha
@@ -23,12 +25,25 @@ import type {ParticleSystem} from './ParticleSystem';
23
25
  */
24
26
  export type SortMode = 'none' | 'distance' | 'oldestInFront' | 'youngestInFront';
25
27
 
28
+ /** How a system draws its particles (Unity's Render Mode), see {@link ParticleSystem.renderMode}. */
26
29
  export enum RenderMode {
30
+ /** A quad facing the camera (Unity's Billboard). The default. */
27
31
  BillBoard = 0,
32
+ /**
33
+ * A quad facing the camera and stretched along the particle's velocity (Unity's Stretched
34
+ * Billboard), by `speedFactor` and `lengthFactor` in the system's `rendererEmitterSettings`.
35
+ */
28
36
  StretchedBillBoard = 1,
37
+ /** The system's mesh, one instance per particle (Unity's Mesh), see {@link ParticleSystem.instancingGeometry}. */
29
38
  Mesh = 2,
39
+ /**
40
+ * A ribbon drawn through the particle's past positions (Unity's Trails module, Niagara's
41
+ * ribbon), facing the camera; its settings are `TrailSettings` in `rendererEmitterSettings`.
42
+ */
30
43
  Trail = 3,
44
+ /** A quad lying flat in the world's XZ plane, facing up (Unity's Horizontal Billboard). */
31
45
  HorizontalBillBoard = 4,
46
+ /** A quad standing upright and turning only about the world's Y axis to face the camera (Unity's Vertical Billboard). */
32
47
  VerticalBillBoard = 5,
33
48
  /**
34
49
  * A quad laid from each particle to the system's {@link ParticleSystem.beamEnd}
@@ -224,8 +239,9 @@ export type GradientMapChannel = 'r' | 'g' | 'b' | 'a' | 'luminance';
224
239
 
225
240
  /**
226
241
  * A gradient map (colour ramp): the map is recoloured by looking its
227
- * {@link channel} up along U of {@link ramp}. See
228
- * {@link VFXBatchSettings.gradientMap}.
242
+ * {@link channel} up along U of {@link ramp}. Without a map the lookup reads
243
+ * white, so the ramp's right-hand end tints every particle, in every render
244
+ * mode alike. See {@link VFXBatchSettings.gradientMap}.
229
245
  */
230
246
  export interface ParticleGradientMap {
231
247
  /**
@@ -301,7 +317,13 @@ export interface ParticleErosion {
301
317
  // Scrolling runs on the renderer's clock, not a system's playback speed.
302
318
 
303
319
  /** {@link ParticleErosion} as given: any field may be left out, the mask's layout too. */
304
- export type ParticleErosionOptions = Partial<Omit<ParticleErosion, 'maskUV'>> & {maskUV?: Partial<TextureUV>};
320
+ export type ParticleErosionOptions = Partial<Omit<ParticleErosion, 'maskUV'>> & {
321
+ /** See {@link ParticleErosion.maskUV}; fields left out take their defaults. */
322
+ maskUV?: Partial<TextureUV>;
323
+ };
324
+
325
+ /** The width of the soft band an erosion cut fades over when `softness` is left out, in mask units. */
326
+ export const EROSION_DEFAULT_SOFTNESS = 0.05;
305
327
 
306
328
  /** A complete erosion from a partial one, every field defaulted, or undefined when `erosion` is unset. */
307
329
  export function normalizeErosion(erosion: ParticleErosionOptions | null | undefined): ParticleErosion | undefined {
@@ -314,7 +336,7 @@ export function normalizeErosion(erosion: ParticleErosionOptions | null | undefi
314
336
  return {
315
337
  mask: erosion.mask ?? null,
316
338
  maskUV: normalizeTextureUV(erosion.maskUV),
317
- softness: Math.max(0.0001, num(erosion.softness, 0.05)),
339
+ softness: Math.max(0.0001, num(erosion.softness, EROSION_DEFAULT_SOFTNESS)),
318
340
  edgeWidth: Math.max(0, num(erosion.edgeWidth, 0)),
319
341
  edgeColor: [num(color[0], 1), num(color[1], 1), num(color[2], 1)],
320
342
  };
@@ -363,7 +385,13 @@ export interface ParticleUVDistortion {
363
385
  }
364
386
 
365
387
  /** {@link ParticleUVDistortion} as given: any field may be left out, the noise's layout too. */
366
- export type ParticleUVDistortionOptions = Partial<Omit<ParticleUVDistortion, 'mapUV'>> & {mapUV?: Partial<TextureUV>};
388
+ export type ParticleUVDistortionOptions = Partial<Omit<ParticleUVDistortion, 'mapUV'>> & {
389
+ /** See {@link ParticleUVDistortion.mapUV}; fields left out take their defaults. */
390
+ mapUV?: Partial<TextureUV>;
391
+ };
392
+
393
+ /** How far a distortion pushes the map's UV at most when `strength` is left out, in UV units. */
394
+ export const UV_DISTORTION_DEFAULT_STRENGTH = 0.05;
367
395
 
368
396
  /** A complete distortion from a partial one, or undefined when unset or without a map. */
369
397
  export function normalizeUVDistortion(
@@ -376,7 +404,10 @@ export function normalizeUVDistortion(
376
404
  return {
377
405
  map: distortion.map,
378
406
  mapUV: normalizeTextureUV(distortion.mapUV, UV_DISTORTION_DEFAULT_UV),
379
- strength: Math.max(0, typeof strength === 'number' && Number.isFinite(strength) ? strength : 0.05),
407
+ strength: Math.max(
408
+ 0,
409
+ typeof strength === 'number' && Number.isFinite(strength) ? strength : UV_DISTORTION_DEFAULT_STRENGTH
410
+ ),
380
411
  };
381
412
  }
382
413
 
@@ -424,31 +455,58 @@ export function readsUV1(settings: {
424
455
  );
425
456
  }
426
457
 
458
+ /**
459
+ * A batch's copy of the {@link VFXBatchSettings} its systems share: every optional field
460
+ * defaulted, and every field the batch's render mode ignores at its default (an opaque batch's
461
+ * `materialBlendMode` reads `ALPHA_DISABLE`, a billboard batch's `rim` is unset). The renderer
462
+ * adds a system to the batch whose settings match its own.
463
+ */
427
464
  export interface StoredBatchSettings {
465
+ /** See {@link VFXBatchSettings.instancingGeometry}. */
428
466
  instancingGeometry: Float32Array;
467
+ /** See {@link VFXBatchSettings.instancingIndices}. */
429
468
  instancingIndices: Uint32Array | Uint16Array;
469
+ /** See {@link VFXBatchSettings.instancingUVs}. */
430
470
  instancingUVs?: Float32Array;
431
471
  /** The mesh's second UV set, see {@link UVChannel}. */
432
472
  instancingUVs2?: Float32Array;
473
+ /** See {@link VFXBatchSettings.instancingNormals}. */
433
474
  instancingNormals?: Float32Array;
475
+ /** See {@link VFXBatchSettings.renderMode}. */
434
476
  renderMode: RenderMode;
477
+ /** See {@link VFXBatchSettings.renderOrder}. */
435
478
  renderOrder: number;
479
+ /** See {@link VFXBatchSettings.uTileCount}. */
436
480
  uTileCount: number;
481
+ /** See {@link VFXBatchSettings.vTileCount}. */
437
482
  vTileCount: number;
483
+ /** See {@link VFXBatchSettings.blendTiles}. */
438
484
  blendTiles: boolean;
485
+ /** See {@link VFXBatchSettings.softParticles}. */
439
486
  softParticles: boolean;
487
+ /** See {@link VFXBatchSettings.softNearFade}. */
440
488
  softNearFade: number;
489
+ /** See {@link VFXBatchSettings.softFarFade}. */
441
490
  softFarFade: number;
491
+ /** See {@link VFXBatchSettings.materialBlendMode}. */
442
492
  materialBlendMode: number;
493
+ /** See {@link VFXBatchSettings.materialTransparent}. */
443
494
  materialTransparent: boolean;
495
+ /** See {@link VFXBatchSettings.materialDepthTest}. */
444
496
  materialDepthTest: boolean;
497
+ /** See {@link VFXBatchSettings.materialDepthWrite}. */
445
498
  materialDepthWrite: boolean;
499
+ /** See {@link VFXBatchSettings.materialAlphaTest}. */
446
500
  materialAlphaTest: number;
447
- texture: any;
501
+ /** See {@link VFXBatchSettings.texture}. */
502
+ texture: BaseTexture | null;
503
+ /** See {@link VFXBatchSettings.reflectionTexture}. */
448
504
  reflectionTexture: BaseTexture | null;
505
+ /** See {@link VFXBatchSettings.reflectionLevel}. */
449
506
  reflectionLevel: number;
450
- reflectionFaces: BaseTexture[] | null;
507
+ /** See {@link VFXBatchSettings.reflectionAtlas}. */
451
508
  reflectionAtlas: BaseTexture | null;
509
+ /** See {@link VFXBatchSettings.layerMask}. */
452
510
  layerMask: number;
453
511
  /** Babylon rendering group the batch mesh draws in, see {@link VFXBatchSettings.renderingGroupId}. */
454
512
  renderingGroupId: number;
@@ -476,56 +534,38 @@ export interface StoredBatchSettings {
476
534
  erosion?: ParticleErosion;
477
535
  }
478
536
 
537
+ /**
538
+ * One draw call of a {@link BatchedRenderer}: the systems that share a material, texture and
539
+ * geometry, drawn together from one instanced mesh. The renderer makes and frees batches as
540
+ * systems are added and their settings change; read them through `renderer.batches` to inspect
541
+ * what is drawn, not to change it.
542
+ */
479
543
  export abstract class VFXBatch {
544
+ /** The Babylon mesh the batch draws with. */
480
545
  mesh: Mesh;
546
+ /** The systems drawn by this batch. */
481
547
  systems: Set<IParticleSystem>;
548
+ /** The settings every system in the batch shares, with what the render mode ignores at its default. */
482
549
  settings: StoredBatchSettings;
550
+ /** @internal */
483
551
  protected maxParticles: number;
552
+ /** @internal */
484
553
  protected scene: Scene;
554
+ /** @internal */
485
555
  protected readonly visibleSystems: IParticleSystem[] = [];
556
+ /**
557
+ * The depth texture soft particles read, from {@link BatchedRenderer.setDepthTexture}.
558
+ * @internal
559
+ */
560
+ protected depth: DepthBinding | null;
486
561
 
487
- protected constructor(settings: VFXBatchSettings, scene: Scene) {
562
+ /** @internal */
563
+ protected constructor(settings: VFXBatchSettings, scene: Scene, depth: DepthBinding | null = null) {
488
564
  this.scene = scene;
565
+ this.depth = depth;
489
566
  this.maxParticles = 1000;
490
567
  this.systems = new Set<IParticleSystem>();
491
- this.settings = {
492
- instancingGeometry: settings.instancingGeometry,
493
- instancingIndices: settings.instancingIndices,
494
- instancingUVs: settings.instancingUVs,
495
- instancingUVs2: settings.instancingUVs2,
496
- instancingNormals: settings.instancingNormals,
497
- renderMode: settings.renderMode,
498
- renderOrder: settings.renderOrder,
499
- uTileCount: settings.uTileCount,
500
- vTileCount: settings.vTileCount,
501
- blendTiles: settings.blendTiles,
502
- softParticles: settings.softParticles,
503
- softNearFade: settings.softNearFade,
504
- softFarFade: settings.softFarFade,
505
- materialBlendMode: settings.materialBlendMode,
506
- materialTransparent: settings.materialTransparent,
507
- materialDepthTest: settings.materialDepthTest,
508
- materialDepthWrite: settings.materialDepthWrite,
509
- materialAlphaTest: settings.materialAlphaTest,
510
- texture: settings.texture,
511
- reflectionTexture: settings.reflectionTexture ?? null,
512
- reflectionLevel: settings.reflectionLevel ?? 1,
513
- reflectionFaces: settings.reflectionFaces ?? null,
514
- reflectionAtlas: settings.reflectionAtlas ?? null,
515
- pivot: settings.pivot?.clone(),
516
- sortMode: settings.sortMode ?? 'none',
517
- minParticleSize: settings.minParticleSize ?? 0,
518
- maxParticleSize: settings.maxParticleSize ?? Infinity,
519
- layerMask: settings.layerMask,
520
- renderingGroupId: settings.renderingGroupId ?? 0,
521
- applyFog: settings.applyFog ?? true,
522
- lighting: settings.lighting,
523
- rim: normalizeMeshRim(settings.rim),
524
- mapUV: normalizeMapUV(settings.mapUV),
525
- uvDistortion: normalizeUVDistortion(settings.uvDistortion),
526
- gradientMap: normalizeGradientMap(settings.gradientMap),
527
- erosion: normalizeErosion(settings.erosion),
528
- };
568
+ this.settings = storedBatchSettings(settings);
529
569
  this.mesh = new Mesh('vfxBatch', scene);
530
570
  this.configureMesh();
531
571
  }
@@ -553,6 +593,12 @@ export abstract class VFXBatch {
553
593
  mesh.alphaIndex = this.settings.renderOrder;
554
594
  // The shaders fog themselves; Babylon's own fog pass does not apply to a ShaderMaterial.
555
595
  mesh.applyFog = false;
596
+ // Its geometry is one quad or mesh at the origin, not the particles, so a pick or a
597
+ // collision against it would hit nothing that is drawn. The renderer rebuilds batches
598
+ // from its systems, so a scene serializer has nothing to write either.
599
+ mesh.isPickable = false;
600
+ mesh.checkCollisions = false;
601
+ mesh.doNotSerialize = true;
556
602
  }
557
603
 
558
604
  private readonly boundsMin_ = new Vector3();
@@ -615,23 +661,26 @@ export abstract class VFXBatch {
615
661
  }
616
662
 
617
663
  /**
618
- * Fog defines and uniforms for a material of this batch, and the per-bind
619
- * upload of the scene's current fog. Mode 0 (none) when the scene has fog
620
- * disabled, so toggling fog at runtime needs no recompile.
664
+ * Writes a uniform that changes per bind, such as one read off the active camera, to the
665
+ * material for the next bind and to its bound effect for this one: ShaderMaterial uploads its
666
+ * stored values before the bind observers run, so a value written only to the material would
667
+ * reach the GPU one draw late, and with two cameras it would always be the other camera's.
621
668
  */
622
- protected static readonly FOG_UNIFORMS = ['vFogInfos', 'vFogColor'];
623
-
624
- protected fogDefines(): string[] {
625
- if (!this.settings.applyFog) {
626
- return [];
669
+ protected static writeBound(material: ShaderMaterial, write: (target: ShaderMaterial | Effect) => void): void {
670
+ write(material);
671
+ // An effect still compiling has no pipeline context to write to; it gets the stored value
672
+ // when it is first bound. A sub mesh's effect override is not reached here.
673
+ const effect = material.getEffect();
674
+ if (effect?.isReady()) {
675
+ write(effect);
627
676
  }
628
- const additive =
629
- this.settings.materialTransparent &&
630
- (this.settings.materialBlendMode === Constants.ALPHA_ADD ||
631
- this.settings.materialBlendMode === Constants.ALPHA_ONEONE);
632
- return additive ? ['FOG', 'FOG_ADDITIVE'] : ['FOG'];
633
677
  }
634
678
 
679
+ /**
680
+ * The per-bind upload of the scene's current fog. Mode 0 (none) when the scene has fog
681
+ * disabled, so toggling fog at runtime needs no recompile.
682
+ * @internal
683
+ */
635
684
  protected bindFog(material: ShaderMaterial): void {
636
685
  if (!this.settings.applyFog) {
637
686
  return;
@@ -659,85 +708,19 @@ export abstract class VFXBatch {
659
708
  return readsUV1(this.settings);
660
709
  }
661
710
 
662
- /** Defines of the texture effects (see `shaders/chunks/surface.ts`) this batch uses. */
663
- protected surfaceDefines(): string[] {
664
- const defines: string[] = [];
665
- const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
666
- if (this.usesUV1()) {
667
- defines.push('UV1');
668
- }
669
- if (mapUV) {
670
- defines.push('MAP_TRANSFORM');
671
- if (mapUV.channel === 1) {
672
- defines.push('MAP_UV1');
673
- }
674
- }
675
- if (uvDistortion) {
676
- defines.push('UV_DISTORTION');
677
- if (uvDistortion.mapUV.channel === 1) {
678
- defines.push('UV_DISTORTION_UV1');
679
- }
680
- }
681
- if (gradientMap) {
682
- defines.push('GRADIENT_MAP');
683
- }
684
- if (erosion) {
685
- defines.push('EROSION');
686
- if (erosion.mask) {
687
- defines.push('EROSION_MASK');
688
- if (erosion.maskUV.channel === 1) {
689
- defines.push('EROSION_MASK_UV1');
690
- }
691
- }
692
- }
693
- return defines;
694
- }
695
-
696
- /** Adds the uniforms and samplers of the texture effects this batch uses. */
697
- protected surfaceUniforms(uniforms: string[], samplers: string[]): void {
698
- const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
699
- if (mapUV) {
700
- uniforms.push('mapTransform');
701
- }
702
- if (uvDistortion) {
703
- samplers.push('distortionMap');
704
- uniforms.push('distortionTransform', 'distortionStrength');
705
- }
706
- if (gradientMap) {
707
- samplers.push('gradientMap');
708
- uniforms.push('gradientMapRange', 'gradientMapChannel');
709
- }
710
- if (erosion) {
711
- uniforms.push('erosionParams', 'erosionEdgeColor');
712
- if (erosion.mask) {
713
- samplers.push('erosionMask');
714
- uniforms.push('erosionMaskTransform');
715
- }
716
- }
717
- }
718
-
719
711
  /** Sets the texture effects' textures and constants, and advances their scrolling on every bind. */
720
712
  protected bindSurface(material: ShaderMaterial): void {
721
713
  const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
722
714
  if (uvDistortion?.map) {
723
715
  material.setTexture('distortionMap', uvDistortion.map);
724
- material.setFloat('distortionStrength', uvDistortion.strength);
725
716
  }
726
717
  if (gradientMap?.ramp) {
727
718
  material.setTexture('gradientMap', gradientMap.ramp);
728
- const [r, g, b, a] = GRADIENT_MAP_WEIGHTS[gradientMap.channel];
729
- material.setVector4('gradientMapChannel', new BVector4(r, g, b, a));
730
719
  }
731
- if (erosion) {
732
- if (erosion.mask) {
733
- material.setTexture('erosionMask', erosion.mask);
734
- }
735
- material.setVector2('erosionParams', new BVector2(erosion.softness, erosion.edgeWidth));
736
- material.setVector3(
737
- 'erosionEdgeColor',
738
- new BVector3(erosion.edgeColor[0], erosion.edgeColor[1], erosion.edgeColor[2])
739
- );
720
+ if (erosion?.mask) {
721
+ material.setTexture('erosionMask', erosion.mask);
740
722
  }
723
+ this.writeSurfaceParams(material);
741
724
  if (!mapUV && !uvDistortion && !erosion?.mask && !gradientMap) {
742
725
  return;
743
726
  }
@@ -760,9 +743,12 @@ export abstract class VFXBatch {
760
743
  );
761
744
  // Written to the material for the next bind and to the bound effect for
762
745
  // this one: ShaderMaterial uploads its values before the bind observers run.
746
+ // The layouts are read off the settings on every bind, so a live change
747
+ // to a tiling or scroll reaches the next draw.
763
748
  const write = () => {
764
749
  const time = this.time;
765
750
  const effect = material.getEffect();
751
+ const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
766
752
  if (mapUV) {
767
753
  transform(mapUV, time, mapTransform);
768
754
  material.setVector4('mapTransform', mapTransform);
@@ -792,11 +778,56 @@ export abstract class VFXBatch {
792
778
  material.onBindObservable.add(write);
793
779
  }
794
780
 
795
- addSystem(system: IParticleSystem) {
781
+ /** Writes the texture effects' constants (the uniforms their live parameters set). */
782
+ private writeSurfaceParams(material: ShaderMaterial): void {
783
+ const {uvDistortion, gradientMap, erosion} = this.settings;
784
+ if (uvDistortion?.map) {
785
+ material.setFloat('distortionStrength', uvDistortion.strength);
786
+ }
787
+ if (gradientMap?.ramp) {
788
+ const [r, g, b, a] = GRADIENT_MAP_WEIGHTS[gradientMap.channel];
789
+ material.setVector4('gradientMapChannel', new BVector4(r, g, b, a));
790
+ }
791
+ if (erosion) {
792
+ material.setVector2('erosionParams', new BVector2(erosion.softness, erosion.edgeWidth));
793
+ material.setVector3(
794
+ 'erosionEdgeColor',
795
+ new BVector3(erosion.edgeColor[0], erosion.edgeColor[1], erosion.edgeColor[2])
796
+ );
797
+ }
798
+ }
799
+
800
+ /**
801
+ * Takes new live parameters (see {@link BatchedRenderer.updateSystem}): settings with the same
802
+ * shader key as the batch's, whose uniform values differ. They are copied into the batch's
803
+ * settings and written to its material, which is kept.
804
+ * @internal
805
+ */
806
+ applyLiveSettings(settings: StoredBatchSettings): void {
807
+ Object.assign(this.settings, settings);
808
+ const material = this.mesh.material;
809
+ if (material instanceof ShaderMaterial) {
810
+ this.writeLiveUniforms(material);
811
+ }
812
+ }
813
+
814
+ /**
815
+ * Writes every uniform a live parameter sets, from the batch's settings. The base writes the
816
+ * ones every render mode has; a mode adds its own.
817
+ * @internal
818
+ */
819
+ protected writeLiveUniforms(material: ShaderMaterial): void {
820
+ this.writeSoftParams(material);
821
+ this.writeSurfaceParams(material);
822
+ }
823
+
824
+ /** @internal */
825
+ addSystem(system: IParticleSystem): void {
796
826
  this.systems.add(system);
797
827
  }
798
828
 
799
- removeSystem(system: IParticleSystem) {
829
+ /** @internal */
830
+ removeSystem(system: IParticleSystem): void {
800
831
  this.systems.delete(system);
801
832
  }
802
833
 
@@ -819,21 +850,90 @@ export abstract class VFXBatch {
819
850
  return visibleSystems;
820
851
  }
821
852
 
822
- applyDepthTexture(depthTexture: BaseTexture | null): void {
853
+ /**
854
+ * Sets the depth texture soft particles read. Soft particles are compiled in only while there
855
+ * is one, as Unity turns them off for a camera without a depth texture, so attaching or
856
+ * removing it, or changing how it is packed, rebuilds a soft batch's material.
857
+ *
858
+ * @param depth - The binding, or a bare texture, read as `'linear'` and unpacked.
859
+ * @internal Use {@link BatchedRenderer.setDepthTexture}.
860
+ */
861
+ applyDepthTexture(depth: DepthBinding | BaseTexture | null): void {
862
+ if (depth instanceof BaseTexture) {
863
+ depth = {texture: depth, encoding: 'linear', packed: false};
864
+ }
865
+ const previous = this.depth;
866
+ this.depth = depth;
867
+ if (!this.settings.softParticles) {
868
+ return;
869
+ }
870
+ if (!previous !== !depth || previous?.packed !== depth?.packed) {
871
+ this.rebuildMaterial();
872
+ return;
873
+ }
823
874
  const material = this.mesh.material;
824
- if (material && material instanceof ShaderMaterial) {
825
- material.setTexture('depthTexture', depthTexture);
875
+ if (depth && material instanceof ShaderMaterial) {
876
+ material.setTexture('depthTexture', depth.texture);
826
877
  }
827
878
  }
828
879
 
829
- /** Sets the light `'fixed'` mesh particles are lit by. Batches that draw no meshes ignore it. */
880
+ /**
881
+ * Sets the soft-particle and alpha-test values on a material, and decodes the depth for the
882
+ * camera of each draw.
883
+ */
884
+ protected bindSoft(material: ShaderMaterial): void {
885
+ this.writeSoftParams(material);
886
+ if (this.drawsSoft()) {
887
+ material.setTexture('depthTexture', this.depth!.texture);
888
+ // Reused across binds: this runs on every draw call.
889
+ const projParams = new BVector4(1, 0, 0, 1);
890
+ material.onBindObservable.add(() => {
891
+ const camera = this.scene.activeCamera;
892
+ if (camera && this.depth) {
893
+ softDepthParams(camera, this.depth.encoding, projParams);
894
+ VFXBatch.writeBound(material, (target) => target.setVector4('projParams', projParams));
895
+ }
896
+ });
897
+ }
898
+ }
899
+
900
+ /** Writes the soft-particle fade and the alpha cutoff, the live parameters every mode shares. */
901
+ private writeSoftParams(material: ShaderMaterial): void {
902
+ if (this.drawsSoft()) {
903
+ material.setVector2(
904
+ 'softParams',
905
+ new BVector2(
906
+ this.settings.softNearFade,
907
+ 1.0 / Math.max(this.settings.softFarFade - this.settings.softNearFade, 0.0001)
908
+ )
909
+ );
910
+ }
911
+ if (this.settings.materialAlphaTest > 0) {
912
+ material.setFloat('alphaTest', this.settings.materialAlphaTest);
913
+ }
914
+ }
915
+
916
+ /** True when this batch draws soft particles: they are on and a depth texture is attached. */
917
+ protected drawsSoft(): boolean {
918
+ return this.settings.softParticles && this.depth !== null;
919
+ }
920
+
921
+ /**
922
+ * Sets the light `'fixed'` mesh particles are lit by. Batches that draw no meshes ignore it.
923
+ * @internal Use {@link BatchedRenderer.setMeshLighting}.
924
+ */
830
925
  applyMeshLighting(_lighting: MeshLightingSettings): void {}
831
926
 
927
+ /** @internal */
832
928
  abstract setupBuffers(): void;
929
+ /** @internal */
833
930
  abstract expandBuffers(target: number): void;
931
+ /** @internal */
834
932
  abstract rebuildMaterial(): void;
933
+ /** @internal */
835
934
  abstract update(): void;
836
935
 
936
+ /** @internal */
837
937
  dispose(): void {
838
938
  this.mesh.dispose();
839
939
  }