@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
@@ -1,6 +1,7 @@
1
1
  import { FrameBudget } from '../budget.ts';
2
2
  import { MaterialChanges, ownsMaterial } from '../materialChanges.ts';
3
- import { FOG_RECEDE, fogModeOf } from '../drawFog.ts';
3
+ import { FOG_OFF, FOG_RECEDE, fogModeOf } from '../drawFog.ts';
4
+ import { blendCovering, blendKeeping } from '../../sceneCoverage.ts';
4
5
  import { mat4 } from 'gl-matrix';
5
6
  import {
6
7
  GL_DEPTH_REMAP,
@@ -1249,6 +1250,11 @@ export class WebGL2Renderer implements RendererApi {
1249
1250
  private readonly surfaceScene = new Float32Array(4);
1250
1251
  /** The two halves of `uWriteMode`, kept here because a `vec2` is written whole. */
1251
1252
  private oitWeight = 0;
1253
+ /**
1254
+ * How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
1255
+ * each `bindMeshPass`. A translucent draw's own fog is measured against this and puts it back.
1256
+ */
1257
+ private surfaceFog = FOG_RECEDE;
1252
1258
  private ditherFade = 0;
1253
1259
  /** Said once, not per frame, where the context cannot give the float target the sum needs. */
1254
1260
  private oitRefused = false;
@@ -1882,6 +1888,7 @@ export class WebGL2Renderer implements RendererApi {
1882
1888
  gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
1883
1889
  if (this.activeDepthPeel) gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
1884
1890
  gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
1891
+ gl.uniform2f(u['uUvOffset'] ?? null, cutout.uOffset, cutout.vOffset);
1885
1892
  gl.uniform1f(u['uAlphaCutout'] ?? null, cutout.cutoff);
1886
1893
  /* Narrowed for the reason the sink's own `mesh` gives: this renderer only hands out its own. */
1887
1894
  (cutout.albedo as SurfaceTexture).bind(gl, SURFACE_TEXTURE_UNIT);
@@ -3453,6 +3460,7 @@ export class WebGL2Renderer implements RendererApi {
3453
3460
  * how big a dome was — WebGPU writes it before its own early return and WebGL2 did not.
3454
3461
  */
3455
3462
  gl.uniform2f(u['uUvScale'] ?? null, material?.uScale ?? 1, material?.vScale ?? 1);
3463
+ gl.uniform2f(u['uUvOffset'] ?? null, material?.uOffset ?? 0, material?.vOffset ?? 0);
3456
3464
  gl.uniform1i(u['uDoubleSided'] ?? null, material?.doubleSided === true ? 1 : 0);
3457
3465
 
3458
3466
  /* The albedo's effects table, or the stand-in the shader reads as none. Before the early
@@ -3585,6 +3593,28 @@ export class WebGL2Renderer implements RendererApi {
3585
3593
  this.writeFlatFloat('uGrain', Math.min(1, Math.max(0, amount)));
3586
3594
  }
3587
3595
 
3596
+ /**
3597
+ * Whether the surfaces drawn next meet the medium: distance fog and the global medium's haze.
3598
+ * True is the default, and what `bindMeshPass` restores.
3599
+ *
3600
+ * **The opaque counterpart of `TranslucentMeshOptions.fog`**, which a translucent draw has had
3601
+ * all along; the shader's per-draw switch was there, and nothing reached it for an ordinary mesh.
3602
+ * What it is for is a medium that darkens one thing and not another — a floor receding into the
3603
+ * distance under figures that must keep their full value however far from the camera they stand.
3604
+ * Material state like `setSurfaceGrain`: instanced and skinned draws read it too, and a
3605
+ * translucent draw's own `fog` is measured against it and puts it back.
3606
+ *
3607
+ * **What it gives up** is the medium's honesty: a surface out of the fog is drawn as though the
3608
+ * air between it and the eye were empty, so it should be a surface the scene means to stand
3609
+ * apart, not one whose distance is the point.
3610
+ */
3611
+ setSurfaceFog(enabled: boolean): void {
3612
+ if (this.contextLost) return;
3613
+ this.materials.dirty();
3614
+ this.surfaceFog = enabled ? FOG_RECEDE : FOG_OFF;
3615
+ this.writeFlatInt('uFogEnabled', this.surfaceFog);
3616
+ }
3617
+
3588
3618
  /**
3589
3619
  * One float of material state, into every flat program.
3590
3620
  *
@@ -3602,6 +3632,15 @@ export class WebGL2Renderer implements RendererApi {
3602
3632
  this.useFlatProgram();
3603
3633
  }
3604
3634
 
3635
+ /** One int of material state, into every flat program, for `writeFlatFloat`'s reason. */
3636
+ private writeFlatInt(name: string, value: number): void {
3637
+ for (const target of this.flatTargets) {
3638
+ this.gl.useProgram(target.program);
3639
+ this.gl.uniform1i(target.uniforms[name] ?? null, value);
3640
+ }
3641
+ this.useFlatProgram();
3642
+ }
3643
+
3605
3644
  /**
3606
3645
  * How much of the surfaces drawn next survives a screen-door dither, for crossfading two levels
3607
3646
  * of detail of one thing.
@@ -3852,7 +3891,7 @@ export class WebGL2Renderer implements RendererApi {
3852
3891
  /* A blended batch shows what is behind it — refraction or glass — as a single draw does, and
3853
3892
  counts a material of its own for it by the rule both backends share (`materialChanges.ts`). */
3854
3893
  const refracting = blend && this.bindSeeThrough(u, options);
3855
- const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
3894
+ const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog);
3856
3895
  if (own) this.materials.dirty();
3857
3896
  this.takeMaterial();
3858
3897
  const depthWrite = options.depthWrite ?? true;
@@ -3876,14 +3915,13 @@ export class WebGL2Renderer implements RendererApi {
3876
3915
  * part of what the count asks.
3877
3916
  */
3878
3917
  if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
3879
- if (fog !== FOG_RECEDE) gl.uniform1i(u['uFogEnabled'] ?? null, fog);
3918
+ if (fog !== this.surfaceFog) gl.uniform1i(u['uFogEnabled'] ?? null, fog);
3880
3919
  if (!toneMapped) gl.uniform1i(u['uOutputTransform'] ?? null, Math.min(this.gradeCode(), 1));
3881
3920
 
3882
3921
  if (blend) {
3883
3922
  gl.enable(gl.BLEND);
3884
- if (options.additive === true) gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE, gl.ZERO, gl.ONE);
3885
- else
3886
- gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
3923
+ if (options.additive === true) blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
3924
+ else blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
3887
3925
  if (!depthWrite) gl.depthMask(false);
3888
3926
  }
3889
3927
  /* The sign follows the compare, which `depthOffsetForLayer` owns — see `drawMesh`, where the
@@ -3916,7 +3954,7 @@ export class WebGL2Renderer implements RendererApi {
3916
3954
  outlives its frame, so a snapshot left on unit 14 is held against the next frame's copy. */
3917
3955
  if (refracting) this.unbindSeeThrough(u);
3918
3956
  if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
3919
- if (fog !== FOG_RECEDE) gl.uniform1i(u['uFogEnabled'] ?? null, FOG_RECEDE);
3957
+ if (fog !== this.surfaceFog) gl.uniform1i(u['uFogEnabled'] ?? null, this.surfaceFog);
3920
3958
  if (!toneMapped) gl.uniform1i(u['uOutputTransform'] ?? null, this.gradeCode());
3921
3959
  if (own) this.materials.dirty();
3922
3960
  this.useFlatProgram();
@@ -5256,6 +5294,7 @@ export class WebGL2Renderer implements RendererApi {
5256
5294
  gl.uniform4fv(u['uGlassLight'] ?? null, this.glassLight);
5257
5295
  if (cutout !== null) {
5258
5296
  gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
5297
+ gl.uniform2f(u['uUvOffset'] ?? null, cutout.uOffset, cutout.vOffset);
5259
5298
  gl.uniform1f(u['uAlphaCutout'] ?? null, cutout.cutoff);
5260
5299
  (cutout.albedo as SurfaceTexture).bind(gl, SURFACE_TEXTURE_UNIT);
5261
5300
  gl.uniform1i(u['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
@@ -6072,7 +6111,7 @@ export class WebGL2Renderer implements RendererApi {
6072
6111
  */
6073
6112
  gl.disable(gl.CULL_FACE);
6074
6113
  gl.enable(gl.BLEND);
6075
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
6114
+ blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
6076
6115
 
6077
6116
  gl.uniform4f(this.panelUniforms['uRect'] ?? null, rect.left, rect.top, rect.width, rect.height);
6078
6117
  gl.uniform2f(
@@ -7509,7 +7548,8 @@ export class WebGL2Renderer implements RendererApi {
7509
7548
  * shape `uOpacity` and `uTint` already use.
7510
7549
  */
7511
7550
  gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
7512
- gl.uniform1i(u['uFogEnabled'] ?? null, 1);
7551
+ gl.uniform1i(u['uFogEnabled'] ?? null, FOG_RECEDE);
7552
+ this.surfaceFog = FOG_RECEDE;
7513
7553
  /* Grain on by default, so every scene written before it was a choice looks unchanged. */
7514
7554
  gl.uniform1f(u['uGrain'] ?? null, 1);
7515
7555
  /* No dither and no weighting: every draw whole, until a crossfade asks. See `setDitherFade`. */
@@ -7709,6 +7749,7 @@ export class WebGL2Renderer implements RendererApi {
7709
7749
  gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
7710
7750
  gl.uniform1i(u['uSurfaceEffects'] ?? null, SURFACE_EFFECTS_TEXTURE_UNIT);
7711
7751
  gl.uniform2f(u['uUvScale'] ?? null, 1, 1);
7752
+ gl.uniform2f(u['uUvOffset'] ?? null, 0, 0);
7712
7753
  gl.uniform1f(u['uAlbedoCutout'] ?? null, 0);
7713
7754
  gl.uniform1i(u['uDoubleSided'] ?? null, 0);
7714
7755
  this.materialDoubleSided = false;
@@ -8061,7 +8102,7 @@ export class WebGL2Renderer implements RendererApi {
8061
8102
  const fog = fogModeOf(options.fog ?? true, options.additive === true);
8062
8103
  const toneMapped = options.toneMapped ?? true;
8063
8104
  if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
8064
- if (fog !== FOG_RECEDE) gl.uniform1i(u['uFogEnabled'] ?? null, fog);
8105
+ if (fog !== this.surfaceFog) gl.uniform1i(u['uFogEnabled'] ?? null, fog);
8065
8106
  /* `1` is sRGB alone: the conversion without the curve, which is what dropping the tone map
8066
8107
  means rather than dropping the whole transform. `Math.min` rather than a literal, so a
8067
8108
  renderer asked for `none` stays at none and one asked for `srgb` is already there. */
@@ -8093,15 +8134,15 @@ export class WebGL2Renderer implements RendererApi {
8093
8134
  */
8094
8135
  /* Taken for itself when any option differs from the pass, and put back below: the rule both
8095
8136
  backends count material changes by. See `materialChanges.ts`. */
8096
- const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
8137
+ const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog);
8097
8138
  if (own) this.materials.dirty();
8098
8139
  this.takeMaterial();
8099
8140
  const ownsState = !this.oitReplaying;
8100
8141
  if (ownsState) {
8101
8142
  gl.enable(gl.BLEND);
8102
8143
  /* Added, where asked: colour times alpha onto what is there, and the target's alpha kept. */
8103
- if (options.additive === true) gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE, gl.ZERO, gl.ONE);
8104
- else gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
8144
+ if (options.additive === true) blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
8145
+ else blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
8105
8146
  }
8106
8147
  /*
8107
8148
  * The two depth options, both scoped to this draw and handed straight back — the same
@@ -8133,7 +8174,7 @@ export class WebGL2Renderer implements RendererApi {
8133
8174
  /* Put back for whatever is drawn next, exactly as `uOpacity` and `uTint` are — a pass
8134
8175
  cannot inherit a material from the draw before it. */
8135
8176
  if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
8136
- if (fog !== FOG_RECEDE) gl.uniform1i(u['uFogEnabled'] ?? null, FOG_RECEDE);
8177
+ if (fog !== this.surfaceFog) gl.uniform1i(u['uFogEnabled'] ?? null, this.surfaceFog);
8137
8178
  if (!toneMapped) gl.uniform1i(u['uOutputTransform'] ?? null, this.gradeCode());
8138
8179
  if (own) this.materials.dirty();
8139
8180
  }
@@ -8561,7 +8602,7 @@ export class WebGL2Renderer implements RendererApi {
8561
8602
  }
8562
8603
 
8563
8604
  gl.enable(gl.BLEND);
8564
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
8605
+ blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
8565
8606
  gl.depthMask(false);
8566
8607
  gl.cullFace(inside ? gl.FRONT : gl.BACK);
8567
8608
  mesh.draw(gl);
@@ -8656,7 +8697,7 @@ export class WebGL2Renderer implements RendererApi {
8656
8697
  );
8657
8698
 
8658
8699
  gl.enable(gl.BLEND);
8659
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
8700
+ blendKeeping(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
8660
8701
  gl.depthMask(false);
8661
8702
  mesh.draw(gl);
8662
8703
  gl.depthMask(true);
@@ -8,6 +8,7 @@ import type { UniformFields } from './scatterPass.ts';
8
8
  import { DEPTH_FORMAT } from './flatPass.ts';
9
9
  import type { PipelineCache } from './pipelineCache.ts';
10
10
  import { shaderModule } from './shaderModules.ts';
11
+ import { SCENE_ALPHA_KEEPS } from '../../sceneCoverage.ts';
11
12
 
12
13
  /**
13
14
  * The caustics pass: a surface lit by nearby water.
@@ -162,7 +163,7 @@ export function causticsPipeline(
162
163
  format: cache.format,
163
164
  blend: {
164
165
  color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
165
- alpha: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
166
+ alpha: SCENE_ALPHA_KEEPS,
166
167
  },
167
168
  },
168
169
  ],
@@ -24,9 +24,10 @@ export const DEPTH_VERT_FIELDS = DEPTH_BINDINGS.DEPTH_VERT.fields;
24
24
  export const DEPTH_VERT_SIZE = DEPTH_BINDINGS.DEPTH_VERT.uniformSize;
25
25
  export const DEPTH_FRAG_SIZE = DEPTH_BINDINGS.DEPTH_FRAG.uniformSize;
26
26
  /*
27
- * The cutout variants' vertex blocks. The rigid one is the plain block with `uUvScale` and
28
- * `uAlphaCutout` after every other field, so a slot written for it is written with the plain
29
- * offsets plus these two; the instanced one has no model and no wind, so its two sit earlier.
27
+ * The cutout variants' vertex blocks. The rigid one is the plain block with `uUvScale`,
28
+ * `uUvOffset` and `uAlphaCutout` after every other field, so a slot written for it is written with
29
+ * the plain offsets plus these three; the instanced one has no model and no wind, so its three sit
30
+ * earlier.
30
31
  */
31
32
  export const DEPTH_CUTOUT_VERT_FIELDS = DEPTH_BINDINGS.DEPTH_CUTOUT_VERT.fields;
32
33
  export const DEPTH_CUTOUT_VERT_SIZE = DEPTH_BINDINGS.DEPTH_CUTOUT_VERT.uniformSize;
@@ -9,6 +9,7 @@ import type { UniformFields } from './scatterPass.ts';
9
9
  import { DEPTH_FORMAT } from './flatPass.ts';
10
10
  import type { PipelineCache } from './pipelineCache.ts';
11
11
  import { shaderModule } from './shaderModules.ts';
12
+ import { SCENE_ALPHA_KEEPS } from '../../sceneCoverage.ts';
12
13
 
13
14
  /**
14
15
  * The film pass: a thin wet layer — an oil slick, a puddle — lying over the world.
@@ -111,7 +112,7 @@ export function filmPipeline(
111
112
  format: cache.format,
112
113
  blend: {
113
114
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
114
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
115
+ alpha: SCENE_ALPHA_KEEPS,
115
116
  },
116
117
  },
117
118
  ],
@@ -7,6 +7,7 @@ import {
7
7
  import { vertexBufferLayouts } from './buffers.ts';
8
8
  import type { LitSwitch, PipelineCache } from './pipelineCache.ts';
9
9
  import { shaderModule } from './shaderModules.ts';
10
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
10
11
 
11
12
  /** The material maps the flat pass reads: `2d-array` colour, filtered, not shadow maps. */
12
13
  const SURFACE_MAPS: ReadonlySet<string> = new Set([
@@ -681,7 +682,7 @@ function flatDescriptor(
681
682
  dstFactor: 'one-minus-src-alpha',
682
683
  operation: 'add',
683
684
  },
684
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
685
+ alpha: SCENE_ALPHA_COVERS,
685
686
  },
686
687
  }
687
688
  : { format: cache.format }),
@@ -9,6 +9,7 @@ import type { UniformFields } from './scatterPass.ts';
9
9
  import { DEPTH_FORMAT } from './flatPass.ts';
10
10
  import type { PipelineCache } from './pipelineCache.ts';
11
11
  import { shaderModule } from './shaderModules.ts';
12
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
12
13
 
13
14
  /**
14
15
  * The line pass: a polyline with a real width, four vertices a segment.
@@ -222,7 +223,7 @@ export function linePipeline(
222
223
  dstFactor: 'one-minus-src-alpha',
223
224
  operation: 'add',
224
225
  },
225
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
226
+ alpha: SCENE_ALPHA_COVERS,
226
227
  },
227
228
  },
228
229
  ],
@@ -7,6 +7,7 @@ import type { UniformFields } from './scatterPass.ts';
7
7
  import { DEPTH_FORMAT } from './flatPass.ts';
8
8
  import type { PipelineCache } from './pipelineCache.ts';
9
9
  import { shaderModule } from './shaderModules.ts';
10
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
10
11
 
11
12
  /**
12
13
  * The panel pass: two triangles in screen space, one colour, one alpha.
@@ -121,7 +122,7 @@ export function panelPipeline(
121
122
  format: cache.format,
122
123
  blend: {
123
124
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
124
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
125
+ alpha: SCENE_ALPHA_COVERS,
125
126
  },
126
127
  },
127
128
  ],
@@ -8,6 +8,7 @@ import { DEPTH_FORMAT } from './flatPass.ts';
8
8
  import type { PipelineCache } from './pipelineCache.ts';
9
9
  import type { UniformFields } from './scatterPass.ts';
10
10
  import { shaderModule } from './shaderModules.ts';
11
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
11
12
 
12
13
  /**
13
14
  * The particle pass: a crossed pair of quads per live particle, rebuilt every frame.
@@ -367,7 +368,7 @@ export function particlePipeline(
367
368
  dstFactor: 'one-minus-src-alpha',
368
369
  operation: 'add',
369
370
  },
370
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
371
+ alpha: SCENE_ALPHA_COVERS,
371
372
  },
372
373
  },
373
374
  ],
@@ -9,6 +9,7 @@ import { DEPTH_FORMAT } from './flatPass.ts';
9
9
  import type { PipelineCache } from './pipelineCache.ts';
10
10
  import type { UniformFields } from './scatterPass.ts';
11
11
  import { shaderModule } from './shaderModules.ts';
12
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
12
13
 
13
14
  /**
14
15
  * The plume pass: crossed quads per plume, displaced and faded in the shaders.
@@ -251,7 +252,7 @@ function blendState(blend: 'additive' | 'alpha'): GPUBlendState {
251
252
  }
252
253
  : {
253
254
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
254
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
255
+ alpha: SCENE_ALPHA_COVERS,
255
256
  };
256
257
  }
257
258
 
@@ -39,6 +39,7 @@ import {
39
39
  import type { UniformFields } from './scatterPass.ts';
40
40
  import type { PipelineCache } from './pipelineCache.ts';
41
41
  import { shaderModule } from './shaderModules.ts';
42
+ import { SCENE_ALPHA_KEEPS, SCENE_ALPHA_TRANSMITS } from '../../sceneCoverage.ts';
42
43
 
43
44
  /**
44
45
  * The composite: everything that happens to the frame after the world is drawn.
@@ -271,7 +272,7 @@ export function createPostStageLayout(
271
272
  */
272
273
  export const OIT_COMPOSITE_BLEND: GPUBlendState = {
273
274
  color: { srcFactor: 'one-minus-src-alpha', dstFactor: 'src-alpha', operation: 'add' },
274
- alpha: { srcFactor: 'one-minus-src-alpha', dstFactor: 'src-alpha', operation: 'add' },
275
+ alpha: SCENE_ALPHA_TRANSMITS,
275
276
  };
276
277
 
277
278
  /**
@@ -290,17 +291,19 @@ export const OIT_COMPOSITE_BLEND: GPUBlendState = {
290
291
  * exactly the integral — and the composite therefore never samples the colour it is modifying,
291
292
  * which is the read both backends are entitled to call undefined.
292
293
  *
293
- * Alpha is `(zero, one)`: the frame's own alpha is not the medium's to spend. The scene target
294
- * carries it to the canvas, and a medium that wrote there would make a foggy frame translucent.
294
+ * Alpha is the transmittance again, `share * a`: the scene's alpha is how much of the pixel is
295
+ * still the opaque surface, which is what the composite lets ambient occlusion darken, and a
296
+ * medium thick enough to hide a corner hides its occlusion too (`sceneCoverage.ts`). It reaches
297
+ * no canvas: both are opaque, and the composite writes 1.
295
298
  */
296
299
  export const MEDIUM_TRANSMIT_BLEND: GPUBlendState = {
297
300
  color: { srcFactor: 'one', dstFactor: 'src-alpha', operation: 'add' },
298
- alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
301
+ alpha: SCENE_ALPHA_TRANSMITS,
299
302
  };
300
303
 
301
304
  export const DECAL_MULTIPLY_BLEND: GPUBlendState = {
302
305
  color: { srcFactor: 'zero', dstFactor: 'src', operation: 'add' },
303
- alpha: { srcFactor: 'zero', dstFactor: 'src', operation: 'add' },
306
+ alpha: SCENE_ALPHA_KEEPS,
304
307
  };
305
308
 
306
309
  /**
@@ -316,6 +319,18 @@ export const PREMULTIPLIED_OVER_BLEND: GPUBlendState = {
316
319
  alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
317
320
  };
318
321
 
322
+ /**
323
+ * The same `over` for the resolve that lands a reflection on the scene, keeping the scene's alpha.
324
+ *
325
+ * A reflection is a layer of the surface it is seen in, so the surface's share of the pixel is
326
+ * unchanged (`sceneCoverage.ts`). The trace keeps `PREMULTIPLIED_OVER_BLEND`, because its target is
327
+ * the reflection's own and its alpha is the coverage this resolve reads.
328
+ */
329
+ export const REFLECTION_OVER_BLEND: GPUBlendState = {
330
+ color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
331
+ alpha: SCENE_ALPHA_KEEPS,
332
+ };
333
+
319
334
  export const ADDITIVE_BLEND: GPUBlendState = {
320
335
  color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
321
336
  alpha: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
@@ -254,6 +254,7 @@ import {
254
254
  DECAL_TEXTURES,
255
255
  DECAL_UNIFORMS,
256
256
  PREMULTIPLIED_OVER_BLEND,
257
+ REFLECTION_OVER_BLEND,
257
258
  SSR_FRAG_FIELDS,
258
259
  SSR_FRAG_SIZE,
259
260
  SSR_RESOLVE_FRAG_WGSL,
@@ -492,7 +493,7 @@ import {
492
493
  import { aimReflection, createGpuReflection, type GpuReflection } from './reflectionPass.ts';
493
494
  import { FrameBudget } from '../budget.ts';
494
495
  import { MaterialChanges, ownsMaterial } from '../materialChanges.ts';
495
- import { FOG_RECEDE, fogModeOf } from '../drawFog.ts';
496
+ import { FOG_OFF, FOG_RECEDE, fogModeOf } from '../drawFog.ts';
496
497
  import { DYNAMIC_ALIGNMENT as DYNAMIC_UNIFORM_ALIGNMENT, UniformRing } from './uniformRing.ts';
497
498
  import { DecalQueue, MAX_DRAWN_DECALS } from '../../decalQueue.ts';
498
499
  import { DistanceFieldScene } from '../../gi/fieldScene.ts';
@@ -2805,6 +2806,13 @@ export class WebGPURenderer implements RendererApi {
2805
2806
  private blankAlbedoBindGroup: GPUBindGroup;
2806
2807
  /** Repeats across the mesh's own UV range. Per draw, because `uUvScale` is a vertex uniform. */
2807
2808
  private readonly uvScale = new Float32Array([1, 1]);
2809
+ /** Where the texture starts, after the scale. Per draw, beside `uvScale`. */
2810
+ private readonly uvOffset = new Float32Array([0, 0]);
2811
+ /**
2812
+ * How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
2813
+ * each `bindMeshPass`. A translucent draw's own fog is measured against this and puts it back.
2814
+ */
2815
+ private surfaceFog = FOG_RECEDE;
2808
2816
  /** Whether the bound material is seen from both faces. See `SurfaceMaterial.doubleSided`. */
2809
2817
  private materialDoubleSided = false;
2810
2818
  /** Whether the bound material has a map, for the warning a mesh with no coordinates earns. */
@@ -2940,6 +2948,8 @@ export class WebGPURenderer implements RendererApi {
2940
2948
  const albedo = material?.albedo ?? null;
2941
2949
  this.uvScale[0] = material?.uScale ?? 1;
2942
2950
  this.uvScale[1] = material?.vScale ?? 1;
2951
+ this.uvOffset[0] = material?.uOffset ?? 0;
2952
+ this.uvOffset[1] = material?.vOffset ?? 0;
2943
2953
  this.materialDoubleSided = material?.doubleSided === true;
2944
2954
  this.materialHasMaps = materialHasMaps(material);
2945
2955
  const normal = (material?.normal ?? null) as GpuSurfaceTexture | null;
@@ -3104,6 +3114,14 @@ export class WebGPURenderer implements RendererApi {
3104
3114
  });
3105
3115
  }
3106
3116
 
3117
+ /** Whether the surfaces drawn next meet the medium. See `renderer.ts`. */
3118
+ setSurfaceFog(enabled: boolean): void {
3119
+ this.surfaceFog = enabled ? FOG_RECEDE : FOG_OFF;
3120
+ this.material((_f, i) => {
3121
+ i[this.materialField('uFogEnabled')] = this.surfaceFog;
3122
+ });
3123
+ }
3124
+
3107
3125
  /**
3108
3126
  * How much of the surfaces drawn next survives a screen-door dither. See `renderer.ts`.
3109
3127
  *
@@ -5392,17 +5410,20 @@ export class WebGPURenderer implements RendererApi {
5392
5410
  return group;
5393
5411
  }
5394
5412
 
5395
- /** The cutoff and UV scale into a cutout draw's slot, at the offsets its own variant declares. */
5413
+ /** The cutoff and UV scale and offset into a cutout draw's slot, at its own variant's offsets. */
5396
5414
  private writeCutout(
5397
5415
  slot: number,
5398
5416
  cutout: CutoutCaster<unknown>,
5399
5417
  fields: {
5400
5418
  readonly uUvScale: { readonly offset: number };
5419
+ readonly uUvOffset: { readonly offset: number };
5401
5420
  readonly uAlphaCutout: { readonly offset: number };
5402
5421
  },
5403
5422
  ): void {
5404
5423
  this.shadowDraws.writeFloat(slot, fields.uUvScale.offset, cutout.u);
5405
5424
  this.shadowDraws.writeFloat(slot, fields.uUvScale.offset + 4, cutout.v);
5425
+ this.shadowDraws.writeFloat(slot, fields.uUvOffset.offset, cutout.uOffset);
5426
+ this.shadowDraws.writeFloat(slot, fields.uUvOffset.offset + 4, cutout.vOffset);
5406
5427
  this.shadowDraws.writeFloat(slot, fields.uAlphaCutout.offset, cutout.cutoff);
5407
5428
  }
5408
5429
 
@@ -7783,7 +7804,8 @@ export class WebGPURenderer implements RendererApi {
7783
7804
  * these per draw through the material block, the same shape `uOpacity` already uses.
7784
7805
  */
7785
7806
  i[at('uLightingEnabled')] = 1;
7786
- i[at('uFogEnabled')] = 1;
7807
+ i[at('uFogEnabled')] = FOG_RECEDE;
7808
+ this.surfaceFog = FOG_RECEDE;
7787
7809
  i[at('uAlbedoEnabled')] = 0;
7788
7810
  this.materialHasMaps = false;
7789
7811
 
@@ -8235,6 +8257,8 @@ export class WebGPURenderer implements RendererApi {
8235
8257
  this.bindGroup = this.blankAlbedoBindGroup;
8236
8258
  this.uvScale[0] = 1;
8237
8259
  this.uvScale[1] = 1;
8260
+ this.uvOffset[0] = 0;
8261
+ this.uvOffset[1] = 0;
8238
8262
  }
8239
8263
 
8240
8264
  /**
@@ -8319,7 +8343,10 @@ export class WebGPURenderer implements RendererApi {
8319
8343
  */
8320
8344
  if (this.tracksMotionNow()) this.recordDrawMotion(mesh, model, previousModel);
8321
8345
  noteMapsWithoutUvs(mesh, mesh.hasUvs, this.materialHasMaps);
8322
- this.submitMesh(mesh, model, tint, 1, false, { depthLayer });
8346
+ /* An opaque draw meets the medium as the dial says: `submitMesh` reads an absent `fog` as
8347
+ true, which is a translucent draw's default and would put every opaque draw back in the fog
8348
+ `setSurfaceFog(false)` took it out of. */
8349
+ this.submitMesh(mesh, model, tint, 1, false, { depthLayer, fog: this.surfaceFog !== FOG_OFF });
8323
8350
  }
8324
8351
 
8325
8352
  /**
@@ -8501,6 +8528,7 @@ export class WebGPURenderer implements RendererApi {
8501
8528
  /* The material's repeats, not a constant: geometry authored in metres is textured at
8502
8529
  whatever density the material asks for without rebuilding it. */
8503
8530
  this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
8531
+ this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
8504
8532
  this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
8505
8533
  /*
8506
8534
  * Whether this mesh's tangent frame is real. WebGPU has no disabled attribute, so location 10
@@ -8534,7 +8562,7 @@ export class WebGPURenderer implements RendererApi {
8534
8562
  /* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
8535
8563
  const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
8536
8564
  const toneMapped = options.toneMapped ?? true;
8537
- const unlitOrUnfogged = !lit || fog !== FOG_RECEDE;
8565
+ const unlitOrUnfogged = !lit || fog !== this.surfaceFog;
8538
8566
  if (unlitOrUnfogged) {
8539
8567
  if (!lit) this.perFrameInts[this.materialField('uLightingEnabled')] = 0;
8540
8568
  this.perFrameInts[this.materialField('uFogEnabled')] = fog;
@@ -8556,7 +8584,7 @@ export class WebGPURenderer implements RendererApi {
8556
8584
  const refracting = this.bindSeeThrough(options);
8557
8585
  /* Taken for itself when any of those differs from the pass, and put back below: the rule both
8558
8586
  backends count material changes by. See `materialChanges.ts`. */
8559
- const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
8587
+ const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog);
8560
8588
  if (own) this.materials.dirty();
8561
8589
  const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
8562
8590
  /*
@@ -8700,7 +8728,7 @@ export class WebGPURenderer implements RendererApi {
8700
8728
  }
8701
8729
  if (unlitOrUnfogged) {
8702
8730
  if (!lit) this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
8703
- this.perFrameInts[this.materialField('uFogEnabled')] = FOG_RECEDE;
8731
+ this.perFrameInts[this.materialField('uFogEnabled')] = this.surfaceFog;
8704
8732
  }
8705
8733
  /* Back to off for every other draw in the frame, exactly as `uOpacity` is: this scratch is
8706
8734
  shared, so a strength left set is worn by everything drawn after it. */
@@ -8947,6 +8975,7 @@ export class WebGPURenderer implements RendererApi {
8947
8975
  lands ? this.correctedViewProj : this.viewProj,
8948
8976
  );
8949
8977
  this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
8978
+ this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
8950
8979
  this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
8951
8980
  this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
8952
8981
 
@@ -8957,7 +8986,7 @@ export class WebGPURenderer implements RendererApi {
8957
8986
  const lit = options.lit ?? true;
8958
8987
  const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
8959
8988
  const toneMapped = options.toneMapped ?? true;
8960
- const unlitOrUnfogged = !lit || fog !== FOG_RECEDE;
8989
+ const unlitOrUnfogged = !lit || fog !== this.surfaceFog;
8961
8990
  if (unlitOrUnfogged) {
8962
8991
  if (!lit) this.perFrameInts[this.materialField('uLightingEnabled')] = 0;
8963
8992
  this.perFrameInts[this.materialField('uFogEnabled')] = fog;
@@ -8970,7 +8999,7 @@ export class WebGPURenderer implements RendererApi {
8970
8999
  const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
8971
9000
  /* A blended batch shows what is behind it — refraction or glass — as a single draw does. */
8972
9001
  const refracting = blend && this.bindSeeThrough(options);
8973
- const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
9002
+ const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog);
8974
9003
  if (own) this.materials.dirty();
8975
9004
  const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
8976
9005
  /* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
@@ -9052,7 +9081,7 @@ export class WebGPURenderer implements RendererApi {
9052
9081
  }
9053
9082
  if (unlitOrUnfogged) {
9054
9083
  this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
9055
- this.perFrameInts[this.materialField('uFogEnabled')] = FOG_RECEDE;
9084
+ this.perFrameInts[this.materialField('uFogEnabled')] = this.surfaceFog;
9056
9085
  }
9057
9086
  if (!toneMapped) {
9058
9087
  this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
@@ -11614,7 +11643,7 @@ export class WebGPURenderer implements RendererApi {
11614
11643
  'post.ssrResolve',
11615
11644
  SSR_RESOLVE_FRAG_WGSL,
11616
11645
  this.pipelines.format,
11617
- PREMULTIPLIED_OVER_BLEND,
11646
+ REFLECTION_OVER_BLEND,
11618
11647
  ),
11619
11648
  );
11620
11649
  resolve.setBindGroup(0, resolveGroup);
@@ -11,6 +11,7 @@ import type { SdfFont } from '../../sdfFont.ts';
11
11
  import { SdfTextLayout, type SdfTextStyle } from '../../sdfTextLayout.ts';
12
12
  import type { GpuSurfaceTexture } from './surfaceTexturePass.ts';
13
13
  import { shaderModule } from './shaderModules.ts';
14
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
14
15
 
15
16
  /**
16
17
  * The SDF text pass: a string as flat, textured quads, drawn into the scene.
@@ -213,7 +214,7 @@ export function sdfTextPipeline(
213
214
  format: cache.format,
214
215
  blend: {
215
216
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
216
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
217
+ alpha: SCENE_ALPHA_COVERS,
217
218
  },
218
219
  },
219
220
  ],
@@ -9,6 +9,7 @@ import type { UniformFields } from './scatterPass.ts';
9
9
  import { DEPTH_FORMAT } from './flatPass.ts';
10
10
  import type { PipelineCache } from './pipelineCache.ts';
11
11
  import { shaderModule } from './shaderModules.ts';
12
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
12
13
 
13
14
  /**
14
15
  * The text pass: a string as instanced glyph cubes, drawn over the scene.
@@ -208,7 +209,7 @@ export function textPipeline(
208
209
  format: cache.format,
209
210
  blend: {
210
211
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
211
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
212
+ alpha: SCENE_ALPHA_COVERS,
212
213
  },
213
214
  },
214
215
  ],
@@ -9,6 +9,7 @@ import { DEPTH_FORMAT } from './flatPass.ts';
9
9
  import type { PipelineCache } from './pipelineCache.ts';
10
10
  import type { UniformFields } from './scatterPass.ts';
11
11
  import { shaderModule } from './shaderModules.ts';
12
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
12
13
 
13
14
  /**
14
15
  * The water pass: two sheets and a patch, displaced by a wave field in the vertex stage.
@@ -199,7 +200,7 @@ export function waterPipeline(
199
200
  format: cache.format,
200
201
  blend: {
201
202
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
202
- alpha: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
203
+ alpha: SCENE_ALPHA_COVERS,
203
204
  },
204
205
  },
205
206
  ],