@driftengine/core 4.11.0 → 4.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.json +72 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +4 -2
  4. package/dist/index.js +1 -1
  5. package/dist/render/ambientOcclusionPass.d.ts +3 -2
  6. package/dist/render/ambientOcclusionPass.js +4 -2
  7. package/dist/render/backend/api.d.ts +21 -1
  8. package/dist/render/backend/webgl2/renderer.d.ts +61 -5
  9. package/dist/render/backend/webgl2/renderer.js +153 -21
  10. package/dist/render/backend/webgpu/renderer.d.ts +54 -1
  11. package/dist/render/backend/webgpu/renderer.js +246 -89
  12. package/dist/render/instances.d.ts +13 -1
  13. package/dist/render/instances.js +10 -0
  14. package/dist/render/occlusionFade.d.ts +21 -0
  15. package/dist/render/occlusionFade.js +27 -0
  16. package/dist/render/sceneTarget.d.ts +2 -0
  17. package/dist/render/sceneTarget.js +1 -1
  18. package/dist/render/shaders/ambientOcclusion.js +12 -1
  19. package/dist/render/shaders/flat/areaLight.d.ts +1 -1
  20. package/dist/render/shaders/flat/areaLight.js +1 -1
  21. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  22. package/dist/render/shaders/flat/glassTint.js +1 -0
  23. package/dist/render/shaders/flat/index.d.ts +5 -0
  24. package/dist/render/shaders/flat/index.js +10 -3
  25. package/dist/render/shaders/flat/layerLooks.d.ts +16 -0
  26. package/dist/render/shaders/flat/layerLooks.js +116 -0
  27. package/dist/render/shaders/flat/layered.d.ts +19 -9
  28. package/dist/render/shaders/flat/layered.js +169 -37
  29. package/dist/render/shaders/flat/main.d.ts +1 -1
  30. package/dist/render/shaders/flat/main.js +17 -5
  31. package/dist/render/shaders/flat/models.js +1 -1
  32. package/dist/render/shaders/flat/normalMap.d.ts +1 -1
  33. package/dist/render/shaders/flat/normalMap.js +11 -2
  34. package/dist/render/shaders/flat/preamble.js +6 -0
  35. package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +6 -1
  36. package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +6 -1
  37. package/dist/render/shaders/generated/flat.wgsl.d.ts +176 -48
  38. package/dist/render/shaders/generated/flat.wgsl.js +215 -87
  39. package/dist/render/surfaceLayerLooks.d.ts +80 -0
  40. package/dist/render/surfaceLayerLooks.js +83 -0
  41. package/dist/render/surfaceLayers.d.ts +128 -19
  42. package/dist/render/surfaceLayers.js +73 -13
  43. package/dist/render/surfaceTexture.d.ts +13 -4
  44. package/dist/render/transmission.d.ts +21 -0
  45. package/dist/render/transmission.js +21 -0
  46. package/dist/ui/splash.d.ts +42 -2
  47. package/dist/ui/splash.js +78 -13
  48. package/package.json +6 -6
  49. package/scripts/browser.d.mts +5 -0
  50. package/scripts/browser.mjs +68 -14
  51. package/scripts/look.d.mts +81 -0
  52. package/scripts/look.mjs +380 -0
  53. package/src/index.ts +4 -0
  54. package/src/render/ambientOcclusionPass.ts +4 -1
  55. package/src/render/backend/api.ts +31 -0
  56. package/src/render/backend/webgl2/renderer.ts +170 -22
  57. package/src/render/backend/webgpu/renderer.ts +379 -139
  58. package/src/render/instances.ts +18 -1
  59. package/src/render/occlusionFade.ts +29 -0
  60. package/src/render/sceneTarget.ts +3 -0
  61. package/src/render/shaders/ambientOcclusion.ts +12 -1
  62. package/src/render/shaders/flat/areaLight.ts +1 -1
  63. package/src/render/shaders/flat/glassTint.ts +3 -1
  64. package/src/render/shaders/flat/index.ts +15 -3
  65. package/src/render/shaders/flat/layerLooks.ts +116 -0
  66. package/src/render/shaders/flat/layered.ts +170 -37
  67. package/src/render/shaders/flat/main.ts +17 -5
  68. package/src/render/shaders/flat/models.ts +1 -1
  69. package/src/render/shaders/flat/normalMap.ts +11 -2
  70. package/src/render/shaders/flat/preamble.ts +6 -0
  71. package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +6 -1
  72. package/src/render/shaders/generated/flat.wgsl.ts +215 -87
  73. package/src/render/surfaceLayerLooks.ts +155 -0
  74. package/src/render/surfaceLayers.ts +187 -23
  75. package/src/render/surfaceTexture.ts +13 -4
  76. package/src/render/transmission.ts +27 -0
  77. package/src/ui/splash.ts +83 -14
@@ -37,8 +37,11 @@ import { compileProgram, finishProgram, startProgram, uniformLocations } from '.
37
37
  import { countUniformVectors, FULL_LIGHT_BUDGET, nextLightBudget, planLightBudget, } from '../../uniformVectorBudget.js';
38
38
  import { OVERLAY_FLOATS, overlayLays, packSurfaceOverlay, } from '../../surfaceOverlay.js';
39
39
  import { PROJECTION_FLOATS, packSurfaceProjection, projects } from '../../surfaceProjection.js';
40
+ import { resolveTransmission } from '../../transmission.js';
41
+ import { resolveOcclusionFade } from '../../occlusionFade.js';
40
42
  import { orthographicDepthSpan } from '../../lightMatrix.js';
41
- import { LAYER_FLOATS, packSurfaceLayers } from '../../surfaceLayers.js';
43
+ import { LAYER_FLOATS, layerMaskMap, layeredProjection, layersFitBeside, packSurfaceLayers, } from '../../surfaceLayers.js';
44
+ import { LOOK_FLOATS, asksLayerLooks, packLayerLooks } from '../../surfaceLayerLooks.js';
42
45
  import { FILM_FRAG, FILM_VERT } from '../../shaders/film.js';
43
46
  import { SceneTarget } from '../../sceneTarget.js';
44
47
  import { FILM_LOOK_WITHOUT_COMPOSITE } from '../../shaders/filmLook.js';
@@ -785,6 +788,8 @@ export class WebGL2Renderer {
785
788
  aoProjScale = new Float32Array(2);
786
789
  /** Where this frame's jitter put the occlusion, in the composite's uv. See `uAoOffset`. */
787
790
  aoOffset = new Float32Array(2);
791
+ /** Where occlusion fades out with distance, `uFade`: none until a caller sets one. */
792
+ aoFade = new Float32Array([-1, 0]);
788
793
  /**
789
794
  * Whether this frame has already been resolved to the canvas.
790
795
  *
@@ -952,14 +957,23 @@ export class WebGL2Renderer {
952
957
  warnedProjectionRoom = false;
953
958
  /** `uWorldUv`, the current material's projection packed. See `surfaceProjection.ts`. */
954
959
  projection = new Float32Array(PROJECTION_FLOATS);
955
- /** The current material's `diffuseTransmission`, `uGlassTint.w`, 0 to 1. */
956
- diffuseTransmission = 0;
960
+ /**
961
+ * `uGlassTint` as the current material leaves it: its light from behind and that light's colour,
962
+ * which a pane overwrites for its own draw and `unbindSeeThrough` puts back. See `transmission.ts`.
963
+ */
964
+ transmission = new Float32Array([-1, -1, -1, 0]);
957
965
  /** `uLayers`, the current material's layers packed. See `surfaceLayers.ts`. */
958
966
  layerFloats = new Float32Array(LAYER_FLOATS);
967
+ /** `uLayerLooks`, the current material's layer picks and looks packed. See `surfaceLayerLooks.ts`. */
968
+ lookFloats = new Float32Array(LOOK_FLOATS);
959
969
  /** Whether the lit stage has room for layers' two vectors, and which refusals were said. */
960
970
  layersRoom = null;
961
971
  warnedLayersRoom = false;
972
+ /** Whether it has room for the layers' looks beside them, and whether the refusal was said. */
973
+ looksRoom = null;
974
+ warnedLooksRoom = false;
962
975
  warnedLayersModelMap = false;
976
+ warnedLayersTriplanar = false;
963
977
  warnedOverlayRoom = false;
964
978
  warnedOverlayBlocks = false;
965
979
  /** Whether a probe is being baked, so the passes that would recurse into one can tell. */
@@ -2861,6 +2875,57 @@ export class WebGL2Renderer {
2861
2875
  * `uScale`/`vScale` are repeats across the mesh's own UV range: geometry authored in
2862
2876
  * metres is textured at whatever density the material wants without rebuilding it.
2863
2877
  */
2878
+ /**
2879
+ * Compile, off the frame, every program a draw of `mesh` in `material` takes that making the mesh
2880
+ * did not, so the first frame that draws it waits on none: the program of the material's shading
2881
+ * model for each vertex variant the mesh can be drawn with (its rig, with a palette; its targets,
2882
+ * with weights and without), a skin's halves under the screen-space blur, and the lit switches
2883
+ * the material turns on. `ready()` resolves once they have.
2884
+ *
2885
+ * **For `pipelineCompile: 'skip'` above all**, where a draw whose program is new is left out until
2886
+ * it lands: a figure brought on screen whole rather than without its face and hair for the frames
2887
+ * those take. Where draws wait, the programs compile here, at the call, rather than in a frame.
2888
+ * Two-sided and cut-out surfaces are draw state on this backend and need nothing; on WebGPU they
2889
+ * are pipelines of their own, prepared the same way.
2890
+ *
2891
+ * **Call it between frames**: it sets `material` to read it and leaves no material set after, as
2892
+ * `setMaterial(null)` does. `options` is the other backend's; blended draws share a program here.
2893
+ */
2894
+ prepareMesh(mesh, material, _options = {}) {
2895
+ if (this.contextLost)
2896
+ return;
2897
+ this.setMaterial(material);
2898
+ const kind = material?.model?.kind ?? null;
2899
+ if (kind !== null) {
2900
+ const eight = mesh.isSkinnedEight;
2901
+ const morphs = mesh.morph !== null;
2902
+ const plain = mesh.isSkinned ? (eight ? 'skinned8' : 'skinned') : 'plain';
2903
+ const morphed = mesh.isSkinned ? (eight ? 'both8' : 'both') : 'morphed';
2904
+ for (const variant of morphs ? [plain, morphed] : [plain]) {
2905
+ this.modelProgram(variant, kind);
2906
+ /* A skin under the screen-space blur draws three halves, each a program of its own. */
2907
+ if (kind === 'skin' && this.skinScatter !== null && this.sceneTarget !== null) {
2908
+ for (const half of SPLIT_SKIN_HALVES)
2909
+ this.modelProgram(variant, kind, half);
2910
+ }
2911
+ }
2912
+ }
2913
+ this.setMaterial(null);
2914
+ }
2915
+ /** `prepareMesh` for an instanced batch: its own programs, and its model's for its variant. */
2916
+ prepareInstanced(batch, material, _options = {}) {
2917
+ if (this.contextLost)
2918
+ return;
2919
+ this.setMaterial(material);
2920
+ const animated = batch.animation !== null;
2921
+ this.ensureInstancedProgram();
2922
+ if (animated)
2923
+ this.ensureAnimatedProgram();
2924
+ const kind = material?.model?.kind ?? null;
2925
+ if (kind !== null)
2926
+ this.modelProgram(animated ? 'animated' : 'instanced', kind);
2927
+ this.setMaterial(null);
2928
+ }
2864
2929
  /**
2865
2930
  * Choose the joint palette the following `drawMesh` calls skin by, or null for none.
2866
2931
  *
@@ -3197,9 +3262,9 @@ export class WebGL2Renderer {
3197
3262
  * and read neither — one decision for every program, as `setMaterial` says.
3198
3263
  */
3199
3264
  const modelMap = material?.modelMap ?? null;
3200
- /* A layered material's mask goes where a model's map would: see `SurfaceMaterial.layers`. */
3265
+ /* A layered material's mask, where it is a map, goes where a model's would: surfaceLayers.ts. */
3201
3266
  const layers = this.layersOf(material);
3202
- const boundMap = modelMap ?? layers?.mask ?? null;
3267
+ const boundMap = modelMap ?? layerMaskMap(layers);
3203
3268
  gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
3204
3269
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
3205
3270
  if (boundMap !== null)
@@ -3208,6 +3273,10 @@ export class WebGL2Renderer {
3208
3273
  if (layers !== null)
3209
3274
  this.askLit('LAYERED');
3210
3275
  gl.uniform4fv(u['uLayers'] ?? null, this.layerFloats);
3276
+ packLayerLooks(layers, this.lookFloats);
3277
+ if (this.looksFit(layers))
3278
+ this.askLit('LAYER_LOOKS');
3279
+ gl.uniform4fv(u['uLayerLooks'] ?? null, this.lookFloats);
3211
3280
  gl.uniform1i(u['uModelMap'] ?? null, MODEL_MAP_TEXTURE_UNIT);
3212
3281
  packModel(material?.model ?? null, modelMap !== null, this.modelParams, material?.physicalSpecular === true);
3213
3282
  /* The first physical highlight asked for is when the lit programs start carrying one. */
@@ -3217,11 +3286,13 @@ export class WebGL2Renderer {
3217
3286
  /* The draw's overlay, which a program born mid-pass must wear too: see `setSurfaceOverlay`. */
3218
3287
  gl.uniform4fv(u['uOverlay'] ?? null, this.overlayFloats);
3219
3288
  gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
3220
- /* How much light from behind the surface lets through, in the glass tint's spare lane. */
3221
- this.diffuseTransmission = Math.min(Math.max(material?.diffuseTransmission ?? 0, 0), 1) || 0;
3222
- gl.uniform4f(u['uGlassTint'] ?? null, 1, 1, 1, this.diffuseTransmission);
3289
+ /* How much light from behind the surface lets through, and its colour: transmission.ts. */
3290
+ resolveTransmission(material, this.transmission);
3291
+ this.writeTransmission(u);
3223
3292
  /* Maps placed by the world, where the material asks and the lit stage has room: worldUv.ts. */
3224
- const projection = material?.projection ?? null;
3293
+ const projection = layeredProjection(material?.projection, layers);
3294
+ if (projection !== (material?.projection ?? null))
3295
+ this.warnLayersTriplanar();
3225
3296
  packSurfaceProjection(this.projectionFits(projection) ? projection : null, this.projection);
3226
3297
  if (this.projection[0] > 0)
3227
3298
  this.askLit('WORLD_UVS');
@@ -3455,14 +3526,15 @@ export class WebGL2Renderer {
3455
3526
  * no room for their two vectors each leave one layer, the last two said once each.
3456
3527
  */
3457
3528
  layersOf(material) {
3458
- const layers = material?.layers ?? null;
3529
+ const layers = (material?.layers ?? null);
3459
3530
  if (layers === null || layers.repeats.length === 0)
3460
3531
  return null;
3461
- if ((material?.modelMap ?? null) !== null) {
3532
+ if (!layersFitBeside(layers, material?.modelMap)) {
3462
3533
  if (!this.warnedLayersModelMap) {
3463
3534
  this.warnedLayersModelMap = true;
3464
- console.warn("Renderer: a material's layers are drawn as one: its mask is read where a model's map " +
3465
- 'goes, and this material carries a `modelMap` too.');
3535
+ console.warn("Renderer: a material's layers are drawn as one: its mask is a map, read where a " +
3536
+ "model's map goes, and this material carries a `modelMap` too. A mask in the ORM " +
3537
+ "array (`mask: 'orm'`) or the vertex colour (`mask: 'vertex'`) leaves the slot free.");
3466
3538
  }
3467
3539
  return null;
3468
3540
  }
@@ -3471,15 +3543,49 @@ export class WebGL2Renderer {
3471
3543
  return layers;
3472
3544
  if (!this.warnedLayersRoom) {
3473
3545
  this.warnedLayersRoom = true;
3474
- console.warn("Renderer: a material's layers are refused on this device. They take two fragment uniform " +
3546
+ console.warn("Renderer: a material's layers are refused on this device. They take four fragment uniform " +
3475
3547
  'vectors the lit shader does not have; the material is drawn as its base layer. A lower ' +
3476
3548
  '`maxLights` makes the room, or WebGPU has it.');
3477
3549
  }
3478
3550
  return null;
3479
3551
  }
3480
- /** Whether the lit shader with `feature` compiled in fits this GPU's fragment uniform vectors. */
3481
- switchFits(feature) {
3552
+ /**
3553
+ * Whether a layered material's picks and looks are drawn: asked for, and room for their fifteen
3554
+ * vectors beside the layers' four. Refused, the layers are drawn reading layer `i` of their arrays
3555
+ * as they are, said once — which on a material sharing its arrays reads the wrong textures, so
3556
+ * the sentence names the way out.
3557
+ */
3558
+ looksFit(layers) {
3559
+ if (!asksLayerLooks(layers))
3560
+ return false;
3561
+ this.looksRoom ??= this.switchFits('LAYER_LOOKS', 'LAYERED');
3562
+ if (this.looksRoom)
3563
+ return true;
3564
+ if (!this.warnedLooksRoom) {
3565
+ this.warnedLooksRoom = true;
3566
+ console.warn("Renderer: a layered material's picks and looks are refused on this device. They take " +
3567
+ 'fifteen fragment uniform vectors the lit shader does not have, so its layers read layer ' +
3568
+ 'i of its arrays, untinted, at one repeat each. A lower `maxLights` makes the room, or ' +
3569
+ 'WebGPU has it.');
3570
+ }
3571
+ return false;
3572
+ }
3573
+ /** Says once that a layered material's triplanar projection is laid on the horizontal plane. */
3574
+ warnLayersTriplanar() {
3575
+ if (this.warnedLayersTriplanar)
3576
+ return;
3577
+ this.warnedLayersTriplanar = true;
3578
+ console.warn("Renderer: a layered material's triplanar projection is laid on the horizontal plane: every " +
3579
+ "layer's maps on three planes would be three times the reads. See `SurfaceMaterial.layers`.");
3580
+ }
3581
+ /**
3582
+ * Whether the lit shader with `feature` compiled in, and `beside` with it where a switch means
3583
+ * nothing alone, fits this GPU's fragment uniform vectors.
3584
+ */
3585
+ switchFits(feature, beside) {
3482
3586
  const asIf = { [feature]: true };
3587
+ if (beside !== undefined)
3588
+ asIf[beside] = true;
3483
3589
  return (countUniformVectors(this.flatFragAt(this.lightBudget, asIf)) <= this.fragmentVectorLimit());
3484
3590
  }
3485
3591
  /**
@@ -3635,7 +3741,8 @@ export class WebGL2Renderer {
3635
3741
  *
3636
3742
  * What it gives up: a fade reads as grain while it lasts, which a temporal resolve smooths and a
3637
3743
  * plain frame does not; a blended draw that asks spends its opacity here and blends none of it;
3638
- * and a lightmapped batch carries regions where the opacities would be, so it has none to fade.
3744
+ * and a lightmapped batch's opacities ride its page regions, so they fade in steps of a
3745
+ * sixty-fourth, which is the pattern's own resolution (`MeshInstances.lightmapRegions`).
3639
3746
  */
3640
3747
  setDitherOpacity(on) {
3641
3748
  if (this.contextLost)
@@ -4106,13 +4213,20 @@ export class WebGL2Renderer {
4106
4213
  const tint = options.refractTint ?? WHITE_TINT;
4107
4214
  gl.uniform4f(u['uRefractTint'] ?? null, tint[0] ?? 1, tint[1] ?? 1, tint[2] ?? 1, 1);
4108
4215
  /* The pane's colour; w is the material's light through from behind, which glass never reads. */
4109
- gl.uniform4f(u['uGlassTint'] ?? null, glass.tint[0] ?? 1, glass.tint[1] ?? 1, glass.tint[2] ?? 1, this.diffuseTransmission);
4216
+ gl.uniform4f(u['uGlassTint'] ?? null, glass.tint[0] ?? 1, glass.tint[1] ?? 1, glass.tint[2] ?? 1, this.transmission[3]);
4110
4217
  return true;
4111
4218
  }
4219
+ /** `uGlassTint` as the current material leaves it. See `transmission`. */
4220
+ writeTransmission(u) {
4221
+ const t = this.transmission;
4222
+ this.gl.uniform4f(u['uGlassTint'] ?? null, t[0] ?? -1, t[1] ?? -1, t[2] ?? -1, t[3] ?? 0);
4223
+ }
4112
4224
  /** Put back what `bindSeeThrough` bound: the binding outlives its frame (the 2026-08-27 rule). */
4113
4225
  unbindSeeThrough(u) {
4114
4226
  const { gl } = this;
4115
4227
  gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
4228
+ /* The material's light through, over the pane's tint: transmission.ts. */
4229
+ this.writeTransmission(u);
4116
4230
  gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
4117
4231
  /* The overlay's atlas, where one is held: it shares the unit. See `setSurfaceOverlay`. */
4118
4232
  gl.bindTexture(gl.TEXTURE_2D, this.overlayTexture ?? this.emptyTexture2D);
@@ -5417,6 +5531,7 @@ export class WebGL2Renderer {
5417
5531
  worldUvs: asIf.WORLD_UVS ?? this.litOn.WORLD_UVS,
5418
5532
  movingSun: asIf.MOVING_SUN ?? this.litOn.MOVING_SUN,
5419
5533
  layered: asIf.LAYERED ?? this.litOn.LAYERED,
5534
+ layerLooks: asIf.LAYER_LOOKS ?? this.litOn.LAYER_LOOKS,
5420
5535
  maxLights: budget.maxLights,
5421
5536
  maxAreaLights: budget.maxAreaLights,
5422
5537
  });
@@ -6177,6 +6292,19 @@ export class WebGL2Renderer {
6177
6292
  }
6178
6293
  this.sceneTarget.setLocalExposure(strength);
6179
6294
  }
6295
+ /**
6296
+ * Where ambient occlusion fades out with distance: whole up to `distance` metres from the eye and
6297
+ * gone `radius` metres past it, so the far depth buffer's steps — a sky dome kilometres off, a
6298
+ * mountain range — are not shaded in rings, and those pixels skip the occlusion's sampling.
6299
+ *
6300
+ * A distance that is not a finite number at or above zero is no fade, which is the default, and
6301
+ * `Infinity` reads as "never fades"; a radius below zero is a cut at `distance`. Set before
6302
+ * `endFrame` and held until changed, like `setDepthOfField`. Nothing at all when
6303
+ * `ambientOcclusion` is 0. See `occlusionFade.ts` for what it gives up.
6304
+ */
6305
+ setAmbientOcclusionFade(distance, radius) {
6306
+ resolveOcclusionFade(distance, radius, this.aoFade);
6307
+ }
6180
6308
  /**
6181
6309
  * Where this frame's lens is focused, how deep the sharp zone is, and how much of the ceiling
6182
6310
  * to take.
@@ -6804,6 +6932,7 @@ export class WebGL2Renderer {
6804
6932
  radius: this.quality.ambientOcclusionRadius,
6805
6933
  projScale: this.aoProjScale,
6806
6934
  invProjection: this.aoInvProjection,
6935
+ fade: this.aoFade,
6807
6936
  offset: this.aoOffset,
6808
6937
  };
6809
6938
  }
@@ -7256,9 +7385,10 @@ export class WebGL2Renderer {
7256
7385
  /* Nor a projection: maps follow the mesh's coordinates until a material asks otherwise. */
7257
7386
  this.projection.fill(0);
7258
7387
  /* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
7259
- this.diffuseTransmission = 0;
7388
+ resolveTransmission(null, this.transmission);
7260
7389
  /* Nor layers: one layer until a material asks for more. */
7261
7390
  this.layerFloats.fill(0);
7391
+ this.lookFloats.fill(0);
7262
7392
  this.overlayMaps = null;
7263
7393
  this.overlayTexture = null;
7264
7394
  /* Nor an ambient of its own: the frame's, until a draw asks. See `setAmbientSH`. */
@@ -7402,12 +7532,14 @@ export class WebGL2Renderer {
7402
7532
  gl.uniform4fv(u['uOverlay'] ?? null, this.overlayFloats);
7403
7533
  gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
7404
7534
  gl.uniform4fv(u['uLayers'] ?? null, this.layerFloats);
7535
+ gl.uniform4fv(u['uLayerLooks'] ?? null, this.lookFloats);
7405
7536
  /* Neither refracting nor glass, which is every draw that does not say otherwise. See
7406
7537
  `bindSeeThrough` for the four numbers in the one vector. */
7407
7538
  gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
7408
7539
  gl.uniform4f(u['uRefractTint'] ?? null, 1, 1, 1, 1);
7409
- /* White, and no light through from behind: no material at a pass's start. */
7410
- gl.uniform4f(u['uGlassTint'] ?? null, 1, 1, 1, this.diffuseTransmission);
7540
+ /* No light through from behind: no material at a pass's start. See `writeTransmission`. */
7541
+ const through = this.transmission;
7542
+ gl.uniform4f(u['uGlassTint'] ?? null, through[0] ?? -1, through[1] ?? -1, through[2] ?? -1, through[3] ?? 0);
7411
7543
  gl.uniform3fv(u['uDirectionalDir'] ?? null, env.directionalDir);
7412
7544
  gl.uniform3fv(u['uDirectionalColor'] ?? null, env.directionalColor);
7413
7545
  gl.uniform3fv(u['uAmbient'] ?? null, env.ambient);
@@ -7,7 +7,7 @@ import type { PickHit, PickableSource } from '../../pickable.ts';
7
7
  import type { RendererApi } from '../api.ts';
8
8
  import { GpuTimestamps } from './gpuTimestamps.ts';
9
9
  import type { Environment, FilmOptions, InsetRect, LightVolumeDrawOptions, SkyColors, TranslucentMeshOptions } from '../webgl2/renderer.ts';
10
- import type { IncrementalMeshHandle, MeshOptions } from '../api.ts';
10
+ import type { IncrementalMeshHandle, MeshOptions, PrepareOptions } from '../api.ts';
11
11
  import type { LightmapPage } from '../../lightmap.ts';
12
12
  import { type BloomResponse } from '../../bloomChain.ts';
13
13
  import type { WaterBody } from '../../waterRenderer.ts';
@@ -851,6 +851,8 @@ export declare class WebGPURenderer implements RendererApi {
851
851
  private temporalHistoryUsable;
852
852
  private readonly aoStaging;
853
853
  private readonly aoFloats;
854
+ /** Where occlusion fades out with distance, `uFade`: none until a caller sets one. */
855
+ private readonly aoFade;
854
856
  private aoBindGroup;
855
857
  private readonly aoBlurLayout;
856
858
  /**
@@ -1187,7 +1189,15 @@ export declare class WebGPURenderer implements RendererApi {
1187
1189
  private readonly projection;
1188
1190
  /** `uLayers`, the material's layers packed by `packSurfaceLayers`. */
1189
1191
  private readonly layerFloats;
1192
+ /** `uLayerLooks`, the material's layer picks and looks packed by `packLayerLooks`. */
1193
+ private readonly lookFloats;
1194
+ /**
1195
+ * `uGlassTint` as the material leaves it, its light from behind and that light's colour, which a
1196
+ * pane overwrites for its own draw and `releaseSeeThrough` puts back. See `transmission.ts`.
1197
+ */
1198
+ private readonly transmission;
1190
1199
  private warnedLayersModelMap;
1200
+ private warnedLayersTriplanar;
1191
1201
  /** Its numbers, `uModelParams`, packed by `packModel`. */
1192
1202
  private readonly modelParams;
1193
1203
  /**
@@ -2093,6 +2103,8 @@ export declare class WebGPURenderer implements RendererApi {
2093
2103
  * said once, leave one layer.
2094
2104
  */
2095
2105
  private layersOf;
2106
+ /** Says once that a layered material's triplanar projection is laid on the horizontal plane. */
2107
+ private warnLayersTriplanar;
2096
2108
  /**
2097
2109
  * What the sun's moving layer is read through: the matrix its shadow pass was last drawn with,
2098
2110
  * where the lit pipelines carry one (`MOVING_SUN`); the environment's otherwise, which every caller
@@ -2478,6 +2490,35 @@ export declare class WebGPURenderer implements RendererApi {
2478
2490
  */
2479
2491
  private refuseOitMultisampled;
2480
2492
  drawTranslucentMesh(mesh: GpuMesh, model: ReadonlyMat4, opacity: number, options?: TranslucentMeshOptions): void;
2493
+ /**
2494
+ * Compile off the main thread every pipeline a draw of `mesh` in `material` takes. **The other
2495
+ * backend's `prepareMesh` carries the contract**; here a draw's key is `meshShape`'s, so a prepared
2496
+ * key is exactly the one the draw asks for, and the compiles are started as a skipping draw starts
2497
+ * them (`PipelineCache.lit`) whatever `pipelineCompile` says, into the frame's own caches.
2498
+ */
2499
+ prepareMesh(mesh: GpuMesh, material: SurfaceMaterial<GpuSurfaceTexture> | null, options?: PrepareOptions): void;
2500
+ /** `prepareMesh` for an instanced batch. See the other backend's `prepareInstanced`. */
2501
+ prepareInstanced(batch: GpuInstancedBatch, material: SurfaceMaterial<GpuSurfaceTexture> | null, options?: PrepareOptions): void;
2502
+ /**
2503
+ * `material` set, every cache a draw in it may build into passed to `start` with whether that draw
2504
+ * blends, compiles started rather than built (`PipelineCache.lit`), and then no material set and
2505
+ * each cache back to what `pipelineCompile` says.
2506
+ */
2507
+ private preparing;
2508
+ /** What `meshShape` worked out for the mesh draw at hand. Reused, so a draw allocates no record. */
2509
+ private readonly drawShape;
2510
+ /**
2511
+ * The key a mesh draw's pipeline is cached under, and the pipeline state that key encodes, under
2512
+ * the current material and these options, into `drawShape`. **The one statement of it**:
2513
+ * `submitMesh` draws by it and `prepareMesh` compiles by it, so the two cannot disagree about a key.
2514
+ */
2515
+ private meshShape;
2516
+ /**
2517
+ * The pipeline `drawShape` names: the one built, one built here, or none where draws skip a
2518
+ * compile and this one has not landed (`PipelineCache.lit`) — and none for a split skin until all
2519
+ * of its halves have.
2520
+ */
2521
+ private meshPipeline;
2481
2522
  /**
2482
2523
  * @param blend Which of the two entry points above this came through, and **not** something to
2483
2524
  * infer from `opacity`.
@@ -2568,6 +2609,13 @@ export declare class WebGPURenderer implements RendererApi {
2568
2609
  * **What it gives up** against `drawMesh` is skinning and morphing, refused where the variant
2569
2610
  * is built, and a per-instance opacity: `opacity` here dims the whole batch.
2570
2611
  */
2612
+ /**
2613
+ * An instanced draw's pipeline under the current material and these options: the one built, one
2614
+ * built here, or none while it compiles (`PipelineCache.lit`), its key, base and model left in
2615
+ * `drawShape`. **The one statement of an instanced key**: `submitInstanced` draws by it and
2616
+ * `prepareInstanced` compiles by it.
2617
+ */
2618
+ private instancedPipeline;
2571
2619
  /**
2572
2620
  * The next of a batch's cull slots this frame, with its cull queued against the current view; null
2573
2621
  * when the batch has drawn from `MAX_CULL_SLOTS` views already or the frame's job ring is full,
@@ -3994,6 +4042,11 @@ export declare class WebGPURenderer implements RendererApi {
3994
4042
  setLocalExposure(strength: number): void;
3995
4043
  /** The exposure pass, built once; the composite's group holds placeholders until it exists. */
3996
4044
  private ensureExposurePass;
4045
+ /**
4046
+ * Where ambient occlusion fades out with distance. **The other backend's
4047
+ * `setAmbientOcclusionFade` carries the reasoning**; this is the same setter over the same state.
4048
+ */
4049
+ setAmbientOcclusionFade(distance: number, radius: number): void;
3997
4050
  /**
3998
4051
  * Where this frame's lens is focused, how deep the sharp zone is, and how much of the ceiling
3999
4052
  * to take. **The other backend's `setDepthOfField` carries the reasoning**, whole: the