@driftengine/core 4.10.0 → 4.11.1

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 (165) hide show
  1. package/CHANGELOG.json +107 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +6 -5
  4. package/dist/index.js +2 -2
  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 +141 -3
  9. package/dist/render/backend/webgl2/renderer.js +454 -42
  10. package/dist/render/backend/webgpu/buffers.d.ts +5 -0
  11. package/dist/render/backend/webgpu/buffers.js +98 -48
  12. package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
  13. package/dist/render/backend/webgpu/clothKernels.js +76 -0
  14. package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
  15. package/dist/render/backend/webgpu/clothOps.js +78 -0
  16. package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
  17. package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
  18. package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
  19. package/dist/render/backend/webgpu/clothSetPack.js +144 -0
  20. package/dist/render/backend/webgpu/drawCommand.d.ts +2 -0
  21. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  22. package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
  23. package/dist/render/backend/webgpu/flatPass.js +2 -1
  24. package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
  25. package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
  26. package/dist/render/backend/webgpu/latePass.js +1 -0
  27. package/dist/render/backend/webgpu/motionPass.js +15 -6
  28. package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
  29. package/dist/render/backend/webgpu/pipelineCache.js +39 -1
  30. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  31. package/dist/render/backend/webgpu/renderer.d.ts +100 -1
  32. package/dist/render/backend/webgpu/renderer.js +423 -95
  33. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
  34. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
  35. package/dist/render/backend/webgpu/staticBundles.js +2 -1
  36. package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
  37. package/dist/render/backend/webgpu/vertexFormats.js +97 -0
  38. package/dist/render/globalMediumPass.d.ts +2 -0
  39. package/dist/render/globalMediumPass.js +1 -0
  40. package/dist/render/indexWidth.d.ts +17 -0
  41. package/dist/render/indexWidth.js +23 -0
  42. package/dist/render/instances.d.ts +13 -1
  43. package/dist/render/instances.js +10 -0
  44. package/dist/render/lightMatrix.d.ts +11 -0
  45. package/dist/render/lightMatrix.js +14 -0
  46. package/dist/render/mesh.d.ts +9 -0
  47. package/dist/render/mesh.js +38 -14
  48. package/dist/render/occlusionFade.d.ts +21 -0
  49. package/dist/render/occlusionFade.js +27 -0
  50. package/dist/render/renderQuality.d.ts +30 -0
  51. package/dist/render/renderQuality.js +5 -0
  52. package/dist/render/rendererHarness.d.ts +7 -0
  53. package/dist/render/rendererHarness.js +12 -0
  54. package/dist/render/sceneTarget.d.ts +2 -0
  55. package/dist/render/sceneTarget.js +1 -1
  56. package/dist/render/shader.d.ts +15 -0
  57. package/dist/render/shader.js +41 -0
  58. package/dist/render/shaders/ambientOcclusion.js +12 -1
  59. package/dist/render/shaders/flat/directionalShadow.js +84 -10
  60. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  61. package/dist/render/shaders/flat/glassTint.js +3 -0
  62. package/dist/render/shaders/flat/index.d.ts +15 -0
  63. package/dist/render/shaders/flat/index.js +24 -4
  64. package/dist/render/shaders/flat/layered.d.ts +27 -0
  65. package/dist/render/shaders/flat/layered.js +198 -0
  66. package/dist/render/shaders/flat/main.d.ts +1 -1
  67. package/dist/render/shaders/flat/main.js +50 -16
  68. package/dist/render/shaders/flat/mapReads.d.ts +10 -0
  69. package/dist/render/shaders/flat/mapReads.js +38 -0
  70. package/dist/render/shaders/flat/movingSun.d.ts +21 -0
  71. package/dist/render/shaders/flat/movingSun.js +46 -0
  72. package/dist/render/shaders/flat/normalMap.d.ts +6 -2
  73. package/dist/render/shaders/flat/normalMap.js +23 -3
  74. package/dist/render/shaders/flat/overlay.d.ts +3 -2
  75. package/dist/render/shaders/flat/overlay.js +41 -19
  76. package/dist/render/shaders/flat/preamble.js +23 -5
  77. package/dist/render/shaders/flat/worldUv.d.ts +22 -0
  78. package/dist/render/shaders/flat/worldUv.js +113 -0
  79. package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +6 -1
  80. package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +6 -1
  81. package/dist/render/shaders/generated/flat.wgsl.d.ts +768 -368
  82. package/dist/render/shaders/generated/flat.wgsl.js +791 -391
  83. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
  84. package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
  85. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
  86. package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
  87. package/dist/render/shaders/globalMedium.js +9 -1
  88. package/dist/render/shaders/lightVolume.js +9 -1
  89. package/dist/render/shaders/tangentFrame.d.ts +1 -1
  90. package/dist/render/shaders/tangentFrame.js +16 -3
  91. package/dist/render/skinnedClothRun.d.ts +28 -0
  92. package/dist/render/skinnedClothRun.js +103 -35
  93. package/dist/render/surfaceLayers.d.ts +107 -0
  94. package/dist/render/surfaceLayers.js +82 -0
  95. package/dist/render/surfaceOverlay.d.ts +16 -3
  96. package/dist/render/surfaceOverlay.js +11 -6
  97. package/dist/render/surfaceProjection.d.ts +34 -0
  98. package/dist/render/surfaceProjection.js +30 -0
  99. package/dist/render/surfaceTexture.d.ts +39 -0
  100. package/dist/render/transmission.d.ts +21 -0
  101. package/dist/render/transmission.js +21 -0
  102. package/dist/render/vertexPacking.d.ts +48 -0
  103. package/dist/render/vertexPacking.js +68 -0
  104. package/dist/ui/splash.d.ts +42 -2
  105. package/dist/ui/splash.js +78 -13
  106. package/package.json +6 -6
  107. package/src/index.ts +6 -2
  108. package/src/render/ambientOcclusionPass.ts +4 -1
  109. package/src/render/backend/api.ts +31 -0
  110. package/src/render/backend/webgl2/renderer.ts +519 -56
  111. package/src/render/backend/webgpu/buffers.ts +153 -54
  112. package/src/render/backend/webgpu/clothKernels.ts +86 -0
  113. package/src/render/backend/webgpu/clothOps.ts +86 -0
  114. package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
  115. package/src/render/backend/webgpu/clothSetPack.ts +197 -0
  116. package/src/render/backend/webgpu/drawCommand.ts +3 -0
  117. package/src/render/backend/webgpu/flatPass.ts +26 -28
  118. package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
  119. package/src/render/backend/webgpu/latePass.ts +1 -0
  120. package/src/render/backend/webgpu/motionPass.ts +18 -7
  121. package/src/render/backend/webgpu/pipelineCache.ts +46 -7
  122. package/src/render/backend/webgpu/renderer.ts +622 -164
  123. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
  124. package/src/render/backend/webgpu/staticBundles.ts +2 -1
  125. package/src/render/backend/webgpu/vertexFormats.ts +113 -0
  126. package/src/render/globalMediumPass.ts +7 -0
  127. package/src/render/indexWidth.ts +27 -0
  128. package/src/render/instances.ts +18 -1
  129. package/src/render/lightMatrix.ts +15 -0
  130. package/src/render/mesh.ts +66 -14
  131. package/src/render/occlusionFade.ts +29 -0
  132. package/src/render/renderQuality.ts +38 -0
  133. package/src/render/rendererHarness.ts +15 -0
  134. package/src/render/sceneTarget.ts +3 -0
  135. package/src/render/shader.ts +59 -0
  136. package/src/render/shaders/ambientOcclusion.ts +12 -1
  137. package/src/render/shaders/flat/directionalShadow.ts +84 -10
  138. package/src/render/shaders/flat/glassTint.ts +7 -1
  139. package/src/render/shaders/flat/index.ts +39 -4
  140. package/src/render/shaders/flat/layered.ts +198 -0
  141. package/src/render/shaders/flat/main.ts +50 -16
  142. package/src/render/shaders/flat/mapReads.ts +38 -0
  143. package/src/render/shaders/flat/movingSun.ts +46 -0
  144. package/src/render/shaders/flat/normalMap.ts +23 -3
  145. package/src/render/shaders/flat/overlay.ts +41 -19
  146. package/src/render/shaders/flat/preamble.ts +23 -5
  147. package/src/render/shaders/flat/worldUv.ts +113 -0
  148. package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +6 -1
  149. package/src/render/shaders/generated/flat.wgsl.ts +791 -391
  150. package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
  151. package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
  152. package/src/render/shaders/globalMedium.ts +9 -1
  153. package/src/render/shaders/lightVolume.ts +9 -1
  154. package/src/render/shaders/tangentFrame.ts +16 -3
  155. package/src/render/skinnedClothRun.ts +138 -38
  156. package/src/render/surfaceLayers.ts +176 -0
  157. package/src/render/surfaceOverlay.ts +24 -7
  158. package/src/render/surfaceProjection.ts +57 -0
  159. package/src/render/surfaceTexture.ts +39 -0
  160. package/src/render/transmission.ts +27 -0
  161. package/src/render/vertexPacking.ts +109 -0
  162. package/src/ui/splash.ts +83 -14
  163. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
  164. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
  165. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
@@ -29,13 +29,56 @@ export const DIRECTIONALSHADOW_GLSL = `vec2 receiverPlaneDepthGradient(vec3 p) {
29
29
  );
30
30
  }
31
31
 
32
- float directionalVisibility(float receiverDepth, float compareDepth, float storedDepth) {
32
+ /* One stored depth against a receiver, in a map spanning \`span\` metres of depth. */
33
+ float directionalVisibilityAt(float receiverDepth, float compareDepth, float storedDepth, float span) {
33
34
  if (compareDepth <= storedDepth) return 1.0;
34
- float rayDistance = max(receiverDepth - storedDepth, 0.0) * uShadowDepthSpan;
35
+ float rayDistance = max(receiverDepth - storedDepth, 0.0) * span;
35
36
  float groundDistance = rayDistance * length(uDirectionalDir.xz);
36
37
  return 1.0 - shadowReach(groundDistance, uShadowMaxDistance);
37
38
  }
38
39
 
40
+ float directionalVisibility(float receiverDepth, float compareDepth, float storedDepth) {
41
+ return directionalVisibilityAt(receiverDepth, compareDepth, storedDepth, uShadowDepthSpan);
42
+ }
43
+
44
+ /* How far inside its square a receiver stands in a layer's map: 1 well inside, 0 at its border. */
45
+ float sunMapFade(vec3 p) {
46
+ if (p.z > 1.0 || p.x < 0.0 || p.x > 1.0 || p.y < 0.0 || p.y > 1.0) return 0.0;
47
+ vec2 fromCentre = abs(p.xy - 0.5) * 2.0;
48
+ return (1.0 - smoothstep(0.72, 0.98, max(fromCentre.x, fromCentre.y))) *
49
+ (1.0 - smoothstep(0.90, 1.0, p.z));
50
+ }
51
+
52
+ /*
53
+ * The moving layer at its own place, where it has a matrix of its own (movingSun.ts): the coarse
54
+ * test of shadowFactor, and its filter, each against the moving map's own depth span, and lit by
55
+ * how far inside the moving map's square the receiver stands rather than cut at its border.
56
+ */
57
+ float movingSunCoarse(vec3 pm, float span) {
58
+ float inside = sunMapFade(pm);
59
+ if (inside <= 0.0) return 1.0;
60
+ float stored = textureLod(uSunShadows, vec3(pm.xy, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
61
+ return mix(1.0, directionalVisibilityAt(pm.z, pm.z - 1.0 / span, stored, span), inside);
62
+ }
63
+
64
+ float movingSunFiltered(vec3 pm, vec2 gradient, float span) {
65
+ float inside = sunMapFade(pm);
66
+ if (inside <= 0.0) return 1.0;
67
+ float texel = 1.35 / uShadowMapSize;
68
+ float slopeLimit = 9.1 / uShadowMapSize;
69
+ float lit = 0.0;
70
+ for (int i = 0; i < ${MAX_SHADOW_FILTER_TAPS}; i++) {
71
+ if (i >= uShadowFilterTaps) break;
72
+ vec2 sampleUv = pm.xy + DIRECTIONAL_PCF_OFFSETS[i] * texel;
73
+ vec2 texelCentre = (floor(sampleUv * uShadowMapSize) + 0.5) / uShadowMapSize;
74
+ float slopeOffset = clamp(dot(gradient, texelCentre - pm.xy), -slopeLimit, slopeLimit);
75
+ float tapReceiverDepth = pm.z + slopeOffset;
76
+ float stored = textureLod(uSunShadows, vec3(sampleUv, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
77
+ lit += directionalVisibilityAt(tapReceiverDepth, tapReceiverDepth - 0.14 / span, stored, span);
78
+ }
79
+ return mix(1.0, lit / float(max(uShadowFilterTaps, 1)), inside);
80
+ }
81
+
39
82
  float shadowFactor(float directionalNdl) {
40
83
  if (uShadowStrength <= 0.0) return 1.0;
41
84
 
@@ -65,6 +108,14 @@ float shadowFactor(float directionalNdl) {
65
108
  vec3 p = vLightPos.xyz / vLightPos.w;
66
109
  p = p * 0.5 + 0.5;
67
110
  vec2 depthGradient = receiverPlaneDepthGradient(p);
111
+ /* The moving layer's own place and plane where it has a matrix of its own (movingSun.ts), taken
112
+ here beside the static layer's for the same reason: above every varying-dependent return. */
113
+ vec3 pm = p;
114
+ vec2 movingGradient = depthGradient;
115
+ if (MOVING_SUN) {
116
+ pm = movingSunAt();
117
+ movingGradient = receiverPlaneDepthGradient(pm);
118
+ }
68
119
 
69
120
  // Near a surface's own light terminator its direct-light term is already
70
121
  // approaching zero, while the orthographic projection of that surface
@@ -129,7 +180,8 @@ float shadowFactor(float directionalNdl) {
129
180
  float coarseCompare = p.z - 1.0 / uShadowDepthSpan;
130
181
  float coarse =
131
182
  directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_STATIC_LAYER}.0), 0.0).r) *
132
- directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_DYNAMIC_LAYER}.0), 0.0).r);
183
+ (MOVING_SUN ? movingSunCoarse(pm, movingSunSpan()) :
184
+ directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_DYNAMIC_LAYER}.0), 0.0).r));
133
185
  if (uPeeledShadowEnabled != 0) {
134
186
  coarse *= directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_PEELED_LAYER}.0), 0.0).r);
135
187
  }
@@ -193,7 +245,6 @@ float shadowFactor(float directionalNdl) {
193
245
  * there is no mip to select and level zero is the only thing \`texture\` could have read.
194
246
  */
195
247
  float staticDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_STATIC_LAYER}.0), 0.0).r;
196
- float dynamicDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
197
248
  staticLit += directionalVisibility(tapReceiverDepth, compare, staticDepth);
198
249
  if (uPeeledShadowEnabled != 0) {
199
250
  float peeledDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_PEELED_LAYER}.0), 0.0).r;
@@ -201,12 +252,18 @@ float shadowFactor(float directionalNdl) {
201
252
  } else {
202
253
  peeledLit += 1.0;
203
254
  }
204
- dynamicLit += directionalVisibility(tapReceiverDepth, compare, dynamicDepth);
255
+ /* The moving layer here where it shares the static one's place; at its own below otherwise.
256
+ Branched on the switch itself, never its negation: see overridableConstants in transform.mjs. */
257
+ if (MOVING_SUN) {
258
+ } else {
259
+ float dynamicDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
260
+ dynamicLit += directionalVisibility(tapReceiverDepth, compare, dynamicDepth);
261
+ }
205
262
  }
206
263
  float tapCount = float(max(uShadowFilterTaps, 1));
207
264
  staticLit /= tapCount;
208
265
  peeledLit /= tapCount;
209
- dynamicLit /= tapCount;
266
+ dynamicLit = MOVING_SUN ? movingSunFiltered(pm, movingGradient, movingSunSpan()) : dynamicLit / tapCount;
210
267
 
211
268
  // Independent layers preserve every recorded fact at an overlap: each
212
269
  // caster keeps its own distance fade, and their transmissions compose
@@ -227,11 +284,11 @@ float shadowFactor(float directionalNdl) {
227
284
  * behind the receiver lending it colour within the spread, which only a receiver between two panes
228
285
  * meets. One function for both layers, because two copies were most of the lit shaders' growth.
229
286
  */
230
- vec3 sunGlassLayer(vec3 p, float paneLayer, float tintLayer, float bias, float texel, float uvPerMetre) {
287
+ vec3 sunGlassLayer(vec3 p, float paneLayer, float tintLayer, float bias, float texel, float uvPerMetre, float span) {
231
288
  float tintSize = float(textureSize(uSunGlassTints, 0).x);
232
289
  float clarity = textureLod(uSunGlassTints, vec3(p.xy, tintLayer), 0.0).a;
233
290
  float behind =
234
- max(p.z - textureLod(uSunShadows, vec3(p.xy, paneLayer), 0.0).r, 0.0) * uShadowDepthSpan;
291
+ max(p.z - textureLod(uSunShadows, vec3(p.xy, paneLayer), 0.0).r, 0.0) * span;
235
292
  float radius = clamp(
236
293
  (1.0 - clarity) * ${FROST_SPREAD.toFixed(6)} * behind * uvPerMetre,
237
294
  texel,
@@ -271,6 +328,13 @@ vec3 sunGlassLookup(float directionalNdl) {
271
328
  /* Map units per metre along the receiver, from derivatives taken first in uniform control flow,
272
329
  so a frost spread stated in metres lands the same whatever size of light matrix a consumer built. */
273
330
  float uvPerMetre = length(dFdx(p.xy)) / max(length(dFdx(vWorldPos)), 1e-6);
331
+ /* The moving glass at its own place where its layer has a matrix of its own: movingSun.ts. */
332
+ vec3 pm = p;
333
+ float movingPerMetre = uvPerMetre;
334
+ if (MOVING_SUN) {
335
+ pm = movingSunAt();
336
+ movingPerMetre = length(dFdx(pm.xy)) / max(length(dFdx(vWorldPos)), 1e-6);
337
+ }
274
338
  if (uShadowStrength <= 0.0 || textureSize(uSunGlassTints, 0).x <= 1) return vec3(1.0);
275
339
  if (directionalNdl <= 0.0) return vec3(1.0);
276
340
  if (p.z > 1.0 || p.x < 0.0 || p.x > 1.0 || p.y < 0.0 || p.y > 1.0) return vec3(1.0);
@@ -288,8 +352,18 @@ vec3 sunGlassLookup(float directionalNdl) {
288
352
 
289
353
  float bias = 0.14 / uShadowDepthSpan;
290
354
  float texel = 1.35 / uShadowMapSize;
291
- vec3 staticTint = sunGlassLayer(p, ${SUN_GLASS_STATIC_LAYER}.0, 0.0, bias, texel, uvPerMetre);
292
- vec3 movingTint = sunGlassLayer(p, ${SUN_GLASS_MOVING_LAYER}.0, 1.0, bias, texel, uvPerMetre);
355
+ vec3 staticTint = sunGlassLayer(p, ${SUN_GLASS_STATIC_LAYER}.0, 0.0, bias, texel, uvPerMetre, uShadowDepthSpan);
356
+ vec3 movingTint = MOVING_SUN && sunMapFade(pm) <= 0.0
357
+ ? vec3(1.0)
358
+ : sunGlassLayer(
359
+ pm,
360
+ ${SUN_GLASS_MOVING_LAYER}.0,
361
+ 1.0,
362
+ MOVING_SUN ? 0.14 / movingSunSpan() : bias,
363
+ texel,
364
+ movingPerMetre,
365
+ MOVING_SUN ? movingSunSpan() : uShadowDepthSpan
366
+ );
293
367
  return mix(vec3(1.0), staticTint * movingTint, strength);
294
368
  }
295
369
 
@@ -8,7 +8,7 @@ export declare function glassTintGlsl(glassShadows: boolean): string;
8
8
  * The lit stage's switches, by the name of the constant each is: the decision both backends make
9
9
  * about which optional code a lit program carries. See `litSwitchesGlsl`.
10
10
  */
11
- export type LitSwitch = 'GLASS_SHADOWS' | 'CLUSTERED_LIGHTS' | 'LIGHT_FIXTURES' | 'SURFACE_EFFECTS' | 'DRIFT_LIGHT' | 'PHYSICAL_SPECULAR' | 'SURFACE_OVERLAY' | 'REFLECTION_MAPS';
11
+ export type LitSwitch = 'GLASS_SHADOWS' | 'CLUSTERED_LIGHTS' | 'LIGHT_FIXTURES' | 'SURFACE_EFFECTS' | 'DRIFT_LIGHT' | 'PHYSICAL_SPECULAR' | 'SURFACE_OVERLAY' | 'REFLECTION_MAPS' | 'WORLD_UVS' | 'MOVING_SUN' | 'LAYERED';
12
12
  /** Every switch off: what a renderer starts from before its profile and its content say otherwise. */
13
13
  export declare function noLitSwitches(): Record<LitSwitch, boolean>;
14
14
  /** Which of the lit stage's optional features a build reads. See `litSwitchesGlsl`. */
@@ -34,6 +34,9 @@ export function noLitSwitches() {
34
34
  PHYSICAL_SPECULAR: false,
35
35
  SURFACE_OVERLAY: false,
36
36
  REFLECTION_MAPS: false,
37
+ WORLD_UVS: false,
38
+ MOVING_SUN: false,
39
+ LAYERED: false,
37
40
  };
38
41
  }
39
42
  /**
@@ -125,6 +125,21 @@ export interface FlatShaderOptions {
125
125
  * the frame's materials' reflections. True when absent. See `reflectionSurfaceGlsl`.
126
126
  */
127
127
  readonly reflectionMaps?: boolean;
128
+ /**
129
+ * Whether it places a material's maps by the world, `uWorldUv` and all (`SurfaceMaterial
130
+ * .projection`). True when absent. See `worldUvGlsl`.
131
+ */
132
+ readonly worldUvs?: boolean;
133
+ /**
134
+ * Whether it reads the sun's moving layer through a matrix of its own, \`uMovingLightViewProj\`
135
+ * and all. True when absent. See \`movingSunGlsl\`.
136
+ */
137
+ readonly movingSun?: boolean;
138
+ /**
139
+ * Whether it blends a material's layers by a mask, \`uLayers\` and all (\`SurfaceMaterial.layers\`).
140
+ * True when absent. See \`layeredGlsl\`.
141
+ */
142
+ readonly layered?: boolean;
128
143
  /**
129
144
  * How many point lights this build declares room for. `MAX_POINT_LIGHTS` when absent.
130
145
  *
@@ -18,9 +18,14 @@ import { modelsGlsl } from './models.js';
18
18
  import { AREA_LIGHT_GLSL } from './areaLight.js';
19
19
  import { NORMAL_MAP_GLSL } from './normalMap.js';
20
20
  import { DRIFT_LIGHT_GLSL } from './driftLight.js';
21
+ import { LIGHTMAP_OPACITY_STEPS } from '../../instances.js';
21
22
  import { SURFACE_EFFECTS_GLSL } from './surfaceEffects.js';
22
23
  import { overlayGlsl } from './overlay.js';
23
24
  import { reflectionSurfaceGlsl } from './reflectionSurface.js';
25
+ import { worldUvGlsl } from './worldUv.js';
26
+ import { movingSunGlsl } from './movingSun.js';
27
+ import { layeredGlsl } from './layered.js';
28
+ import { MAP_READS_GLSL } from './mapReads.js';
24
29
  import { CHANNEL_BEND, LIT_CHANNEL_ATTRIBUTE } from '../vertexChannel.js';
25
30
  import { BONE_ANIMATION_GLSL } from '../boneAnimation.js';
26
31
  import { FULL_LIGHT_BUDGET } from '../../uniformVectorBudget.js';
@@ -337,10 +342,16 @@ void main() {
337
342
  vAlpha = aInstanceTint.w;
338
343
  vThickness = 1.0;
339
344
  if (LIGHTMAP_REGIONS) {
345
+ /*
346
+ * The opacity's transparency rides the U offset as whole steps of 2, the offset itself under 1
347
+ * (\`MeshInstances.lightmapRegions\`): taken off, and none where the instance is opaque.
348
+ */
349
+ float steps = floor(aInstanceTint.z * 0.5);
350
+ float offsetU = aInstanceTint.z - 2.0 * steps;
340
351
  /* Out of the lane's \`-1 - uv\`, through the region, and back into it. See lightmap.ts. */
341
- vGrain = -1.0 - ((-1.0 - aGrain) * aInstanceTint.x + aInstanceTint.z);
352
+ vGrain = -1.0 - ((-1.0 - aGrain) * aInstanceTint.x + offsetU);
342
353
  vRelief = -1.0 - ((-1.0 - aRelief) * aInstanceTint.y + aInstanceTint.w);
343
- vAlpha = 1.0;
354
+ vAlpha = 1.0 - steps / ${LIGHTMAP_OPACITY_STEPS.toFixed(1)};
344
355
  }
345
356
  #else
346
357
  vSkyDirect = aChannel.y;
@@ -461,6 +472,8 @@ export function flatFrag(options) {
461
472
  surfaceEffects: options.surfaceEffects ?? true,
462
473
  driftLight: options.driftLight ?? true,
463
474
  }),
475
+ /* Depth zero and ahead of the sun's lookup, which calls it; its switch takes the next id. */
476
+ movingSunGlsl(options.movingSun ?? true),
464
477
  LOBES_GLSL,
465
478
  POINTSHADOW_GLSL,
466
479
  DIRECTIONALSHADOW_GLSL,
@@ -486,8 +499,6 @@ export function flatFrag(options) {
486
499
  * and `main` is unconditional by construction.
487
500
  */
488
501
  TANGENT_FRAME_GLSL,
489
- /* After the frame it calls, unconditional for the same reason. See normalMap.ts. */
490
- NORMAL_MAP_GLSL,
491
502
  /* Unconditional for the same reason, and called only where a cutoff was asked for. */
492
503
  CUTOUT_COVERAGE_GLSL,
493
504
  CUTOUT_DITHER_GLSL,
@@ -499,6 +510,15 @@ export function flatFrag(options) {
499
510
  overlayGlsl(options.surfaceOverlay ?? true),
500
511
  /* After the overlay, so its switch takes the next id. See reflectionSurface.ts. */
501
512
  reflectionSurfaceGlsl(options.reflectionMaps ?? true),
513
+ /* After the reflection maps, so its switch takes the next id; main calls it. worldUv.ts. */
514
+ worldUvGlsl(options.worldUvs ?? true),
515
+ /* After the projection, so its switch takes the next id; the normal map below calls it. */
516
+ layeredGlsl(options.layered ?? true),
517
+ /* Where each map is read, after both of the ways it can be read otherwise. See mapReads.ts. */
518
+ MAP_READS_GLSL,
519
+ /* After the frame and the projection it calls, unconditional for the same reason as the
520
+ frame. See normalMap.ts. */
521
+ NORMAL_MAP_GLSL,
502
522
  /* After the models, whose answer to a rectangle it asks for. See areaLight.ts. */
503
523
  AREA_LIGHT_GLSL,
504
524
  MAIN_GLSL,
@@ -0,0 +1,27 @@
1
+ /**
2
+ * A material blended from layers by a mask: `SurfaceMaterial.layers`, packed into `uLayers` by
3
+ * `surfaceLayers.ts`.
4
+ *
5
+ * **Layer `i` is layer `i` of the material's arrays**, each read at its own repeat of where the
6
+ * layers are laid: the mesh's coordinates, or the world's horizontal plane where the material has a
7
+ * projection (`worldUv.ts`). The mask's channels weigh layers 1 to 4 — red, green, blue, alpha — and
8
+ * come from a map read where a shading model's would be, from the ORM array's layer past the layers,
9
+ * or from the vertex colour; an added mask placed as the layers are raises one layer's weight, and
10
+ * one layer may be weighed by how much the surface faces up instead of by its channel.
11
+ *
12
+ * **Two ways to combine them.** Laid over, each layer covers everything before it, so a layer's
13
+ * share is its weight times one less each later weight, and the base keeps what none took. Summed,
14
+ * the base is mixed toward each layer times its weight by the weights' sum held to 1, so each
15
+ * layer's share is its weight times that and the base keeps the rest. The shares are worked out once
16
+ * a pixel, in `layeredBegin`; colour, roughness and occlusion are blended by them, and the normal
17
+ * map's tangent-space texels too, before the frame turns them into the world — around a normal read
18
+ * at the mesh's own coordinates, where the material lays one under the layers.
19
+ *
20
+ * **Every layer present is read, whatever its share.** A read whose level is implicit is legal only
21
+ * in uniform control flow (AGENTS.md 2026-08-07), so the loop is bounded by the layer count, a
22
+ * uniform, and never skips on a share, which is the pixel's own.
23
+ *
24
+ * **A lit switch, `LAYERED`**, off until a material first asks, declared last so every switch before
25
+ * it keeps its id. Compiled out, its four vectors are not declared and its calls are constants.
26
+ */
27
+ export declare function layeredGlsl(on: boolean): string;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * A material blended from layers by a mask: `SurfaceMaterial.layers`, packed into `uLayers` by
3
+ * `surfaceLayers.ts`.
4
+ *
5
+ * **Layer `i` is layer `i` of the material's arrays**, each read at its own repeat of where the
6
+ * layers are laid: the mesh's coordinates, or the world's horizontal plane where the material has a
7
+ * projection (`worldUv.ts`). The mask's channels weigh layers 1 to 4 — red, green, blue, alpha — and
8
+ * come from a map read where a shading model's would be, from the ORM array's layer past the layers,
9
+ * or from the vertex colour; an added mask placed as the layers are raises one layer's weight, and
10
+ * one layer may be weighed by how much the surface faces up instead of by its channel.
11
+ *
12
+ * **Two ways to combine them.** Laid over, each layer covers everything before it, so a layer's
13
+ * share is its weight times one less each later weight, and the base keeps what none took. Summed,
14
+ * the base is mixed toward each layer times its weight by the weights' sum held to 1, so each
15
+ * layer's share is its weight times that and the base keeps the rest. The shares are worked out once
16
+ * a pixel, in `layeredBegin`; colour, roughness and occlusion are blended by them, and the normal
17
+ * map's tangent-space texels too, before the frame turns them into the world — around a normal read
18
+ * at the mesh's own coordinates, where the material lays one under the layers.
19
+ *
20
+ * **Every layer present is read, whatever its share.** A read whose level is implicit is legal only
21
+ * in uniform control flow (AGENTS.md 2026-08-07), so the loop is bounded by the layer count, a
22
+ * uniform, and never skips on a share, which is the pixel's own.
23
+ *
24
+ * **A lit switch, `LAYERED`**, off until a material first asks, declared last so every switch before
25
+ * it keeps its id. Compiled out, its four vectors are not declared and its calls are constants.
26
+ */
27
+ export function layeredGlsl(on) {
28
+ if (!on) {
29
+ return /* glsl */ `
30
+ const bool LAYERED = false; // wgsl:override
31
+ bool layered() { return false; }
32
+ void layeredBegin() {}
33
+ vec3 layeredColor(vec3 color) { return color; }
34
+ vec4 layeredAlbedo() { return vec4(1.0); }
35
+ vec3 layeredOrm() { return vec3(1.0); }
36
+ vec3 layeredTexel() { return vec3(0.0, 0.0, 1.0); }
37
+ bool layeredMeshNormal() { return false; }
38
+ vec3 layeredMeshTexel() { return vec3(0.0, 0.0, 1.0); }
39
+ bool layeredGlows() { return false; }
40
+ vec3 layeredEmissive() { return vec3(0.0); }
41
+ `;
42
+ }
43
+ return /* glsl */ `
44
+ /* Whether this program blends a material's layers. Off but where one has asked: layered.ts. */
45
+ const bool LAYERED = true; // wgsl:override
46
+ /*
47
+ * Four vectors, as surfaceLayers.ts packs them: the five repeats, base first (x y z w, then x); the
48
+ * count (y), the glowing layer or -1 (z) and where the mask is, 0 a map, 1 the ORM array, 2 the
49
+ * vertex colour (w); the blend, 0 over and 1 summed (x), whether a mesh normal is laid under the
50
+ * layers (y), the added mask's layer or -1 (z) and its repeat (w); its intensity (x), the facing
51
+ * layer or -1 (y), its bias (z) and its sharpness (w).
52
+ */
53
+ uniform vec4 uLayers[4]; // wgsl:material
54
+
55
+ /* Each layer's share of this point, the base's and layers 1 to 4's: worked out once, in layeredBegin. */
56
+ float lShareBase;
57
+ vec4 lShare;
58
+
59
+ bool layered() {
60
+ return uLayers[1].y > 0.5;
61
+ }
62
+
63
+ float layerRepeat(int i) {
64
+ return i < 4 ? uLayers[0][i] : uLayers[1].x;
65
+ }
66
+
67
+ /* Where the layers are laid: the world's horizontal plane under a projection, the mesh's otherwise. */
68
+ vec2 layerPlace() {
69
+ if (WORLD_UVS && projected()) return planarAt();
70
+ return vUv.xy;
71
+ }
72
+
73
+ vec3 layerAt(int i) {
74
+ return vec3(layerPlace() * layerRepeat(i), float(i));
75
+ }
76
+
77
+ /* The weights as the material keeps them: the mask's channels, at the mesh's own coordinates. */
78
+ vec4 layerWeights() {
79
+ float source = uLayers[1].w;
80
+ if (source > 1.5) return vec4(vColor, 0.0);
81
+ if (source > 0.5) return texture(uOrmMap, vec3(vUv.xy, uLayers[1].y));
82
+ return texture(uModelMap, vec3(vUv.xy, 0.0));
83
+ }
84
+
85
+ /* Which of layers 1 to 4 a packed layer number names, as a vector of one 1: none for -1. */
86
+ vec4 layerPick(float layer) {
87
+ return vec4(equal(vec4(layer), vec4(1.0, 2.0, 3.0, 4.0)));
88
+ }
89
+
90
+ /*
91
+ * The weights, changed where the material asks and zero past the count, and every share from them.
92
+ * Under a branch on uniforms, as its reads are.
93
+ */
94
+ void layeredBegin() {
95
+ vec4 m = layerWeights();
96
+ if (uLayers[2].z > 0.5) {
97
+ /* The ORM array's layer past the layers, and past the mask where the array carries that too. */
98
+ float at = uLayers[1].y + (abs(uLayers[1].w - 1.0) < 0.5 ? 1.0 : 0.0);
99
+ float added = texture(uOrmMap, vec3(layerPlace() * uLayers[2].w, at)).r * uLayers[3].x;
100
+ m = mix(m, clamp(m + added, 0.0, 1.0), layerPick(uLayers[2].z));
101
+ }
102
+ if (uLayers[3].y > 0.5) {
103
+ float up = normalize(vNormal).y * 0.5 + 0.5;
104
+ float facing = clamp(uLayers[3].z + uLayers[3].w * up, 0.0, 1.0);
105
+ m = mix(m, vec4(facing), layerPick(uLayers[3].y));
106
+ }
107
+ float n = uLayers[1].y;
108
+ m *= vec4(n > 1.5 ? 1.0 : 0.0, n > 2.5 ? 1.0 : 0.0, n > 3.5 ? 1.0 : 0.0, n > 4.5 ? 1.0 : 0.0);
109
+ if (uLayers[2].x > 0.5) {
110
+ float taken = min(m.x + m.y + m.z + m.w, 1.0);
111
+ lShare = m * taken;
112
+ lShareBase = 1.0 - taken;
113
+ return;
114
+ }
115
+ /* Each over everything before it: a layer keeps its weight of what every later one leaves. */
116
+ float keep = 1.0;
117
+ lShare.w = m.w * keep;
118
+ keep *= 1.0 - m.w;
119
+ lShare.z = m.z * keep;
120
+ keep *= 1.0 - m.z;
121
+ lShare.y = m.y * keep;
122
+ keep *= 1.0 - m.y;
123
+ lShare.x = m.x * keep;
124
+ keep *= 1.0 - m.x;
125
+ lShareBase = keep;
126
+ }
127
+
128
+ float layerShare(int i) {
129
+ if (i == 0) return lShareBase;
130
+ return lShare[i - 1];
131
+ }
132
+
133
+ /* The vertex colour's part in the albedo: none where it is the mask instead. */
134
+ vec3 layeredColor(vec3 color) {
135
+ return uLayers[1].w > 1.5 ? vec3(1.0) : color;
136
+ }
137
+
138
+ /* Colour by the shares, and the base layer's coverage, which a cutout reads. */
139
+ vec4 layeredAlbedo() {
140
+ vec4 sum = vec4(0.0);
141
+ for (int i = 0; i < 5; i++) {
142
+ if (float(i) >= uLayers[1].y) break;
143
+ vec4 t = texture(uAlbedo, layerAt(i));
144
+ sum.rgb += t.rgb * layerShare(i);
145
+ if (i == 0) sum.a = t.a;
146
+ }
147
+ return sum;
148
+ }
149
+
150
+ vec3 layeredOrm() {
151
+ vec3 sum = vec3(0.0);
152
+ for (int i = 0; i < 5; i++) {
153
+ if (float(i) >= uLayers[1].y) break;
154
+ sum += texture(uOrmMap, layerAt(i)).rgb * layerShare(i);
155
+ }
156
+ return sum;
157
+ }
158
+
159
+ /* A tangent-space texel off the normal array, its z rebuilt where a two-channel map stored none. */
160
+ vec3 layerNormalTexel(vec3 at) {
161
+ vec3 t = texture(uNormalMap, at).xyz * 2.0 - 1.0;
162
+ t.z = t.z > 0.0 ? t.z : sqrt(max(0.0, 1.0 - dot(t.xy, t.xy)));
163
+ return t;
164
+ }
165
+
166
+ /* The tangent-space texels blended by the shares. */
167
+ vec3 layeredTexel() {
168
+ vec3 sum = vec3(0.0);
169
+ for (int i = 0; i < 5; i++) {
170
+ if (float(i) >= uLayers[1].y) break;
171
+ sum += layerNormalTexel(layerAt(i)) * layerShare(i);
172
+ }
173
+ float len = length(sum);
174
+ return len > 1e-5 ? sum / len : vec3(0.0, 0.0, 1.0);
175
+ }
176
+
177
+ /* Whether a normal read at the mesh's own coordinates lies under the layers. */
178
+ bool layeredMeshNormal() {
179
+ return uLayers[2].y > 0.5;
180
+ }
181
+
182
+ /* That normal's texel: the normal array's layer past the layers, at the mesh's coordinates. */
183
+ vec3 layeredMeshTexel() {
184
+ return layerNormalTexel(vec3(vUv.xy, uLayers[1].y));
185
+ }
186
+
187
+ /* Whether the emissive map belongs to one layer rather than to the whole surface. */
188
+ bool layeredGlows() {
189
+ return uLayers[1].z > -0.5;
190
+ }
191
+
192
+ /* The emissive map at its layer's place, glowing by that layer's share. */
193
+ vec3 layeredEmissive() {
194
+ int glow = int(uLayers[1].z + 0.5);
195
+ return texture(uEmissiveMap, layerAt(glow)).rgb * layerShare(glow);
196
+ }
197
+ `;
198
+ }