@driftengine/core 4.8.5 → 4.8.7

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 (214) hide show
  1. package/CHANGELOG.json +4392 -4250
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +12 -4
  4. package/dist/index.js +12 -2
  5. package/dist/render/ambientHarmonics.d.ts +46 -0
  6. package/dist/render/ambientHarmonics.js +80 -0
  7. package/dist/render/anisotropicLobe.d.ts +10 -0
  8. package/dist/render/anisotropicLobe.js +19 -0
  9. package/dist/render/areaLights.d.ts +54 -0
  10. package/dist/render/areaLights.js +55 -0
  11. package/dist/render/backend/api.d.ts +43 -2
  12. package/dist/render/backend/webgl2/instanced.d.ts +2 -0
  13. package/dist/render/backend/webgl2/instanced.js +11 -2
  14. package/dist/render/backend/webgl2/renderer.d.ts +120 -11
  15. package/dist/render/backend/webgl2/renderer.js +356 -53
  16. package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
  17. package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
  18. package/dist/render/backend/webgl2/skinScatter.d.ts +14 -0
  19. package/dist/render/backend/webgl2/skinScatter.js +88 -26
  20. package/dist/render/backend/webgpu/buffers.js +2 -1
  21. package/dist/render/backend/webgpu/clusterBinner.js +3 -2
  22. package/dist/render/backend/webgpu/flatPass.d.ts +7 -5
  23. package/dist/render/backend/webgpu/flatPass.js +15 -13
  24. package/dist/render/backend/webgpu/particlePass.d.ts +37 -4
  25. package/dist/render/backend/webgpu/particlePass.js +81 -12
  26. package/dist/render/backend/webgpu/postPass.d.ts +6 -1
  27. package/dist/render/backend/webgpu/postPass.js +7 -2
  28. package/dist/render/backend/webgpu/renderer.d.ts +93 -8
  29. package/dist/render/backend/webgpu/renderer.js +483 -93
  30. package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
  31. package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
  32. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
  33. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.js +41 -1
  34. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +40 -28
  35. package/dist/render/backend/webgpu/skinPaletteRing.js +69 -32
  36. package/dist/render/backend/webgpu/skinScatterPass.d.ts +10 -2
  37. package/dist/render/backend/webgpu/skinScatterPass.js +59 -9
  38. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -1
  39. package/dist/render/backend/webgpu/surfaceTexturePass.js +45 -5
  40. package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
  41. package/dist/render/bloomChain.d.ts +44 -0
  42. package/dist/render/bloomChain.js +28 -0
  43. package/dist/render/bloomPass.d.ts +3 -1
  44. package/dist/render/bloomPass.js +16 -2
  45. package/dist/render/clusteredLights.d.ts +78 -6
  46. package/dist/render/clusteredLights.js +214 -13
  47. package/dist/render/driftLight/bake.d.ts +2 -0
  48. package/dist/render/driftLight/bake.js +1 -1
  49. package/dist/render/driftLight/falloff.d.ts +4 -2
  50. package/dist/render/driftLight/falloff.js +8 -2
  51. package/dist/render/fixtureAtlas.js +1 -1
  52. package/dist/render/gi/bounceLights.d.ts +3 -2
  53. package/dist/render/gi/bounceLights.js +8 -3
  54. package/dist/render/instanceCull.js +12 -0
  55. package/dist/render/instances.d.ts +47 -11
  56. package/dist/render/instances.js +24 -9
  57. package/dist/render/lightBudget.d.ts +38 -2
  58. package/dist/render/lightBudget.js +48 -5
  59. package/dist/render/lightmap.d.ts +116 -0
  60. package/dist/render/lightmap.js +140 -0
  61. package/dist/render/mesh.js +2 -1
  62. package/dist/render/particleBatch.d.ts +59 -3
  63. package/dist/render/particleBatch.js +53 -2
  64. package/dist/render/particleMaterial.d.ts +6 -1
  65. package/dist/render/particleMaterial.js +10 -2
  66. package/dist/render/particlePool.d.ts +32 -0
  67. package/dist/render/particlePool.js +9 -0
  68. package/dist/render/particleSort.d.ts +34 -0
  69. package/dist/render/particleSort.js +96 -0
  70. package/dist/render/pointLightSelection.d.ts +13 -0
  71. package/dist/render/pointLightSelection.js +3 -1
  72. package/dist/render/renderQuality.d.ts +8 -7
  73. package/dist/render/rendererHarness.js +3 -0
  74. package/dist/render/rgb9e5.d.ts +22 -0
  75. package/dist/render/rgb9e5.js +47 -0
  76. package/dist/render/sceneCapture.d.ts +29 -0
  77. package/dist/render/sceneCapture.js +36 -0
  78. package/dist/render/sceneTarget.d.ts +5 -0
  79. package/dist/render/sceneTarget.js +8 -1
  80. package/dist/render/screenSpaceReflection.d.ts +27 -1
  81. package/dist/render/screenSpaceReflection.js +9 -0
  82. package/dist/render/shaders/bloom.d.ts +8 -4
  83. package/dist/render/shaders/bloom.js +20 -3
  84. package/dist/render/shaders/bolt.d.ts +1 -1
  85. package/dist/render/shaders/bolt.js +7 -1
  86. package/dist/render/shaders/depth.js +11 -0
  87. package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
  88. package/dist/render/shaders/flat/anisotropic.js +37 -3
  89. package/dist/render/shaders/flat/areaLight.d.ts +10 -0
  90. package/dist/render/shaders/flat/areaLight.js +142 -0
  91. package/dist/render/shaders/flat/glassTint.d.ts +3 -2
  92. package/dist/render/shaders/flat/glassTint.js +3 -1
  93. package/dist/render/shaders/flat/index.d.ts +5 -0
  94. package/dist/render/shaders/flat/index.js +46 -6
  95. package/dist/render/shaders/flat/lobes.d.ts +6 -0
  96. package/dist/render/shaders/flat/lobes.js +49 -43
  97. package/dist/render/shaders/flat/main.d.ts +1 -1
  98. package/dist/render/shaders/flat/main.js +158 -121
  99. package/dist/render/shaders/flat/models.d.ts +21 -6
  100. package/dist/render/shaders/flat/models.js +161 -17
  101. package/dist/render/shaders/flat/normalMap.d.ts +10 -0
  102. package/dist/render/shaders/flat/normalMap.js +25 -0
  103. package/dist/render/shaders/flat/preamble.js +84 -40
  104. package/dist/render/shaders/flat/skin.js +6 -5
  105. package/dist/render/shaders/generated/bloom.wgsl.d.ts +13 -3
  106. package/dist/render/shaders/generated/bloom.wgsl.js +13 -3
  107. package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
  108. package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
  109. package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
  110. package/dist/render/shaders/generated/depth.wgsl.js +4 -4
  111. package/dist/render/shaders/generated/flat.wgsl.d.ts +1647 -2044
  112. package/dist/render/shaders/generated/flat.wgsl.js +1685 -2082
  113. package/dist/render/shaders/generated/particle.wgsl.d.ts +115 -1
  114. package/dist/render/shaders/generated/particle.wgsl.js +115 -1
  115. package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +12 -2
  116. package/dist/render/shaders/generated/skinBlur.wgsl.js +12 -2
  117. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +11 -6
  118. package/dist/render/shaders/generated/ssrTrace.wgsl.js +11 -6
  119. package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
  120. package/dist/render/shaders/generated/water.wgsl.js +23 -16
  121. package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
  122. package/dist/render/shaders/particle.d.ts +17 -1
  123. package/dist/render/shaders/particle.js +133 -1
  124. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  125. package/dist/render/shaders/recon/motion.wgsl.js +13 -2
  126. package/dist/render/shaders/skinBlur.js +25 -3
  127. package/dist/render/shaders/ssrTrace.js +20 -1
  128. package/dist/render/shaders/water.d.ts +1 -1
  129. package/dist/render/shaders/water.js +12 -3
  130. package/dist/render/skinBlur.d.ts +8 -5
  131. package/dist/render/skinBlur.js +8 -5
  132. package/dist/render/skinScatter.d.ts +3 -1
  133. package/dist/render/skinScatter.js +3 -1
  134. package/dist/render/ssrPass.js +1 -0
  135. package/dist/render/surfaceModel.d.ts +49 -8
  136. package/dist/render/surfaceModel.js +54 -8
  137. package/dist/render/surfaceTexture.d.ts +38 -1
  138. package/dist/render/surfaceTexture.js +37 -2
  139. package/package.json +6 -6
  140. package/src/index.ts +26 -3
  141. package/src/render/ambientHarmonics.ts +92 -0
  142. package/src/render/anisotropicLobe.ts +29 -0
  143. package/src/render/areaLights.ts +97 -0
  144. package/src/render/backend/api.ts +61 -1
  145. package/src/render/backend/webgl2/instanced.ts +12 -2
  146. package/src/render/backend/webgl2/renderer.ts +470 -59
  147. package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
  148. package/src/render/backend/webgl2/skinScatter.ts +120 -43
  149. package/src/render/backend/webgpu/buffers.ts +2 -1
  150. package/src/render/backend/webgpu/clusterBinner.ts +3 -1
  151. package/src/render/backend/webgpu/flatPass.ts +18 -13
  152. package/src/render/backend/webgpu/particlePass.ts +119 -12
  153. package/src/render/backend/webgpu/postPass.ts +7 -2
  154. package/src/render/backend/webgpu/renderer.ts +589 -113
  155. package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
  156. package/src/render/backend/webgpu/shaders/clusterBin.wgsl.ts +41 -1
  157. package/src/render/backend/webgpu/skinPaletteRing.ts +65 -33
  158. package/src/render/backend/webgpu/skinScatterPass.ts +59 -9
  159. package/src/render/backend/webgpu/surfaceTexturePass.ts +57 -6
  160. package/src/render/bloomChain.ts +69 -0
  161. package/src/render/bloomPass.ts +26 -2
  162. package/src/render/clusteredLights.ts +259 -12
  163. package/src/render/driftLight/bake.ts +3 -1
  164. package/src/render/driftLight/falloff.ts +13 -2
  165. package/src/render/fixtureAtlas.ts +1 -1
  166. package/src/render/gi/bounceLights.ts +12 -3
  167. package/src/render/instanceCull.ts +9 -0
  168. package/src/render/instances.ts +61 -12
  169. package/src/render/lightBudget.ts +88 -4
  170. package/src/render/lightmap.ts +243 -0
  171. package/src/render/mesh.ts +2 -1
  172. package/src/render/particleBatch.ts +95 -4
  173. package/src/render/particleMaterial.ts +11 -1
  174. package/src/render/particlePool.ts +36 -0
  175. package/src/render/particleSort.ts +133 -0
  176. package/src/render/pointLightSelection.ts +20 -1
  177. package/src/render/renderQuality.ts +8 -7
  178. package/src/render/rendererHarness.ts +3 -0
  179. package/src/render/rgb9e5.ts +52 -0
  180. package/src/render/sceneCapture.ts +56 -0
  181. package/src/render/sceneTarget.ts +18 -1
  182. package/src/render/screenSpaceReflection.ts +33 -1
  183. package/src/render/shaders/bloom.ts +20 -3
  184. package/src/render/shaders/bolt.ts +7 -1
  185. package/src/render/shaders/depth.ts +11 -0
  186. package/src/render/shaders/flat/anisotropic.ts +37 -3
  187. package/src/render/shaders/flat/areaLight.ts +142 -0
  188. package/src/render/shaders/flat/glassTint.ts +9 -2
  189. package/src/render/shaders/flat/index.ts +51 -6
  190. package/src/render/shaders/flat/lobes.ts +50 -43
  191. package/src/render/shaders/flat/main.ts +158 -121
  192. package/src/render/shaders/flat/models.ts +170 -21
  193. package/src/render/shaders/flat/normalMap.ts +25 -0
  194. package/src/render/shaders/flat/preamble.ts +84 -40
  195. package/src/render/shaders/flat/skin.ts +6 -5
  196. package/src/render/shaders/generated/bloom.wgsl.ts +13 -3
  197. package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
  198. package/src/render/shaders/generated/depth.wgsl.ts +4 -4
  199. package/src/render/shaders/generated/flat.wgsl.ts +1685 -2082
  200. package/src/render/shaders/generated/particle.wgsl.ts +116 -1
  201. package/src/render/shaders/generated/skinBlur.wgsl.ts +12 -2
  202. package/src/render/shaders/generated/ssrTrace.wgsl.ts +11 -6
  203. package/src/render/shaders/generated/water.wgsl.ts +23 -16
  204. package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
  205. package/src/render/shaders/particle.ts +134 -1
  206. package/src/render/shaders/recon/motion.wgsl.ts +13 -2
  207. package/src/render/shaders/skinBlur.ts +25 -3
  208. package/src/render/shaders/ssrTrace.ts +20 -1
  209. package/src/render/shaders/water.ts +12 -3
  210. package/src/render/skinBlur.ts +8 -5
  211. package/src/render/skinScatter.ts +3 -1
  212. package/src/render/ssrPass.ts +1 -0
  213. package/src/render/surfaceModel.ts +82 -10
  214. package/src/render/surfaceTexture.ts +101 -3
@@ -0,0 +1,130 @@
1
+ import { glSceneDepthFormat } from '../../depthConvention.ts';
2
+ import type { SurfaceTexture } from '../../surfaceTexture.ts';
3
+
4
+ /**
5
+ * A scene capture's framebuffers on WebGL2: the capture's own texture as the colour a pass resolves
6
+ * into, and a depth of its own, at the capture's size. See `sceneCapture.ts` for what a capture is.
7
+ *
8
+ * **At the frame's sample count, as WebGPU's is**, so a capture's edges are antialiased exactly as
9
+ * the frame's are: drawn into multisampled renderbuffers and blitted into the texture at the end,
10
+ * since a texture layer cannot be multisampled and sampled both. A count the driver refuses falls
11
+ * back to one, as the scene target's does, rather than drawing nothing.
12
+ */
13
+ export class GlSceneCaptureTarget {
14
+ /** Where the capture resolves: its texture's layer, and a depth when it is drawn into directly. */
15
+ private readonly resolveFramebuffer: WebGLFramebuffer;
16
+ private readonly resolveDepth: WebGLRenderbuffer;
17
+ /** Where it is drawn above one sample, or null. */
18
+ private multisampleFramebuffer: WebGLFramebuffer | null = null;
19
+ private multisampleColor: WebGLRenderbuffer | null = null;
20
+ private multisampleDepth: WebGLRenderbuffer | null = null;
21
+ /** False where the driver refused the attachment, said once by the renderer. */
22
+ readonly usable: boolean;
23
+
24
+ constructor(
25
+ gl: WebGL2RenderingContext,
26
+ texture: SurfaceTexture,
27
+ readonly width: number,
28
+ readonly height: number,
29
+ samples: number,
30
+ float: boolean,
31
+ ) {
32
+ const depthFormat = glSceneDepthFormat(gl).internalFormat;
33
+ this.resolveFramebuffer = must(gl.createFramebuffer());
34
+ this.resolveDepth = must(gl.createRenderbuffer());
35
+ gl.bindRenderbuffer(gl.RENDERBUFFER, this.resolveDepth);
36
+ gl.renderbufferStorage(gl.RENDERBUFFER, depthFormat, width, height);
37
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.resolveFramebuffer);
38
+ texture.attachColor(gl);
39
+ gl.framebufferRenderbuffer(
40
+ gl.FRAMEBUFFER,
41
+ gl.DEPTH_ATTACHMENT,
42
+ gl.RENDERBUFFER,
43
+ this.resolveDepth,
44
+ );
45
+ this.usable = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
46
+
47
+ const maximum = gl.getParameter(gl.MAX_SAMPLES) as number;
48
+ const count = Math.max(
49
+ 1,
50
+ Math.min(Math.round(samples), Number.isFinite(maximum) ? maximum : 1),
51
+ );
52
+ if (this.usable && count > 1) {
53
+ const color = must(gl.createRenderbuffer());
54
+ const depth = must(gl.createRenderbuffer());
55
+ gl.bindRenderbuffer(gl.RENDERBUFFER, color);
56
+ gl.renderbufferStorageMultisample(
57
+ gl.RENDERBUFFER,
58
+ count,
59
+ float ? gl.RGBA16F : gl.RGBA8,
60
+ width,
61
+ height,
62
+ );
63
+ gl.bindRenderbuffer(gl.RENDERBUFFER, depth);
64
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, count, depthFormat, width, height);
65
+ const framebuffer = must(gl.createFramebuffer());
66
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
67
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, color);
68
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depth);
69
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) {
70
+ this.multisampleFramebuffer = framebuffer;
71
+ this.multisampleColor = color;
72
+ this.multisampleDepth = depth;
73
+ } else {
74
+ gl.deleteFramebuffer(framebuffer);
75
+ gl.deleteRenderbuffer(color);
76
+ gl.deleteRenderbuffer(depth);
77
+ }
78
+ }
79
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
80
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
81
+ }
82
+
83
+ /** Bind where the pass draws, size the viewport and clear: colour to `clear`, depth to `depthClear`. */
84
+ begin(
85
+ gl: WebGL2RenderingContext,
86
+ clear: readonly [number, number, number],
87
+ depthClear: number,
88
+ ): void {
89
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.multisampleFramebuffer ?? this.resolveFramebuffer);
90
+ gl.viewport(0, 0, this.width, this.height);
91
+ gl.clearColor(clear[0], clear[1], clear[2], 1);
92
+ gl.clearDepth(depthClear);
93
+ gl.depthMask(true);
94
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
95
+ }
96
+
97
+ /** Resolve the multisampled colour into the texture, where there is any. */
98
+ end(gl: WebGL2RenderingContext): void {
99
+ if (this.multisampleFramebuffer === null) return;
100
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.multisampleFramebuffer);
101
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.resolveFramebuffer);
102
+ gl.blitFramebuffer(
103
+ 0,
104
+ 0,
105
+ this.width,
106
+ this.height,
107
+ 0,
108
+ 0,
109
+ this.width,
110
+ this.height,
111
+ gl.COLOR_BUFFER_BIT,
112
+ gl.NEAREST,
113
+ );
114
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
115
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
116
+ }
117
+
118
+ dispose(gl: WebGL2RenderingContext): void {
119
+ gl.deleteFramebuffer(this.resolveFramebuffer);
120
+ gl.deleteRenderbuffer(this.resolveDepth);
121
+ if (this.multisampleFramebuffer !== null) gl.deleteFramebuffer(this.multisampleFramebuffer);
122
+ if (this.multisampleColor !== null) gl.deleteRenderbuffer(this.multisampleColor);
123
+ if (this.multisampleDepth !== null) gl.deleteRenderbuffer(this.multisampleDepth);
124
+ }
125
+ }
126
+
127
+ function must<T>(made: T | null): T {
128
+ if (made === null) throw new Error('GlSceneCaptureTarget: a GL object could not be created');
129
+ return made;
130
+ }
@@ -5,14 +5,19 @@
5
5
  *
6
6
  * **Drawn as it goes, not kept**: this backend issues a draw when it is asked for, and the state a
7
7
  * skin draw reads — its joint palette, its uniforms — is rewritten by the draw after it. So the
8
- * renderer draws each skin twice in place, the frame's half and then at once its diffuse half into
9
- * this target, which shares the frame's depth and tests for equality against it.
8
+ * renderer draws each skin three times in place: the frame's half, then at once the diffuse's light
9
+ * alone into this target and its colour alone into the other, both sharing the frame's depth and
10
+ * testing for equality against it. The blur spreads the light and multiplies it by the colour.
10
11
  *
11
12
  * **The units it reads are its own** — `SKIN_BLUR_TEXTURE_UNIT` and the one after — past every
12
13
  * unit a lit program binds, because it runs in the middle of a frame and the draws after it must
13
14
  * find their textures where they left them.
14
15
  */
15
- import { SKIN_BLUR_DEPTH_UNIT, SKIN_BLUR_TEXTURE_UNIT } from '../../lightBudget.ts';
16
+ import {
17
+ SKIN_BLUR_ALBEDO_UNIT,
18
+ SKIN_BLUR_DEPTH_UNIT,
19
+ SKIN_BLUR_TEXTURE_UNIT,
20
+ } from '../../lightBudget.ts';
16
21
  import { blendKeeping } from '../../sceneCoverage.ts';
17
22
  import type { SceneTarget } from '../../sceneTarget.ts';
18
23
  import { compileProgram, uniformLocations } from '../../shader.ts';
@@ -33,9 +38,13 @@ export class GlSkinScatter {
33
38
  private readonly depthToViewZ = new Float32Array(4);
34
39
  private diffuse: WebGLTexture | null = null;
35
40
  private across: WebGLTexture | null = null;
41
+ private albedo: WebGLTexture | null = null;
36
42
  private multisampled: WebGLRenderbuffer | null = null;
43
+ private albedoMultisampled: WebGLRenderbuffer | null = null;
37
44
  private drawFramebuffer: WebGLFramebuffer | null = null;
38
45
  private resolveFramebuffer: WebGLFramebuffer | null = null;
46
+ private albedoFramebuffer: WebGLFramebuffer | null = null;
47
+ private albedoResolveFramebuffer: WebGLFramebuffer | null = null;
39
48
  private acrossFramebuffer: WebGLFramebuffer | null = null;
40
49
  private width = 0;
41
50
  private height = 0;
@@ -44,6 +53,7 @@ export class GlSkinScatter {
44
53
  /** Refused once and for good: a driver that will not render to half floats. */
45
54
  private unavailable = false;
46
55
  private cleared = false;
56
+ private albedoCleared = false;
47
57
  private drawn = false;
48
58
 
49
59
  constructor(gl: WebGL2RenderingContext) {
@@ -115,6 +125,17 @@ export class GlSkinScatter {
115
125
  this.drawn = true;
116
126
  }
117
127
 
128
+ /** Bind the target for the same skin's colour half, cleared the first time a frame does. */
129
+ beginAlbedo(): void {
130
+ const { gl } = this;
131
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.albedoFramebuffer);
132
+ gl.viewport(0, 0, this.width, this.height);
133
+ if (!this.albedoCleared) {
134
+ gl.clearBufferfv(gl.COLOR, 0, CLEAR);
135
+ this.albedoCleared = true;
136
+ }
137
+ }
138
+
118
139
  /**
119
140
  * Across into the second target, then down and added into the frame, which `scene` binds. `depth`
120
141
  * is the frame's depth as a single-sampled texture, or null where none could be had — and then
@@ -129,20 +150,8 @@ export class GlSkinScatter {
129
150
  ): void {
130
151
  const { gl } = this;
131
152
  if (this.multisampled !== null) {
132
- gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.drawFramebuffer);
133
- gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.resolveFramebuffer);
134
- gl.blitFramebuffer(
135
- 0,
136
- 0,
137
- this.width,
138
- this.height,
139
- 0,
140
- 0,
141
- this.width,
142
- this.height,
143
- gl.COLOR_BUFFER_BIT,
144
- gl.NEAREST,
145
- );
153
+ this.resolve(this.drawFramebuffer, this.resolveFramebuffer);
154
+ this.resolve(this.albedoFramebuffer, this.albedoResolveFramebuffer);
146
155
  }
147
156
  gl.disable(gl.DEPTH_TEST);
148
157
  gl.disable(gl.CULL_FACE);
@@ -153,6 +162,9 @@ export class GlSkinScatter {
153
162
  const u = this.uniforms;
154
163
  gl.uniform1i(u['uSkin'] ?? null, SKIN_BLUR_TEXTURE_UNIT);
155
164
  gl.uniform1i(u['uDepth'] ?? null, SKIN_BLUR_DEPTH_UNIT);
165
+ gl.uniform1i(u['uAlbedo'] ?? null, SKIN_BLUR_ALBEDO_UNIT);
166
+ gl.activeTexture(gl.TEXTURE0 + SKIN_BLUR_ALBEDO_UNIT);
167
+ gl.bindTexture(gl.TEXTURE_2D, this.albedo ?? empty);
156
168
  /* The four terms that carry a depth back to view-space metres: the occlusion blur's own. */
157
169
  this.depthToViewZ[0] = (inverse[10] as number) ?? 0;
158
170
  this.depthToViewZ[1] = (inverse[14] as number) ?? 0;
@@ -175,6 +187,8 @@ export class GlSkinScatter {
175
187
  this.step[0] = 1 / this.width;
176
188
  this.step[1] = 0;
177
189
  gl.uniform2fv(u['uStep'] ?? null, this.step);
190
+ /* The light spreads on both axes; the colour is applied once, on the one adding into the frame. */
191
+ gl.uniform1f(u['uApplyAlbedo'] ?? null, 0);
178
192
  gl.drawArrays(gl.TRIANGLES, 0, 3);
179
193
 
180
194
  scene.bind();
@@ -182,6 +196,7 @@ export class GlSkinScatter {
182
196
  this.step[0] = 0;
183
197
  this.step[1] = 1 / this.height;
184
198
  gl.uniform2fv(u['uStep'] ?? null, this.step);
199
+ gl.uniform1f(u['uApplyAlbedo'] ?? null, 1);
185
200
  /* Added: its colour onto the frame's, the frame's alpha kept. */
186
201
  gl.enable(gl.BLEND);
187
202
  gl.blendEquation(gl.FUNC_ADD);
@@ -199,9 +214,29 @@ export class GlSkinScatter {
199
214
  /** Spent at the frame's start: what was drawn into the target was last frame's. */
200
215
  reset(): void {
201
216
  this.cleared = false;
217
+ this.albedoCleared = false;
202
218
  this.drawn = false;
203
219
  }
204
220
 
221
+ /** A multisampled target into its single-sampled texture. */
222
+ private resolve(from: WebGLFramebuffer | null, to: WebGLFramebuffer | null): void {
223
+ const { gl } = this;
224
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, from);
225
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, to);
226
+ gl.blitFramebuffer(
227
+ 0,
228
+ 0,
229
+ this.width,
230
+ this.height,
231
+ 0,
232
+ 0,
233
+ this.width,
234
+ this.height,
235
+ gl.COLOR_BUFFER_BIT,
236
+ gl.NEAREST,
237
+ );
238
+ }
239
+
205
240
  dispose(): void {
206
241
  const { gl } = this;
207
242
  this.release();
@@ -226,19 +261,59 @@ export class GlSkinScatter {
226
261
  this.sharedDepth = depthRenderbuffer ?? depthTexture;
227
262
  this.diffuse = this.halfFloatTexture(width, height);
228
263
  this.across = this.halfFloatTexture(width, height);
264
+ this.albedo = this.halfFloatTexture(width, height);
229
265
 
230
- this.drawFramebuffer = gl.createFramebuffer();
231
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.drawFramebuffer);
232
- if (samples > 1 && depthRenderbuffer !== null) {
233
- this.multisampled = gl.createRenderbuffer();
234
- gl.bindRenderbuffer(gl.RENDERBUFFER, this.multisampled);
235
- gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA16F, width, height);
236
- gl.framebufferRenderbuffer(
237
- gl.FRAMEBUFFER,
238
- gl.COLOR_ATTACHMENT0,
266
+ const multisampled = samples > 1 && depthRenderbuffer !== null;
267
+ const diffuse = this.drawTarget(this.diffuse, multisampled, depthTexture, depthRenderbuffer);
268
+ this.drawFramebuffer = diffuse.framebuffer;
269
+ this.multisampled = diffuse.multisampled;
270
+ this.resolveFramebuffer = diffuse.resolve;
271
+ const albedo = this.drawTarget(this.albedo, multisampled, depthTexture, depthRenderbuffer);
272
+ this.albedoFramebuffer = albedo.framebuffer;
273
+ this.albedoMultisampled = albedo.multisampled;
274
+ this.albedoResolveFramebuffer = albedo.resolve;
275
+
276
+ this.acrossFramebuffer = gl.createFramebuffer();
277
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.acrossFramebuffer);
278
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.across, 0);
279
+ const acrossReady = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
280
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
281
+ if (diffuse.ready && albedo.ready && acrossReady) return true;
282
+ this.release();
283
+ this.refuse();
284
+ return false;
285
+ }
286
+
287
+ /**
288
+ * A framebuffer a skin half draws into against the frame's depth: the texture itself, or a
289
+ * multisampled renderbuffer and the framebuffer that resolves it into the texture.
290
+ */
291
+ private drawTarget(
292
+ texture: WebGLTexture | null,
293
+ multisampled: boolean,
294
+ depthTexture: WebGLTexture | null,
295
+ depthRenderbuffer: WebGLRenderbuffer | null,
296
+ ): {
297
+ framebuffer: WebGLFramebuffer | null;
298
+ multisampled: WebGLRenderbuffer | null;
299
+ resolve: WebGLFramebuffer | null;
300
+ ready: boolean;
301
+ } {
302
+ const { gl } = this;
303
+ const framebuffer = gl.createFramebuffer();
304
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
305
+ let samples: WebGLRenderbuffer | null = null;
306
+ if (multisampled && depthRenderbuffer !== null) {
307
+ samples = gl.createRenderbuffer();
308
+ gl.bindRenderbuffer(gl.RENDERBUFFER, samples);
309
+ gl.renderbufferStorageMultisample(
239
310
  gl.RENDERBUFFER,
240
- this.multisampled,
311
+ this.samples,
312
+ gl.RGBA16F,
313
+ this.width,
314
+ this.height,
241
315
  );
316
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, samples);
242
317
  gl.framebufferRenderbuffer(
243
318
  gl.FRAMEBUFFER,
244
319
  gl.DEPTH_ATTACHMENT,
@@ -246,25 +321,17 @@ export class GlSkinScatter {
246
321
  depthRenderbuffer,
247
322
  );
248
323
  } else {
249
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.diffuse, 0);
324
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
250
325
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, 0);
251
326
  }
252
- const drawReady = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
253
-
254
- if (this.multisampled !== null) {
255
- this.resolveFramebuffer = gl.createFramebuffer();
256
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.resolveFramebuffer);
257
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.diffuse, 0);
327
+ const ready = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
328
+ let resolve: WebGLFramebuffer | null = null;
329
+ if (samples !== null) {
330
+ resolve = gl.createFramebuffer();
331
+ gl.bindFramebuffer(gl.FRAMEBUFFER, resolve);
332
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
258
333
  }
259
- this.acrossFramebuffer = gl.createFramebuffer();
260
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.acrossFramebuffer);
261
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.across, 0);
262
- const acrossReady = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
263
- gl.bindRenderbuffer(gl.RENDERBUFFER, null);
264
- if (drawReady && acrossReady) return true;
265
- this.release();
266
- this.refuse();
267
- return false;
334
+ return { framebuffer, multisampled: samples, resolve, ready };
268
335
  }
269
336
 
270
337
  private refuse(): void {
@@ -294,16 +361,26 @@ export class GlSkinScatter {
294
361
  const { gl } = this;
295
362
  if (this.drawFramebuffer !== null) gl.deleteFramebuffer(this.drawFramebuffer);
296
363
  if (this.resolveFramebuffer !== null) gl.deleteFramebuffer(this.resolveFramebuffer);
364
+ if (this.albedoFramebuffer !== null) gl.deleteFramebuffer(this.albedoFramebuffer);
365
+ if (this.albedoResolveFramebuffer !== null) {
366
+ gl.deleteFramebuffer(this.albedoResolveFramebuffer);
367
+ }
297
368
  if (this.acrossFramebuffer !== null) gl.deleteFramebuffer(this.acrossFramebuffer);
298
369
  if (this.multisampled !== null) gl.deleteRenderbuffer(this.multisampled);
370
+ if (this.albedoMultisampled !== null) gl.deleteRenderbuffer(this.albedoMultisampled);
299
371
  if (this.diffuse !== null) gl.deleteTexture(this.diffuse);
300
372
  if (this.across !== null) gl.deleteTexture(this.across);
373
+ if (this.albedo !== null) gl.deleteTexture(this.albedo);
301
374
  this.drawFramebuffer = null;
302
375
  this.resolveFramebuffer = null;
376
+ this.albedoFramebuffer = null;
377
+ this.albedoResolveFramebuffer = null;
303
378
  this.acrossFramebuffer = null;
304
379
  this.multisampled = null;
380
+ this.albedoMultisampled = null;
305
381
  this.diffuse = null;
306
382
  this.across = null;
383
+ this.albedo = null;
307
384
  this.sharedDepth = null;
308
385
  }
309
386
  }
@@ -766,7 +766,8 @@ const INSTANCE_ATTRIBUTES: GPUVertexAttribute[] = [
766
766
  { shaderLocation: 12, offset: 16, format: 'float32x4' },
767
767
  { shaderLocation: 13, offset: 32, format: 'float32x4' },
768
768
  { shaderLocation: 14, offset: 48, format: 'float32x4' },
769
- { shaderLocation: 15, offset: 64, format: 'float32x3' },
769
+ /* The tint and, in w, the opacity. See `INSTANCE_FLOATS`. */
770
+ { shaderLocation: 15, offset: 64, format: 'float32x4' },
770
771
  ];
771
772
 
772
773
  /** The locations above, as the set the base layout must stand clear of on an instanced draw. */
@@ -5,6 +5,7 @@ import {
5
5
  TABLE_HEIGHT,
6
6
  TABLE_WIDTH,
7
7
  type ClusterLightSet,
8
+ clusteredLightTotal,
8
9
  writeLightRecord,
9
10
  } from '../../clusteredLights.ts';
10
11
  import { MAX_POINT_LIGHTS } from '../../lightBudget.ts';
@@ -167,7 +168,8 @@ export class ClusterBinner {
167
168
  if (frame === null || device === null || pipeline === null || group === null) return;
168
169
  if (params === null || lightBuffer === null) return;
169
170
 
170
- const count = Math.min(frame.lights.count, MAX_CLUSTERED_LIGHTS);
171
+ /* The lamps and the clustered rectangles after them. See `ClusterLightSet.areas`. */
172
+ const count = clusteredLightTotal(frame.lights);
171
173
 
172
174
  /*
173
175
  * The records, by the function `buildLightClusters` writes them with, because the shader
@@ -1,6 +1,6 @@
1
1
  import { SURFACE_MODEL_SWITCH } from '../../surfaceModel.ts';
2
2
  import type { SurfaceModelKind } from '../../surfaceModel.ts';
3
- import type { SkinHalf } from '../../shaders/flat/models.ts';
3
+ import { SKIN_HALF_SWITCH, type SkinHalf } from '../../shaders/flat/models.ts';
4
4
  import { DEPTH_COMPARE, DEPTH_FORMAT, depthOffsetForLayer } from '../../depthConvention.ts';
5
5
  import {
6
6
  FLAT_BINDINGS,
@@ -153,8 +153,8 @@ export function flatVertexBindings(
153
153
  const PLAIN_VERTEX = flatVertexBindings(false);
154
154
 
155
155
  /**
156
- * A vertex stage's constants, keyed by each override's **id** as the fragment's are: `SKIN_EIGHT` in
157
- * a skinned stage, nothing in any other. Every override a stage declares is set, and one this does
156
+ * A vertex stage's constants, keyed by each override's **id** as the fragment's are: `SKIN_EIGHT` and
157
+ * `CLOTH_BOUND` in a skinned stage, `LIGHTMAP_REGIONS` in an instanced one, nothing in any other. Every override a stage declares is set, and one this does
158
158
  * not know is refused here rather than defaulted. Shared with the shadow and glass-tint casters,
159
159
  * whose skinned stages declare the same switch.
160
160
  */
@@ -162,11 +162,14 @@ export function vertexConstants(
162
162
  bindings: StageBindings,
163
163
  skinEight: boolean,
164
164
  cloth = false,
165
+ /** Whether an instanced stage reads its tint lane as a lightmap region. See `lightmap.ts`. */
166
+ lightmapRegions = false,
165
167
  ): Record<string, number> {
166
168
  const constants: Record<string, number> = {};
167
169
  for (const [name, id] of Object.entries(bindings.overrides ?? {})) {
168
170
  if (name === 'SKIN_EIGHT') constants[String(id)] = skinEight ? 1 : 0;
169
171
  else if (name === 'CLOTH_BOUND') constants[String(id)] = cloth ? 1 : 0;
172
+ else if (name === 'LIGHTMAP_REGIONS') constants[String(id)] = lightmapRegions ? 1 : 0;
170
173
  else throw new Error(`flatPass: a vertex stage declares a switch "${name}" nothing sets`);
171
174
  }
172
175
  return constants;
@@ -263,13 +266,9 @@ function flatFragmentConstants(
263
266
  constants[String(id)] = name === modelSwitch ? 1 : 0;
264
267
  continue;
265
268
  }
266
- /* Skin's two halves under the screen-space blur, set from the key as a model's switch is. */
267
- if (name === 'SKIN_SCREEN' || name === 'SKIN_DIFFUSE') {
268
- constants[String(id)] =
269
- (name === 'SKIN_SCREEN' && skinPass === 'scene') ||
270
- (name === 'SKIN_DIFFUSE' && skinPass === 'diffuse')
271
- ? 1
272
- : 0;
269
+ /* Skin's halves under the screen-space blur, set from the key as a model's switch is. */
270
+ if (name === 'SKIN_SCREEN' || name === 'SKIN_DIFFUSE' || name === 'SKIN_ALBEDO') {
271
+ constants[String(id)] = skinPass !== 'whole' && name === SKIN_HALF_SWITCH[skinPass] ? 1 : 0;
273
272
  continue;
274
273
  }
275
274
  if (!(name in cache.litSwitches)) {
@@ -608,7 +607,7 @@ export function flatPipeline(
608
607
  cloth = false,
609
608
  /** Which shading model its fragment stage shades by, null the standard. The key must carry it. */
610
609
  model: SurfaceModelKind | null = null,
611
- /** Which half of a skin it draws. The key must carry it: `|ss` or `|sd`. See `SkinHalf`. */
610
+ /** Which half of a skin it draws. The key must carry it: `|ss`, `|sd` or `|sa`. See `SkinHalf`. */
612
611
  skinPass: SkinHalf = 'whole',
613
612
  ): GPURenderPipeline {
614
613
  return cache.get(
@@ -768,7 +767,8 @@ function flatDescriptor(
768
767
  skinPass: SkinHalf = 'whole',
769
768
  ): GPURenderPipelineDescriptor {
770
769
  const offset = depthOffsetForLayer(depthLayer);
771
- const diffuseAlone = skinPass === 'diffuse';
770
+ /* The diffuse and colour halves are the same surface again, into the blur's targets. */
771
+ const diffuseAlone = skinPass === 'diffuse' || skinPass === 'albedo';
772
772
  return {
773
773
  label: key,
774
774
  layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
@@ -784,7 +784,12 @@ function flatDescriptor(
784
784
  }),
785
785
  entryPoint: 'main',
786
786
  buffers: vertexBufferLayouts(present, instanced),
787
- constants: vertexConstants(flatVertexBindings(skinned, morphed, instanced), skinEight, cloth),
787
+ constants: vertexConstants(
788
+ flatVertexBindings(skinned, morphed, instanced),
789
+ skinEight,
790
+ cloth,
791
+ model === 'lightmap',
792
+ ),
788
793
  },
789
794
  fragment: {
790
795
  module: shaderModule(device, {