@driftengine/core 4.13.3 → 4.13.5

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 (153) hide show
  1. package/CHANGELOG.json +52 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +3 -2
  4. package/dist/render/anisotropicLobe.d.ts +5 -0
  5. package/dist/render/anisotropicLobe.js +13 -0
  6. package/dist/render/atmosphere.d.ts +79 -1
  7. package/dist/render/atmosphere.js +66 -19
  8. package/dist/render/backend/webgl2/renderer.d.ts +34 -2
  9. package/dist/render/backend/webgl2/renderer.js +44 -12
  10. package/dist/render/backend/webgpu/boltPass.d.ts +1 -1
  11. package/dist/render/backend/webgpu/causticsPass.d.ts +1 -1
  12. package/dist/render/backend/webgpu/clothSetPack.js +1 -0
  13. package/dist/render/backend/webgpu/clothSolvePack.d.ts +5 -2
  14. package/dist/render/backend/webgpu/clothSolvePack.js +2 -1
  15. package/dist/render/backend/webgpu/filmPass.d.ts +1 -1
  16. package/dist/render/backend/webgpu/gpuClothSet.js +1 -1
  17. package/dist/render/backend/webgpu/gpuDrivenPass.js +20 -3
  18. package/dist/render/backend/webgpu/linePass.d.ts +1 -1
  19. package/dist/render/backend/webgpu/mediumFields.d.ts +17 -0
  20. package/dist/render/backend/webgpu/mediumFields.js +74 -0
  21. package/dist/render/backend/webgpu/plumePass.d.ts +3 -3
  22. package/dist/render/backend/webgpu/postPass.d.ts +7 -2
  23. package/dist/render/backend/webgpu/renderer.d.ts +13 -3
  24. package/dist/render/backend/webgpu/renderer.js +86 -150
  25. package/dist/render/backend/webgpu/scatterPass.d.ts +1 -1
  26. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +1 -1
  27. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +7 -5
  28. package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
  29. package/dist/render/environmentMixing.d.ts +18 -0
  30. package/dist/render/environmentMixing.js +40 -0
  31. package/dist/render/fog.d.ts +43 -1
  32. package/dist/render/fog.js +87 -22
  33. package/dist/render/globalMedium.d.ts +112 -10
  34. package/dist/render/globalMedium.js +96 -1
  35. package/dist/render/globalMediumPass.d.ts +7 -3
  36. package/dist/render/globalMediumPass.js +22 -1
  37. package/dist/render/hairLobes.d.ts +27 -2
  38. package/dist/render/hairLobes.js +57 -4
  39. package/dist/render/lightBudget.d.ts +12 -0
  40. package/dist/render/lightBudget.js +24 -0
  41. package/dist/render/sceneTarget.d.ts +5 -3
  42. package/dist/render/sceneTarget.js +14 -8
  43. package/dist/render/shaders/arcane.d.ts +1 -1
  44. package/dist/render/shaders/bolt.d.ts +1 -1
  45. package/dist/render/shaders/caustics.d.ts +1 -1
  46. package/dist/render/shaders/film.d.ts +1 -1
  47. package/dist/render/shaders/film.js +4 -1
  48. package/dist/render/shaders/fire.d.ts +1 -1
  49. package/dist/render/shaders/flat/anisotropic.d.ts +8 -1
  50. package/dist/render/shaders/flat/anisotropic.js +25 -3
  51. package/dist/render/shaders/flat/hair.js +77 -27
  52. package/dist/render/shaders/flat/main.d.ts +1 -1
  53. package/dist/render/shaders/flat/main.js +23 -3
  54. package/dist/render/shaders/flat/models.js +12 -5
  55. package/dist/render/shaders/flat/preamble.js +7 -0
  56. package/dist/render/shaders/fog.d.ts +4 -4
  57. package/dist/render/shaders/fog.js +81 -32
  58. package/dist/render/shaders/generated/arcane.wgsl.d.ts +29 -39
  59. package/dist/render/shaders/generated/arcane.wgsl.js +29 -39
  60. package/dist/render/shaders/generated/bolt.wgsl.d.ts +26 -36
  61. package/dist/render/shaders/generated/bolt.wgsl.js +26 -36
  62. package/dist/render/shaders/generated/caustics.wgsl.d.ts +25 -35
  63. package/dist/render/shaders/generated/caustics.wgsl.js +25 -35
  64. package/dist/render/shaders/generated/film.wgsl.d.ts +28 -38
  65. package/dist/render/shaders/generated/film.wgsl.js +28 -38
  66. package/dist/render/shaders/generated/fire.wgsl.d.ts +28 -38
  67. package/dist/render/shaders/generated/fire.wgsl.js +28 -38
  68. package/dist/render/shaders/generated/flat.wgsl.d.ts +1466 -1546
  69. package/dist/render/shaders/generated/flat.wgsl.js +1489 -1569
  70. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +98 -12
  71. package/dist/render/shaders/generated/globalMedium.wgsl.js +98 -12
  72. package/dist/render/shaders/generated/line.wgsl.d.ts +25 -35
  73. package/dist/render/shaders/generated/line.wgsl.js +25 -35
  74. package/dist/render/shaders/generated/particle.wgsl.d.ts +100 -140
  75. package/dist/render/shaders/generated/particle.wgsl.js +100 -140
  76. package/dist/render/shaders/generated/rush.wgsl.d.ts +20 -10
  77. package/dist/render/shaders/generated/rush.wgsl.js +20 -10
  78. package/dist/render/shaders/generated/scatter.wgsl.d.ts +25 -35
  79. package/dist/render/shaders/generated/scatter.wgsl.js +25 -35
  80. package/dist/render/shaders/generated/smoke.wgsl.d.ts +28 -38
  81. package/dist/render/shaders/generated/smoke.wgsl.js +28 -38
  82. package/dist/render/shaders/generated/water.wgsl.d.ts +29 -39
  83. package/dist/render/shaders/generated/water.wgsl.js +29 -39
  84. package/dist/render/shaders/globalMedium.js +114 -10
  85. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +2 -1
  86. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +10 -4
  87. package/dist/render/shaders/gpudriven/fog.wgsl.js +77 -19
  88. package/dist/render/shaders/gpudriven/frameBlock.wgsl.d.ts +1 -1
  89. package/dist/render/shaders/gpudriven/frameBlock.wgsl.js +12 -1
  90. package/dist/render/shaders/gpudriven/shade.wgsl.js +3 -1
  91. package/dist/render/shaders/line.d.ts +1 -1
  92. package/dist/render/shaders/line.js +4 -2
  93. package/dist/render/shaders/localExposure.d.ts +42 -1
  94. package/dist/render/shaders/localExposure.js +73 -6
  95. package/dist/render/shaders/particle.d.ts +3 -3
  96. package/dist/render/shaders/particle.js +10 -6
  97. package/dist/render/shaders/scatter.d.ts +1 -1
  98. package/dist/render/shaders/scatter.js +4 -2
  99. package/dist/render/shaders/smoke.d.ts +1 -1
  100. package/dist/render/shaders/water.d.ts +1 -1
  101. package/dist/render/shaders/water.js +2 -1
  102. package/dist/render/surfaceModel.d.ts +23 -4
  103. package/dist/render/surfaceModel.js +12 -2
  104. package/package.json +6 -6
  105. package/src/index.ts +12 -2
  106. package/src/render/anisotropicLobe.ts +20 -0
  107. package/src/render/atmosphere.ts +153 -20
  108. package/src/render/backend/webgl2/renderer.ts +63 -13
  109. package/src/render/backend/webgpu/clothSetPack.ts +1 -0
  110. package/src/render/backend/webgpu/clothSolvePack.ts +6 -2
  111. package/src/render/backend/webgpu/gpuClothSet.ts +1 -1
  112. package/src/render/backend/webgpu/gpuDrivenPass.ts +20 -3
  113. package/src/render/backend/webgpu/mediumFields.ts +87 -0
  114. package/src/render/backend/webgpu/renderer.ts +109 -157
  115. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +7 -5
  116. package/src/render/environmentMixing.ts +48 -0
  117. package/src/render/fog.ts +125 -25
  118. package/src/render/globalMedium.ts +214 -11
  119. package/src/render/globalMediumPass.ts +29 -4
  120. package/src/render/hairLobes.ts +109 -3
  121. package/src/render/lightBudget.ts +32 -0
  122. package/src/render/sceneTarget.ts +15 -11
  123. package/src/render/shaders/film.ts +4 -1
  124. package/src/render/shaders/flat/anisotropic.ts +25 -3
  125. package/src/render/shaders/flat/hair.ts +77 -27
  126. package/src/render/shaders/flat/main.ts +23 -3
  127. package/src/render/shaders/flat/models.ts +12 -5
  128. package/src/render/shaders/flat/preamble.ts +7 -0
  129. package/src/render/shaders/fog.ts +81 -32
  130. package/src/render/shaders/generated/arcane.wgsl.ts +29 -39
  131. package/src/render/shaders/generated/bolt.wgsl.ts +26 -36
  132. package/src/render/shaders/generated/caustics.wgsl.ts +25 -35
  133. package/src/render/shaders/generated/film.wgsl.ts +28 -38
  134. package/src/render/shaders/generated/fire.wgsl.ts +28 -38
  135. package/src/render/shaders/generated/flat.wgsl.ts +1489 -1569
  136. package/src/render/shaders/generated/globalMedium.wgsl.ts +98 -12
  137. package/src/render/shaders/generated/line.wgsl.ts +25 -35
  138. package/src/render/shaders/generated/particle.wgsl.ts +100 -140
  139. package/src/render/shaders/generated/rush.wgsl.ts +20 -10
  140. package/src/render/shaders/generated/scatter.wgsl.ts +25 -35
  141. package/src/render/shaders/generated/smoke.wgsl.ts +28 -38
  142. package/src/render/shaders/generated/water.wgsl.ts +29 -39
  143. package/src/render/shaders/globalMedium.ts +114 -10
  144. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +2 -1
  145. package/src/render/shaders/gpudriven/fog.wgsl.ts +78 -19
  146. package/src/render/shaders/gpudriven/frameBlock.wgsl.ts +12 -1
  147. package/src/render/shaders/gpudriven/shade.wgsl.ts +3 -1
  148. package/src/render/shaders/line.ts +4 -2
  149. package/src/render/shaders/localExposure.ts +115 -6
  150. package/src/render/shaders/particle.ts +10 -6
  151. package/src/render/shaders/scatter.ts +4 -2
  152. package/src/render/shaders/water.ts +2 -1
  153. package/src/render/surfaceModel.ts +41 -6
@@ -60,7 +60,13 @@ import type {
60
60
  TranslucentMeshOptions,
61
61
  } from '../webgl2/renderer.ts';
62
62
  import type { IncrementalMeshHandle, MeshOptions, PrepareOptions } from '../api.ts';
63
- import { resolveAtmosphere, type ResolvedAtmosphere } from '../../atmosphere.ts';
63
+ import { writeMediumFields, writeMediumSlot } from './mediumFields.ts';
64
+ import { noEnvironmentMixing, resolveEnvironmentMixing } from '../../environmentMixing.ts';
65
+ import {
66
+ createResolvedAtmosphere,
67
+ resolveAtmosphere,
68
+ type ResolvedAtmosphere,
69
+ } from '../../atmosphere.ts';
64
70
  import { AMBIENT_SH_FLOATS, packAmbientSH } from '../../ambientHarmonics.ts';
65
71
  import {
66
72
  lightmapTexels,
@@ -286,7 +292,8 @@ import {
286
292
  import { ExposurePass } from './exposurePass.ts';
287
293
  import { AO_STORE } from '../../shaders/ambientOcclusion.ts';
288
294
  import { clampAutoExposure } from '../../shaders/exposure.ts';
289
- import { clampLocalExposure } from '../../shaders/localExposure.ts';
295
+ import { LOCAL_EXPOSURE_FLOATS, resolveLocalExposure } from '../../shaders/localExposure.ts';
296
+ import type { LocalExposureOptions } from '../../shaders/localExposure.ts';
290
297
  import type { UniformFields } from './scatterPass.ts';
291
298
  import {
292
299
  createDepthResolveLayout,
@@ -425,16 +432,23 @@ import {
425
432
  resolveScatterDeform,
426
433
  type ScatterDeform,
427
434
  } from '../../scatterDeform.ts';
428
- import { resolvePointLights, type ResolvedPointLights } from '../../lightBudget.ts';
435
+ import {
436
+ createResolvedPointLights,
437
+ resolvePointLights,
438
+ type ResolvedPointLights,
439
+ } from '../../lightBudget.ts';
429
440
  import { resolveRenderQuality, type RenderQuality } from '../../renderQuality.ts';
430
441
  import {
431
442
  DEFAULT_GLOBAL_MEDIUM,
443
+ WEAK_GPU_MEDIUM_STEPS,
444
+ createResolvedMediumLamps,
432
445
  mediumActive,
433
446
  mediumTargetSize,
434
- WEAK_GPU_MEDIUM_STEPS,
435
447
  resolveGlobalMedium,
448
+ resolveGlobalMediumSettings,
449
+ resolveMediumLamps,
436
450
  } from '../../globalMedium.ts';
437
- import type { GlobalMediumOptions } from '../../globalMedium.ts';
451
+ import type { GlobalMediumSettings, ResolvedGlobalMedium } from '../../globalMedium.ts';
438
452
  import type { MeshData } from '../../mesh.ts';
439
453
  import { castsDepth, glassOf } from '../../shadowCasters.ts';
440
454
  import { materialHasMaps, noteMapsWithoutUvs } from '../../mapsWithoutUvs.ts';
@@ -2242,7 +2256,10 @@ export class WebGPURenderer implements RendererApi {
2242
2256
  /** The inverse projection the composite linearises a depth with, built once per frame. */
2243
2257
  private readonly mediumInvProjection = mat4.create();
2244
2258
  /** This frame's medium. Off until a caller says otherwise. See `setGlobalMedium`. */
2245
- private mediumOptions: GlobalMediumOptions = DEFAULT_GLOBAL_MEDIUM;
2259
+ private mediumOptions: ResolvedGlobalMedium = resolveGlobalMedium(DEFAULT_GLOBAL_MEDIUM);
2260
+ /** The lamps the medium is lit by, and the lit stage's lamps they come from, refilled a frame. */
2261
+ private readonly mediumLamps = createResolvedMediumLamps();
2262
+ private readonly mediumLights = createResolvedPointLights();
2246
2263
  /**
2247
2264
  * The temporal resolve: its block, its ping-pong pair, and the jitter state.
2248
2265
  *
@@ -2948,16 +2965,7 @@ export class WebGPURenderer implements RendererApi {
2948
2965
  this.quality.underwaterAtmosphere,
2949
2966
  this.medium,
2950
2967
  );
2951
- f.set(medium.fogColor, atF('uFogColor'));
2952
- f[atF('uFogDensity')] = medium.fogDensity;
2953
- f[atF('uFogHeightFalloff')] = medium.fogHeightFalloff;
2954
- f[atF('uFogEyeY')] = medium.fogEyeY;
2955
- f.set(medium.underwaterColor, atF('uUnderwaterColor'));
2956
- f[atF('uUnderwaterFogDensity')] = medium.underwaterFogDensity;
2957
- f[atF('uUnderwaterFactor')] = medium.underwaterFactor;
2958
- i[atF('uFogMode')] = medium.fogMode;
2959
- f[atF('uFogNear')] = medium.fogNear;
2960
- f[atF('uFogFar')] = medium.fogFar;
2968
+ writeMediumFields(FILM_FRAG_FIELDS, f, medium);
2961
2969
  this.filmFrags.writeBlock(fragmentSlot, this.filmFragInts);
2962
2970
 
2963
2971
  const filmPipe = filmPipeline(
@@ -3791,6 +3799,17 @@ export class WebGPURenderer implements RendererApi {
3791
3799
  this.writeEnvironmentDials();
3792
3800
  }
3793
3801
 
3802
+ /** The environment brought to the light where a surface stands. See `renderer.ts`. */
3803
+ setEnvironmentMixing(weight: number, roughnessLow = 0.1, roughnessHigh = 0.3): void {
3804
+ resolveEnvironmentMixing(weight, roughnessLow, roughnessHigh, this.environmentMixing);
3805
+ this.material((f) => {
3806
+ f.set(this.environmentMixing, this.materialField('uEnvironmentMixing'));
3807
+ });
3808
+ }
3809
+
3810
+ /** `uEnvironmentMixing` as it stands; none until a draw asks. See `setEnvironmentMixing`. */
3811
+ private readonly environmentMixing = noEnvironmentMixing(new Float32Array(4));
3812
+
3794
3813
  /**
3795
3814
  * The reflectivity and environment gain the setters gave the pass, and the ones the material
3796
3815
  * set now states, or null where it states none (`SurfaceMaterial.reflectivity`). A draw wears the
@@ -4683,16 +4702,7 @@ export class WebGPURenderer implements RendererApi {
4683
4702
  this.quality.underwaterAtmosphere,
4684
4703
  this.medium,
4685
4704
  );
4686
- f.set(medium.fogColor, atF('uFogColor'));
4687
- f[atF('uFogDensity')] = medium.fogDensity;
4688
- f[atF('uFogHeightFalloff')] = medium.fogHeightFalloff;
4689
- f[atF('uFogEyeY')] = medium.fogEyeY;
4690
- f.set(medium.underwaterColor, atF('uUnderwaterColor'));
4691
- f[atF('uUnderwaterFogDensity')] = medium.underwaterFogDensity;
4692
- f[atF('uUnderwaterFactor')] = medium.underwaterFactor;
4693
- i[atF('uFogMode')] = medium.fogMode;
4694
- f[atF('uFogNear')] = medium.fogNear;
4695
- f[atF('uFogFar')] = medium.fogFar;
4705
+ writeMediumFields(fields, f, medium);
4696
4706
  /* Only the mote fragment declares this — spark and smoke stay unconditionally fogged and
4697
4707
  have no such field. See ParticleBatchOptions.fog. */
4698
4708
  if (has('uFogEnabled')) i[atF('uFogEnabled')] = batch.fog ? 1 : 0;
@@ -4943,16 +4953,7 @@ export class WebGPURenderer implements RendererApi {
4943
4953
  this.quality.underwaterAtmosphere,
4944
4954
  this.medium,
4945
4955
  );
4946
- frags.writeFloats(fragmentSlot, atF('uFogColor'), medium.fogColor);
4947
- frags.writeFloat(fragmentSlot, atF('uFogDensity'), medium.fogDensity);
4948
- frags.writeFloat(fragmentSlot, atF('uFogHeightFalloff'), medium.fogHeightFalloff);
4949
- frags.writeFloat(fragmentSlot, atF('uFogEyeY'), medium.fogEyeY);
4950
- frags.writeFloats(fragmentSlot, atF('uUnderwaterColor'), medium.underwaterColor);
4951
- frags.writeFloat(fragmentSlot, atF('uUnderwaterFogDensity'), medium.underwaterFogDensity);
4952
- frags.writeFloat(fragmentSlot, atF('uUnderwaterFactor'), medium.underwaterFactor);
4953
- frags.writeInt(fragmentSlot, atF('uFogMode'), medium.fogMode);
4954
- frags.writeFloat(fragmentSlot, atF('uFogNear'), medium.fogNear);
4955
- frags.writeFloat(fragmentSlot, atF('uFogFar'), medium.fogFar);
4956
+ writeMediumSlot(frags, fragmentSlot, PLUME_FRAG_FIELDS, medium);
4956
4957
  frags.writeInt(fragmentSlot, atF('uOutputTransform'), this.passGrade());
4957
4958
  frags.writeFloat(fragmentSlot, atF('uOutputExposure'), this.passGradeExposure());
4958
4959
  /*
@@ -5141,16 +5142,7 @@ export class WebGPURenderer implements RendererApi {
5141
5142
  this.quality.underwaterAtmosphere,
5142
5143
  this.medium,
5143
5144
  );
5144
- f.set(medium.fogColor, atF('uFogColor'));
5145
- f[atF('uFogDensity')] = medium.fogDensity;
5146
- f[atF('uFogHeightFalloff')] = medium.fogHeightFalloff;
5147
- f[atF('uFogEyeY')] = medium.fogEyeY;
5148
- f.set(medium.underwaterColor, atF('uUnderwaterColor'));
5149
- f[atF('uUnderwaterFogDensity')] = medium.underwaterFogDensity;
5150
- f[atF('uUnderwaterFactor')] = medium.underwaterFactor;
5151
- i[atF('uFogMode')] = medium.fogMode;
5152
- f[atF('uFogNear')] = medium.fogNear;
5153
- f[atF('uFogFar')] = medium.fogFar;
5145
+ writeMediumFields(WATER_FRAG_FIELDS, f, medium);
5154
5146
 
5155
5147
  /* No reflection pass yet: the shader's own "off" path, not an improvised one. */
5156
5148
  i[atF('uReflectionEnabled')] = mirror === null ? 0 : 1;
@@ -5766,16 +5758,7 @@ export class WebGPURenderer implements RendererApi {
5766
5758
  this.quality.underwaterAtmosphere,
5767
5759
  this.medium,
5768
5760
  );
5769
- f.set(medium.fogColor, atF('uFogColor'));
5770
- f[atF('uFogDensity')] = medium.fogDensity;
5771
- f[atF('uFogHeightFalloff')] = medium.fogHeightFalloff;
5772
- f[atF('uFogEyeY')] = medium.fogEyeY;
5773
- f.set(medium.underwaterColor, atF('uUnderwaterColor'));
5774
- f[atF('uUnderwaterFogDensity')] = medium.underwaterFogDensity;
5775
- f[atF('uUnderwaterFactor')] = medium.underwaterFactor;
5776
- i[atF('uFogMode')] = medium.fogMode;
5777
- f[atF('uFogNear')] = medium.fogNear;
5778
- f[atF('uFogFar')] = medium.fogFar;
5761
+ writeMediumFields(BOLT_FRAG_FIELDS, f, medium);
5779
5762
 
5780
5763
  this.boltVerts.writeBlock(vertexSlot, this.boltVertBlock);
5781
5764
  this.boltFrags.writeBlock(fragmentSlot, this.boltFragInts);
@@ -5903,20 +5886,7 @@ export class WebGPURenderer implements RendererApi {
5903
5886
  this.quality.underwaterAtmosphere,
5904
5887
  this.medium,
5905
5888
  );
5906
- this.lineFrags.writeFloats(fragmentSlot, atF('uFogColor'), medium.fogColor);
5907
- this.lineFrags.writeFloat(fragmentSlot, atF('uFogDensity'), medium.fogDensity);
5908
- this.lineFrags.writeFloat(fragmentSlot, atF('uFogHeightFalloff'), medium.fogHeightFalloff);
5909
- this.lineFrags.writeFloat(fragmentSlot, atF('uFogEyeY'), medium.fogEyeY);
5910
- this.lineFrags.writeFloats(fragmentSlot, atF('uUnderwaterColor'), medium.underwaterColor);
5911
- this.lineFrags.writeFloat(
5912
- fragmentSlot,
5913
- atF('uUnderwaterFogDensity'),
5914
- medium.underwaterFogDensity,
5915
- );
5916
- this.lineFrags.writeFloat(fragmentSlot, atF('uUnderwaterFactor'), medium.underwaterFactor);
5917
- this.lineFrags.writeInt(fragmentSlot, atF('uFogMode'), medium.fogMode);
5918
- this.lineFrags.writeFloat(fragmentSlot, atF('uFogNear'), medium.fogNear);
5919
- this.lineFrags.writeFloat(fragmentSlot, atF('uFogFar'), medium.fogFar);
5889
+ writeMediumSlot(this.lineFrags, fragmentSlot, LINE_FRAG_FIELDS, medium);
5920
5890
 
5921
5891
  const linePipe = linePipeline(
5922
5892
  this.blendedPipelines(),
@@ -6079,39 +6049,11 @@ export class WebGPURenderer implements RendererApi {
6079
6049
  }
6080
6050
 
6081
6051
  /** Refilled per frame rather than allocated; see `resolvePointLights`. */
6082
- private readonly lights: ResolvedPointLights = {
6083
- count: 0,
6084
- falloff: 0,
6085
- positions: new Float32Array(0),
6086
- colors: new Float32Array(0),
6087
- radii: new Float32Array(0),
6088
- sourceRadii: new Float32Array(0),
6089
- weights: new Float32Array(0),
6090
- directions: new Float32Array(0),
6091
- coneCos: new Float32Array(0),
6092
- iesProfiles: new Float32Array(0),
6093
- iesAxes: new Float32Array(0),
6094
- cookies: new Float32Array(0),
6095
- heldSourceRadii: new Float32Array(MAX_POINT_LIGHTS),
6096
- axesAndChannels: new Float32Array(MAX_POINT_LIGHTS * 4),
6097
- conesAndFalloffs: new Float32Array(MAX_POINT_LIGHTS * 4),
6098
- heldWeights: new Float32Array(MAX_POINT_LIGHTS),
6099
- };
6052
+ private readonly lights: ResolvedPointLights = createResolvedPointLights();
6100
6053
  /** The fixed arm's rectangles as `uAreaLightSize` holds them. See `packAreaSizesAndDoors`. */
6101
6054
  private readonly areaSizesAndDoors = new Float32Array(MAX_AREA_LIGHTS * 4);
6102
6055
  /** Refilled per frame rather than allocated; see `resolveAtmosphere`. */
6103
- private readonly medium: ResolvedAtmosphere = {
6104
- fogColor: new Float32Array(3),
6105
- underwaterColor: new Float32Array(3),
6106
- fogDensity: 0,
6107
- fogHeightFalloff: 0,
6108
- fogEyeY: 0,
6109
- fogMode: 0,
6110
- fogNear: 0,
6111
- fogFar: 1,
6112
- underwaterFogDensity: 0,
6113
- underwaterFactor: 0,
6114
- };
6056
+ private readonly medium: ResolvedAtmosphere = createResolvedAtmosphere();
6115
6057
  /* -- Overlays: panels and text ------------------------------------------------------ */
6116
6058
 
6117
6059
  private readonly panelLayout: GPUBindGroupLayout;
@@ -7779,6 +7721,7 @@ export class WebGPURenderer implements RendererApi {
7779
7721
  this.mediumLayout = createPostStageLayout(device, MEDIUM_UNIFORMS, MEDIUM_FRAG_SIZE, [
7780
7722
  { binding: MEDIUM_TEXTURES.uDepth, filterable: false },
7781
7723
  { binding: MEDIUM_TEXTURES.uSunShadows, filterable: false, dimension: '2d-array' },
7724
+ { binding: MEDIUM_TEXTURES.uPointShadows, filterable: false, dimension: '2d-array' },
7782
7725
  ]);
7783
7726
  this.mediumUniforms = device.createBuffer({
7784
7727
  label: 'post.mediumUniforms',
@@ -9178,16 +9121,7 @@ export class WebGPURenderer implements RendererApi {
9178
9121
  this.quality.underwaterAtmosphere,
9179
9122
  this.medium,
9180
9123
  );
9181
- f.set(meshMedium.fogColor, at('uFogColor'));
9182
- f[at('uFogDensity')] = meshMedium.fogDensity;
9183
- f[at('uFogHeightFalloff')] = meshMedium.fogHeightFalloff;
9184
- f[at('uFogEyeY')] = meshMedium.fogEyeY;
9185
- i[at('uFogMode')] = meshMedium.fogMode;
9186
- f[at('uFogNear')] = meshMedium.fogNear;
9187
- f[at('uFogFar')] = meshMedium.fogFar;
9188
- f.set(meshMedium.underwaterColor, at('uUnderwaterColor'));
9189
- f[at('uUnderwaterFogDensity')] = meshMedium.underwaterFogDensity;
9190
- f[at('uUnderwaterFactor')] = meshMedium.underwaterFactor;
9124
+ writeMediumFields(this.fragment.fields, f, meshMedium);
9191
9125
  /*
9192
9126
  * The mirror's half-space, and only while the mirror is being drawn.
9193
9127
  *
@@ -9236,6 +9170,8 @@ export class WebGPURenderer implements RendererApi {
9236
9170
  f[at('uWriteMode') + 2] = 0;
9237
9171
  /* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
9238
9172
  f[at('uEnvironmentDials') + 2] = 1;
9173
+ /* And no mixing, which is stored less one. See `setEnvironmentMixing`. */
9174
+ f.set(noEnvironmentMixing(this.environmentMixing), at('uEnvironmentMixing'));
9239
9175
  /* No overlay until a draw sets one. See `setSurfaceOverlay`. */
9240
9176
  this.overlayFloats.fill(0);
9241
9177
  f.fill(0, at('uOverlay'), at('uOverlay') + OVERLAY_FLOATS);
@@ -9491,16 +9427,7 @@ export class WebGPURenderer implements RendererApi {
9491
9427
  this.quality.underwaterAtmosphere,
9492
9428
  this.medium,
9493
9429
  );
9494
- f.set(medium.fogColor, at('uFogColor'));
9495
- f[at('uFogDensity')] = medium.fogDensity;
9496
- f[at('uFogHeightFalloff')] = medium.fogHeightFalloff;
9497
- f[at('uFogEyeY')] = medium.fogEyeY;
9498
- i[at('uFogMode')] = medium.fogMode;
9499
- f[at('uFogNear')] = medium.fogNear;
9500
- f[at('uFogFar')] = medium.fogFar;
9501
- f.set(medium.underwaterColor, at('uUnderwaterColor'));
9502
- f[at('uUnderwaterFogDensity')] = medium.underwaterFogDensity;
9503
- f[at('uUnderwaterFactor')] = medium.underwaterFactor;
9430
+ writeMediumFields(this.fragment.fields, f, medium);
9504
9431
 
9505
9432
  /*
9506
9433
  * The shadow terms, matching what `renderer.ts` binds at default quality.
@@ -11120,16 +11047,7 @@ export class WebGPURenderer implements RendererApi {
11120
11047
  this.quality.underwaterAtmosphere,
11121
11048
  this.medium,
11122
11049
  );
11123
- f.set(medium.fogColor, atF('uFogColor'));
11124
- f[atF('uFogDensity')] = medium.fogDensity;
11125
- f[atF('uFogHeightFalloff')] = medium.fogHeightFalloff;
11126
- f[atF('uFogEyeY')] = medium.fogEyeY;
11127
- f.set(medium.underwaterColor, atF('uUnderwaterColor'));
11128
- f[atF('uUnderwaterFogDensity')] = medium.underwaterFogDensity;
11129
- f[atF('uUnderwaterFactor')] = medium.underwaterFactor;
11130
- i[atF('uFogMode')] = medium.fogMode;
11131
- f[atF('uFogNear')] = medium.fogNear;
11132
- f[atF('uFogFar')] = medium.fogFar;
11050
+ writeMediumFields(SCATTER_FRAG_FIELDS, f, medium);
11133
11051
  i[atF('uOutputTransform')] = this.passGrade();
11134
11052
  f[atF('uOutputExposure')] = this.passGradeExposure();
11135
11053
 
@@ -12034,6 +11952,8 @@ export class WebGPURenderer implements RendererApi {
12034
11952
  * resolve, which is what sampling a stand-in does.
12035
11953
  */
12036
11954
  this.rebuildFlatBindGroup();
11955
+ /* The medium reads the same array: see `buildMediumBindGroup`. */
11956
+ this.mediumBindGroup = this.buildMediumBindGroup();
12037
11957
  }
12038
11958
  this.pointShadows.prepareStaticMaps(lights.length);
12039
11959
  this.preparedPointLightCount = lights.length;
@@ -12351,6 +12271,8 @@ export class WebGPURenderer implements RendererApi {
12351
12271
  if (old !== null) {
12352
12272
  this.retiredPointTexture = old;
12353
12273
  this.rebuildFlatBindGroup();
12274
+ /* The medium reads the same array: see `buildMediumBindGroup`. */
12275
+ this.mediumBindGroup = this.buildMediumBindGroup();
12354
12276
  }
12355
12277
  }
12356
12278
  }
@@ -12892,7 +12814,7 @@ export class WebGPURenderer implements RendererApi {
12892
12814
  * it. The held brightness is kept for either, since local exposure moves regions relative to it.
12893
12815
  * See `setAutoExposure` and `setLocalExposure`.
12894
12816
  */
12895
- const localising = this.localExposure > 0;
12817
+ const localising = this.localExposure;
12896
12818
  const measuring = (this.autoExposure > 0 || localising) && this.exposurePass !== null;
12897
12819
  const adapting = measuring && this.autoExposure > 0;
12898
12820
  const shownScene = this.reconstructing ? this.reconShownView : this.sceneColorView;
@@ -12966,7 +12888,13 @@ export class WebGPURenderer implements RendererApi {
12966
12888
  f[at('uVignette')] = this.vignette;
12967
12889
  f.set(this.fringe, at('uFringe'));
12968
12890
  f[at('uAutoExposure')] = adapting ? this.autoExposure : 0;
12969
- f[at('uLocalExposure')] = measuring && localising ? this.localExposure : 0;
12891
+ f[at('uLocalExposure')] = measuring && localising ? 1 : 0;
12892
+ const shapeAt = at('uLocalShape');
12893
+ const pivotAt = at('uLocalPivot');
12894
+ for (let k = 0; k < 4; k++) {
12895
+ f[shapeAt + k] = this.localExposureShape[k] as number;
12896
+ f[pivotAt + k] = this.localExposureShape[4 + k] as number;
12897
+ }
12970
12898
  f[at('uGrain')] = this.grain;
12971
12899
  f[at('uGrain') + 1] = this.grainSeed;
12972
12900
  device.queue.writeBuffer(this.rushUniforms, 0, this.rushStaging);
@@ -14404,7 +14332,17 @@ export class WebGPURenderer implements RendererApi {
14404
14332
  f.set(env?.directionalColor ?? [0, 0, 0], at('uSunColor'));
14405
14333
  f.set(env?.ambient ?? [0, 0, 0], at('uAmbient'));
14406
14334
  f[at('uDensity')] = options.density;
14407
- f[at('uAlbedo')] = options.albedo;
14335
+ const height = at('uHeight');
14336
+ f[height] = options.heightFalloff;
14337
+ f[height + 1] = options.baseY;
14338
+ const layer2 = at('uLayer2');
14339
+ f[layer2] = options.density2;
14340
+ f[layer2 + 1] = options.heightFalloff2;
14341
+ f[layer2 + 2] = options.baseY2;
14342
+ f.set(options.albedo, at('uAlbedo'));
14343
+ const scatter = at('uScatter');
14344
+ f[scatter] = options.sunScatter;
14345
+ f[scatter + 1] = options.skyScatter;
14408
14346
  f[at('uAnisotropy')] = options.anisotropy;
14409
14347
  f[at('uMaxDistance')] = options.maxDistance;
14410
14348
  ints[at('uSteps')] = this.quality.globalMediumSteps;
@@ -14425,6 +14363,26 @@ export class WebGPURenderer implements RendererApi {
14425
14363
  at('uMovingLightViewProj'),
14426
14364
  );
14427
14365
  ints[at('uPeeledShadowEnabled')] = this.peelView === null || !this.peelFilled ? 0 : 1;
14366
+ /* The lit stage's lamps and the shadows it resolved for them, this frame's. */
14367
+ const lamps = resolveMediumLamps(
14368
+ options,
14369
+ resolvePointLights(env, this.quality.pointLightFalloff, this.mediumLights),
14370
+ this.pointShadows === null ? null : this.resolvedPointShadows,
14371
+ env?.lightMediumScatter,
14372
+ this.mediumLamps,
14373
+ );
14374
+ ints[at('uLampCount')] = lamps.count;
14375
+ ints[at('uLampFalloff')] = lamps.falloff;
14376
+ if (lamps.count > 0) {
14377
+ f.set(lamps.position, at('uLampPosition'));
14378
+ f.set(lamps.color, at('uLampColor'));
14379
+ f.set(lamps.aim, at('uLampAim'));
14380
+ f.set(lamps.cone, at('uLampCone'));
14381
+ f.set(lamps.shadow, at('uLampShadow'));
14382
+ f.set(lamps.shadowAt, at('uLampShadowAt'));
14383
+ f.set(lamps.live, at('uLampLive'));
14384
+ f.set(lamps.liveAt, at('uLampLiveAt'));
14385
+ }
14428
14386
  device.queue.writeBuffer(this.mediumUniforms, 0, this.mediumStaging);
14429
14387
 
14430
14388
  const g = this.mediumUpsampleFloats;
@@ -17147,8 +17105,9 @@ export class WebGPURenderer implements RendererApi {
17147
17105
  /** Eye adaptation: how far, this frame's time step, and the pass once asked for. */
17148
17106
  private autoExposure = 0;
17149
17107
  private autoExposureDt = 0;
17150
- /** How far each region is brought toward the frame. See `setLocalExposure`. */
17151
- private localExposure = 0;
17108
+ /** Whether regions move, and how, as `resolveLocalExposure` packs it. See `setLocalExposure`. */
17109
+ private localExposure = false;
17110
+ private readonly localExposureShape = new Float32Array(LOCAL_EXPOSURE_FLOATS);
17152
17111
  private exposurePass: ExposurePass | null = null;
17153
17112
 
17154
17113
  /**
@@ -17312,17 +17271,12 @@ export class WebGPURenderer implements RendererApi {
17312
17271
  * draws nothing, and the three optional arguments are held until changed.
17313
17272
  */
17314
17273
  setGlobalMedium(
17315
- density: number,
17316
- albedo?: number,
17274
+ density: number | GlobalMediumSettings,
17275
+ albedo?: number | Vec3,
17317
17276
  anisotropy?: number,
17318
17277
  maxDistance?: number,
17319
17278
  ): void {
17320
- this.mediumOptions = resolveGlobalMedium({
17321
- density: Number.isFinite(density) ? density : 0,
17322
- albedo,
17323
- anisotropy,
17324
- maxDistance,
17325
- });
17279
+ this.mediumOptions = resolveGlobalMediumSettings(density, albedo, anisotropy, maxDistance);
17326
17280
  }
17327
17281
 
17328
17282
  /** How much speed blur the frame should resolve with, 0 to 1. */
@@ -17397,13 +17351,13 @@ export class WebGPURenderer implements RendererApi {
17397
17351
  * the same setter over the same state, sharing eye adaptation's pass, which is built the first
17398
17352
  * time either is asked for.
17399
17353
  */
17400
- setLocalExposure(strength: number): void {
17354
+ setLocalExposure(setting: number | LocalExposureOptions): void {
17401
17355
  if (!this.hasComposite || !this.quality.hdrScene) {
17402
- this.warnLookWithoutComposite(strength);
17356
+ this.warnLookWithoutComposite(typeof setting === 'number' ? setting : 1);
17403
17357
  return;
17404
17358
  }
17405
- this.localExposure = clampLocalExposure(strength);
17406
- if (this.localExposure > 0) this.ensureExposurePass();
17359
+ this.localExposure = resolveLocalExposure(setting, this.localExposureShape);
17360
+ if (this.localExposure) this.ensureExposurePass();
17407
17361
  }
17408
17362
 
17409
17363
  /** The exposure pass, built once; the composite's group holds placeholders until it exists. */
@@ -17572,16 +17526,7 @@ export class WebGPURenderer implements RendererApi {
17572
17526
  this.quality.underwaterAtmosphere,
17573
17527
  this.medium,
17574
17528
  );
17575
- f.set(medium.fogColor, atF('uFogColor'));
17576
- f[atF('uFogDensity')] = medium.fogDensity;
17577
- f[atF('uFogHeightFalloff')] = medium.fogHeightFalloff;
17578
- f[atF('uFogEyeY')] = medium.fogEyeY;
17579
- f.set(medium.underwaterColor, atF('uUnderwaterColor'));
17580
- f[atF('uUnderwaterFogDensity')] = medium.underwaterFogDensity;
17581
- f[atF('uUnderwaterFactor')] = medium.underwaterFactor;
17582
- i[atF('uFogMode')] = medium.fogMode;
17583
- f[atF('uFogNear')] = medium.fogNear;
17584
- f[atF('uFogFar')] = medium.fogFar;
17529
+ writeMediumFields(CAUSTICS_FRAG_FIELDS, f, medium);
17585
17530
 
17586
17531
  this.causticsVerts.writeBlock(vertexSlot, this.causticsVertBlock);
17587
17532
  this.causticsFrags.writeBlock(fragmentSlot, this.causticsFragInts);
@@ -18658,6 +18603,13 @@ export class WebGPURenderer implements RendererApi {
18658
18603
  { binding: MEDIUM_TEXTURES.uDepth.sampler, resource: this.postDepthSampler },
18659
18604
  { binding: MEDIUM_TEXTURES.uSunShadows.texture, resource: this.sunArrayView },
18660
18605
  { binding: MEDIUM_TEXTURES.uSunShadows.sampler, resource: this.volumeShadowSampler },
18606
+ /* The lamps' maps, the lit stage's own array, rebuilt here whenever that array is
18607
+ replaced; the white stand-in reads as nothing in the way, and no lamp reads it. */
18608
+ {
18609
+ binding: MEDIUM_TEXTURES.uPointShadows.texture,
18610
+ resource: this.pointShadowArray?.view ?? (this.shadowBlankView as GPUTextureView),
18611
+ },
18612
+ { binding: MEDIUM_TEXTURES.uPointShadows.sampler, resource: this.volumeShadowSampler },
18661
18613
  ],
18662
18614
  });
18663
18615
  }
@@ -88,8 +88,9 @@ struct Pace {
88
88
  colliderBase: u32,
89
89
  colliders: u32,
90
90
  width: u32,
91
- /* The share of its offset from its skinned position a particle keeps a substep: the pull's. */
92
- pullKeep: f32,
91
+ /* The garment's pull as a substep's stiffness, pull · h²: a particle weighted w keeps
92
+ 1 / (1 + w · pullStep) of its offset from its skinned position. */
93
+ pullStep: f32,
93
94
  keep: f32,
94
95
  dragKeep: f32,
95
96
  maxDistanceScale: f32,
@@ -119,7 +120,7 @@ struct Batch {
119
120
 
120
121
  /* The record of particle i's garment this round: which garment a particle is, in statics. */
121
122
  fn paceAt(i: u32) -> u32 {
122
- return round.at + u32(statics[29u * c.count + i]);
123
+ return round.at + u32(statics[30u * c.count + i]);
123
124
  }
124
125
 
125
126
  fn stateAt(section: u32, i: u32) -> u32 {
@@ -446,8 +447,9 @@ fn clothLimit(@builtin(global_invocation_id) id: vec3<u32>) {
446
447
  var p = load(at);
447
448
  let t = skinnedAt(i, g);
448
449
  /* Only where there is a pull, as on the CPU: t + (p - t) is not p in floats. */
449
- if (paces[g].pullKeep < 1.0) {
450
- p = t + (p - t) * paces[g].pullKeep;
450
+ let pull = statics[29u * n + i] * paces[g].pullStep;
451
+ if (pull > 0.0) {
452
+ p = t + (p - t) * (1.0 / (1.0 + pull));
451
453
  }
452
454
  /* A max distance below zero is none: the CPU's Infinity, which a device may not be handed. */
453
455
  let maxDistance = statics[n + i];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The environment a surface reflects brought to the light where it stands: `setEnvironmentMixing`'s
3
+ * numbers as the lit stage reads them, `uEnvironmentMixing`. Both backends fill it here and upload
4
+ * it, so a clamp cannot differ between them.
5
+ *
6
+ * **The weight is stored less one**, so a program a binder never wrote, all zeros, reflects exactly
7
+ * as it did before the field existed; the two roughnesses beside it are where the weight is mixed in
8
+ * between, smooth to rough.
9
+ */
10
+
11
+ /** `uEnvironmentMixing` mixing nothing: a weight of 1, between the default roughnesses. */
12
+ export function noEnvironmentMixing(out: Float32Array): Float32Array {
13
+ out[0] = 0;
14
+ out[1] = 0.1;
15
+ out[2] = 0.3;
16
+ out[3] = 0;
17
+ return out;
18
+ }
19
+
20
+ /**
21
+ * A caller's weight and roughnesses, into `out`: a weight that is not a number at or above zero is
22
+ * 1, the roughnesses are held to 0 to 1, and the upper never below the lower.
23
+ */
24
+ export function resolveEnvironmentMixing(
25
+ weight: number,
26
+ roughnessLow: number,
27
+ roughnessHigh: number,
28
+ out: Float32Array,
29
+ ): Float32Array {
30
+ const unit = (value: number, fallback: number): number =>
31
+ Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : fallback;
32
+ out[0] = (Number.isFinite(weight) && weight >= 0 ? weight : 1) - 1;
33
+ const low = unit(roughnessLow, 0.1);
34
+ out[1] = low;
35
+ out[2] = Math.max(low, unit(roughnessHigh, 0.3));
36
+ out[3] = 0;
37
+ return out;
38
+ }
39
+
40
+ /** The factor a surface of `roughness` reflects by, as the lit stage computes it. */
41
+ export function environmentMixingFactor(mixing: ArrayLike<number>, roughness: number): number {
42
+ const less = mixing[0] as number;
43
+ if (less === 0) return 1;
44
+ const low = mixing[1] as number;
45
+ const span = Math.max((mixing[2] as number) - low, 1e-4);
46
+ const t = Math.min(1, Math.max(0, (roughness - low) / span));
47
+ return 1 + less * t * t * (3 - 2 * t);
48
+ }