@driftengine/core 4.13.4 → 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 (143) hide show
  1. package/CHANGELOG.json +36 -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/filmPass.d.ts +1 -1
  13. package/dist/render/backend/webgpu/gpuDrivenPass.js +20 -3
  14. package/dist/render/backend/webgpu/linePass.d.ts +1 -1
  15. package/dist/render/backend/webgpu/mediumFields.d.ts +17 -0
  16. package/dist/render/backend/webgpu/mediumFields.js +74 -0
  17. package/dist/render/backend/webgpu/plumePass.d.ts +3 -3
  18. package/dist/render/backend/webgpu/postPass.d.ts +7 -2
  19. package/dist/render/backend/webgpu/renderer.d.ts +13 -3
  20. package/dist/render/backend/webgpu/renderer.js +86 -150
  21. package/dist/render/backend/webgpu/scatterPass.d.ts +1 -1
  22. package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
  23. package/dist/render/environmentMixing.d.ts +18 -0
  24. package/dist/render/environmentMixing.js +40 -0
  25. package/dist/render/fog.d.ts +43 -1
  26. package/dist/render/fog.js +87 -22
  27. package/dist/render/globalMedium.d.ts +112 -10
  28. package/dist/render/globalMedium.js +96 -1
  29. package/dist/render/globalMediumPass.d.ts +7 -3
  30. package/dist/render/globalMediumPass.js +22 -1
  31. package/dist/render/hairLobes.d.ts +5 -3
  32. package/dist/render/hairLobes.js +11 -7
  33. package/dist/render/lightBudget.d.ts +12 -0
  34. package/dist/render/lightBudget.js +24 -0
  35. package/dist/render/sceneTarget.d.ts +5 -3
  36. package/dist/render/sceneTarget.js +14 -8
  37. package/dist/render/shaders/arcane.d.ts +1 -1
  38. package/dist/render/shaders/bolt.d.ts +1 -1
  39. package/dist/render/shaders/caustics.d.ts +1 -1
  40. package/dist/render/shaders/film.d.ts +1 -1
  41. package/dist/render/shaders/film.js +4 -1
  42. package/dist/render/shaders/fire.d.ts +1 -1
  43. package/dist/render/shaders/flat/anisotropic.d.ts +8 -1
  44. package/dist/render/shaders/flat/anisotropic.js +25 -3
  45. package/dist/render/shaders/flat/hair.js +31 -7
  46. package/dist/render/shaders/flat/main.d.ts +1 -1
  47. package/dist/render/shaders/flat/main.js +20 -1
  48. package/dist/render/shaders/flat/models.js +1 -1
  49. package/dist/render/shaders/flat/preamble.js +7 -0
  50. package/dist/render/shaders/fog.d.ts +4 -4
  51. package/dist/render/shaders/fog.js +81 -32
  52. package/dist/render/shaders/generated/arcane.wgsl.d.ts +29 -39
  53. package/dist/render/shaders/generated/arcane.wgsl.js +29 -39
  54. package/dist/render/shaders/generated/bolt.wgsl.d.ts +26 -36
  55. package/dist/render/shaders/generated/bolt.wgsl.js +26 -36
  56. package/dist/render/shaders/generated/caustics.wgsl.d.ts +25 -35
  57. package/dist/render/shaders/generated/caustics.wgsl.js +25 -35
  58. package/dist/render/shaders/generated/film.wgsl.d.ts +28 -38
  59. package/dist/render/shaders/generated/film.wgsl.js +28 -38
  60. package/dist/render/shaders/generated/fire.wgsl.d.ts +28 -38
  61. package/dist/render/shaders/generated/fire.wgsl.js +28 -38
  62. package/dist/render/shaders/generated/flat.wgsl.d.ts +1466 -1546
  63. package/dist/render/shaders/generated/flat.wgsl.js +1489 -1569
  64. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +98 -12
  65. package/dist/render/shaders/generated/globalMedium.wgsl.js +98 -12
  66. package/dist/render/shaders/generated/line.wgsl.d.ts +25 -35
  67. package/dist/render/shaders/generated/line.wgsl.js +25 -35
  68. package/dist/render/shaders/generated/particle.wgsl.d.ts +100 -140
  69. package/dist/render/shaders/generated/particle.wgsl.js +100 -140
  70. package/dist/render/shaders/generated/rush.wgsl.d.ts +20 -10
  71. package/dist/render/shaders/generated/rush.wgsl.js +20 -10
  72. package/dist/render/shaders/generated/scatter.wgsl.d.ts +25 -35
  73. package/dist/render/shaders/generated/scatter.wgsl.js +25 -35
  74. package/dist/render/shaders/generated/smoke.wgsl.d.ts +28 -38
  75. package/dist/render/shaders/generated/smoke.wgsl.js +28 -38
  76. package/dist/render/shaders/generated/water.wgsl.d.ts +29 -39
  77. package/dist/render/shaders/generated/water.wgsl.js +29 -39
  78. package/dist/render/shaders/globalMedium.js +114 -10
  79. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +2 -1
  80. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +10 -4
  81. package/dist/render/shaders/gpudriven/fog.wgsl.js +77 -19
  82. package/dist/render/shaders/gpudriven/frameBlock.wgsl.d.ts +1 -1
  83. package/dist/render/shaders/gpudriven/frameBlock.wgsl.js +12 -1
  84. package/dist/render/shaders/gpudriven/shade.wgsl.js +3 -1
  85. package/dist/render/shaders/line.d.ts +1 -1
  86. package/dist/render/shaders/line.js +4 -2
  87. package/dist/render/shaders/localExposure.d.ts +42 -1
  88. package/dist/render/shaders/localExposure.js +73 -6
  89. package/dist/render/shaders/particle.d.ts +3 -3
  90. package/dist/render/shaders/particle.js +10 -6
  91. package/dist/render/shaders/scatter.d.ts +1 -1
  92. package/dist/render/shaders/scatter.js +4 -2
  93. package/dist/render/shaders/smoke.d.ts +1 -1
  94. package/dist/render/shaders/water.d.ts +1 -1
  95. package/dist/render/shaders/water.js +2 -1
  96. package/dist/render/surfaceModel.d.ts +23 -4
  97. package/dist/render/surfaceModel.js +12 -2
  98. package/package.json +6 -6
  99. package/src/index.ts +12 -2
  100. package/src/render/anisotropicLobe.ts +20 -0
  101. package/src/render/atmosphere.ts +153 -20
  102. package/src/render/backend/webgl2/renderer.ts +63 -13
  103. package/src/render/backend/webgpu/gpuDrivenPass.ts +20 -3
  104. package/src/render/backend/webgpu/mediumFields.ts +87 -0
  105. package/src/render/backend/webgpu/renderer.ts +109 -157
  106. package/src/render/environmentMixing.ts +48 -0
  107. package/src/render/fog.ts +125 -25
  108. package/src/render/globalMedium.ts +214 -11
  109. package/src/render/globalMediumPass.ts +29 -4
  110. package/src/render/hairLobes.ts +35 -4
  111. package/src/render/lightBudget.ts +32 -0
  112. package/src/render/sceneTarget.ts +15 -11
  113. package/src/render/shaders/film.ts +4 -1
  114. package/src/render/shaders/flat/anisotropic.ts +25 -3
  115. package/src/render/shaders/flat/hair.ts +31 -7
  116. package/src/render/shaders/flat/main.ts +20 -1
  117. package/src/render/shaders/flat/models.ts +1 -1
  118. package/src/render/shaders/flat/preamble.ts +7 -0
  119. package/src/render/shaders/fog.ts +81 -32
  120. package/src/render/shaders/generated/arcane.wgsl.ts +29 -39
  121. package/src/render/shaders/generated/bolt.wgsl.ts +26 -36
  122. package/src/render/shaders/generated/caustics.wgsl.ts +25 -35
  123. package/src/render/shaders/generated/film.wgsl.ts +28 -38
  124. package/src/render/shaders/generated/fire.wgsl.ts +28 -38
  125. package/src/render/shaders/generated/flat.wgsl.ts +1489 -1569
  126. package/src/render/shaders/generated/globalMedium.wgsl.ts +98 -12
  127. package/src/render/shaders/generated/line.wgsl.ts +25 -35
  128. package/src/render/shaders/generated/particle.wgsl.ts +100 -140
  129. package/src/render/shaders/generated/rush.wgsl.ts +20 -10
  130. package/src/render/shaders/generated/scatter.wgsl.ts +25 -35
  131. package/src/render/shaders/generated/smoke.wgsl.ts +28 -38
  132. package/src/render/shaders/generated/water.wgsl.ts +29 -39
  133. package/src/render/shaders/globalMedium.ts +114 -10
  134. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +2 -1
  135. package/src/render/shaders/gpudriven/fog.wgsl.ts +78 -19
  136. package/src/render/shaders/gpudriven/frameBlock.wgsl.ts +12 -1
  137. package/src/render/shaders/gpudriven/shade.wgsl.ts +3 -1
  138. package/src/render/shaders/line.ts +4 -2
  139. package/src/render/shaders/localExposure.ts +115 -6
  140. package/src/render/shaders/particle.ts +10 -6
  141. package/src/render/shaders/scatter.ts +4 -2
  142. package/src/render/shaders/water.ts +2 -1
  143. package/src/render/surfaceModel.ts +41 -6
@@ -0,0 +1,74 @@
1
+ /** A field's byte offset in the block, or -1 where the shader compiled it out. */
2
+ function placed(fields, name) {
3
+ return fields[name]?.offset ?? -1;
4
+ }
5
+ /** Into a block staged as floats, indexed in four-byte words. */
6
+ export function writeMediumFields(fields, f, m) {
7
+ let at = placed(fields, 'uFogColor');
8
+ if (at >= 0)
9
+ f.set(m.fogColor, at / 4);
10
+ at = placed(fields, 'uFogMedium');
11
+ if (at >= 0) {
12
+ f[at / 4] = m.fogDensity;
13
+ f[at / 4 + 1] = m.fogHeightFalloff;
14
+ f[at / 4 + 2] = m.fogEyeY;
15
+ f[at / 4 + 3] = m.underwaterFogDensity;
16
+ }
17
+ at = placed(fields, 'uFogRange');
18
+ if (at >= 0) {
19
+ f[at / 4] = m.fogNear;
20
+ f[at / 4 + 1] = m.fogFar;
21
+ f[at / 4 + 2] = m.fogMode;
22
+ f[at / 4 + 3] = 0;
23
+ }
24
+ at = placed(fields, 'uUnderwaterColor');
25
+ if (at >= 0)
26
+ f.set(m.underwaterColor, at / 4);
27
+ at = placed(fields, 'uUnderwaterFactor');
28
+ if (at >= 0)
29
+ f[at / 4] = m.underwaterFactor;
30
+ at = placed(fields, 'uFogLayer2');
31
+ if (at >= 0)
32
+ f.set(m.fogLayer2, at / 4);
33
+ at = placed(fields, 'uFogGlow');
34
+ if (at >= 0)
35
+ f.set(m.fogGlow, at / 4);
36
+ at = placed(fields, 'uFogGlowDir');
37
+ if (at >= 0)
38
+ f.set(m.fogGlowDir, at / 4);
39
+ }
40
+ /** Into one slot of a ring, at the fields' byte offsets. */
41
+ export function writeMediumSlot(ring, slot, fields, m) {
42
+ let at = placed(fields, 'uFogColor');
43
+ if (at >= 0)
44
+ ring.writeFloats(slot, at, m.fogColor);
45
+ at = placed(fields, 'uFogMedium');
46
+ if (at >= 0) {
47
+ ring.writeFloat(slot, at, m.fogDensity);
48
+ ring.writeFloat(slot, at + 4, m.fogHeightFalloff);
49
+ ring.writeFloat(slot, at + 8, m.fogEyeY);
50
+ ring.writeFloat(slot, at + 12, m.underwaterFogDensity);
51
+ }
52
+ at = placed(fields, 'uFogRange');
53
+ if (at >= 0) {
54
+ ring.writeFloat(slot, at, m.fogNear);
55
+ ring.writeFloat(slot, at + 4, m.fogFar);
56
+ ring.writeFloat(slot, at + 8, m.fogMode);
57
+ ring.writeFloat(slot, at + 12, 0);
58
+ }
59
+ at = placed(fields, 'uUnderwaterColor');
60
+ if (at >= 0)
61
+ ring.writeFloats(slot, at, m.underwaterColor);
62
+ at = placed(fields, 'uUnderwaterFactor');
63
+ if (at >= 0)
64
+ ring.writeFloat(slot, at, m.underwaterFactor);
65
+ at = placed(fields, 'uFogLayer2');
66
+ if (at >= 0)
67
+ ring.writeFloats(slot, at, m.fogLayer2);
68
+ at = placed(fields, 'uFogGlow');
69
+ if (at >= 0)
70
+ ring.writeFloats(slot, at, m.fogGlow);
71
+ at = placed(fields, 'uFogGlowDir');
72
+ if (at >= 0)
73
+ ring.writeFloats(slot, at, m.fogGlowDir);
74
+ }
@@ -15,9 +15,9 @@ export declare const PLUME_VERT_SIZE: 112;
15
15
  export declare const PLUME_FRAG_FIELDS: UniformFields;
16
16
  export declare const PLUME_FRAG_SIZE: number;
17
17
  /** Each material's own block, so a change to one and not the others is caught rather than assumed. */
18
- export declare const FIRE_FRAG_SIZE: 128;
19
- export declare const SMOKE_FRAG_SIZE: 128;
20
- export declare const ARCANE_FRAG_SIZE: 144;
18
+ export declare const FIRE_FRAG_SIZE: 176;
19
+ export declare const SMOKE_FRAG_SIZE: 176;
20
+ export declare const ARCANE_FRAG_SIZE: 192;
21
21
  export declare function plumeVertexLayouts(): GPUVertexBufferLayout[];
22
22
  /** Plume geometry on the device, behind the surface's opaque handle. */
23
23
  export interface GpuPlumes {
@@ -26,7 +26,7 @@ import type { PipelineCache } from './pipelineCache.ts';
26
26
  * float target the generated shaders can sample as an ordinary texture.
27
27
  */
28
28
  export declare const RUSH_FRAG_FIELDS: UniformFields;
29
- export declare const RUSH_FRAG_SIZE: 288;
29
+ export declare const RUSH_FRAG_SIZE: 320;
30
30
  declare const RUSH_UNIFORMS: 1;
31
31
  declare const RUSH_TEXTURES: {
32
32
  readonly uScene: {
@@ -75,7 +75,7 @@ export declare const AO_FRAG_SIZE: 96;
75
75
  export declare const AO_BLUR_FIELDS: UniformFields;
76
76
  export declare const AO_BLUR_SIZE: 48;
77
77
  export declare const MEDIUM_FRAG_FIELDS: UniformFields;
78
- export declare const MEDIUM_FRAG_SIZE: 304;
78
+ export declare const MEDIUM_FRAG_SIZE: 2400;
79
79
  export declare const MEDIUM_UPSAMPLE_FIELDS: UniformFields;
80
80
  export declare const MEDIUM_UPSAMPLE_SIZE: 48;
81
81
  export declare const BLOOM_PREFILTER_FIELDS: UniformFields;
@@ -369,6 +369,11 @@ export declare const MEDIUM_TEXTURES: {
369
369
  readonly sampler: 35;
370
370
  readonly type: "sampler2DArray";
371
371
  };
372
+ readonly uPointShadows: {
373
+ readonly texture: 36;
374
+ readonly sampler: 37;
375
+ readonly type: "sampler2DArray";
376
+ };
372
377
  };
373
378
  export declare const MEDIUM_UPSAMPLE_UNIFORMS: 1;
374
379
  export declare const MEDIUM_UPSAMPLE_TEXTURES: {
@@ -35,6 +35,7 @@ import type { SurfaceMaterial, SurfaceTextureOptions } from '../../surfaceTextur
35
35
  import type { RenderBackend } from '../api.ts';
36
36
  import { type DisplayRange } from '../../displayRange.ts';
37
37
  import { type FilmicCurve } from '../../filmicCurve.ts';
38
+ import type { LocalExposureOptions } from '../../shaders/localExposure.ts';
38
39
  import { type ColourGradeLut } from '../../colourGrade.ts';
39
40
  import type { CausticSheet } from '../../causticsRenderer.ts';
40
41
  import { type GpuCaustics } from './causticsPass.ts';
@@ -45,6 +46,7 @@ import type { DeviceParticles, ParticleInstances } from '../../particlePool.ts';
45
46
  import type { PlumeOptions, PlumePlacement } from '../../plumeRenderer.ts';
46
47
  import type { InstanceData } from '../../instancedMesh.ts';
47
48
  import { type RenderQuality } from '../../renderQuality.ts';
49
+ import type { GlobalMediumSettings } from '../../globalMedium.ts';
48
50
  import type { MeshData } from '../../mesh.ts';
49
51
  import type { ShadowCasters } from '../../shadowCasters.ts';
50
52
  import { type GpuMesh } from './buffers.ts';
@@ -724,6 +726,9 @@ export declare class WebGPURenderer implements RendererApi {
724
726
  private readonly mediumInvProjection;
725
727
  /** This frame's medium. Off until a caller says otherwise. See `setGlobalMedium`. */
726
728
  private mediumOptions;
729
+ /** The lamps the medium is lit by, and the lit stage's lamps they come from, refilled a frame. */
730
+ private readonly mediumLamps;
731
+ private readonly mediumLights;
727
732
  /**
728
733
  * The temporal resolve: its block, its ping-pong pair, and the jitter state.
729
734
  *
@@ -1412,6 +1417,10 @@ export declare class WebGPURenderer implements RendererApi {
1412
1417
  setSurfaceReflectivity(amount: number): void;
1413
1418
  /** How bright the environment the surfaces drawn next reflect is. See `renderer.ts`. */
1414
1419
  setEnvironmentGain(gain: number): void;
1420
+ /** The environment brought to the light where a surface stands. See `renderer.ts`. */
1421
+ setEnvironmentMixing(weight: number, roughnessLow?: number, roughnessHigh?: number): void;
1422
+ /** `uEnvironmentMixing` as it stands; none until a draw asks. See `setEnvironmentMixing`. */
1423
+ private readonly environmentMixing;
1415
1424
  /**
1416
1425
  * The reflectivity and environment gain the setters gave the pass, and the ones the material
1417
1426
  * set now states, or null where it states none (`SurfaceMaterial.reflectivity`). A draw wears the
@@ -3980,8 +3989,9 @@ export declare class WebGPURenderer implements RendererApi {
3980
3989
  /** Eye adaptation: how far, this frame's time step, and the pass once asked for. */
3981
3990
  private autoExposure;
3982
3991
  private autoExposureDt;
3983
- /** How far each region is brought toward the frame. See `setLocalExposure`. */
3992
+ /** Whether regions move, and how, as `resolveLocalExposure` packs it. See `setLocalExposure`. */
3984
3993
  private localExposure;
3994
+ private readonly localExposureShape;
3985
3995
  private exposurePass;
3986
3996
  /**
3987
3997
  * How strongly the lens darkens the frame's corners, held until changed: 0 is none, 0.5 is about
@@ -4039,7 +4049,7 @@ export declare class WebGPURenderer implements RendererApi {
4039
4049
  * version is that `density` is extinction per metre, 0 is a real off that allocates nothing and
4040
4050
  * draws nothing, and the three optional arguments are held until changed.
4041
4051
  */
4042
- setGlobalMedium(density: number, albedo?: number, anisotropy?: number, maxDistance?: number): void;
4052
+ setGlobalMedium(density: number | GlobalMediumSettings, albedo?: number | Vec3, anisotropy?: number, maxDistance?: number): void;
4043
4053
  /** How much speed blur the frame should resolve with, 0 to 1. */
4044
4054
  setSpeedRush(strength: number): void;
4045
4055
  /**
@@ -4083,7 +4093,7 @@ export declare class WebGPURenderer implements RendererApi {
4083
4093
  * the same setter over the same state, sharing eye adaptation's pass, which is built the first
4084
4094
  * time either is asked for.
4085
4095
  */
4086
- setLocalExposure(strength: number): void;
4096
+ setLocalExposure(setting: number | LocalExposureOptions): void;
4087
4097
  /** The exposure pass, built once; the composite's group holds placeholders until it exists. */
4088
4098
  private ensureExposurePass;
4089
4099
  /**
@@ -24,7 +24,9 @@ import { Camera } from '../../camera.js';
24
24
  import { PickableSet } from '../../pickable.js';
25
25
  import { GpuTimestamps } from './gpuTimestamps.js';
26
26
  import { isWeakGpuFamily } from '../../gpuCapability.js';
27
- import { resolveAtmosphere } from '../../atmosphere.js';
27
+ import { writeMediumFields, writeMediumSlot } from './mediumFields.js';
28
+ import { noEnvironmentMixing, resolveEnvironmentMixing } from '../../environmentMixing.js';
29
+ import { createResolvedAtmosphere, resolveAtmosphere, } from '../../atmosphere.js';
28
30
  import { AMBIENT_SH_FLOATS, packAmbientSH } from '../../ambientHarmonics.js';
29
31
  import { lightmapTexels, warnRegionsWithoutPage, warnUnpagedLightmap, withLightmapUvs, } from '../../lightmap.js';
30
32
  import { BLOOM_FILTER_RADIUS_UV, BLOOM_LEVELS, bloomLevelSizes, bloomThresholdOf, bloomProfileWarning, defaultBloomResponse, resolveBloomResponse, } from '../../bloomChain.js';
@@ -88,7 +90,7 @@ import { DEFAULT_FILMIC_CURVE, FILMIC_CLAMPED, FILMIC_CONSTANTS, FILMIC_WITHOUT_
88
90
  import { ExposurePass } from './exposurePass.js';
89
91
  import { AO_STORE } from '../../shaders/ambientOcclusion.js';
90
92
  import { clampAutoExposure } from '../../shaders/exposure.js';
91
- import { clampLocalExposure } from '../../shaders/localExposure.js';
93
+ import { LOCAL_EXPOSURE_FLOATS, resolveLocalExposure } from '../../shaders/localExposure.js';
92
94
  import { createDepthResolveLayout, createPostStageLayout, depthResolvePipeline, postPipeline, postPipelineTargets, sceneColorFormat, AO_BLUR_FIELDS, AO_BLUR_FRAG_WGSL, AO_BLUR_SIZE, AO_BLUR_TEXTURES, AO_BLUR_UNIFORMS, AO_FRAG_FIELDS, AO_FRAG_SIZE, AO_FRAG_WGSL, AO_TEXTURES, AO_UNIFORMS, BLOOM_DOWNSAMPLE_FIELDS, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_PREFILTER_FIELDS, BLOOM_PREFILTER_FRAG_WGSL, BLOOM_STAGE_SIZE, BLOOM_TEXTURES, BLOOM_UNIFORMS, BLOOM_UPSAMPLE_FIELDS, TINTED_ADD_BLEND, BLOOM_UPSAMPLE_FRAG_WGSL, RESOLVED_DEPTH_FORMAT, RUSH_FRAG_FIELDS, RUSH_FRAG_SIZE, RUSH_FRAG_WGSL, RUSH_TEXTURES, RUSH_UNIFORMS, TAA_FRAG_FIELDS, TAA_FRAG_SIZE, TAA_TEXTURES, TAA_UNIFORMS, TEMPORAL_RESOLVE_FRAG_WGSL, MEDIUM_FRAG_FIELDS, MEDIUM_FRAG_SIZE, MEDIUM_FRAG_WGSL, MEDIUM_TEXTURES, MEDIUM_TRANSMIT_BLEND, MEDIUM_UNIFORMS, MEDIUM_UPSAMPLE_FIELDS, MEDIUM_UPSAMPLE_FRAG_WGSL, MEDIUM_UPSAMPLE_SIZE, MEDIUM_UPSAMPLE_TEXTURES, MEDIUM_UPSAMPLE_UNIFORMS, OIT_COMPOSITE_BLEND, OIT_RESOLVE_FRAG_WGSL, OIT_TEXTURES, DECAL_FRAG_FIELDS, DECAL_FRAG_SIZE, DECAL_MULTIPLY_BLEND, DECAL_PROJECT_FRAG_WGSL, DECAL_TEXTURES, DECAL_UNIFORMS, PREMULTIPLIED_OVER_BLEND, FRAME_REFLECTION_BLEND, REFLECTION_OVER_BLEND, SSR_FRAG_FIELDS, SSR_FRAG_SIZE, SSR_MATERIAL_FIELDS, SSR_MATERIAL_RESOLVE_FRAG_WGSL, SSR_MATERIAL_SIZE, SSR_MATERIAL_TEXTURES, SSR_MATERIAL_UNIFORMS, SSR_RESOLVE_FRAG_WGSL, SSR_RESOLVE_TEXTURES, SSR_TEXTURES, SSR_TRACE_FRAG_WGSL, SSR_UNIFORMS, createOitResolveLayout, createSsrResolveLayout, } from './postPass.js';
93
95
  import { GRADE_PLACEHOLDER_SIZE, identityGradeLut, validateGradeLut, } from '../../colourGrade.js';
94
96
  import { FILM_LOOK_WITHOUT_COMPOSITE, clampGrain, clampVignette, grainSeed, } from '../../shaders/filmLook.js';
@@ -102,9 +104,9 @@ import { buildPlumeGeometry } from '../../plumeGeometry.js';
102
104
  import { facingCode } from '../../particleBatch.js';
103
105
  import { PARTICLE_INDICES } from '../../shaders/particle.js';
104
106
  import { createScatterDeform, resolveScatterDeform, } from '../../scatterDeform.js';
105
- import { resolvePointLights } from '../../lightBudget.js';
107
+ import { createResolvedPointLights, resolvePointLights, } from '../../lightBudget.js';
106
108
  import { resolveRenderQuality } from '../../renderQuality.js';
107
- import { DEFAULT_GLOBAL_MEDIUM, mediumActive, mediumTargetSize, WEAK_GPU_MEDIUM_STEPS, resolveGlobalMedium, } from '../../globalMedium.js';
109
+ import { DEFAULT_GLOBAL_MEDIUM, WEAK_GPU_MEDIUM_STEPS, createResolvedMediumLamps, mediumActive, mediumTargetSize, resolveGlobalMedium, resolveGlobalMediumSettings, resolveMediumLamps, } from '../../globalMedium.js';
108
110
  import { castsDepth, glassOf } from '../../shadowCasters.js';
109
111
  import { materialHasMaps, noteMapsWithoutUvs } from '../../mapsWithoutUvs.js';
110
112
  import { VIEW_MODEL_DEPTH_SHARE, viewModelDepthRange } from '../../viewModel.js';
@@ -1532,7 +1534,10 @@ export class WebGPURenderer {
1532
1534
  /** The inverse projection the composite linearises a depth with, built once per frame. */
1533
1535
  mediumInvProjection = mat4.create();
1534
1536
  /** This frame's medium. Off until a caller says otherwise. See `setGlobalMedium`. */
1535
- mediumOptions = DEFAULT_GLOBAL_MEDIUM;
1537
+ mediumOptions = resolveGlobalMedium(DEFAULT_GLOBAL_MEDIUM);
1538
+ /** The lamps the medium is lit by, and the lit stage's lamps they come from, refilled a frame. */
1539
+ mediumLamps = createResolvedMediumLamps();
1540
+ mediumLights = createResolvedPointLights();
1536
1541
  /**
1537
1542
  * The temporal resolve: its block, its ping-pong pair, and the jitter state.
1538
1543
  *
@@ -2194,16 +2199,7 @@ export class WebGPURenderer {
2194
2199
  f[atF('uFilmRoughness')] = Math.min(1, Math.max(0, options.roughness ?? 0));
2195
2200
  f[atF('uFilmRoughnessCycles')] = Math.max(0.01, options.roughnessCyclesPerMetre ?? 60);
2196
2201
  const medium = resolveAtmosphere(env, this.atmosphereHeight(camera), this.quality.underwaterAtmosphere, this.medium);
2197
- f.set(medium.fogColor, atF('uFogColor'));
2198
- f[atF('uFogDensity')] = medium.fogDensity;
2199
- f[atF('uFogHeightFalloff')] = medium.fogHeightFalloff;
2200
- f[atF('uFogEyeY')] = medium.fogEyeY;
2201
- f.set(medium.underwaterColor, atF('uUnderwaterColor'));
2202
- f[atF('uUnderwaterFogDensity')] = medium.underwaterFogDensity;
2203
- f[atF('uUnderwaterFactor')] = medium.underwaterFactor;
2204
- i[atF('uFogMode')] = medium.fogMode;
2205
- f[atF('uFogNear')] = medium.fogNear;
2206
- f[atF('uFogFar')] = medium.fogFar;
2202
+ writeMediumFields(FILM_FRAG_FIELDS, f, medium);
2207
2203
  this.filmFrags.writeBlock(fragmentSlot, this.filmFragInts);
2208
2204
  const filmPipe = filmPipeline(this.blendedPipelines(), this.surface.device, this.filmLayout, `film|${geometry.key ?? ''}`, this.presentOf(geometry));
2209
2205
  /* The mirror is an attachment right now, so the group that does not hold it. */
@@ -2931,6 +2927,15 @@ export class WebGPURenderer {
2931
2927
  this.passEnvironmentGain = Math.max(0, gain);
2932
2928
  this.writeEnvironmentDials();
2933
2929
  }
2930
+ /** The environment brought to the light where a surface stands. See `renderer.ts`. */
2931
+ setEnvironmentMixing(weight, roughnessLow = 0.1, roughnessHigh = 0.3) {
2932
+ resolveEnvironmentMixing(weight, roughnessLow, roughnessHigh, this.environmentMixing);
2933
+ this.material((f) => {
2934
+ f.set(this.environmentMixing, this.materialField('uEnvironmentMixing'));
2935
+ });
2936
+ }
2937
+ /** `uEnvironmentMixing` as it stands; none until a draw asks. See `setEnvironmentMixing`. */
2938
+ environmentMixing = noEnvironmentMixing(new Float32Array(4));
2934
2939
  /**
2935
2940
  * The reflectivity and environment gain the setters gave the pass, and the ones the material
2936
2941
  * set now states, or null where it states none (`SurfaceMaterial.reflectivity`). A draw wears the
@@ -3716,16 +3721,7 @@ export class WebGPURenderer {
3716
3721
  scatterInto(f, fields['uLightWeight'], lights.weights, 1);
3717
3722
  }
3718
3723
  const medium = resolveAtmosphere(env, camera.position[1] ?? 0, this.quality.underwaterAtmosphere, this.medium);
3719
- f.set(medium.fogColor, atF('uFogColor'));
3720
- f[atF('uFogDensity')] = medium.fogDensity;
3721
- f[atF('uFogHeightFalloff')] = medium.fogHeightFalloff;
3722
- f[atF('uFogEyeY')] = medium.fogEyeY;
3723
- f.set(medium.underwaterColor, atF('uUnderwaterColor'));
3724
- f[atF('uUnderwaterFogDensity')] = medium.underwaterFogDensity;
3725
- f[atF('uUnderwaterFactor')] = medium.underwaterFactor;
3726
- i[atF('uFogMode')] = medium.fogMode;
3727
- f[atF('uFogNear')] = medium.fogNear;
3728
- f[atF('uFogFar')] = medium.fogFar;
3724
+ writeMediumFields(fields, f, medium);
3729
3725
  /* Only the mote fragment declares this — spark and smoke stay unconditionally fogged and
3730
3726
  have no such field. See ParticleBatchOptions.fog. */
3731
3727
  if (has('uFogEnabled'))
@@ -3929,16 +3925,7 @@ export class WebGPURenderer {
3929
3925
  frags.writeInt(fragmentSlot, atF('uClipEnabled'), this.reflectionPassActive ? 1 : 0);
3930
3926
  frags.writeFloats(fragmentSlot, atF('uClipPlane'), this.reflection?.clipPlane ?? NO_CLIP_PLANE);
3931
3927
  const medium = resolveAtmosphere(env, this.atmosphereHeight(camera), this.quality.underwaterAtmosphere, this.medium);
3932
- frags.writeFloats(fragmentSlot, atF('uFogColor'), medium.fogColor);
3933
- frags.writeFloat(fragmentSlot, atF('uFogDensity'), medium.fogDensity);
3934
- frags.writeFloat(fragmentSlot, atF('uFogHeightFalloff'), medium.fogHeightFalloff);
3935
- frags.writeFloat(fragmentSlot, atF('uFogEyeY'), medium.fogEyeY);
3936
- frags.writeFloats(fragmentSlot, atF('uUnderwaterColor'), medium.underwaterColor);
3937
- frags.writeFloat(fragmentSlot, atF('uUnderwaterFogDensity'), medium.underwaterFogDensity);
3938
- frags.writeFloat(fragmentSlot, atF('uUnderwaterFactor'), medium.underwaterFactor);
3939
- frags.writeInt(fragmentSlot, atF('uFogMode'), medium.fogMode);
3940
- frags.writeFloat(fragmentSlot, atF('uFogNear'), medium.fogNear);
3941
- frags.writeFloat(fragmentSlot, atF('uFogFar'), medium.fogFar);
3928
+ writeMediumSlot(frags, fragmentSlot, PLUME_FRAG_FIELDS, medium);
3942
3929
  frags.writeInt(fragmentSlot, atF('uOutputTransform'), this.passGrade());
3943
3930
  frags.writeFloat(fragmentSlot, atF('uOutputExposure'), this.passGradeExposure());
3944
3931
  /*
@@ -4079,16 +4066,7 @@ export class WebGPURenderer {
4079
4066
  scatterInto(f, WATER_FRAG_FIELDS['uLightWeight'], lights.weights, 1);
4080
4067
  scatterInto(f, WATER_FRAG_FIELDS['uLightCone'], lights.conesAndFalloffs, 4);
4081
4068
  const medium = resolveAtmosphere(env, camera.position[1] ?? 0, this.quality.underwaterAtmosphere, this.medium);
4082
- f.set(medium.fogColor, atF('uFogColor'));
4083
- f[atF('uFogDensity')] = medium.fogDensity;
4084
- f[atF('uFogHeightFalloff')] = medium.fogHeightFalloff;
4085
- f[atF('uFogEyeY')] = medium.fogEyeY;
4086
- f.set(medium.underwaterColor, atF('uUnderwaterColor'));
4087
- f[atF('uUnderwaterFogDensity')] = medium.underwaterFogDensity;
4088
- f[atF('uUnderwaterFactor')] = medium.underwaterFactor;
4089
- i[atF('uFogMode')] = medium.fogMode;
4090
- f[atF('uFogNear')] = medium.fogNear;
4091
- f[atF('uFogFar')] = medium.fogFar;
4069
+ writeMediumFields(WATER_FRAG_FIELDS, f, medium);
4092
4070
  /* No reflection pass yet: the shader's own "off" path, not an improvised one. */
4093
4071
  i[atF('uReflectionEnabled')] = mirror === null ? 0 : 1;
4094
4072
  i[atF('uReflectionFilterTaps')] = water.reflectionFilterTaps;
@@ -4587,16 +4565,7 @@ export class WebGPURenderer {
4587
4565
  f.set(camera.position, atF('uCameraPos'));
4588
4566
  /* The medium, through the same resolver every other pass here binds from. */
4589
4567
  const medium = resolveAtmosphere(env, camera.position[1] ?? 0, this.quality.underwaterAtmosphere, this.medium);
4590
- f.set(medium.fogColor, atF('uFogColor'));
4591
- f[atF('uFogDensity')] = medium.fogDensity;
4592
- f[atF('uFogHeightFalloff')] = medium.fogHeightFalloff;
4593
- f[atF('uFogEyeY')] = medium.fogEyeY;
4594
- f.set(medium.underwaterColor, atF('uUnderwaterColor'));
4595
- f[atF('uUnderwaterFogDensity')] = medium.underwaterFogDensity;
4596
- f[atF('uUnderwaterFactor')] = medium.underwaterFactor;
4597
- i[atF('uFogMode')] = medium.fogMode;
4598
- f[atF('uFogNear')] = medium.fogNear;
4599
- f[atF('uFogFar')] = medium.fogFar;
4568
+ writeMediumFields(BOLT_FRAG_FIELDS, f, medium);
4600
4569
  this.boltVerts.writeBlock(vertexSlot, this.boltVertBlock);
4601
4570
  this.boltFrags.writeBlock(fragmentSlot, this.boltFragInts);
4602
4571
  const boltPipe = boltPipeline(this.blendedPipelines(), this.surface.device, this.boltLayout);
@@ -4699,16 +4668,7 @@ export class WebGPURenderer {
4699
4668
  this.lineFrags.writeFloat(fragmentSlot, atF('uOutputExposure'), this.passGradeExposure());
4700
4669
  /* The medium, through the same resolver every other pass here binds from. */
4701
4670
  const medium = resolveAtmosphere(env, camera.position[1] ?? 0, this.quality.underwaterAtmosphere, this.medium);
4702
- this.lineFrags.writeFloats(fragmentSlot, atF('uFogColor'), medium.fogColor);
4703
- this.lineFrags.writeFloat(fragmentSlot, atF('uFogDensity'), medium.fogDensity);
4704
- this.lineFrags.writeFloat(fragmentSlot, atF('uFogHeightFalloff'), medium.fogHeightFalloff);
4705
- this.lineFrags.writeFloat(fragmentSlot, atF('uFogEyeY'), medium.fogEyeY);
4706
- this.lineFrags.writeFloats(fragmentSlot, atF('uUnderwaterColor'), medium.underwaterColor);
4707
- this.lineFrags.writeFloat(fragmentSlot, atF('uUnderwaterFogDensity'), medium.underwaterFogDensity);
4708
- this.lineFrags.writeFloat(fragmentSlot, atF('uUnderwaterFactor'), medium.underwaterFactor);
4709
- this.lineFrags.writeInt(fragmentSlot, atF('uFogMode'), medium.fogMode);
4710
- this.lineFrags.writeFloat(fragmentSlot, atF('uFogNear'), medium.fogNear);
4711
- this.lineFrags.writeFloat(fragmentSlot, atF('uFogFar'), medium.fogFar);
4671
+ writeMediumSlot(this.lineFrags, fragmentSlot, LINE_FRAG_FIELDS, medium);
4712
4672
  const linePipe = linePipeline(this.blendedPipelines(), this.surface.device, this.lineLayout, additive);
4713
4673
  /* Two uniform blocks and three vertex buffers, and no attachment among them. */
4714
4674
  const lineCommand = this.recordBlended(0);
@@ -4855,39 +4815,11 @@ export class WebGPURenderer {
4855
4815
  this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
4856
4816
  }
4857
4817
  /** Refilled per frame rather than allocated; see `resolvePointLights`. */
4858
- lights = {
4859
- count: 0,
4860
- falloff: 0,
4861
- positions: new Float32Array(0),
4862
- colors: new Float32Array(0),
4863
- radii: new Float32Array(0),
4864
- sourceRadii: new Float32Array(0),
4865
- weights: new Float32Array(0),
4866
- directions: new Float32Array(0),
4867
- coneCos: new Float32Array(0),
4868
- iesProfiles: new Float32Array(0),
4869
- iesAxes: new Float32Array(0),
4870
- cookies: new Float32Array(0),
4871
- heldSourceRadii: new Float32Array(MAX_POINT_LIGHTS),
4872
- axesAndChannels: new Float32Array(MAX_POINT_LIGHTS * 4),
4873
- conesAndFalloffs: new Float32Array(MAX_POINT_LIGHTS * 4),
4874
- heldWeights: new Float32Array(MAX_POINT_LIGHTS),
4875
- };
4818
+ lights = createResolvedPointLights();
4876
4819
  /** The fixed arm's rectangles as `uAreaLightSize` holds them. See `packAreaSizesAndDoors`. */
4877
4820
  areaSizesAndDoors = new Float32Array(MAX_AREA_LIGHTS * 4);
4878
4821
  /** Refilled per frame rather than allocated; see `resolveAtmosphere`. */
4879
- medium = {
4880
- fogColor: new Float32Array(3),
4881
- underwaterColor: new Float32Array(3),
4882
- fogDensity: 0,
4883
- fogHeightFalloff: 0,
4884
- fogEyeY: 0,
4885
- fogMode: 0,
4886
- fogNear: 0,
4887
- fogFar: 1,
4888
- underwaterFogDensity: 0,
4889
- underwaterFactor: 0,
4890
- };
4822
+ medium = createResolvedAtmosphere();
4891
4823
  /* -- Overlays: panels and text ------------------------------------------------------ */
4892
4824
  panelLayout;
4893
4825
  panelCorners;
@@ -6313,6 +6245,7 @@ export class WebGPURenderer {
6313
6245
  this.mediumLayout = createPostStageLayout(device, MEDIUM_UNIFORMS, MEDIUM_FRAG_SIZE, [
6314
6246
  { binding: MEDIUM_TEXTURES.uDepth, filterable: false },
6315
6247
  { binding: MEDIUM_TEXTURES.uSunShadows, filterable: false, dimension: '2d-array' },
6248
+ { binding: MEDIUM_TEXTURES.uPointShadows, filterable: false, dimension: '2d-array' },
6316
6249
  ]);
6317
6250
  this.mediumUniforms = device.createBuffer({
6318
6251
  label: 'post.mediumUniforms',
@@ -7365,16 +7298,7 @@ export class WebGPURenderer {
7365
7298
  * Found by a voxel scene that is the first in the tree to use `fogMode: 'linear'` on WebGPU.
7366
7299
  */
7367
7300
  const meshMedium = resolveAtmosphere(env, this.atmosphereHeight(camera), this.quality.underwaterAtmosphere, this.medium);
7368
- f.set(meshMedium.fogColor, at('uFogColor'));
7369
- f[at('uFogDensity')] = meshMedium.fogDensity;
7370
- f[at('uFogHeightFalloff')] = meshMedium.fogHeightFalloff;
7371
- f[at('uFogEyeY')] = meshMedium.fogEyeY;
7372
- i[at('uFogMode')] = meshMedium.fogMode;
7373
- f[at('uFogNear')] = meshMedium.fogNear;
7374
- f[at('uFogFar')] = meshMedium.fogFar;
7375
- f.set(meshMedium.underwaterColor, at('uUnderwaterColor'));
7376
- f[at('uUnderwaterFogDensity')] = meshMedium.underwaterFogDensity;
7377
- f[at('uUnderwaterFactor')] = meshMedium.underwaterFactor;
7301
+ writeMediumFields(this.fragment.fields, f, meshMedium);
7378
7302
  /*
7379
7303
  * The mirror's half-space, and only while the mirror is being drawn.
7380
7304
  *
@@ -7419,6 +7343,8 @@ export class WebGPURenderer {
7419
7343
  f[at('uWriteMode') + 2] = 0;
7420
7344
  /* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
7421
7345
  f[at('uEnvironmentDials') + 2] = 1;
7346
+ /* And no mixing, which is stored less one. See `setEnvironmentMixing`. */
7347
+ f.set(noEnvironmentMixing(this.environmentMixing), at('uEnvironmentMixing'));
7422
7348
  /* No overlay until a draw sets one. See `setSurfaceOverlay`. */
7423
7349
  this.overlayFloats.fill(0);
7424
7350
  f.fill(0, at('uOverlay'), at('uOverlay') + OVERLAY_FLOATS);
@@ -7662,16 +7588,7 @@ export class WebGPURenderer {
7662
7588
  * drift `bindAtmosphere` was written to prevent.
7663
7589
  */
7664
7590
  const medium = resolveAtmosphere(env, this.atmosphereHeight(camera), this.quality.underwaterAtmosphere, this.medium);
7665
- f.set(medium.fogColor, at('uFogColor'));
7666
- f[at('uFogDensity')] = medium.fogDensity;
7667
- f[at('uFogHeightFalloff')] = medium.fogHeightFalloff;
7668
- f[at('uFogEyeY')] = medium.fogEyeY;
7669
- i[at('uFogMode')] = medium.fogMode;
7670
- f[at('uFogNear')] = medium.fogNear;
7671
- f[at('uFogFar')] = medium.fogFar;
7672
- f.set(medium.underwaterColor, at('uUnderwaterColor'));
7673
- f[at('uUnderwaterFogDensity')] = medium.underwaterFogDensity;
7674
- f[at('uUnderwaterFactor')] = medium.underwaterFactor;
7591
+ writeMediumFields(this.fragment.fields, f, medium);
7675
7592
  /*
7676
7593
  * The shadow terms, matching what `renderer.ts` binds at default quality.
7677
7594
  *
@@ -8976,16 +8893,7 @@ export class WebGPURenderer {
8976
8893
  f.set(env.ambient, atF('uAmbient'));
8977
8894
  f.set(camera.position, atF('uCameraPos'));
8978
8895
  const medium = resolveAtmosphere(env, camera.position[1] ?? 0, this.quality.underwaterAtmosphere, this.medium);
8979
- f.set(medium.fogColor, atF('uFogColor'));
8980
- f[atF('uFogDensity')] = medium.fogDensity;
8981
- f[atF('uFogHeightFalloff')] = medium.fogHeightFalloff;
8982
- f[atF('uFogEyeY')] = medium.fogEyeY;
8983
- f.set(medium.underwaterColor, atF('uUnderwaterColor'));
8984
- f[atF('uUnderwaterFogDensity')] = medium.underwaterFogDensity;
8985
- f[atF('uUnderwaterFactor')] = medium.underwaterFactor;
8986
- i[atF('uFogMode')] = medium.fogMode;
8987
- f[atF('uFogNear')] = medium.fogNear;
8988
- f[atF('uFogFar')] = medium.fogFar;
8896
+ writeMediumFields(SCATTER_FRAG_FIELDS, f, medium);
8989
8897
  i[atF('uOutputTransform')] = this.passGrade();
8990
8898
  f[atF('uOutputExposure')] = this.passGradeExposure();
8991
8899
  const fragmentSlot = this.scatterFrags.allocate();
@@ -9799,6 +9707,8 @@ export class WebGPURenderer {
9799
9707
  * resolve, which is what sampling a stand-in does.
9800
9708
  */
9801
9709
  this.rebuildFlatBindGroup();
9710
+ /* The medium reads the same array: see `buildMediumBindGroup`. */
9711
+ this.mediumBindGroup = this.buildMediumBindGroup();
9802
9712
  }
9803
9713
  this.pointShadows.prepareStaticMaps(lights.length);
9804
9714
  this.preparedPointLightCount = lights.length;
@@ -10041,6 +9951,8 @@ export class WebGPURenderer {
10041
9951
  if (old !== null) {
10042
9952
  this.retiredPointTexture = old;
10043
9953
  this.rebuildFlatBindGroup();
9954
+ /* The medium reads the same array: see `buildMediumBindGroup`. */
9955
+ this.mediumBindGroup = this.buildMediumBindGroup();
10044
9956
  }
10045
9957
  }
10046
9958
  }
@@ -10523,7 +10435,7 @@ export class WebGPURenderer {
10523
10435
  * it. The held brightness is kept for either, since local exposure moves regions relative to it.
10524
10436
  * See `setAutoExposure` and `setLocalExposure`.
10525
10437
  */
10526
- const localising = this.localExposure > 0;
10438
+ const localising = this.localExposure;
10527
10439
  const measuring = (this.autoExposure > 0 || localising) && this.exposurePass !== null;
10528
10440
  const adapting = measuring && this.autoExposure > 0;
10529
10441
  const shownScene = this.reconstructing ? this.reconShownView : this.sceneColorView;
@@ -10596,7 +10508,13 @@ export class WebGPURenderer {
10596
10508
  f[at('uVignette')] = this.vignette;
10597
10509
  f.set(this.fringe, at('uFringe'));
10598
10510
  f[at('uAutoExposure')] = adapting ? this.autoExposure : 0;
10599
- f[at('uLocalExposure')] = measuring && localising ? this.localExposure : 0;
10511
+ f[at('uLocalExposure')] = measuring && localising ? 1 : 0;
10512
+ const shapeAt = at('uLocalShape');
10513
+ const pivotAt = at('uLocalPivot');
10514
+ for (let k = 0; k < 4; k++) {
10515
+ f[shapeAt + k] = this.localExposureShape[k];
10516
+ f[pivotAt + k] = this.localExposureShape[4 + k];
10517
+ }
10600
10518
  f[at('uGrain')] = this.grain;
10601
10519
  f[at('uGrain') + 1] = this.grainSeed;
10602
10520
  device.queue.writeBuffer(this.rushUniforms, 0, this.rushStaging);
@@ -11810,7 +11728,17 @@ export class WebGPURenderer {
11810
11728
  f.set(env?.directionalColor ?? [0, 0, 0], at('uSunColor'));
11811
11729
  f.set(env?.ambient ?? [0, 0, 0], at('uAmbient'));
11812
11730
  f[at('uDensity')] = options.density;
11813
- f[at('uAlbedo')] = options.albedo;
11731
+ const height = at('uHeight');
11732
+ f[height] = options.heightFalloff;
11733
+ f[height + 1] = options.baseY;
11734
+ const layer2 = at('uLayer2');
11735
+ f[layer2] = options.density2;
11736
+ f[layer2 + 1] = options.heightFalloff2;
11737
+ f[layer2 + 2] = options.baseY2;
11738
+ f.set(options.albedo, at('uAlbedo'));
11739
+ const scatter = at('uScatter');
11740
+ f[scatter] = options.sunScatter;
11741
+ f[scatter + 1] = options.skyScatter;
11814
11742
  f[at('uAnisotropy')] = options.anisotropy;
11815
11743
  f[at('uMaxDistance')] = options.maxDistance;
11816
11744
  ints[at('uSteps')] = this.quality.globalMediumSteps;
@@ -11828,6 +11756,20 @@ export class WebGPURenderer {
11828
11756
  f.set(env?.lightViewProj ?? this.mediumIdentity, at('uLightViewProj'));
11829
11757
  f.set((env === null ? this.mediumIdentity : this.movingLightFor(env)), at('uMovingLightViewProj'));
11830
11758
  ints[at('uPeeledShadowEnabled')] = this.peelView === null || !this.peelFilled ? 0 : 1;
11759
+ /* The lit stage's lamps and the shadows it resolved for them, this frame's. */
11760
+ const lamps = resolveMediumLamps(options, resolvePointLights(env, this.quality.pointLightFalloff, this.mediumLights), this.pointShadows === null ? null : this.resolvedPointShadows, env?.lightMediumScatter, this.mediumLamps);
11761
+ ints[at('uLampCount')] = lamps.count;
11762
+ ints[at('uLampFalloff')] = lamps.falloff;
11763
+ if (lamps.count > 0) {
11764
+ f.set(lamps.position, at('uLampPosition'));
11765
+ f.set(lamps.color, at('uLampColor'));
11766
+ f.set(lamps.aim, at('uLampAim'));
11767
+ f.set(lamps.cone, at('uLampCone'));
11768
+ f.set(lamps.shadow, at('uLampShadow'));
11769
+ f.set(lamps.shadowAt, at('uLampShadowAt'));
11770
+ f.set(lamps.live, at('uLampLive'));
11771
+ f.set(lamps.liveAt, at('uLampLiveAt'));
11772
+ }
11831
11773
  device.queue.writeBuffer(this.mediumUniforms, 0, this.mediumStaging);
11832
11774
  const g = this.mediumUpsampleFloats;
11833
11775
  const upsampleAt = (name) => this.postField(MEDIUM_UPSAMPLE_FIELDS, name);
@@ -14236,8 +14178,9 @@ export class WebGPURenderer {
14236
14178
  /** Eye adaptation: how far, this frame's time step, and the pass once asked for. */
14237
14179
  autoExposure = 0;
14238
14180
  autoExposureDt = 0;
14239
- /** How far each region is brought toward the frame. See `setLocalExposure`. */
14240
- localExposure = 0;
14181
+ /** Whether regions move, and how, as `resolveLocalExposure` packs it. See `setLocalExposure`. */
14182
+ localExposure = false;
14183
+ localExposureShape = new Float32Array(LOCAL_EXPOSURE_FLOATS);
14241
14184
  exposurePass = null;
14242
14185
  /**
14243
14186
  * How strongly the lens darkens the frame's corners, held until changed: 0 is none, 0.5 is about
@@ -14387,12 +14330,7 @@ export class WebGPURenderer {
14387
14330
  * draws nothing, and the three optional arguments are held until changed.
14388
14331
  */
14389
14332
  setGlobalMedium(density, albedo, anisotropy, maxDistance) {
14390
- this.mediumOptions = resolveGlobalMedium({
14391
- density: Number.isFinite(density) ? density : 0,
14392
- albedo,
14393
- anisotropy,
14394
- maxDistance,
14395
- });
14333
+ this.mediumOptions = resolveGlobalMediumSettings(density, albedo, anisotropy, maxDistance);
14396
14334
  }
14397
14335
  /** How much speed blur the frame should resolve with, 0 to 1. */
14398
14336
  setSpeedRush(strength) {
@@ -14463,13 +14401,13 @@ export class WebGPURenderer {
14463
14401
  * the same setter over the same state, sharing eye adaptation's pass, which is built the first
14464
14402
  * time either is asked for.
14465
14403
  */
14466
- setLocalExposure(strength) {
14404
+ setLocalExposure(setting) {
14467
14405
  if (!this.hasComposite || !this.quality.hdrScene) {
14468
- this.warnLookWithoutComposite(strength);
14406
+ this.warnLookWithoutComposite(typeof setting === 'number' ? setting : 1);
14469
14407
  return;
14470
14408
  }
14471
- this.localExposure = clampLocalExposure(strength);
14472
- if (this.localExposure > 0)
14409
+ this.localExposure = resolveLocalExposure(setting, this.localExposureShape);
14410
+ if (this.localExposure)
14473
14411
  this.ensureExposurePass();
14474
14412
  }
14475
14413
  /** The exposure pass, built once; the composite's group holds placeholders until it exists. */
@@ -14606,16 +14544,7 @@ export class WebGPURenderer {
14606
14544
  f[atF('uWindDir') + 1] = inv === 0 ? 0 : windZ * inv;
14607
14545
  f[atF('uWaveGain')] = sea.steepness;
14608
14546
  const medium = resolveAtmosphere(env, this.atmosphereHeight(camera), this.quality.underwaterAtmosphere, this.medium);
14609
- f.set(medium.fogColor, atF('uFogColor'));
14610
- f[atF('uFogDensity')] = medium.fogDensity;
14611
- f[atF('uFogHeightFalloff')] = medium.fogHeightFalloff;
14612
- f[atF('uFogEyeY')] = medium.fogEyeY;
14613
- f.set(medium.underwaterColor, atF('uUnderwaterColor'));
14614
- f[atF('uUnderwaterFogDensity')] = medium.underwaterFogDensity;
14615
- f[atF('uUnderwaterFactor')] = medium.underwaterFactor;
14616
- i[atF('uFogMode')] = medium.fogMode;
14617
- f[atF('uFogNear')] = medium.fogNear;
14618
- f[atF('uFogFar')] = medium.fogFar;
14547
+ writeMediumFields(CAUSTICS_FRAG_FIELDS, f, medium);
14619
14548
  this.causticsVerts.writeBlock(vertexSlot, this.causticsVertBlock);
14620
14549
  this.causticsFrags.writeBlock(fragmentSlot, this.causticsFragInts);
14621
14550
  const causticsPipe = causticsPipeline(this.blendedPipelines(), this.surface.device, this.causticsLayout);
@@ -15556,6 +15485,13 @@ export class WebGPURenderer {
15556
15485
  { binding: MEDIUM_TEXTURES.uDepth.sampler, resource: this.postDepthSampler },
15557
15486
  { binding: MEDIUM_TEXTURES.uSunShadows.texture, resource: this.sunArrayView },
15558
15487
  { binding: MEDIUM_TEXTURES.uSunShadows.sampler, resource: this.volumeShadowSampler },
15488
+ /* The lamps' maps, the lit stage's own array, rebuilt here whenever that array is
15489
+ replaced; the white stand-in reads as nothing in the way, and no lamp reads it. */
15490
+ {
15491
+ binding: MEDIUM_TEXTURES.uPointShadows.texture,
15492
+ resource: this.pointShadowArray?.view ?? this.shadowBlankView,
15493
+ },
15494
+ { binding: MEDIUM_TEXTURES.uPointShadows.sampler, resource: this.volumeShadowSampler },
15559
15495
  ],
15560
15496
  });
15561
15497
  }
@@ -21,7 +21,7 @@ export declare const SCATTER_DEPTH_SIZE: 240;
21
21
  export declare const SCATTER_VERT_FIELDS: UniformFields;
22
22
  export declare const SCATTER_VERT_SIZE: 240;
23
23
  export declare const SCATTER_FRAG_FIELDS: UniformFields;
24
- export declare const SCATTER_FRAG_SIZE: 144;
24
+ export declare const SCATTER_FRAG_SIZE: 192;
25
25
  export declare function scatterVertexLayouts(): GPUVertexBufferLayout[];
26
26
  /** Geometry and placement on the device, behind the opaque handle the surface hands out. */
27
27
  export interface GpuScatter {
@@ -11,7 +11,7 @@ import type { UniformFields } from './scatterPass.ts';
11
11
  export declare const WATER_VERT_FIELDS: UniformFields;
12
12
  export declare const WATER_VERT_SIZE: 208;
13
13
  export declare const WATER_FRAG_FIELDS: UniformFields;
14
- export declare const WATER_FRAG_SIZE: 1520;
14
+ export declare const WATER_FRAG_SIZE: 1568;
15
15
  /** The two meshes and the numbers the grid decided, behind the surface's opaque handle. */
16
16
  export interface GpuWater {
17
17
  /** The ocean: near sheet and far skirt in one buffer. */