@driftengine/core 4.8.2 → 4.8.3

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 (121) hide show
  1. package/CHANGELOG.json +56 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +6 -0
  5. package/dist/render/backend/materialChanges.d.ts +7 -2
  6. package/dist/render/backend/materialChanges.js +8 -3
  7. package/dist/render/backend/webgl2/renderer.d.ts +23 -0
  8. package/dist/render/backend/webgl2/renderer.js +58 -17
  9. package/dist/render/backend/webgpu/causticsPass.js +2 -1
  10. package/dist/render/backend/webgpu/depthPass.d.ts +13 -3
  11. package/dist/render/backend/webgpu/depthPass.js +4 -3
  12. package/dist/render/backend/webgpu/filmPass.js +2 -1
  13. package/dist/render/backend/webgpu/flatPass.d.ts +5 -0
  14. package/dist/render/backend/webgpu/flatPass.js +2 -1
  15. package/dist/render/backend/webgpu/linePass.js +2 -1
  16. package/dist/render/backend/webgpu/panelPass.js +2 -1
  17. package/dist/render/backend/webgpu/particlePass.js +2 -1
  18. package/dist/render/backend/webgpu/plumePass.js +2 -1
  19. package/dist/render/backend/webgpu/postPass.d.ts +12 -2
  20. package/dist/render/backend/webgpu/postPass.js +19 -5
  21. package/dist/render/backend/webgpu/renderer.d.ts +10 -1
  22. package/dist/render/backend/webgpu/renderer.js +38 -12
  23. package/dist/render/backend/webgpu/sdfTextPass.js +2 -1
  24. package/dist/render/backend/webgpu/textPass.js +2 -1
  25. package/dist/render/backend/webgpu/waterPass.js +2 -1
  26. package/dist/render/backend/webgpu/windStreakPass.js +2 -1
  27. package/dist/render/boltBatch.js +2 -1
  28. package/dist/render/causticsRenderer.js +2 -1
  29. package/dist/render/cutoutCaster.d.ts +3 -1
  30. package/dist/render/cutoutCaster.js +4 -0
  31. package/dist/render/decalPass.js +2 -1
  32. package/dist/render/globalMediumPass.js +3 -2
  33. package/dist/render/lineBatch.js +5 -1
  34. package/dist/render/oitPass.js +2 -1
  35. package/dist/render/particleBatch.js +3 -2
  36. package/dist/render/particlePool.d.ts +6 -2
  37. package/dist/render/plumeRenderer.js +3 -2
  38. package/dist/render/renderQuality.d.ts +9 -2
  39. package/dist/render/sceneCoverage.d.ts +54 -0
  40. package/dist/render/sceneCoverage.js +72 -0
  41. package/dist/render/sdfTextRenderer.js +2 -1
  42. package/dist/render/shaders/depth.js +3 -2
  43. package/dist/render/shaders/flat/index.js +6 -1
  44. package/dist/render/shaders/generated/depth.wgsl.d.ts +32 -12
  45. package/dist/render/shaders/generated/depth.wgsl.js +32 -12
  46. package/dist/render/shaders/generated/flat.wgsl.d.ts +25 -0
  47. package/dist/render/shaders/generated/flat.wgsl.js +47 -22
  48. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  49. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  50. package/dist/render/shaders/generated/particle.wgsl.d.ts +3 -3
  51. package/dist/render/shaders/generated/particle.wgsl.js +3 -3
  52. package/dist/render/shaders/generated/rush.wgsl.d.ts +1 -1
  53. package/dist/render/shaders/generated/rush.wgsl.js +1 -1
  54. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +1 -1
  55. package/dist/render/shaders/generated/sdfText.wgsl.js +1 -1
  56. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
  57. package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
  58. package/dist/render/shaders/line.d.ts +1 -1
  59. package/dist/render/shaders/outputTransform.d.ts +14 -1
  60. package/dist/render/shaders/outputTransform.js +23 -1
  61. package/dist/render/shaders/particle.d.ts +3 -3
  62. package/dist/render/shaders/recon/resolve.wgsl.js +15 -1
  63. package/dist/render/shaders/rush.js +13 -2
  64. package/dist/render/shaders/sdfText.d.ts +1 -1
  65. package/dist/render/shaders/temporalResolve.js +12 -5
  66. package/dist/render/ssrPass.js +2 -1
  67. package/dist/render/surfaceTexture.d.ts +11 -0
  68. package/dist/render/textRenderer.js +2 -1
  69. package/dist/render/vertexDefaults.js +1 -0
  70. package/dist/render/waterRenderer.js +2 -1
  71. package/dist/render/windStreakRenderer.js +2 -1
  72. package/package.json +6 -6
  73. package/src/index.ts +13 -0
  74. package/src/render/backend/materialChanges.ts +8 -3
  75. package/src/render/backend/webgl2/renderer.ts +57 -16
  76. package/src/render/backend/webgpu/causticsPass.ts +2 -1
  77. package/src/render/backend/webgpu/depthPass.ts +4 -3
  78. package/src/render/backend/webgpu/filmPass.ts +2 -1
  79. package/src/render/backend/webgpu/flatPass.ts +2 -1
  80. package/src/render/backend/webgpu/linePass.ts +2 -1
  81. package/src/render/backend/webgpu/panelPass.ts +2 -1
  82. package/src/render/backend/webgpu/particlePass.ts +2 -1
  83. package/src/render/backend/webgpu/plumePass.ts +2 -1
  84. package/src/render/backend/webgpu/postPass.ts +20 -5
  85. package/src/render/backend/webgpu/renderer.ts +40 -11
  86. package/src/render/backend/webgpu/sdfTextPass.ts +2 -1
  87. package/src/render/backend/webgpu/textPass.ts +2 -1
  88. package/src/render/backend/webgpu/waterPass.ts +2 -1
  89. package/src/render/backend/webgpu/windStreakPass.ts +2 -1
  90. package/src/render/boltBatch.ts +2 -1
  91. package/src/render/causticsRenderer.ts +2 -1
  92. package/src/render/cutoutCaster.ts +15 -2
  93. package/src/render/decalPass.ts +2 -1
  94. package/src/render/globalMediumPass.ts +3 -2
  95. package/src/render/lineBatch.ts +3 -1
  96. package/src/render/oitPass.ts +2 -1
  97. package/src/render/particleBatch.ts +3 -2
  98. package/src/render/particlePool.ts +6 -2
  99. package/src/render/plumeRenderer.ts +3 -2
  100. package/src/render/renderQuality.ts +9 -2
  101. package/src/render/sceneCoverage.ts +78 -0
  102. package/src/render/sdfTextRenderer.ts +2 -1
  103. package/src/render/shaders/depth.ts +3 -2
  104. package/src/render/shaders/flat/index.ts +6 -1
  105. package/src/render/shaders/generated/depth.wgsl.ts +32 -12
  106. package/src/render/shaders/generated/flat.wgsl.ts +47 -22
  107. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  108. package/src/render/shaders/generated/particle.wgsl.ts +3 -3
  109. package/src/render/shaders/generated/rush.wgsl.ts +1 -1
  110. package/src/render/shaders/generated/sdfText.wgsl.ts +1 -1
  111. package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
  112. package/src/render/shaders/outputTransform.ts +24 -1
  113. package/src/render/shaders/recon/resolve.wgsl.ts +15 -1
  114. package/src/render/shaders/rush.ts +13 -2
  115. package/src/render/shaders/temporalResolve.ts +12 -5
  116. package/src/render/ssrPass.ts +2 -1
  117. package/src/render/surfaceTexture.ts +11 -0
  118. package/src/render/textRenderer.ts +2 -1
  119. package/src/render/vertexDefaults.ts +1 -0
  120. package/src/render/waterRenderer.ts +2 -1
  121. package/src/render/windStreakRenderer.ts +2 -1
package/CHANGELOG.json CHANGED
@@ -1,4 +1,60 @@
1
1
  [
2
+ {
3
+ "version": "4.8.3",
4
+ "date": "2026-10-04",
5
+ "entries": [
6
+ {
7
+ "kind": "fixed",
8
+ "short": "Ambient occlusion no longer darkens smoke, glass, water or text by the corner behind them: it darkens only the part of a pixel they let through, on both backends.",
9
+ "text": "Occlusion is applied in the composite from the depth the opaque world leaves, after every translucent draw has landed, and a particle or a pane writes no depth, so a puff of smoke in front of a wall was darkened by the corner behind it: reported as walls and figures behind a cloud showing through it as dark outlines, and nothing at all with occlusion off. The scene's alpha now carries how much of each pixel is still the opaque surface. The frame clears it to 1, an opaque draw writes 1, and every blended draw states what it does to it: what covers (glass, smoke, water, lines, text, panels, sprites, splats) multiplies it by what it lets through, what transmits (the global medium, the order-independent composite) by its own transmittance, and what adds light or is a layer of the surface (caustics, light volumes, bolts, a wet film, decals, reflections) leaves it alone. The composite darkens that share and no more, and the temporal resolve and the reconstruction carry it through. Measured on both backends, plain, with temporal antialiasing and with DriftTR, against a darkened join: a pane at 0.85 opacity keeps 0.15 of the darkening behind it and a cluster of smoke none, while the open join and every frame with occlusion off are unchanged to the pixel. What it gives up is light added in front of an occluded corner, which is darkened with the surface it lands on as before, and the GPU-driven pipeline's own blended surfaces, which still take the occlusion behind them because its blit's alpha is the mask that says where it drew. A pass of your own states its blending with `SCENE_ALPHA_COVERS`, `SCENE_ALPHA_TRANSMITS` or `SCENE_ALPHA_KEEPS` on WebGPU, and `blendCovering`, `blendTransmitting` or `blendKeeping` on WebGL2."
10
+ },
11
+ {
12
+ "kind": "fixed",
13
+ "short": "A particle's size is documented as what it always was, a half-width from the centre to an edge: a particle of size 0.5 is a metre across.",
14
+ "text": "`ParticleInstances.sizes` and a pool's `sizeStart` and `sizeEnd` were documented as metres across, and the quad spans twice the size, a corner at minus and plus one on each axis, so a caller who sized by the documentation drew every particle twice as wide as meant. The drawing is unchanged, because every effect already tuned against it would have halved; the type, the pool's options and the manual now say half-width."
15
+ },
16
+ {
17
+ "kind": "fixed",
18
+ "short": "A bone too short for a body no longer cuts a ragdoll in two: what hangs below it is jointed to the bone above, and stops colliding with the bones it meets there.",
19
+ "text": "A rig often puts one joint exactly on another, a collar on the chest joint or a helper on the hips, and a bone shorter than `minLength` gets no body. What hung below such a bone was jointed to nothing, because a bone was jointed to the bone ending at its parent joint and that joint had none: in a limp fall the spine above a collar flew 3.9 m off the hips. And it collided with every bone it met at that point, because the shared-end rule compared parent joints by index, so capsules out of one point separated whichever way rounding said and a reaction depended on which way the character faced. A short bone is now transparent: each joint resolves to the nearest one above it that a body ends at, for jointing and for collision alike, and where a short bone lies between, the joint is anchored where the two bones meet at rest, so a still doll does not move on its first tick."
20
+ },
21
+ {
22
+ "kind": "fixed",
23
+ "short": "A ragdoll draws every bone under a joint on its own body, so after a fall a second thigh or a clavicle no longer hangs off the body beside it.",
24
+ "text": "`writePose` turned a joint by its first bone below and placed every child by the rig's offset from that turn, so at a joint with several bones below it, hips over a spine and two thighs, a chest over a neck and two clavicles, every branch but the first was swung by the first one's rotation and drawn off its own body. Reported from a game as a foot 0.24 m and an arm 0.17 m from their bodies after a fall; a test fall measured a thigh 97.5 cm out. Each joint is now placed along its own bone as that bone's body holds it, at the rig's length and in the parent's frame, which for the first branch is the rig's offset exactly. Lengths never stretch. A joint with no bone of its own keeps the rig's offset."
25
+ },
26
+ {
27
+ "kind": "added",
28
+ "short": "`setSurfaceFog(false)` takes the next opaque draws out of the fog, so a floor can recede into the medium while figures standing on it keep their colour.",
29
+ "text": "Opaque draws could not opt out of the medium: `drawTranslucentMesh` had `fog: false` and the shader a per-draw switch, and nothing reached it for `drawMesh`. A ported game darkening its floor with distance had to darken its figures with it, or leave the far floor 18% too bright. `setSurfaceFog(enabled)` is a surface dial like `setSurfaceGrain`: material state for the draws that follow, read by instanced and skinned draws too, and restored by `bindMeshPass`. A translucent draw's own `fog` is measured against it and puts it back. Verified on both backends with two identical far pillars, one fogged and one not, and a pane between them asking for fog under the dial."
30
+ },
31
+ {
32
+ "kind": "added",
33
+ "short": "`outputTransform: 'shoulder'` keeps sRGB's midtones and eases only the highlights, by their brightest channel, so an overbright red stays red instead of turning pink.",
34
+ "text": "A fourth output transform. `'srgb'` clips each channel at 1, so an overbright colour shifts hue on its way to white: `(1.6, 0.4, 0.2)` clips to `(1, 0.4, 0.2)`, pinker and wider. `'shoulder'` leaves everything below 0.8 alone and eases the brightest channel toward 1, as `0.8 + 0.2e / (e + 0.2)` of an excess `e`, scaling the other two with it, so the same colour becomes `(0.96, 0.24, 0.12)`. It is the curve a ported game's own resolve applies, and it has none of ACES's toe or desaturation. One definition shared by the forward passes and the resolve, measured on both backends with the composite on and off: `(250, 134, 97)` against sRGB's `(255, 170, 124)`."
35
+ },
36
+ {
37
+ "kind": "added",
38
+ "short": "Materials take `uOffset` and `vOffset`, so one quad draws any cell of a flipbook or an atlas, and a cutout's shadow is cut from the same cell.",
39
+ "text": "`SurfaceMaterial` had a UV scale and no offset, so a flipbook could not choose its cell through the material and a game built one quad mesh per cell, 106 meshes for four strips. `uOffset` and `vOffset` are added after the scale, `uv * scale + offset`, for every map of the material, and they reach the cutout depth and shadow passes, so a card's shadow has the holes of the cell its colour shows. Both backends, mesh and instanced draws."
40
+ },
41
+ {
42
+ "kind": "added",
43
+ "short": "`AudioGraph.restart(fromSec)` starts every stem from a point in the track, locked together, for music held to a game's own clock.",
44
+ "text": "The transport could only go back to the top, so music held to a frame counter, which has to resume where that counter is after a skip, ran its own source into the music bus outside every stem's lock. `restart(fromSec)` stops the stems and starts them all from that point at one instant, wrapped by each stem's length, and returns how long until it is heard. `restart()` is unchanged."
45
+ },
46
+ {
47
+ "kind": "changed",
48
+ "short": "A sound slot registered without a synth is required: if none of its files loads, `load` rejects naming it, after every other slot has loaded.",
49
+ "text": "Every slot had to carry a synthesised stand-in, which is right for a footstep and wrong for a score that has to be the file or nothing, so such a score was decoded outside the registry. `SoundSource.synth` is optional now. A slot without one is required: when none of its candidates loads it is listed in `unbuilt`, and `load` still settles every other slot before rejecting with an error naming each missing required slot and the files it tried. A slot with a synth behaves as before."
50
+ },
51
+ {
52
+ "kind": "fixed",
53
+ "short": "A WebSocket or WebRTC transport reads a text frame as its UTF-8 bytes, and counts a frame it cannot read as dropped, where both vanished in silence.",
54
+ "text": "`WebSocketTransport` and `WebRtcTransport` discarded any message that arrived as a string, without counting it in `dropped`, on the ground that a payload through UTF-8 loses its high bytes. That is true of binary data sent as text and not of a peer that speaks text: a text frame is lossless UTF-8 by the protocol, and a client against a server sending text received nothing at all. Text now arrives as exactly the bytes the sender wrote, anything else unreadable is counted, and both transports share the one rule."
55
+ }
56
+ ]
57
+ },
2
58
  {
3
59
  "version": "4.8.2",
4
60
  "date": "2026-10-03",
package/README.md CHANGED
@@ -6,7 +6,7 @@ carries the loop, the renderer, cameras, input, the scene graph, geometry, text
6
6
  and the whole of [`@driftengine/physics`](https://driftengine.dev/docs/simulation/rigid-bodies),
7
7
  which it re-exports. The rest of the engine is separate packages you add when you need them.
8
8
 
9
- **Cost: 699.0 KB gzipped, importing `createRenderer`.** Measured by `scripts/size-gate.test.mjs`,
9
+ **Cost: 710.5 KB gzipped, importing `createRenderer`.** Measured by `scripts/size-gate.test.mjs`,
10
10
  which fails the build if the figure drifts by more than 3%.
11
11
 
12
12
  The manual is at **[driftengine.dev/docs](https://driftengine.dev/docs)**, with an example running
package/dist/index.d.ts CHANGED
@@ -277,6 +277,7 @@ export type { GroundSurface, RibbonSurfaceOptions, SurfaceHit } from './physics/
277
277
  export { CompositeSurface } from './physics/compositeSurface.ts';
278
278
  export { colliderSurface } from './physics/colliderSurface.ts';
279
279
  export { DEPTH_CLEAR, DEPTH_COMPARE, DEPTH_COMPARE_EQUAL, DEPTH_FORMAT, DEPTH_OFFSET_SIGN, REVERSED_DEPTH, conventionalDepth, } from './render/depthConvention.ts';
280
+ export { SCENE_ALPHA_COVERS, SCENE_ALPHA_KEEPS, SCENE_ALPHA_TRANSMITS, blendCovering, blendKeeping, blendTransmitting, } from './render/sceneCoverage.ts';
280
281
  export { VIEW_MODEL_DEPTH_SHARE } from './render/viewModel.ts';
281
282
  export type { ColliderSurfaceOptions } from './physics/colliderSurface.ts';
282
283
  export { BoxSurface } from './physics/boxSurface.ts';
package/dist/index.js CHANGED
@@ -338,6 +338,12 @@ export { colliderSurface } from './physics/colliderSurface.js';
338
338
  * `EXT_clip_control`.
339
339
  */
340
340
  export { DEPTH_CLEAR, DEPTH_COMPARE, DEPTH_COMPARE_EQUAL, DEPTH_FORMAT, DEPTH_OFFSET_SIGN, REVERSED_DEPTH, conventionalDepth, } from './render/depthConvention.js';
341
+ /*
342
+ * **What a contributed pass's blended draw does to the scene's alpha**, which carries how much of
343
+ * each pixel is still the opaque surface: the share the composite's ambient occlusion may darken.
344
+ * A pass blending into the scene states one of these for its alpha. See `sceneCoverage.ts`.
345
+ */
346
+ export { SCENE_ALPHA_COVERS, SCENE_ALPHA_KEEPS, SCENE_ALPHA_TRANSMITS, blendCovering, blendKeeping, blendTransmitting, } from './render/sceneCoverage.js';
341
347
  /* How much of the depth range `beginViewModel` takes when it is given no share. See `viewModel.ts`. */
342
348
  export { VIEW_MODEL_DEPTH_SHARE } from './render/viewModel.js';
343
349
  export { BoxSurface } from './physics/boxSurface.js';
@@ -27,8 +27,13 @@ export interface DrawMaterialOptions {
27
27
  /** Whether the draw refracts this time, which a backend decides by whether it has a snapshot. */
28
28
  readonly refracting: boolean;
29
29
  }
30
- /** Whether a draw's options differ from the pass's, so it takes a material and leaves one to restore. */
31
- export declare function ownsMaterial(options: DrawMaterialOptions): boolean;
30
+ /**
31
+ * Whether a draw's options differ from the pass's, so it takes a material and leaves one to restore.
32
+ *
33
+ * `passFog` is how the surfaces around it meet the medium — `FOG_RECEDE` unless `setSurfaceFog`
34
+ * turned it off — so a translucent draw asking for what the pass already has takes nothing.
35
+ */
36
+ export declare function ownsMaterial(options: DrawMaterialOptions, passFog?: number): boolean;
32
37
  /** Whether the next draw shares the open material or opens a new one. */
33
38
  export declare class MaterialChanges {
34
39
  /**
@@ -18,11 +18,16 @@
18
18
  * slot on the backend that keeps slots, and comparing every field would cost more than the slot.
19
19
  */
20
20
  import { FOG_RECEDE } from './drawFog.js';
21
- /** Whether a draw's options differ from the pass's, so it takes a material and leaves one to restore. */
22
- export function ownsMaterial(options) {
21
+ /**
22
+ * Whether a draw's options differ from the pass's, so it takes a material and leaves one to restore.
23
+ *
24
+ * `passFog` is how the surfaces around it meet the medium — `FOG_RECEDE` unless `setSurfaceFog`
25
+ * turned it off — so a translucent draw asking for what the pass already has takes nothing.
26
+ */
27
+ export function ownsMaterial(options, passFog = FOG_RECEDE) {
23
28
  return (options.opacity < 1 ||
24
29
  !options.lit ||
25
- options.fog !== FOG_RECEDE ||
30
+ options.fog !== passFog ||
26
31
  !options.toneMapped ||
27
32
  options.refracting);
28
33
  }
@@ -936,6 +936,11 @@ export declare class WebGL2Renderer implements RendererApi {
936
936
  private readonly surfaceScene;
937
937
  /** The two halves of `uWriteMode`, kept here because a `vec2` is written whole. */
938
938
  private oitWeight;
939
+ /**
940
+ * How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
941
+ * each `bindMeshPass`. A translucent draw's own fog is measured against this and puts it back.
942
+ */
943
+ private surfaceFog;
939
944
  private ditherFade;
940
945
  /** Said once, not per frame, where the context cannot give the float target the sum needs. */
941
946
  private oitRefused;
@@ -1866,6 +1871,22 @@ export declare class WebGL2Renderer implements RendererApi {
1866
1871
  */
1867
1872
  setEnvironmentAmbient(_amount: number): void;
1868
1873
  setSurfaceGrain(amount: number): void;
1874
+ /**
1875
+ * Whether the surfaces drawn next meet the medium: distance fog and the global medium's haze.
1876
+ * True is the default, and what `bindMeshPass` restores.
1877
+ *
1878
+ * **The opaque counterpart of `TranslucentMeshOptions.fog`**, which a translucent draw has had
1879
+ * all along; the shader's per-draw switch was there, and nothing reached it for an ordinary mesh.
1880
+ * What it is for is a medium that darkens one thing and not another — a floor receding into the
1881
+ * distance under figures that must keep their full value however far from the camera they stand.
1882
+ * Material state like `setSurfaceGrain`: instanced and skinned draws read it too, and a
1883
+ * translucent draw's own `fog` is measured against it and puts it back.
1884
+ *
1885
+ * **What it gives up** is the medium's honesty: a surface out of the fog is drawn as though the
1886
+ * air between it and the eye were empty, so it should be a surface the scene means to stand
1887
+ * apart, not one whose distance is the point.
1888
+ */
1889
+ setSurfaceFog(enabled: boolean): void;
1869
1890
  /**
1870
1891
  * One float of material state, into every flat program.
1871
1892
  *
@@ -1876,6 +1897,8 @@ export declare class WebGL2Renderer implements RendererApi {
1876
1897
  * reads, drew what was asked.
1877
1898
  */
1878
1899
  private writeFlatFloat;
1900
+ /** One int of material state, into every flat program, for `writeFlatFloat`'s reason. */
1901
+ private writeFlatInt;
1879
1902
  /**
1880
1903
  * How much of the surfaces drawn next survives a screen-door dither, for crossfading two levels
1881
1904
  * of detail of one thing.
@@ -1,6 +1,7 @@
1
1
  import { FrameBudget } from '../budget.js';
2
2
  import { MaterialChanges, ownsMaterial } from '../materialChanges.js';
3
- import { FOG_RECEDE, fogModeOf } from '../drawFog.js';
3
+ import { FOG_OFF, FOG_RECEDE, fogModeOf } from '../drawFog.js';
4
+ import { blendCovering, blendKeeping } from '../../sceneCoverage.js';
4
5
  import { mat4 } from 'gl-matrix';
5
6
  import { GL_DEPTH_REMAP, MAX_DEPTH_LAYER, depthClearFor, depthOffsetForLayer, GL_SHADOW_REMAP, REVERSED_DEPTH, SHADOW_DEPTH_CLEAR, glDepthFuncEqual, glShadowDepthFunc, } from '../../depthConvention.js';
6
7
  import { resolveGlass } from '../../glass.js';
@@ -560,6 +561,11 @@ export class WebGL2Renderer {
560
561
  surfaceScene = new Float32Array(4);
561
562
  /** The two halves of `uWriteMode`, kept here because a `vec2` is written whole. */
562
563
  oitWeight = 0;
564
+ /**
565
+ * How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
566
+ * each `bindMeshPass`. A translucent draw's own fog is measured against this and puts it back.
567
+ */
568
+ surfaceFog = FOG_RECEDE;
563
569
  ditherFade = 0;
564
570
  /** Said once, not per frame, where the context cannot give the float target the sum needs. */
565
571
  oitRefused = false;
@@ -1169,6 +1175,7 @@ export class WebGL2Renderer {
1169
1175
  if (this.activeDepthPeel)
1170
1176
  gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
1171
1177
  gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
1178
+ gl.uniform2f(u['uUvOffset'] ?? null, cutout.uOffset, cutout.vOffset);
1172
1179
  gl.uniform1f(u['uAlphaCutout'] ?? null, cutout.cutoff);
1173
1180
  /* Narrowed for the reason the sink's own `mesh` gives: this renderer only hands out its own. */
1174
1181
  cutout.albedo.bind(gl, SURFACE_TEXTURE_UNIT);
@@ -2541,6 +2548,7 @@ export class WebGL2Renderer {
2541
2548
  * how big a dome was — WebGPU writes it before its own early return and WebGL2 did not.
2542
2549
  */
2543
2550
  gl.uniform2f(u['uUvScale'] ?? null, material?.uScale ?? 1, material?.vScale ?? 1);
2551
+ gl.uniform2f(u['uUvOffset'] ?? null, material?.uOffset ?? 0, material?.vOffset ?? 0);
2544
2552
  gl.uniform1i(u['uDoubleSided'] ?? null, material?.doubleSided === true ? 1 : 0);
2545
2553
  /* The albedo's effects table, or the stand-in the shader reads as none. Before the early
2546
2554
  return, so a material with no albedo still leaves a complete texture on the unit. */
@@ -2669,6 +2677,28 @@ export class WebGL2Renderer {
2669
2677
  this.materials.dirty();
2670
2678
  this.writeFlatFloat('uGrain', Math.min(1, Math.max(0, amount)));
2671
2679
  }
2680
+ /**
2681
+ * Whether the surfaces drawn next meet the medium: distance fog and the global medium's haze.
2682
+ * True is the default, and what `bindMeshPass` restores.
2683
+ *
2684
+ * **The opaque counterpart of `TranslucentMeshOptions.fog`**, which a translucent draw has had
2685
+ * all along; the shader's per-draw switch was there, and nothing reached it for an ordinary mesh.
2686
+ * What it is for is a medium that darkens one thing and not another — a floor receding into the
2687
+ * distance under figures that must keep their full value however far from the camera they stand.
2688
+ * Material state like `setSurfaceGrain`: instanced and skinned draws read it too, and a
2689
+ * translucent draw's own `fog` is measured against it and puts it back.
2690
+ *
2691
+ * **What it gives up** is the medium's honesty: a surface out of the fog is drawn as though the
2692
+ * air between it and the eye were empty, so it should be a surface the scene means to stand
2693
+ * apart, not one whose distance is the point.
2694
+ */
2695
+ setSurfaceFog(enabled) {
2696
+ if (this.contextLost)
2697
+ return;
2698
+ this.materials.dirty();
2699
+ this.surfaceFog = enabled ? FOG_RECEDE : FOG_OFF;
2700
+ this.writeFlatInt('uFogEnabled', this.surfaceFog);
2701
+ }
2672
2702
  /**
2673
2703
  * One float of material state, into every flat program.
2674
2704
  *
@@ -2685,6 +2715,14 @@ export class WebGL2Renderer {
2685
2715
  }
2686
2716
  this.useFlatProgram();
2687
2717
  }
2718
+ /** One int of material state, into every flat program, for `writeFlatFloat`'s reason. */
2719
+ writeFlatInt(name, value) {
2720
+ for (const target of this.flatTargets) {
2721
+ this.gl.useProgram(target.program);
2722
+ this.gl.uniform1i(target.uniforms[name] ?? null, value);
2723
+ }
2724
+ this.useFlatProgram();
2725
+ }
2688
2726
  /**
2689
2727
  * How much of the surfaces drawn next survives a screen-door dither, for crossfading two levels
2690
2728
  * of detail of one thing.
@@ -2913,7 +2951,7 @@ export class WebGL2Renderer {
2913
2951
  /* A blended batch shows what is behind it — refraction or glass — as a single draw does, and
2914
2952
  counts a material of its own for it by the rule both backends share (`materialChanges.ts`). */
2915
2953
  const refracting = blend && this.bindSeeThrough(u, options);
2916
- const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
2954
+ const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog);
2917
2955
  if (own)
2918
2956
  this.materials.dirty();
2919
2957
  this.takeMaterial();
@@ -2939,16 +2977,16 @@ export class WebGL2Renderer {
2939
2977
  */
2940
2978
  if (!lit)
2941
2979
  gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
2942
- if (fog !== FOG_RECEDE)
2980
+ if (fog !== this.surfaceFog)
2943
2981
  gl.uniform1i(u['uFogEnabled'] ?? null, fog);
2944
2982
  if (!toneMapped)
2945
2983
  gl.uniform1i(u['uOutputTransform'] ?? null, Math.min(this.gradeCode(), 1));
2946
2984
  if (blend) {
2947
2985
  gl.enable(gl.BLEND);
2948
2986
  if (options.additive === true)
2949
- gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE, gl.ZERO, gl.ONE);
2987
+ blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
2950
2988
  else
2951
- gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
2989
+ blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
2952
2990
  if (!depthWrite)
2953
2991
  gl.depthMask(false);
2954
2992
  }
@@ -2988,8 +3026,8 @@ export class WebGL2Renderer {
2988
3026
  this.unbindSeeThrough(u);
2989
3027
  if (!lit)
2990
3028
  gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
2991
- if (fog !== FOG_RECEDE)
2992
- gl.uniform1i(u['uFogEnabled'] ?? null, FOG_RECEDE);
3029
+ if (fog !== this.surfaceFog)
3030
+ gl.uniform1i(u['uFogEnabled'] ?? null, this.surfaceFog);
2993
3031
  if (!toneMapped)
2994
3032
  gl.uniform1i(u['uOutputTransform'] ?? null, this.gradeCode());
2995
3033
  if (own)
@@ -4068,6 +4106,7 @@ export class WebGL2Renderer {
4068
4106
  gl.uniform4fv(u['uGlassLight'] ?? null, this.glassLight);
4069
4107
  if (cutout !== null) {
4070
4108
  gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
4109
+ gl.uniform2f(u['uUvOffset'] ?? null, cutout.uOffset, cutout.vOffset);
4071
4110
  gl.uniform1f(u['uAlphaCutout'] ?? null, cutout.cutoff);
4072
4111
  cutout.albedo.bind(gl, SURFACE_TEXTURE_UNIT);
4073
4112
  gl.uniform1i(u['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
@@ -4757,7 +4796,7 @@ export class WebGL2Renderer {
4757
4796
  */
4758
4797
  gl.disable(gl.CULL_FACE);
4759
4798
  gl.enable(gl.BLEND);
4760
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
4799
+ blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
4761
4800
  gl.uniform4f(this.panelUniforms['uRect'] ?? null, rect.left, rect.top, rect.width, rect.height);
4762
4801
  gl.uniform2f(this.panelUniforms['uViewport'] ?? null, Math.max(this.canvas.clientWidth, 1), Math.max(this.canvas.clientHeight, 1));
4763
4802
  /* Identity: this backend's clip space is the one the shader was written against. */
@@ -6048,7 +6087,8 @@ export class WebGL2Renderer {
6048
6087
  * shape `uOpacity` and `uTint` already use.
6049
6088
  */
6050
6089
  gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
6051
- gl.uniform1i(u['uFogEnabled'] ?? null, 1);
6090
+ gl.uniform1i(u['uFogEnabled'] ?? null, FOG_RECEDE);
6091
+ this.surfaceFog = FOG_RECEDE;
6052
6092
  /* Grain on by default, so every scene written before it was a choice looks unchanged. */
6053
6093
  gl.uniform1f(u['uGrain'] ?? null, 1);
6054
6094
  /* No dither and no weighting: every draw whole, until a crossfade asks. See `setDitherFade`. */
@@ -6233,6 +6273,7 @@ export class WebGL2Renderer {
6233
6273
  gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
6234
6274
  gl.uniform1i(u['uSurfaceEffects'] ?? null, SURFACE_EFFECTS_TEXTURE_UNIT);
6235
6275
  gl.uniform2f(u['uUvScale'] ?? null, 1, 1);
6276
+ gl.uniform2f(u['uUvOffset'] ?? null, 0, 0);
6236
6277
  gl.uniform1f(u['uAlbedoCutout'] ?? null, 0);
6237
6278
  gl.uniform1i(u['uDoubleSided'] ?? null, 0);
6238
6279
  this.materialDoubleSided = false;
@@ -6581,7 +6622,7 @@ export class WebGL2Renderer {
6581
6622
  const toneMapped = options.toneMapped ?? true;
6582
6623
  if (!lit)
6583
6624
  gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
6584
- if (fog !== FOG_RECEDE)
6625
+ if (fog !== this.surfaceFog)
6585
6626
  gl.uniform1i(u['uFogEnabled'] ?? null, fog);
6586
6627
  /* `1` is sRGB alone: the conversion without the curve, which is what dropping the tone map
6587
6628
  means rather than dropping the whole transform. `Math.min` rather than a literal, so a
@@ -6616,7 +6657,7 @@ export class WebGL2Renderer {
6616
6657
  */
6617
6658
  /* Taken for itself when any option differs from the pass, and put back below: the rule both
6618
6659
  backends count material changes by. See `materialChanges.ts`. */
6619
- const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
6660
+ const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog);
6620
6661
  if (own)
6621
6662
  this.materials.dirty();
6622
6663
  this.takeMaterial();
@@ -6625,9 +6666,9 @@ export class WebGL2Renderer {
6625
6666
  gl.enable(gl.BLEND);
6626
6667
  /* Added, where asked: colour times alpha onto what is there, and the target's alpha kept. */
6627
6668
  if (options.additive === true)
6628
- gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE, gl.ZERO, gl.ONE);
6669
+ blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
6629
6670
  else
6630
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
6671
+ blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
6631
6672
  }
6632
6673
  /*
6633
6674
  * The two depth options, both scoped to this draw and handed straight back — the same
@@ -6667,8 +6708,8 @@ export class WebGL2Renderer {
6667
6708
  cannot inherit a material from the draw before it. */
6668
6709
  if (!lit)
6669
6710
  gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
6670
- if (fog !== FOG_RECEDE)
6671
- gl.uniform1i(u['uFogEnabled'] ?? null, FOG_RECEDE);
6711
+ if (fog !== this.surfaceFog)
6712
+ gl.uniform1i(u['uFogEnabled'] ?? null, this.surfaceFog);
6672
6713
  if (!toneMapped)
6673
6714
  gl.uniform1i(u['uOutputTransform'] ?? null, this.gradeCode());
6674
6715
  if (own)
@@ -7036,7 +7077,7 @@ export class WebGL2Renderer {
7036
7077
  gl.uniform1i(u['uSceneDepth'] ?? null, 3);
7037
7078
  }
7038
7079
  gl.enable(gl.BLEND);
7039
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
7080
+ blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
7040
7081
  gl.depthMask(false);
7041
7082
  gl.cullFace(inside ? gl.FRONT : gl.BACK);
7042
7083
  mesh.draw(gl);
@@ -7104,7 +7145,7 @@ export class WebGL2Renderer {
7104
7145
  }
7105
7146
  bindAtmosphere(gl, u, env, this.reflectionPassActive ? this.reflectionAtmosphereY : (camera.position[1] ?? 0), this.quality.underwaterAtmosphere);
7106
7147
  gl.enable(gl.BLEND);
7107
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
7148
+ blendKeeping(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
7108
7149
  gl.depthMask(false);
7109
7150
  mesh.draw(gl);
7110
7151
  gl.depthMask(true);
@@ -2,6 +2,7 @@ import { DEPTH_COMPARE } from '../../depthConvention.js';
2
2
  import { CAUSTICS_BINDINGS, CAUSTICS_FRAG_WGSL, CAUSTICS_VERT_WGSL, } from '../../shaders/generated/caustics.wgsl.js';
3
3
  import { DEPTH_FORMAT } from './flatPass.js';
4
4
  import { shaderModule } from './shaderModules.js';
5
+ import { SCENE_ALPHA_KEEPS } from '../../sceneCoverage.js';
5
6
  /**
6
7
  * The caustics pass: a surface lit by nearby water.
7
8
  *
@@ -126,7 +127,7 @@ export function causticsPipeline(cache, device, layout) {
126
127
  format: cache.format,
127
128
  blend: {
128
129
  color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
129
- alpha: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
130
+ alpha: SCENE_ALPHA_KEEPS,
130
131
  },
131
132
  },
132
133
  ],
@@ -87,13 +87,18 @@ export declare const DEPTH_CUTOUT_VERT_FIELDS: {
87
87
  readonly size: 8;
88
88
  readonly type: "vec2";
89
89
  };
90
- readonly uAlphaCutout: {
90
+ readonly uUvOffset: {
91
91
  readonly offset: 168;
92
+ readonly size: 8;
93
+ readonly type: "vec2";
94
+ };
95
+ readonly uAlphaCutout: {
96
+ readonly offset: 176;
92
97
  readonly size: 4;
93
98
  readonly type: "float";
94
99
  };
95
100
  };
96
- export declare const DEPTH_CUTOUT_VERT_SIZE: 176;
101
+ export declare const DEPTH_CUTOUT_VERT_SIZE: 192;
97
102
  export declare const DEPTH_INSTANCED_CUTOUT_VERT_FIELDS: {
98
103
  readonly uLightViewProj: {
99
104
  readonly offset: 0;
@@ -105,8 +110,13 @@ export declare const DEPTH_INSTANCED_CUTOUT_VERT_FIELDS: {
105
110
  readonly size: 8;
106
111
  readonly type: "vec2";
107
112
  };
108
- readonly uAlphaCutout: {
113
+ readonly uUvOffset: {
109
114
  readonly offset: 72;
115
+ readonly size: 8;
116
+ readonly type: "vec2";
117
+ };
118
+ readonly uAlphaCutout: {
119
+ readonly offset: 80;
110
120
  readonly size: 4;
111
121
  readonly type: "float";
112
122
  };
@@ -12,9 +12,10 @@ export const DEPTH_VERT_FIELDS = DEPTH_BINDINGS.DEPTH_VERT.fields;
12
12
  export const DEPTH_VERT_SIZE = DEPTH_BINDINGS.DEPTH_VERT.uniformSize;
13
13
  export const DEPTH_FRAG_SIZE = DEPTH_BINDINGS.DEPTH_FRAG.uniformSize;
14
14
  /*
15
- * The cutout variants' vertex blocks. The rigid one is the plain block with `uUvScale` and
16
- * `uAlphaCutout` after every other field, so a slot written for it is written with the plain
17
- * offsets plus these two; the instanced one has no model and no wind, so its two sit earlier.
15
+ * The cutout variants' vertex blocks. The rigid one is the plain block with `uUvScale`,
16
+ * `uUvOffset` and `uAlphaCutout` after every other field, so a slot written for it is written with
17
+ * the plain offsets plus these three; the instanced one has no model and no wind, so its three sit
18
+ * earlier.
18
19
  */
19
20
  export const DEPTH_CUTOUT_VERT_FIELDS = DEPTH_BINDINGS.DEPTH_CUTOUT_VERT.fields;
20
21
  export const DEPTH_CUTOUT_VERT_SIZE = DEPTH_BINDINGS.DEPTH_CUTOUT_VERT.uniformSize;
@@ -3,6 +3,7 @@ import { FILM_BINDINGS, FILM_FRAG_WGSL, FILM_VERT_WGSL, } from '../../shaders/ge
3
3
  import { vertexBufferLayouts } from './buffers.js';
4
4
  import { DEPTH_FORMAT } from './flatPass.js';
5
5
  import { shaderModule } from './shaderModules.js';
6
+ import { SCENE_ALPHA_KEEPS } from '../../sceneCoverage.js';
6
7
  /**
7
8
  * The film pass: a thin wet layer — an oil slick, a puddle — lying over the world.
8
9
  *
@@ -85,7 +86,7 @@ export function filmPipeline(cache, device, layout, key, present) {
85
86
  format: cache.format,
86
87
  blend: {
87
88
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
88
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
89
+ alpha: SCENE_ALPHA_KEEPS,
89
90
  },
90
91
  },
91
92
  ],
@@ -86,6 +86,11 @@ export declare const FLAT_VERT_FIELDS: {
86
86
  readonly size: 8;
87
87
  readonly type: "vec2";
88
88
  };
89
+ readonly uUvOffset: {
90
+ readonly offset: 152;
91
+ readonly size: 8;
92
+ readonly type: "vec2";
93
+ };
89
94
  readonly uModel: {
90
95
  readonly offset: 160;
91
96
  readonly size: 64;
@@ -2,6 +2,7 @@ import { DEPTH_COMPARE, DEPTH_FORMAT, depthOffsetForLayer } from '../../depthCon
2
2
  import { FLAT_BINDINGS, FLAT_FRAG_WGSL, FLAT_VERT_WGSL, } from '../../shaders/generated/flat.wgsl.js';
3
3
  import { vertexBufferLayouts } from './buffers.js';
4
4
  import { shaderModule } from './shaderModules.js';
5
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
5
6
  /** The material maps the flat pass reads: `2d-array` colour, filtered, not shadow maps. */
6
7
  const SURFACE_MAPS = new Set([
7
8
  'uAlbedo',
@@ -517,7 +518,7 @@ doubleSided = false) {
517
518
  dstFactor: 'one-minus-src-alpha',
518
519
  operation: 'add',
519
520
  },
520
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
521
+ alpha: SCENE_ALPHA_COVERS,
521
522
  },
522
523
  }
523
524
  : { format: cache.format }),
@@ -3,6 +3,7 @@ import { LINE_BINDINGS, LINE_FRAG_WGSL, LINE_VERT_WGSL, } from '../../shaders/ge
3
3
  import { buildSegmentQuads } from '../../segmentQuads.js';
4
4
  import { DEPTH_FORMAT } from './flatPass.js';
5
5
  import { shaderModule } from './shaderModules.js';
6
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
6
7
  /**
7
8
  * The line pass: a polyline with a real width, four vertices a segment.
8
9
  *
@@ -179,7 +180,7 @@ additive = false) {
179
180
  dstFactor: 'one-minus-src-alpha',
180
181
  operation: 'add',
181
182
  },
182
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
183
+ alpha: SCENE_ALPHA_COVERS,
183
184
  },
184
185
  },
185
186
  ],
@@ -1,6 +1,7 @@
1
1
  import { PANEL_BINDINGS, PANEL_FRAG_WGSL, PANEL_VERT_WGSL, } from '../../shaders/generated/panel.wgsl.js';
2
2
  import { DEPTH_FORMAT } from './flatPass.js';
3
3
  import { shaderModule } from './shaderModules.js';
4
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
4
5
  /**
5
6
  * The panel pass: two triangles in screen space, one colour, one alpha.
6
7
  *
@@ -97,7 +98,7 @@ export function panelPipeline(cache, device, layout) {
97
98
  format: cache.format,
98
99
  blend: {
99
100
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
100
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
101
+ alpha: SCENE_ALPHA_COVERS,
101
102
  },
102
103
  },
103
104
  ],
@@ -4,6 +4,7 @@ import { PARTICLE_BINDINGS } from '../../shaders/generated/particle.wgsl.js';
4
4
  import { PARTICLE_BLADES, PARTICLE_INDICES, PARTICLE_VERTS } from '../../shaders/particle.js';
5
5
  import { DEPTH_FORMAT } from './flatPass.js';
6
6
  import { shaderModule } from './shaderModules.js';
7
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
7
8
  /**
8
9
  * The particle pass: a crossed pair of quads per live particle, rebuilt every frame.
9
10
  *
@@ -269,7 +270,7 @@ export function particlePipeline(cache, device, layout, material, blend) {
269
270
  dstFactor: 'one-minus-src-alpha',
270
271
  operation: 'add',
271
272
  },
272
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
273
+ alpha: SCENE_ALPHA_COVERS,
273
274
  },
274
275
  },
275
276
  ],
@@ -6,6 +6,7 @@ import { FIRE_BINDINGS } from '../../shaders/generated/fire.wgsl.js';
6
6
  import { SMOKE_BINDINGS } from '../../shaders/generated/smoke.wgsl.js';
7
7
  import { DEPTH_FORMAT } from './flatPass.js';
8
8
  import { shaderModule } from './shaderModules.js';
9
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
9
10
  /**
10
11
  * The plume pass: crossed quads per plume, displaced and faded in the shaders.
11
12
  *
@@ -179,7 +180,7 @@ function blendState(blend) {
179
180
  }
180
181
  : {
181
182
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
182
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
183
+ alpha: SCENE_ALPHA_COVERS,
183
184
  };
184
185
  }
185
186
  /**
@@ -156,8 +156,10 @@ export declare const OIT_COMPOSITE_BLEND: GPUBlendState;
156
156
  * exactly the integral — and the composite therefore never samples the colour it is modifying,
157
157
  * which is the read both backends are entitled to call undefined.
158
158
  *
159
- * Alpha is `(zero, one)`: the frame's own alpha is not the medium's to spend. The scene target
160
- * carries it to the canvas, and a medium that wrote there would make a foggy frame translucent.
159
+ * Alpha is the transmittance again, `share * a`: the scene's alpha is how much of the pixel is
160
+ * still the opaque surface, which is what the composite lets ambient occlusion darken, and a
161
+ * medium thick enough to hide a corner hides its occlusion too (`sceneCoverage.ts`). It reaches
162
+ * no canvas: both are opaque, and the composite writes 1.
161
163
  */
162
164
  export declare const MEDIUM_TRANSMIT_BLEND: GPUBlendState;
163
165
  export declare const DECAL_MULTIPLY_BLEND: GPUBlendState;
@@ -170,6 +172,14 @@ export declare const DECAL_MULTIPLY_BLEND: GPUBlendState;
170
172
  * sample the colour it is compositing onto.
171
173
  */
172
174
  export declare const PREMULTIPLIED_OVER_BLEND: GPUBlendState;
175
+ /**
176
+ * The same `over` for the resolve that lands a reflection on the scene, keeping the scene's alpha.
177
+ *
178
+ * A reflection is a layer of the surface it is seen in, so the surface's share of the pixel is
179
+ * unchanged (`sceneCoverage.ts`). The trace keeps `PREMULTIPLIED_OVER_BLEND`, because its target is
180
+ * the reflection's own and its alpha is the coverage this resolve reads.
181
+ */
182
+ export declare const REFLECTION_OVER_BLEND: GPUBlendState;
173
183
  export declare const ADDITIVE_BLEND: GPUBlendState;
174
184
  /** A fullscreen pass over one or more inputs, writing to one colour target. */
175
185
  export declare function postPipeline(cache: PipelineCache, device: GPUDevice, layout: GPUBindGroupLayout, key: string, fragment: string, format: GPUTextureFormat, blend?: GPUBlendState): GPURenderPipeline;