@driftengine/core 4.8.4 → 4.8.6

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 (138) hide show
  1. package/CHANGELOG.json +4332 -4250
  2. package/dist/build/shaderComments.js +0 -0
  3. package/dist/index.d.ts +10 -2
  4. package/dist/index.js +11 -1
  5. package/dist/render/ambientHarmonics.d.ts +46 -0
  6. package/dist/render/ambientHarmonics.js +80 -0
  7. package/dist/render/areaLights.d.ts +19 -0
  8. package/dist/render/areaLights.js +20 -0
  9. package/dist/render/backend/api.d.ts +13 -2
  10. package/dist/render/backend/webgl2/instanced.d.ts +2 -0
  11. package/dist/render/backend/webgl2/instanced.js +11 -2
  12. package/dist/render/backend/webgl2/renderer.d.ts +74 -1
  13. package/dist/render/backend/webgl2/renderer.js +229 -37
  14. package/dist/render/backend/webgl2/skinScatter.d.ts +14 -0
  15. package/dist/render/backend/webgl2/skinScatter.js +88 -26
  16. package/dist/render/backend/webgpu/buffers.js +2 -1
  17. package/dist/render/backend/webgpu/clusterBinner.js +3 -2
  18. package/dist/render/backend/webgpu/flatPass.d.ts +7 -5
  19. package/dist/render/backend/webgpu/flatPass.js +15 -13
  20. package/dist/render/backend/webgpu/particlePass.d.ts +37 -4
  21. package/dist/render/backend/webgpu/particlePass.js +80 -12
  22. package/dist/render/backend/webgpu/postPass.d.ts +6 -1
  23. package/dist/render/backend/webgpu/postPass.js +7 -2
  24. package/dist/render/backend/webgpu/renderer.d.ts +58 -7
  25. package/dist/render/backend/webgpu/renderer.js +329 -78
  26. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
  27. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.js +41 -1
  28. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +40 -28
  29. package/dist/render/backend/webgpu/skinPaletteRing.js +69 -32
  30. package/dist/render/backend/webgpu/skinScatterPass.d.ts +10 -2
  31. package/dist/render/backend/webgpu/skinScatterPass.js +59 -9
  32. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +2 -1
  33. package/dist/render/backend/webgpu/surfaceTexturePass.js +18 -4
  34. package/dist/render/bloomChain.d.ts +44 -0
  35. package/dist/render/bloomChain.js +28 -0
  36. package/dist/render/bloomPass.d.ts +3 -1
  37. package/dist/render/bloomPass.js +16 -2
  38. package/dist/render/clusteredLights.d.ts +55 -1
  39. package/dist/render/clusteredLights.js +182 -8
  40. package/dist/render/fixtureAtlas.js +1 -1
  41. package/dist/render/instanceCull.js +7 -0
  42. package/dist/render/instances.d.ts +32 -10
  43. package/dist/render/instances.js +17 -8
  44. package/dist/render/lightBudget.d.ts +26 -1
  45. package/dist/render/lightBudget.js +26 -3
  46. package/dist/render/lightmap.d.ts +102 -0
  47. package/dist/render/lightmap.js +118 -0
  48. package/dist/render/mesh.js +2 -1
  49. package/dist/render/particleBatch.d.ts +59 -3
  50. package/dist/render/particleBatch.js +53 -2
  51. package/dist/render/particleMaterial.d.ts +6 -1
  52. package/dist/render/particleMaterial.js +10 -2
  53. package/dist/render/particlePool.d.ts +11 -0
  54. package/dist/render/particlePool.js +2 -0
  55. package/dist/render/particleSort.d.ts +34 -0
  56. package/dist/render/particleSort.js +96 -0
  57. package/dist/render/sceneTarget.d.ts +5 -0
  58. package/dist/render/sceneTarget.js +8 -1
  59. package/dist/render/shaders/bloom.d.ts +8 -4
  60. package/dist/render/shaders/bloom.js +20 -3
  61. package/dist/render/shaders/flat/areaLight.d.ts +10 -0
  62. package/dist/render/shaders/flat/areaLight.js +96 -0
  63. package/dist/render/shaders/flat/index.js +30 -4
  64. package/dist/render/shaders/flat/main.d.ts +1 -1
  65. package/dist/render/shaders/flat/main.js +107 -105
  66. package/dist/render/shaders/flat/models.d.ts +16 -4
  67. package/dist/render/shaders/flat/models.js +64 -4
  68. package/dist/render/shaders/flat/normalMap.d.ts +10 -0
  69. package/dist/render/shaders/flat/normalMap.js +25 -0
  70. package/dist/render/shaders/flat/preamble.js +74 -37
  71. package/dist/render/shaders/flat/skin.js +6 -5
  72. package/dist/render/shaders/generated/bloom.wgsl.d.ts +13 -3
  73. package/dist/render/shaders/generated/bloom.wgsl.js +13 -3
  74. package/dist/render/shaders/generated/flat.wgsl.d.ts +1631 -2044
  75. package/dist/render/shaders/generated/flat.wgsl.js +1669 -2082
  76. package/dist/render/shaders/generated/particle.wgsl.d.ts +115 -1
  77. package/dist/render/shaders/generated/particle.wgsl.js +115 -1
  78. package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +12 -2
  79. package/dist/render/shaders/generated/skinBlur.wgsl.js +12 -2
  80. package/dist/render/shaders/particle.d.ts +17 -1
  81. package/dist/render/shaders/particle.js +133 -1
  82. package/dist/render/shaders/skinBlur.js +25 -3
  83. package/dist/render/skinBlur.d.ts +8 -5
  84. package/dist/render/skinBlur.js +8 -5
  85. package/dist/render/surfaceModel.d.ts +21 -1
  86. package/dist/render/surfaceModel.js +22 -0
  87. package/dist/render/surfaceTexture.d.ts +14 -1
  88. package/dist/render/surfaceTexture.js +15 -2
  89. package/package.json +6 -6
  90. package/src/build/shaderComments.ts +0 -0
  91. package/src/index.ts +20 -1
  92. package/src/render/ambientHarmonics.ts +92 -0
  93. package/src/render/areaLights.ts +39 -0
  94. package/src/render/backend/api.ts +14 -1
  95. package/src/render/backend/webgl2/instanced.ts +12 -2
  96. package/src/render/backend/webgl2/renderer.ts +292 -35
  97. package/src/render/backend/webgl2/skinScatter.ts +120 -43
  98. package/src/render/backend/webgpu/buffers.ts +2 -1
  99. package/src/render/backend/webgpu/clusterBinner.ts +3 -1
  100. package/src/render/backend/webgpu/flatPass.ts +18 -13
  101. package/src/render/backend/webgpu/particlePass.ts +118 -12
  102. package/src/render/backend/webgpu/postPass.ts +7 -2
  103. package/src/render/backend/webgpu/renderer.ts +396 -95
  104. package/src/render/backend/webgpu/shaders/clusterBin.wgsl.ts +41 -1
  105. package/src/render/backend/webgpu/skinPaletteRing.ts +65 -33
  106. package/src/render/backend/webgpu/skinScatterPass.ts +59 -9
  107. package/src/render/backend/webgpu/surfaceTexturePass.ts +26 -5
  108. package/src/render/bloomChain.ts +69 -0
  109. package/src/render/bloomPass.ts +26 -2
  110. package/src/render/clusteredLights.ts +221 -7
  111. package/src/render/fixtureAtlas.ts +1 -1
  112. package/src/render/instanceCull.ts +5 -0
  113. package/src/render/instances.ts +40 -11
  114. package/src/render/lightBudget.ts +48 -2
  115. package/src/render/lightmap.ts +202 -0
  116. package/src/render/mesh.ts +2 -1
  117. package/src/render/particleBatch.ts +95 -4
  118. package/src/render/particleMaterial.ts +11 -1
  119. package/src/render/particlePool.ts +13 -0
  120. package/src/render/particleSort.ts +133 -0
  121. package/src/render/sceneTarget.ts +18 -1
  122. package/src/render/shaders/bloom.ts +20 -3
  123. package/src/render/shaders/flat/areaLight.ts +96 -0
  124. package/src/render/shaders/flat/index.ts +30 -4
  125. package/src/render/shaders/flat/main.ts +107 -105
  126. package/src/render/shaders/flat/models.ts +73 -8
  127. package/src/render/shaders/flat/normalMap.ts +25 -0
  128. package/src/render/shaders/flat/preamble.ts +74 -37
  129. package/src/render/shaders/flat/skin.ts +6 -5
  130. package/src/render/shaders/generated/bloom.wgsl.ts +13 -3
  131. package/src/render/shaders/generated/flat.wgsl.ts +1669 -2082
  132. package/src/render/shaders/generated/particle.wgsl.ts +116 -1
  133. package/src/render/shaders/generated/skinBlur.wgsl.ts +12 -2
  134. package/src/render/shaders/particle.ts +134 -1
  135. package/src/render/shaders/skinBlur.ts +25 -3
  136. package/src/render/skinBlur.ts +8 -5
  137. package/src/render/surfaceModel.ts +36 -1
  138. package/src/render/surfaceTexture.ts +41 -3
@@ -22,7 +22,7 @@ import { batchBoxVisible, cullsInstances } from '../../instanceCull.js';
22
22
  import { OcclusionBuffer } from '../../occlusion.js';
23
23
  import { boundsVisible } from '../../visibility.js';
24
24
  import { createPassRegistry, drainRegistry, passAt, registerIn, unregisterIn } from '../../pass.js';
25
- import { TABLE_HEIGHT, TABLE_WIDTH, buildLightClusters, clusteredMode, createClusterLightSet, createClusterTable, fillClusterLightSet, } from '../../clusteredLights.js';
25
+ import { TABLE_HEIGHT, TABLE_WIDTH, buildLightClusters, clusteredMode, createClusterLightSet, createClusterTable, clusteredLightTotal, fillClusterLightSet, lightChannelsOf, } from '../../clusteredLights.js';
26
26
  import { clamp } from '../../../math/scalar.js';
27
27
  import { attachContextLoss } from '../../contextLoss.js';
28
28
  import { isWeakGpuFamily, webgl2RendererName } from '../../gpuCapability.js';
@@ -32,7 +32,8 @@ import { FULL_LIGHT_BUDGET, nextLightBudget, planLightBudget, } from '../../unif
32
32
  import { FILM_FRAG, FILM_VERT } from '../../shaders/film.js';
33
33
  import { SceneTarget } from '../../sceneTarget.js';
34
34
  import { FILM_LOOK_WITHOUT_COMPOSITE } from '../../shaders/filmLook.js';
35
- import { bloomProfileWarning, bloomThresholdOf } from '../../bloomChain.js';
35
+ import { AMBIENT_SH_FLOATS, packAmbientSH } from '../../ambientHarmonics.js';
36
+ import { bloomProfileWarning, bloomThresholdOf, defaultBloomResponse, resolveBloomResponse, } from '../../bloomChain.js';
36
37
  import { createEmptyTexture2D, createEmptyTexture2DArray, createEmptyTexture3D, createEmptyTextureCube, } from '../../emptyTexture.js';
37
38
  import { LightField } from '../../driftLight/lightField.js';
38
39
  import { WorldLightField } from '../../driftLight/worldLightField.js';
@@ -41,7 +42,8 @@ import { flatFrag, flatVert } from '../../shaders/flat/index.js';
41
42
  import { skinEight } from '../../shaders/skinning.js';
42
43
  import { MODEL_MAP_TEXTURE_UNIT, MORPH_DELTA_TEXTURE_UNIT, SKIN_PALETTE_TEXTURE_UNIT, } from '../../lightBudget.js';
43
44
  import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel } from '../../surfaceModel.js';
44
- import { modelBound, skinHalfBound } from '../../shaders/flat/models.js';
45
+ import { lightmapRegionsBound, modelBound, skinHalfBound } from '../../shaders/flat/models.js';
46
+ import { lightmapTexels, warnRegionsWithoutPage, warnUnpagedLightmap, withLightmapUvs, } from '../../lightmap.js';
45
47
  import { GlSkinScatter } from './skinScatter.js';
46
48
  import { CLOTH_BINDING_TEXTURE_UNIT, CLOTH_PARTICLES_TEXTURE_UNIT, CLOTH_REST_TEXTURE_UNIT, } from '../../lightBudget.js';
47
49
  import { SkinPaletteTexture } from './skinPaletteTexture.js';
@@ -79,7 +81,7 @@ import { GlassTintPrograms } from './glassTintPrograms.js';
79
81
  import { FACE_COUNT, PointShadowMap } from '../../pointShadowMap.js';
80
82
  import { createResolvedPointShadows, DEFAULT_SOURCE_RADIUS } from '../../pointShadowImage.js';
81
83
  import { createBakeScratch, runPointShadowBakes, selectCastingLights, } from '../../pointShadowBudget.js';
82
- import { DIRECTIONAL_SHADOW_UNITS, SUN_GLASS_TINT_TEXTURE_UNIT, POINT_GLASS_TINT_TEXTURE_UNIT, LIVE_POINT_SHADOW_MAPS, MAX_POINT_LIGHTS, NORMAL_TEXTURE_UNIT, ORM_TEXTURE_UNIT, POINT_SHADOW_POOL, POINT_SHADOW_UNITS, CLUSTER_TABLE_TEXTURE_UNIT, EMISSIVE_TEXTURE_UNIT, ENVIRONMENT_TEXTURE_UNIT, SURFACE_TEXTURE_UNIT, bindPointLights, REFRACT_SCENE_TEXTURE_UNIT, DRIFT_LIGHT_ATLAS_TEXTURE_UNIT, DRIFT_LIGHT_INDEX_TEXTURE_UNIT, COOKIE_TILE, FIXTURE_ATLAS_TEXTURE_UNIT, SURFACE_EFFECTS_TEXTURE_UNIT, bindAreaLights, bindAreaShadows, openCones, } from '../../lightBudget.js';
84
+ import { DIRECTIONAL_SHADOW_UNITS, SUN_GLASS_TINT_TEXTURE_UNIT, POINT_GLASS_TINT_TEXTURE_UNIT, LIVE_POINT_SHADOW_MAPS, MAX_POINT_LIGHTS, NORMAL_TEXTURE_UNIT, ORM_TEXTURE_UNIT, POINT_SHADOW_POOL, POINT_SHADOW_UNITS, CLUSTER_TABLE_TEXTURE_UNIT, EMISSIVE_TEXTURE_UNIT, ENVIRONMENT_TEXTURE_UNIT, SURFACE_TEXTURE_UNIT, bindPointLights, REFRACT_SCENE_TEXTURE_UNIT, DRIFT_LIGHT_ATLAS_TEXTURE_UNIT, DRIFT_LIGHT_INDEX_TEXTURE_UNIT, COOKIE_TILE, FIXTURE_ATLAS_TEXTURE_UNIT, SURFACE_EFFECTS_TEXTURE_UNIT, bindAreaLights, bindAreaShadows, openCones, PARTICLE_DEPTH_UNIT, } from '../../lightBudget.js';
83
85
  import { resolveRenderQuality } from '../../renderQuality.js';
84
86
  import { drawingBufferSize } from '../../drawingBuffer.js';
85
87
  import { Mesh, createMeshIncremental } from '../../mesh.js';
@@ -180,6 +182,8 @@ export function createEnvironment(overrides = {}) {
180
182
  lightIesAxes: new Float32Array(MAX_POINT_LIGHTS * 3),
181
183
  /* −1, because tile 0 is a real cookie and no light here has asked for one. */
182
184
  lightCookies: new Float32Array(MAX_POINT_LIGHTS).fill(-1),
185
+ /* Channel 1, the default every surface is on, so every light lights everything. */
186
+ lightChannels: new Float32Array(MAX_POINT_LIGHTS).fill(1),
183
187
  /* None, so the shader's loop breaks before it reads anything. */
184
188
  areaLights: null,
185
189
  activeLightWorldIndices: new Int32Array(MAX_POINT_LIGHTS).fill(-1),
@@ -257,6 +261,8 @@ const IDENTITY_MAT4 = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
257
261
  */
258
262
  export { MAX_DEPTH_LAYER } from '../../depthConvention.js';
259
263
  /** The identity tint, handed back after any draw that asked for one. See `drawMesh`. */
264
+ /** The far corner of no highlight box: what an environment that names none reads. */
265
+ const NO_HIGHLIGHT = [0, 0, 0];
260
266
  const WHITE_TINT = new Float32Array([1, 1, 1]);
261
267
  /** The lit stage's clip plane when there is no mirror: zero, which clips nothing. */
262
268
  const NO_CLIP_PLANE = new Float32Array(4);
@@ -640,6 +646,23 @@ export class WebGL2Renderer {
640
646
  motionBlurScale = 1;
641
647
  /** How much of the bloom ceiling this frame takes. See `setBloom`. */
642
648
  bloomScale = 1;
649
+ /** The draw's own ambient, as `packAmbientSH` writes it; zeros are the frame's. */
650
+ ambientSH = new Float32Array(AMBIENT_SH_FLOATS);
651
+ /** Scratch for a sprite's depth conversion, so a particle draw allocates nothing. */
652
+ spriteInverseProjection = mat4.create();
653
+ /** Said once: a sprite asked for a soft edge where no depth copy could be had. */
654
+ warnedSpriteDepth = false;
655
+ /** Said once: more rectangles than the fixed arm shades, and no froxel table for the rest. */
656
+ warnedAreaOverflow = false;
657
+ /**
658
+ * `uEnvironmentDials` as it stands: which chain the probe holds, the reflectivity, the gain. One
659
+ * vector on the device, so written whole, and kept here so a setter moving one keeps the others.
660
+ */
661
+ environmentDials = new Float32Array(3);
662
+ /** `uEmission` as it stands: the emissive gain and the night factor. */
663
+ emission = new Float32Array([1, 0]);
664
+ /** The ramp and each level's tint, as `resolveBloomResponse` writes them; the subtraction and white. */
665
+ bloomResponse = defaultBloomResponse();
643
666
  /** In scene units, once `setBloom` has moved it; null reads the profile's. */
644
667
  bloomThresholdSet = null;
645
668
  /**
@@ -1675,9 +1698,7 @@ export class WebGL2Renderer {
1675
1698
  gl.activeTexture(gl.TEXTURE0 + FIXTURE_ATLAS_TEXTURE_UNIT);
1676
1699
  gl.bindTexture(gl.TEXTURE_2D, this.fixtureTexture);
1677
1700
  gl.uniform1i(u['uFixtureAtlas'] ?? null, FIXTURE_ATLAS_TEXTURE_UNIT);
1678
- gl.uniform1f(u['uIesAtlasRows'] ?? null, this.iesRows);
1679
- gl.uniform1f(u['uIesPlaneCount'] ?? null, this.iesPlanes);
1680
- gl.uniform1f(u['uCookieTiles'] ?? null, this.cookieTiles);
1701
+ gl.uniform3f(u['uFixtureShape'] ?? null, this.iesRows, this.iesPlanes, this.cookieTiles);
1681
1702
  gl.activeTexture(gl.TEXTURE0);
1682
1703
  }
1683
1704
  bindClusters(u, camera, env) {
@@ -1723,7 +1744,8 @@ export class WebGL2Renderer {
1723
1744
  gl.uniform4fv(u['uClusterFrustum'] ?? null, this.clusterFrustum);
1724
1745
  gl.uniformMatrix4fv(u['uView'] ?? null, false, camera.view);
1725
1746
  this.clusterTable ??= createClusterTable();
1726
- const lights = fillClusterLightSet(env, this.clusterLights);
1747
+ /* The rectangles past the fixed arm's go into the table. See `ClusterLightSet.areas`. */
1748
+ const lights = fillClusterLightSet(env, this.clusterLights, this.shadedAreaLights);
1727
1749
  /* A slot is read only under `POINT_SHADOWS`; see `lightHasFixture`. */
1728
1750
  const shadowSlots = this.quality.pointShadows ? this.lightBudget.maxLights : 0;
1729
1751
  buildLightClusters(lights, camera.view, near, far, tanHalfFovY, aspect, this.clusterTable,
@@ -1735,7 +1757,7 @@ export class WebGL2Renderer {
1735
1757
  * guards the read as well, for a build that reaches it another way.
1736
1758
  */
1737
1759
  shadowSlots);
1738
- gl.uniform1i(u['uClustered'] ?? null, clusteredMode(lights, lights.count, shadowSlots));
1760
+ gl.uniform1i(u['uClustered'] ?? null, clusteredMode(lights, clusteredLightTotal(lights), shadowSlots));
1739
1761
  gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, TABLE_WIDTH, TABLE_HEIGHT, gl.RGBA_INTEGER, gl.UNSIGNED_INT, this.clusterTable);
1740
1762
  }
1741
1763
  /**
@@ -2340,8 +2362,9 @@ export class WebGL2Renderer {
2340
2362
  * one. Measured on hardware: WebGL2 drew neither the bend nor the bulge while WebGPU drew
2341
2363
  * both, which is the two-backend comparison doing exactly what it is for.
2342
2364
  */
2343
- this.warmVariants(data);
2344
- return new Mesh(this.gl, data, options.dynamic === true);
2365
+ const drawn = withLightmapUvs(data);
2366
+ this.warmVariants(drawn);
2367
+ return new Mesh(this.gl, drawn, options.dynamic === true);
2345
2368
  }
2346
2369
  /**
2347
2370
  * Geometry in, handle out — but the geometry lands over as many frames as the caller gives it.
@@ -2353,8 +2376,9 @@ export class WebGL2Renderer {
2353
2376
  * the signal that it was abandoned.
2354
2377
  */
2355
2378
  createMeshIncremental(data, options = {}) {
2356
- this.warmVariants(data);
2357
- const { mesh, upload } = createMeshIncremental(this.gl, data, options.dynamic === true);
2379
+ const drawn = withLightmapUvs(data);
2380
+ this.warmVariants(drawn);
2381
+ const { mesh, upload } = createMeshIncremental(this.gl, drawn, options.dynamic === true);
2358
2382
  return {
2359
2383
  mesh,
2360
2384
  upload: {
@@ -2517,6 +2541,10 @@ export class WebGL2Renderer {
2517
2541
  createSurfaceTextureArray(sources, options = {}) {
2518
2542
  return new SurfaceTexture(this.gl, sources, options, this.compressedFormats);
2519
2543
  }
2544
+ /** A baked lightmap page, as a `lightmapModel` material's `modelMap`. See `RendererApi`. */
2545
+ createLightmap(page) {
2546
+ return new SurfaceTexture(this.gl, lightmapTexels(page), { wrap: 'clamp', mipmap: false });
2547
+ }
2520
2548
  /**
2521
2549
  * Replace a surface texture's pixels, keeping the GPU object and its sampler state.
2522
2550
  *
@@ -2590,7 +2618,7 @@ export class WebGL2Renderer {
2590
2618
  return held;
2591
2619
  const { gl } = this;
2592
2620
  const label = `flat.${variant}.${kind}.${half}`;
2593
- const program = compileProgram(gl, litVertexOf(variant), skinHalfBound(modelBound(this.flatFragSource, kind), half), label);
2621
+ const program = compileProgram(gl, modelVertexOf(variant, kind), skinHalfBound(modelBound(this.flatFragSource, kind), half), label);
2594
2622
  const made = { program, uniforms: uniformLocations(gl, program, label) };
2595
2623
  this.modelFlat.set(key, made);
2596
2624
  this.flatTargets.push(made);
@@ -2738,6 +2766,7 @@ export class WebGL2Renderer {
2738
2766
  this.currentMaterial = material;
2739
2767
  /* What the blur spreads this skin by: the last material to name a profile says. */
2740
2768
  const model = material?.model;
2769
+ warnUnpagedLightmap(model, material?.modelMap);
2741
2770
  if (model?.kind === 'skin' && this.skinScatter !== null) {
2742
2771
  this.skinScatter.setProfile(model.profile, model.scatterColor[0] * model.radius, model.scatterColor[1] * model.radius, model.scatterColor[2] * model.radius);
2743
2772
  }
@@ -2773,7 +2802,8 @@ export class WebGL2Renderer {
2773
2802
  v[0] = material?.cutout ?? 0;
2774
2803
  v[1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveNow(material, translucent)];
2775
2804
  v[2] = this.cutoutFrame;
2776
- v[3] = 0;
2805
+ /* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
2806
+ v[3] = lightChannelsOf(material?.lightChannels);
2777
2807
  return v;
2778
2808
  }
2779
2809
  /**
@@ -2884,6 +2914,8 @@ export class WebGL2Renderer {
2884
2914
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
2885
2915
  this.materialFlags[0] = 0;
2886
2916
  gl.uniform4iv(u['uMaterialFlags'] ?? null, this.materialFlags);
2917
+ /* No image to cut, and the channels all the same: a material needs no albedo to name them. */
2918
+ gl.uniform4f(u['uCutout'] ?? null, 0, 0, 0, lightChannelsOf(material?.lightChannels));
2887
2919
  return;
2888
2920
  }
2889
2921
  albedo.bind(gl, SURFACE_TEXTURE_UNIT);
@@ -2932,6 +2964,17 @@ export class WebGL2Renderer {
2932
2964
  * is 1 and `bindMeshPass` restores it, so it scales geometry that declared grain and
2933
2965
  * leaves geometry that did not exactly as it is.
2934
2966
  */
2967
+ /**
2968
+ * The ambient the following draws take, as nine spherical-harmonic coefficients of incoming
2969
+ * radiance — red, green and blue of each, twenty-seven numbers — or `null` for the frame's own:
2970
+ * the sky's gradient, or the probes'. See `ambientHarmonics.ts` for the basis and the axes.
2971
+ *
2972
+ * **For a thing lit by where it stands**: a consumer sampling a stage's baked volume of indirect
2973
+ * light at a character every frame sets the result around that character's draws, and the
2974
+ * character takes the light of its spot rather than the frame's. It replaces the diffuse ambient
2975
+ * only — a glossy surface still reflects the probes — and it is pass state like the material,
2976
+ * which `bindMeshPass` clears, so a mirror or a probe bake drawn after does not inherit it.
2977
+ */
2935
2978
  /**
2936
2979
  * How much of the environment the following draws mirror, 0 to 1.
2937
2980
  *
@@ -2942,11 +2985,23 @@ export class WebGL2Renderer {
2942
2985
  * Defaults to zero and bindMeshPass restores it, so a scene that never calls this looks
2943
2986
  * exactly as it did.
2944
2987
  */
2988
+ setAmbientSH(coefficients) {
2989
+ if (this.contextLost)
2990
+ return;
2991
+ packAmbientSH(coefficients, this.ambientSH);
2992
+ this.materials.dirty();
2993
+ for (const target of this.flatTargets) {
2994
+ this.gl.useProgram(target.program);
2995
+ this.gl.uniform4fv(target.uniforms['uAmbientSH[0]'] ?? null, this.ambientSH);
2996
+ }
2997
+ this.useFlatProgram();
2998
+ }
2945
2999
  setSurfaceReflectivity(amount) {
2946
3000
  if (this.contextLost)
2947
3001
  return;
2948
3002
  this.materials.dirty();
2949
- this.writeFlatFloat('uReflectivity', Math.min(1, Math.max(0, amount)));
3003
+ this.environmentDials[1] = Math.min(1, Math.max(0, amount));
3004
+ this.writeFlatVector('uEnvironmentDials', this.environmentDials);
2950
3005
  }
2951
3006
  /**
2952
3007
  * How bright the environment the surfaces drawn next reflect is. 1 is the default and identity.
@@ -2964,7 +3019,8 @@ export class WebGL2Renderer {
2964
3019
  if (this.contextLost)
2965
3020
  return;
2966
3021
  this.materials.dirty();
2967
- this.writeFlatFloat('uEnvironmentGain', Math.max(0, gain));
3022
+ this.environmentDials[2] = Math.max(0, gain);
3023
+ this.writeFlatVector('uEnvironmentDials', this.environmentDials);
2968
3024
  }
2969
3025
  /**
2970
3026
  * Kept as a no-op that says so once, because it was a stand-in for a term that now exists.
@@ -3031,6 +3087,20 @@ export class WebGL2Renderer {
3031
3087
  }
3032
3088
  this.useFlatProgram();
3033
3089
  }
3090
+ /** A vector of material state, two to four floats, into every flat program. */
3091
+ writeFlatVector(name, value) {
3092
+ for (const target of this.flatTargets) {
3093
+ this.gl.useProgram(target.program);
3094
+ const at = target.uniforms[name] ?? null;
3095
+ if (value.length === 2)
3096
+ this.gl.uniform2fv(at, value);
3097
+ else if (value.length === 3)
3098
+ this.gl.uniform3fv(at, value);
3099
+ else
3100
+ this.gl.uniform4fv(at, value);
3101
+ }
3102
+ this.useFlatProgram();
3103
+ }
3034
3104
  /** One int of material state, into every flat program, for `writeFlatFloat`'s reason. */
3035
3105
  writeFlatInt(name, value) {
3036
3106
  for (const target of this.flatTargets) {
@@ -3094,8 +3164,13 @@ export class WebGL2Renderer {
3094
3164
  if (this.contextLost)
3095
3165
  return;
3096
3166
  this.materials.dirty();
3097
- this.writeFlatFloat('uRelief', Math.min(1, Math.max(0, amount)));
3098
- this.writeFlatFloat('uReliefCycles', Math.max(0.01, cyclesPerMetre));
3167
+ const strength = Math.min(1, Math.max(0, amount));
3168
+ const cycles = Math.max(0.01, cyclesPerMetre);
3169
+ for (const target of this.flatTargets) {
3170
+ this.gl.useProgram(target.program);
3171
+ this.gl.uniform2f(target.uniforms['uRelief'] ?? null, strength, cycles);
3172
+ }
3173
+ this.useFlatProgram();
3099
3174
  }
3100
3175
  /**
3101
3176
  * How hard the bound surface texture's own luminance turns the shading normal, 0 for not at all.
@@ -3153,7 +3228,8 @@ export class WebGL2Renderer {
3153
3228
  if (this.contextLost)
3154
3229
  return;
3155
3230
  this.materials.dirty();
3156
- this.writeFlatFloat('uEmissiveGain', gain);
3231
+ this.emission[0] = gain;
3232
+ this.writeFlatVector('uEmission', this.emission);
3157
3233
  }
3158
3234
  /* -- Instanced meshes --------------------------------------------------------------- */
3159
3235
  /** The instanced program, compiled on first use. See `ensureInstancedProgram`. */
@@ -3241,6 +3317,7 @@ export class WebGL2Renderer {
3241
3317
  submitInstanced(batch, data, opacity, blend, options) {
3242
3318
  if (this.contextLost)
3243
3319
  return;
3320
+ warnRegionsWithoutPage(this.currentMaterial?.model, data.lightmapRegions);
3244
3321
  const mesh = batch.mesh;
3245
3322
  /* Nor a batch of a mesh with nothing in it: see `Mesh.indexCount`. */
3246
3323
  if (!mesh.complete || mesh.indexCount === 0)
@@ -3702,8 +3779,10 @@ export class WebGL2Renderer {
3702
3779
  const { gl } = this;
3703
3780
  const u = batch.uniforms;
3704
3781
  gl.useProgram(batch.program);
3705
- batch.upload(gl, data);
3782
+ batch.upload(gl, data, camera.position);
3706
3783
  batch.bindMaterial(gl);
3784
+ if (batch.material === 'sprite')
3785
+ this.bindSpriteFades(batch, camera);
3707
3786
  gl.uniformMatrix4fv(u['uViewProj'] ?? null, false, this.viewProjFor(camera));
3708
3787
  gl.uniform3fv(u['uCameraPos'] ?? null, camera.position);
3709
3788
  gl.uniform1f(u['uTime'] ?? null, timeSeconds);
@@ -3730,6 +3809,35 @@ export class WebGL2Renderer {
3730
3809
  if (this.materialDoubleSided)
3731
3810
  gl.enable(gl.CULL_FACE);
3732
3811
  }
3812
+ /**
3813
+ * A sprite's two fades and the depth copy the soft one reads. The copy is the frame's opaque
3814
+ * depth, so it is taken only in the frame's own picture: in a mirror or a probe's face it would
3815
+ * be another view's, and the edge stays hard there. Without a scene target there is no copy at
3816
+ * all, and the renderer says so once rather than drawing a hard edge that was asked to be soft.
3817
+ */
3818
+ bindSpriteFades(batch, camera) {
3819
+ const { gl } = this;
3820
+ const u = batch.uniforms;
3821
+ const scene = this.sceneTarget;
3822
+ let depth = null;
3823
+ if (batch.softDepth > 0 && !this.reflectionPassActive && !this.probePassActive) {
3824
+ depth = scene?.snapshotDepth(true) ?? null;
3825
+ if (depth === null && !this.warnedSpriteDepth) {
3826
+ this.warnedSpriteDepth = true;
3827
+ console.warn("WebGL2: a 'sprite' asked for a soft edge, which reads a copy of the frame's depth, and " +
3828
+ 'this profile keeps none — screenEffects is off, or the driver refused the copy. The ' +
3829
+ 'edge is hard.');
3830
+ }
3831
+ }
3832
+ gl.activeTexture(gl.TEXTURE0 + PARTICLE_DEPTH_UNIT);
3833
+ gl.bindTexture(gl.TEXTURE_2D, depth ?? this.emptyTexture2D);
3834
+ gl.uniform1i(u['uDepth'] ?? null, PARTICLE_DEPTH_UNIT);
3835
+ gl.uniform4f(u['uFade'] ?? null, batch.softDepth, batch.cameraFade, depth === null ? 0 : 1, 0);
3836
+ mat4.invert(this.spriteInverseProjection, camera.projection);
3837
+ const inverse = this.spriteInverseProjection;
3838
+ gl.uniform4f(u['uDepthToViewZ'] ?? null, inverse[10] ?? 0, inverse[14] ?? 0, inverse[11] ?? 0, inverse[15] ?? 1);
3839
+ gl.uniform2f(u['uViewport'] ?? null, scene?.frameWidth ?? gl.drawingBufferWidth, scene?.frameHeight ?? gl.drawingBufferHeight);
3840
+ }
3733
3841
  /** Build a batch for electrical arcs. `capacity` is in segments, not arcs. */
3734
3842
  createBolts(segmentCapacity, label = 'bolts') {
3735
3843
  return new BoltBatch(this.gl, segmentCapacity, label);
@@ -4643,7 +4751,7 @@ export class WebGL2Renderer {
4643
4751
  for (const [key, entry] of this.modelFlat) {
4644
4752
  const [variant, kind, half] = key.split('|');
4645
4753
  const label = `flat.${variant}.${kind}.${half}`;
4646
- const program = compileProgram(gl, litVertexOf(variant), skinHalfBound(modelBound(this.flatFragSource, kind), half), label);
4754
+ const program = compileProgram(gl, modelVertexOf(variant, kind), skinHalfBound(modelBound(this.flatFragSource, kind), half), label);
4647
4755
  gl.deleteProgram(entry.program);
4648
4756
  this.modelFlat.set(key, { program, uniforms: uniformLocations(gl, program, label) });
4649
4757
  }
@@ -5386,11 +5494,19 @@ export class WebGL2Renderer {
5386
5494
  * and wrong for a day whose exposure spans 2.5 to 14: a courtyard blooming at noon, or candles
5387
5495
  * never blooming at night. A caller whose exposure moves passes what it wants on screen over the
5388
5496
  * exposure. Omitted leaves it where it was, so a caller that never passes one keeps the profile's.
5497
+ *
5498
+ * **`response` is how a colour comes in past the threshold and what colour each octave of the
5499
+ * halo takes** — a ramp in place of the subtraction, and a tint a level — held until moved like
5500
+ * the threshold; `null` goes back to the subtraction and white. See `BloomResponse`: it is what
5501
+ * matches a stage built for another engine's bloom, whose tints are how a strength past 1 is
5502
+ * asked for.
5389
5503
  */
5390
- setBloom(scale, threshold) {
5504
+ setBloom(scale, threshold, response) {
5391
5505
  this.bloomScale = Math.min(Math.max(scale, 0), 1);
5392
5506
  if (threshold !== undefined)
5393
5507
  this.bloomThresholdSet = bloomThresholdOf(threshold);
5508
+ if (response !== undefined)
5509
+ resolveBloomResponse(response, this.bloomResponse);
5394
5510
  }
5395
5511
  /**
5396
5512
  * How thick the air is, this frame: a global participating medium filling the whole frustum.
@@ -5906,6 +6022,7 @@ export class WebGL2Renderer {
5906
6022
  ? {
5907
6023
  strength: bloomStrength,
5908
6024
  threshold: this.bloomThresholdSet ?? this.quality.bloomThreshold,
6025
+ response: this.bloomResponse,
5909
6026
  }
5910
6027
  : undefined;
5911
6028
  /* The grade travels to the resolve only where the mesh pass gave it up. See `bindMeshPass`. */
@@ -6188,6 +6305,15 @@ export class WebGL2Renderer {
6188
6305
  bindMeshPass(camera, env) {
6189
6306
  /* The pass's own material is reopened, so the next draw opens one. See `materialChanges.ts`. */
6190
6307
  this.materials.dirty();
6308
+ /* Rectangles past the fixed arm's are the froxel table's, and without it nobody shades them. */
6309
+ if (!this.warnedAreaOverflow &&
6310
+ !this.quality.clusteredLights &&
6311
+ (env.areaLights?.count ?? 0) > this.shadedAreaLights) {
6312
+ this.warnedAreaOverflow = true;
6313
+ console.warn(`WebGL2: ${env.areaLights?.count ?? 0} rectangular lights and ${this.shadedAreaLights} ` +
6314
+ 'shaded without clusteredLights: the rest are not drawn. Turn clusteredLights on, and the ' +
6315
+ 'froxel table shades every one past the fixed ones, unshadowed.');
6316
+ }
6191
6317
  /*
6192
6318
  * Captured here rather than taken as an argument to `endFrame`, because this is the
6193
6319
  * camera the world was drawn with and a caller should not have to hand it over twice.
@@ -6297,6 +6423,13 @@ export class WebGL2Renderer {
6297
6423
  this.materialHasMaps = false;
6298
6424
  this.currentMaterial = null;
6299
6425
  this.currentSurfaceTexture = null;
6426
+ /* Nor an ambient of its own: the frame's, until a draw asks. See `setAmbientSH`. */
6427
+ this.ambientSH.fill(0);
6428
+ /* No reflection and an environment at its own brightness; the emission the world asks for. */
6429
+ this.environmentDials[1] = 0;
6430
+ this.environmentDials[2] = 1;
6431
+ this.emission[0] = env.emissiveGain;
6432
+ this.emission[1] = env.nightFactor;
6300
6433
  for (const target of this.flatTargets) {
6301
6434
  this.gl.useProgram(target.program);
6302
6435
  this.writeMeshPassState(target.uniforms, camera, env);
@@ -6492,13 +6625,18 @@ export class WebGL2Renderer {
6492
6625
  this.oitWeight = 0;
6493
6626
  this.ditherFade = 0;
6494
6627
  gl.uniform2f(u['uWriteMode'] ?? null, 0, 0);
6495
- /* No environment reflection unless a caller asks, so existing scenes are unchanged. */
6496
- gl.uniform1f(u['uReflectivity'] ?? null, 0);
6497
- /* 1, not 0: this one is a multiplier and its identity is one. See `setEnvironmentGain`. */
6498
- gl.uniform1f(u['uEnvironmentGain'] ?? null, 1);
6628
+ /* The draw's own ambient as it stands, which `bindMeshPass` has just cleared: a program built
6629
+ in the middle of a pass takes what the caller set. See `setAmbientSH`. */
6630
+ gl.uniform4fv(u['uAmbientSH[0]'] ?? null, this.ambientSH);
6631
+ /*
6632
+ * Which chain the probe holds, no environment reflection, and an environment at its own
6633
+ * brightness — the reflectivity's identity is 0 and the gain's is 1 — as `bindMeshPass` set
6634
+ * them, or as a draw since has: a program built in the middle of a pass takes what stands.
6635
+ * See `setSurfaceReflectivity` and `setEnvironmentGain`.
6636
+ */
6637
+ gl.uniform3fv(u['uEnvironmentDials'] ?? null, this.environmentDials);
6499
6638
  /* And no relief unless a caller asks, for the same reason. */
6500
- gl.uniform1f(u['uRelief'] ?? null, 0);
6501
- gl.uniform1f(u['uReliefCycles'] ?? null, 60);
6639
+ gl.uniform2f(u['uRelief'] ?? null, 0, 60);
6502
6640
  /* And no relief read off a surface texture either, for the same reason. */
6503
6641
  gl.uniform1f(u['uTextureRelief'] ?? null, 0);
6504
6642
  /* The mirror's plane in a reflection pass, and zero — which clips nothing — in every other. */
@@ -6507,8 +6645,8 @@ export class WebGL2Renderer {
6507
6645
  resolveSurfaceScene(env, this.surfaceScene);
6508
6646
  gl.uniform4fv(u['uSurfaceScene'] ?? null, this.surfaceScene);
6509
6647
  bindAtmosphere(gl, u, env, this.reflectionPassActive ? this.reflectionAtmosphereY : (camera.position[1] ?? 0), this.quality.underwaterAtmosphere);
6510
- gl.uniform1f(u['uEmissiveGain'] ?? null, env.emissiveGain);
6511
- gl.uniform1f(u['uNightFactor'] ?? null, env.nightFactor);
6648
+ /* The emissive gain and the night factor, as the pass set them. See `uEmission`. */
6649
+ gl.uniform2fv(u['uEmission'] ?? null, this.emission);
6512
6650
  /* Absent means zero, so a world written before this term existed adds nothing. */
6513
6651
  gl.uniform1f(u['uNightEmissive'] ?? null, env.nightEmissive ?? 0);
6514
6652
  /*
@@ -6530,8 +6668,10 @@ export class WebGL2Renderer {
6530
6668
  gl.uniform1f(u['uShadowMaxSlope'] ?? null, this.quality.directionalShadowMaxSlope);
6531
6669
  gl.uniform1i(u['uShadowFilterTaps'] ?? null, this.quality.shadowFilterTaps);
6532
6670
  gl.uniform3fv(u['uHighlightMin'] ?? null, env.highlightMin);
6533
- gl.uniform3fv(u['uHighlightMax'] ?? null, env.highlightMax);
6534
- gl.uniform1f(u['uHighlightGain'] ?? null, env.highlightGain);
6671
+ /* The far corner, and the gain in its w. See `uHighlightMax`. */
6672
+ /* An environment written by hand may leave the box out; then there is none, as before. */
6673
+ const max = env.highlightMax ?? NO_HIGHLIGHT;
6674
+ gl.uniform4f(u['uHighlightMax'] ?? null, max[0] ?? 0, max[1] ?? 0, max[2] ?? 0, env.highlightGain ?? 0);
6535
6675
  bindPointLights(gl, u, env, this.quality.pointLightFalloff);
6536
6676
  this.bindClusters(u, camera, env);
6537
6677
  this.bindIesAtlas(u);
@@ -6627,7 +6767,8 @@ export class WebGL2Renderer {
6627
6767
  * between them; there is one array, so `RenderQuality.environmentPrefilter` decides what the
6628
6768
  * convolution writes and a profile that changes it has to bake again.
6629
6769
  */
6630
- gl.uniform1f(u['uEnvironmentPrefiltered'] ?? null, this.quality.environmentPrefilter ? 1 : 0);
6770
+ this.environmentDials[0] = this.quality.environmentPrefilter ? 1 : 0;
6771
+ gl.uniform3fv(u['uEnvironmentDials'] ?? null, this.environmentDials);
6631
6772
  gl.uniform1f(u['uEnvironmentMaxLod'] ?? null, array?.ggxMaxLevel ?? 0);
6632
6773
  /*
6633
6774
  * The map's edge, which used to be `exp2(maxLod)` and is not any more: the chain stops one
@@ -6817,12 +6958,17 @@ export class WebGL2Renderer {
6817
6958
  return;
6818
6959
  this.drawBudget.ask();
6819
6960
  this.takeMaterial();
6820
- /* Skin under the screen-space blur draws its two halves: the frame's, then at once its diffuse. */
6961
+ /*
6962
+ * Skin under the screen-space blur draws its halves: the frame's, then at once its diffuse's
6963
+ * light and its colour, each into the blur's own target.
6964
+ */
6821
6965
  const splits = this.skinSplits();
6822
6966
  this.drawMeshHalf(mesh, model, depthLayer, tint, splits ? 'scene' : 'whole');
6823
6967
  if (splits && this.skinScatter !== null) {
6824
6968
  this.skinScatter.begin();
6825
6969
  this.drawMeshHalf(mesh, model, depthLayer, tint, 'diffuse');
6970
+ this.skinScatter.beginAlbedo();
6971
+ this.drawMeshHalf(mesh, model, depthLayer, tint, 'albedo');
6826
6972
  this.sceneTarget?.bind();
6827
6973
  }
6828
6974
  }
@@ -6996,8 +7142,9 @@ export class WebGL2Renderer {
6996
7142
  * depth 1.0 against a buffer cleared to 1.0, and under `LESS` it would not draw at
6997
7143
  * all. Film, caustics and text lie flush on surfaces for the same reason.
6998
7144
  */
6999
- /* A skin's diffuse half is the same surface again: it finds the depth its frame half wrote. */
7000
- const again = half === 'diffuse';
7145
+ /* A skin's diffuse and colour halves are the same surface again: each finds the depth its
7146
+ frame half wrote. */
7147
+ const again = half === 'diffuse' || half === 'albedo';
7001
7148
  gl.depthFunc(again ? gl.EQUAL : this.reversedDepth ? gl.GREATER : gl.LESS);
7002
7149
  if (again)
7003
7150
  gl.depthMask(false);
@@ -7441,6 +7588,43 @@ export class WebGL2Renderer {
7441
7588
  this.probeSweeps?.baked(0);
7442
7589
  return true;
7443
7590
  }
7591
+ /**
7592
+ * One layer of the declared probe grid from an image, the way `setEnvironmentImage` fills a
7593
+ * single probe: the same equirectangular image of linear RGB floats, projected onto the capture
7594
+ * cube and prefiltered into that layer, the others left as they are. Returns false — and says
7595
+ * why — where there is no probe, no grid, or no such layer.
7596
+ *
7597
+ * **What it is for**: a scene baked somewhere else. Another engine's reflection captures are
7598
+ * images taken where each one stands; a consumer fits them to a lattice — for each point, the
7599
+ * capture whose reach holds it — and hands each point its image here, where `bakeProbe` would
7600
+ * have drawn this engine's own world into it.
7601
+ *
7602
+ * `options.irradiance` decides, at grid scope as a bake's does, whether the grid also lights the
7603
+ * scene's diffuse ambient. A grid that crossfades writes the set being swept, as a bake does.
7604
+ */
7605
+ setProbeLayerImage(layer, image, options) {
7606
+ if (this.contextLost)
7607
+ return false;
7608
+ const probe = this.probeCapture;
7609
+ const array = this.probeArray;
7610
+ const grid = this.probes;
7611
+ const sweeps = this.probeSweeps;
7612
+ if (probe === null || array === null || grid === null || sweeps === null) {
7613
+ console.warn('Renderer: setProbeLayerImage needs a reflection probe (`reflectionProbeSize`) and a grid ' +
7614
+ 'declared with setProbeGrid; the image was ignored.');
7615
+ return false;
7616
+ }
7617
+ if (!Number.isInteger(layer) || layer < 0 || layer >= grid.layers) {
7618
+ console.warn(`Renderer: setProbeLayerImage was given layer ${layer}, and the grid has ${grid.layers}.`);
7619
+ return false;
7620
+ }
7621
+ if (!probe.upload(this.gl, equirectToCubeFaces(image, probe.size)))
7622
+ return false;
7623
+ this.probeAmbient = options?.irradiance ?? true;
7624
+ this.environmentPrefilter?.run(this.gl, probe, array, sweeps.writeLayer(layer), ENVIRONMENT_TEXTURE_UNIT, this.quality.environmentPrefilterSamples, this.quality.environmentPrefilter);
7625
+ sweeps.baked(layer);
7626
+ return true;
7627
+ }
7444
7628
  /**
7445
7629
  * Draw a volume of light: a beam from a lamp, a shaft through a window, the cone under a
7446
7630
  * street light.
@@ -7758,6 +7942,14 @@ const NO_MODEL_PARAMS = new Float32Array(MODEL_PARAM_FLOATS);
7758
7942
  * The lit vertex stage a program of this variant compiles: the standard programs' and the cloth's,
7759
7943
  * which `modelProgram` pairs a model's fragment stage with.
7760
7944
  */
7945
+ /**
7946
+ * The vertex stage a model's program draws with: the variant's own, but for a lightmapped instanced
7947
+ * batch, whose tint lane is each instance's region of the page. See `lightmap.ts`.
7948
+ */
7949
+ function modelVertexOf(variant, kind) {
7950
+ const vertex = litVertexOf(variant);
7951
+ return kind === 'lightmap' && variant === 'instanced' ? lightmapRegionsBound(vertex) : vertex;
7952
+ }
7761
7953
  function litVertexOf(variant) {
7762
7954
  if (variant.startsWith('cloth')) {
7763
7955
  const eight = variant[5] === '8';
@@ -10,9 +10,13 @@ export declare class GlSkinScatter {
10
10
  private readonly depthToViewZ;
11
11
  private diffuse;
12
12
  private across;
13
+ private albedo;
13
14
  private multisampled;
15
+ private albedoMultisampled;
14
16
  private drawFramebuffer;
15
17
  private resolveFramebuffer;
18
+ private albedoFramebuffer;
19
+ private albedoResolveFramebuffer;
16
20
  private acrossFramebuffer;
17
21
  private width;
18
22
  private height;
@@ -21,6 +25,7 @@ export declare class GlSkinScatter {
21
25
  /** Refused once and for good: a driver that will not render to half floats. */
22
26
  private unavailable;
23
27
  private cleared;
28
+ private albedoCleared;
24
29
  private drawn;
25
30
  constructor(gl: WebGL2RenderingContext);
26
31
  /** Whether any skin's diffuse was drawn this frame. */
@@ -34,6 +39,8 @@ export declare class GlSkinScatter {
34
39
  ready(scene: SceneTarget): boolean;
35
40
  /** Bind the target for one skin's diffuse half, cleared the first time a frame does. */
36
41
  begin(): void;
42
+ /** Bind the target for the same skin's colour half, cleared the first time a frame does. */
43
+ beginAlbedo(): void;
37
44
  /**
38
45
  * Across into the second target, then down and added into the frame, which `scene` binds. `depth`
39
46
  * is the frame's depth as a single-sampled texture, or null where none could be had — and then
@@ -42,8 +49,15 @@ export declare class GlSkinScatter {
42
49
  spread(scene: SceneTarget, depth: WebGLTexture | null, empty: WebGLTexture, projection: ArrayLike<number>, inverse: ArrayLike<number>): void;
43
50
  /** Spent at the frame's start: what was drawn into the target was last frame's. */
44
51
  reset(): void;
52
+ /** A multisampled target into its single-sampled texture. */
53
+ private resolve;
45
54
  dispose(): void;
46
55
  private allocate;
56
+ /**
57
+ * A framebuffer a skin half draws into against the frame's depth: the texture itself, or a
58
+ * multisampled renderbuffer and the framebuffer that resolves it into the texture.
59
+ */
60
+ private drawTarget;
47
61
  private refuse;
48
62
  private halfFloatTexture;
49
63
  private release;