@driftengine/core 4.4.1 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/CHANGELOG.json +72 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +5 -1
  4. package/dist/index.js +7 -0
  5. package/dist/render/backend/api.d.ts +17 -11
  6. package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
  7. package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
  8. package/dist/render/backend/webgl2/renderer.d.ts +82 -8
  9. package/dist/render/backend/webgl2/renderer.js +373 -105
  10. package/dist/render/backend/webgpu/buffers.d.ts +19 -1
  11. package/dist/render/backend/webgpu/buffers.js +27 -1
  12. package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
  13. package/dist/render/backend/webgpu/depthPass.js +2 -1
  14. package/dist/render/backend/webgpu/flatPass.d.ts +2 -0
  15. package/dist/render/backend/webgpu/flatPass.js +20 -1
  16. package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
  17. package/dist/render/backend/webgpu/glassTintPass.js +205 -0
  18. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
  19. package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
  20. package/dist/render/backend/webgpu/instanced.d.ts +12 -1
  21. package/dist/render/backend/webgpu/instanced.js +20 -1
  22. package/dist/render/backend/webgpu/latePass.d.ts +26 -0
  23. package/dist/render/backend/webgpu/latePass.js +133 -0
  24. package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
  25. package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
  26. package/dist/render/backend/webgpu/motionPass.js +491 -0
  27. package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
  28. package/dist/render/backend/webgpu/pipelineCache.js +11 -1
  29. package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
  30. package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
  31. package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
  32. package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
  33. package/dist/render/backend/webgpu/postPass.d.ts +21 -15
  34. package/dist/render/backend/webgpu/postPass.js +17 -4
  35. package/dist/render/backend/webgpu/renderer.d.ts +181 -54
  36. package/dist/render/backend/webgpu/renderer.js +1011 -394
  37. package/dist/render/backend/webgpu/scatterPass.js +2 -1
  38. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
  39. package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
  40. package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
  41. package/dist/render/backend/webgpu/snapshotMips.js +52 -0
  42. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
  43. package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
  44. package/dist/render/flameLight.js +15 -2
  45. package/dist/render/glass.d.ts +45 -0
  46. package/dist/render/glass.js +39 -0
  47. package/dist/render/glassCasters.d.ts +40 -0
  48. package/dist/render/glassCasters.js +84 -0
  49. package/dist/render/glassShadow.d.ts +78 -0
  50. package/dist/render/glassShadow.js +125 -0
  51. package/dist/render/globalMediumPass.d.ts +2 -3
  52. package/dist/render/globalMediumPass.js +2 -8
  53. package/dist/render/gpudriven/lit.d.ts +12 -0
  54. package/dist/render/gpudriven/lit.js +23 -0
  55. package/dist/render/gpudriven/materialTable.d.ts +15 -5
  56. package/dist/render/gpudriven/materialTable.js +26 -6
  57. package/dist/render/gpudriven/pipeline.d.ts +1 -1
  58. package/dist/render/gpudriven/pipeline.js +4 -3
  59. package/dist/render/gpudriven/sunGlass.d.ts +35 -0
  60. package/dist/render/gpudriven/sunGlass.js +89 -0
  61. package/dist/render/lightBudget.d.ts +17 -5
  62. package/dist/render/lightBudget.js +17 -5
  63. package/dist/render/pointGlassTint.d.ts +59 -0
  64. package/dist/render/pointGlassTint.js +198 -0
  65. package/dist/render/pointShadowArray.d.ts +56 -3
  66. package/dist/render/pointShadowArray.js +127 -16
  67. package/dist/render/pointShadowBudget.js +51 -41
  68. package/dist/render/pointShadowMap.d.ts +23 -1
  69. package/dist/render/pointShadowMap.js +27 -1
  70. package/dist/render/recon/changeFrames.d.ts +15 -0
  71. package/dist/render/recon/changeFrames.js +21 -0
  72. package/dist/render/recon/disocclusion.d.ts +36 -3
  73. package/dist/render/recon/disocclusion.js +56 -4
  74. package/dist/render/recon/lateRouting.d.ts +14 -0
  75. package/dist/render/recon/lateRouting.js +18 -0
  76. package/dist/render/recon/mover.d.ts +57 -0
  77. package/dist/render/recon/mover.js +86 -0
  78. package/dist/render/recon/resolve.js +95 -12
  79. package/dist/render/renderQuality.d.ts +35 -0
  80. package/dist/render/renderQuality.js +7 -0
  81. package/dist/render/rendererHarness.js +12 -0
  82. package/dist/render/sceneTarget.d.ts +9 -1
  83. package/dist/render/sceneTarget.js +59 -13
  84. package/dist/render/shaders/depth.d.ts +16 -1
  85. package/dist/render/shaders/depth.js +85 -9
  86. package/dist/render/shaders/flat/directionalShadow.js +91 -6
  87. package/dist/render/shaders/flat/glassTint.d.ts +6 -0
  88. package/dist/render/shaders/flat/glassTint.js +25 -0
  89. package/dist/render/shaders/flat/index.d.ts +13 -0
  90. package/dist/render/shaders/flat/index.js +4 -0
  91. package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
  92. package/dist/render/shaders/flat/lampGlass.js +183 -0
  93. package/dist/render/shaders/flat/main.d.ts +1 -1
  94. package/dist/render/shaders/flat/main.js +115 -15
  95. package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
  96. package/dist/render/shaders/flat/pointShadow.js +2 -0
  97. package/dist/render/shaders/flat/preamble.js +21 -6
  98. package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
  99. package/dist/render/shaders/generated/depth.wgsl.js +227 -4
  100. package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
  101. package/dist/render/shaders/generated/flat.wgsl.js +376 -328
  102. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
  103. package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
  104. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
  105. package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
  106. package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
  107. package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
  108. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +31 -6
  109. package/dist/render/shaders/generated/temporalResolve.wgsl.js +31 -6
  110. package/dist/render/shaders/globalMedium.js +6 -6
  111. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
  112. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
  113. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
  114. package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
  115. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
  116. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
  117. package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
  118. package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
  119. package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
  120. package/dist/render/shaders/lightVolume.js +6 -6
  121. package/dist/render/shaders/octahedralResolve.d.ts +8 -0
  122. package/dist/render/shaders/octahedralResolve.js +48 -17
  123. package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
  124. package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
  125. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  126. package/dist/render/shaders/recon/motion.wgsl.js +84 -0
  127. package/dist/render/shaders/recon/resolve.wgsl.d.ts +1 -1
  128. package/dist/render/shaders/recon/resolve.wgsl.js +112 -8
  129. package/dist/render/shaders/temporalResolve.js +133 -5
  130. package/dist/render/shadowCasters.d.ts +20 -1
  131. package/dist/render/shadowCasters.js +15 -1
  132. package/dist/render/shadowMap.d.ts +54 -5
  133. package/dist/render/shadowMap.js +190 -36
  134. package/dist/render/sunGlassTint.d.ts +25 -0
  135. package/dist/render/sunGlassTint.js +79 -0
  136. package/dist/render/temporalAa.d.ts +145 -0
  137. package/dist/render/temporalAa.js +190 -0
  138. package/dist/render/temporalPass.d.ts +16 -14
  139. package/dist/render/temporalPass.js +59 -14
  140. package/dist/render/translucentQueue.d.ts +6 -0
  141. package/dist/render/translucentQueue.js +15 -1
  142. package/package.json +6 -6
  143. package/src/index.ts +10 -0
  144. package/src/render/backend/api.ts +15 -9
  145. package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
  146. package/src/render/backend/webgl2/renderer.ts +422 -109
  147. package/src/render/backend/webgpu/buffers.ts +50 -2
  148. package/src/render/backend/webgpu/depthPass.ts +2 -1
  149. package/src/render/backend/webgpu/flatPass.ts +22 -1
  150. package/src/render/backend/webgpu/glassTintPass.ts +244 -0
  151. package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
  152. package/src/render/backend/webgpu/instanced.ts +21 -1
  153. package/src/render/backend/webgpu/latePass.ts +152 -0
  154. package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
  155. package/src/render/backend/webgpu/motionPass.ts +535 -0
  156. package/src/render/backend/webgpu/pipelineCache.ts +12 -1
  157. package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
  158. package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
  159. package/src/render/backend/webgpu/postPass.ts +34 -4
  160. package/src/render/backend/webgpu/renderer.ts +1178 -425
  161. package/src/render/backend/webgpu/scatterPass.ts +2 -1
  162. package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
  163. package/src/render/backend/webgpu/snapshotMips.ts +80 -0
  164. package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
  165. package/src/render/flameLight.ts +15 -2
  166. package/src/render/glass.ts +74 -0
  167. package/src/render/glassCasters.ts +164 -0
  168. package/src/render/glassShadow.ts +153 -0
  169. package/src/render/globalMediumPass.ts +4 -11
  170. package/src/render/gpudriven/lit.ts +31 -0
  171. package/src/render/gpudriven/materialTable.ts +28 -6
  172. package/src/render/gpudriven/pipeline.ts +4 -3
  173. package/src/render/gpudriven/sunGlass.ts +117 -0
  174. package/src/render/lightBudget.ts +19 -5
  175. package/src/render/pointGlassTint.ts +250 -0
  176. package/src/render/pointShadowArray.ts +188 -22
  177. package/src/render/pointShadowBudget.ts +49 -39
  178. package/src/render/pointShadowMap.ts +41 -0
  179. package/src/render/recon/changeFrames.ts +24 -0
  180. package/src/render/recon/disocclusion.ts +54 -3
  181. package/src/render/recon/lateRouting.ts +29 -0
  182. package/src/render/recon/mover.ts +118 -0
  183. package/src/render/recon/resolve.ts +137 -10
  184. package/src/render/renderQuality.ts +47 -0
  185. package/src/render/rendererHarness.ts +12 -0
  186. package/src/render/sceneTarget.ts +66 -12
  187. package/src/render/shaders/depth.ts +109 -9
  188. package/src/render/shaders/flat/directionalShadow.ts +97 -6
  189. package/src/render/shaders/flat/glassTint.ts +25 -0
  190. package/src/render/shaders/flat/index.ts +17 -0
  191. package/src/render/shaders/flat/lampGlass.ts +185 -0
  192. package/src/render/shaders/flat/main.ts +115 -15
  193. package/src/render/shaders/flat/pointShadow.ts +2 -0
  194. package/src/render/shaders/flat/preamble.ts +21 -6
  195. package/src/render/shaders/generated/depth.wgsl.ts +234 -4
  196. package/src/render/shaders/generated/flat.wgsl.ts +376 -328
  197. package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
  198. package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
  199. package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
  200. package/src/render/shaders/generated/temporalResolve.wgsl.ts +31 -6
  201. package/src/render/shaders/globalMedium.ts +6 -6
  202. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
  203. package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
  204. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
  205. package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
  206. package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
  207. package/src/render/shaders/lightVolume.ts +6 -6
  208. package/src/render/shaders/octahedralResolve.ts +50 -17
  209. package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
  210. package/src/render/shaders/recon/motion.wgsl.ts +84 -0
  211. package/src/render/shaders/recon/resolve.wgsl.ts +112 -8
  212. package/src/render/shaders/temporalResolve.ts +141 -5
  213. package/src/render/shadowCasters.ts +28 -1
  214. package/src/render/shadowMap.ts +231 -37
  215. package/src/render/sunGlassTint.ts +86 -0
  216. package/src/render/temporalAa.ts +227 -0
  217. package/src/render/temporalPass.ts +88 -12
  218. package/src/render/translucentQueue.ts +20 -1
@@ -83,6 +83,11 @@ export const MAIN_GLSL = `void main() {
83
83
  * the branch below builds a shaded result on top of it.
84
84
  */
85
85
  vec3 lit = albedo;
86
+ /* The light arriving at a glass pane from behind it, gathered in each light loop below and added
87
+ over the see-through at the end. Stays zero for anything that is not glass. See glass.ts. */
88
+ vec3 glassGlow = vec3(0.0);
89
+ float glassTransmission = uSeeThrough.z;
90
+ float glassFrost = uSeeThrough.w;
86
91
 
87
92
  if (uLightingEnabled != 0) {
88
93
  vec3 n = normalize(vNormal);
@@ -94,7 +99,13 @@ export const MAIN_GLSL = `void main() {
94
99
  * back of the sheet; what that gives up is a sliver at a curved silhouette, where a front face's
95
100
  * interpolated normal already leans away and is lit as the back.
96
101
  */
97
- bool backFace = uDoubleSided != 0 && dot(n, uCameraPos - vWorldPos) < 0.0;
102
+ /*
103
+ * **Glass is two-sided whatever its material says**: a pane is symmetric, so from behind the
104
+ * side the eye sees is the one lit, and the light on its far side is what glows through it.
105
+ * Taken from the stored normal alone, a pane seen from behind glowed with the light on the
106
+ * eye's side and not with the sun shining through it.
107
+ */
108
+ bool backFace = (uDoubleSided != 0 || glassTransmission > 0.0) && dot(n, uCameraPos - vWorldPos) < 0.0;
98
109
  /*
99
110
  * The authored normal, before either relief perturbs it.
100
111
  *
@@ -313,9 +324,13 @@ export const MAIN_GLSL = `void main() {
313
324
  float surfaceNdl = dot(surfaceN, uDirectionalDir);
314
325
  #if DIRECTIONAL_SHADOWS
315
326
  float sunShade = shadowFactor(max(surfaceNdl, 0.0));
327
+ /* What the sun keeps through any glass on its way here: see sunGlassLookup. */
328
+ vec3 sunGlass = sunGlassTint(max(surfaceNdl, 0.0));
316
329
  #else
317
330
  float sunShade = 1.0;
331
+ vec3 sunGlass = vec3(1.0);
318
332
  #endif
333
+ vec3 sunColor = uDirectionalColor * sunGlass;
319
334
  /*
320
335
  * **How much sky this vertex can see, applied to the sun and to nothing else.**
321
336
  *
@@ -343,6 +358,8 @@ export const MAIN_GLSL = `void main() {
343
358
  */
344
359
  float sunFacing = smoothstep(0.0, 0.02, surfaceNdl);
345
360
  float direct = ndl * sunShade * sunFacing;
361
+ /* Glass: the sun on the far side of a pane lights it through the glass. See glassGlow. */
362
+ if (glassTransmission > 0.0) glassGlow += sunColor * max(-surfaceNdl, 0.0) * sunShade;
346
363
 
347
364
  /*
348
365
  * Hemispheric: sky above, ground below, mixed by which way the surface looks. An
@@ -389,7 +406,7 @@ export const MAIN_GLSL = `void main() {
389
406
  * smallest change available in a shader with a documented history of shared subexpressions
390
407
  * rounding differently when their neighbours move. See the night-emissive term.
391
408
  */
392
- lit = albedo * (ambient + uDirectionalColor * direct * (1.0 - metal));
409
+ lit = albedo * (ambient + sunColor * direct * (1.0 - metal));
393
410
 
394
411
  /*
395
412
  * A sun highlight, for the few surfaces that ask for one.
@@ -451,7 +468,7 @@ export const MAIN_GLSL = `void main() {
451
468
  * before: \`sunSpec\` is \`vec3(vSpecular)\` at metal 0 and \`vSpecular\` defaults to 0.
452
469
  */
453
470
  vec3 sunHighlight =
454
- uDirectionalColor * specularLobe(max(dot(n, halfway), 0.0), surfaceRoughness) * sunSpec * sunShade * sunFacing;
471
+ sunColor * specularLobe(max(dot(n, halfway), 0.0), surfaceRoughness) * sunSpec * sunShade * sunFacing;
455
472
  /*
456
473
  * The dielectric's share goes in here, where it has always gone, so the environment blend
457
474
  * below dims it by exactly the reflectance it always dimmed it by. The metal's share is added
@@ -1023,7 +1040,14 @@ export const MAIN_GLSL = `void main() {
1023
1040
  }
1024
1041
  if (falloff <= 0.0) continue;
1025
1042
  float ndl = max(dot(n, toLight / max(dist, 1e-4)), 0.0);
1026
- if (ndl <= 0.0) continue;
1043
+ /*
1044
+ * **A lamp behind a pane is not skipped when the surface is glass**: it lights the pane through
1045
+ * the glass, shaped and shadowed like any lamp, and is added to glassGlow once its shape is
1046
+ * known below. For everything else a lamp behind the surface is skipped here, as always.
1047
+ */
1048
+ float backNdl =
1049
+ glassTransmission > 0.0 ? max(-dot(n, toLight / max(dist, 1e-4)), 0.0) : 0.0;
1050
+ if (ndl <= 0.0 && backNdl <= 0.0) continue;
1027
1051
 
1028
1052
  /*
1029
1053
  * Normal-offset bias.
@@ -1076,7 +1100,17 @@ export const MAIN_GLSL = `void main() {
1076
1100
  */
1077
1101
  int shadowRead = clamp(shadowSlot, 0, MAX_LIGHTS - 1);
1078
1102
  int layer = shadowSlot >= 0 && shadowSlot < MAX_LIGHTS ? uPointShadowLayer[shadowRead] : -1;
1103
+ /* What the panes between this receiver and the lamp let through, per map: lampGlass.ts. */
1104
+ vec3 staticGlass = vec3(1.0);
1105
+ vec3 liveGlass = vec3(1.0);
1079
1106
  if (layer >= 0 && shadowWeight > POINT_SHADOW_MIN_WEIGHT) {
1107
+ staticGlass = pointGlassTint(
1108
+ float(layer),
1109
+ receiverPoint - uPointShadowProjection[shadowRead].xyz,
1110
+ uPointShadowProjection[shadowRead].w,
1111
+ uPointShadowNear[shadowRead],
1112
+ uPointShadowSize[shadowRead]
1113
+ );
1080
1114
  occl = mix(
1081
1115
  1.0,
1082
1116
  pointShadow(
@@ -1116,10 +1150,20 @@ export const MAIN_GLSL = `void main() {
1116
1150
  uLivePointShadowSize[shadowRead]
1117
1151
  );
1118
1152
  liveWeight = uLivePointShadowWeight[shadowRead];
1153
+ liveGlass = pointGlassTint(
1154
+ float(liveLayer),
1155
+ receiverPoint - uLivePointShadowProjection[shadowRead].xyz,
1156
+ uLivePointShadowProjection[shadowRead].w,
1157
+ uLivePointShadowNear[shadowRead],
1158
+ uLivePointShadowSize[shadowRead]
1159
+ );
1119
1160
  }
1120
1161
  occl *= mix(1.0, liveOccl, liveWeight);
1162
+ /* Each map's glass faded in by that map's presence, as its occlusion is. */
1163
+ vec3 lampGlass = mix(vec3(1.0), staticGlass, uPointShadowWeight[shadowRead]) * mix(vec3(1.0), liveGlass, liveWeight);
1121
1164
  #else
1122
1165
  float occl = 1.0;
1166
+ vec3 lampGlass = vec3(1.0);
1123
1167
  #endif
1124
1168
 
1125
1169
  // Blend occlusion out as the light fades, so a shadow can never be more
@@ -1240,6 +1284,8 @@ export const MAIN_GLSL = `void main() {
1240
1284
  float photometric = mix(1.0, iesGain, step(0.0, iesProfile));
1241
1285
 
1242
1286
  float shape = (uLightFalloff == 1 ? falloff : falloff * falloff) * coneFalloff * photometric;
1287
+ glassGlow += lightColor * backNdl * shape * lightWeight * shaded * lampGlass;
1288
+ if (ndl <= 0.0) continue;
1243
1289
  /*
1244
1290
  * The cookie tints rather than scales, because a mask may be coloured — a stained window is
1245
1291
  * the case that makes the difference — and a greyscale one is exactly a scale. Applied to
@@ -1278,7 +1324,7 @@ export const MAIN_GLSL = `void main() {
1278
1324
  */
1279
1325
  vec3 lampDiffuse = albedo * lightColor * ndl * shape * lightWeight * (1.0 - metal);
1280
1326
  lampOpen += lampDiffuse;
1281
- lampShadowed += lampDiffuse * shaded;
1327
+ lampShadowed += lampDiffuse * shaded * lampGlass;
1282
1328
 
1283
1329
  /*
1284
1330
  * A lamp reflected in a polished surface, which point lights could not do at all.
@@ -1313,7 +1359,7 @@ export const MAIN_GLSL = `void main() {
1313
1359
  lightColor * sphereLobe(max(dot(n, lampHalfway), 0.0), surfaceRoughness, lightSourceRadius, dist)
1314
1360
  * lampSpec * shape * lightWeight;
1315
1361
  lampOpen += lampHighlight;
1316
- lampShadowed += lampHighlight * shaded;
1362
+ lampShadowed += lampHighlight * shaded * lampGlass;
1317
1363
  }
1318
1364
  /*
1319
1365
  * The most any light in range shadows this point, for the emissive term below —
@@ -1374,7 +1420,11 @@ export const MAIN_GLSL = `void main() {
1374
1420
  */
1375
1421
  float signedForm = quadFormFactor(n, vWorldPos, c0, c1, c2, c3);
1376
1422
  float form = mix(max(0.0, signedForm), abs(signedForm), uAreaLightTwoSided[a]);
1377
- if (form <= 0.0) continue;
1423
+ /* The form factor is linear in the normal, so the one seen from behind is its negation. */
1424
+ float backForm = glassTransmission > 0.0
1425
+ ? mix(max(0.0, -signedForm), abs(signedForm), uAreaLightTwoSided[a])
1426
+ : 0.0;
1427
+ if (form <= 0.0 && backForm <= 0.0) continue;
1378
1428
 
1379
1429
  /*
1380
1430
  * **How much of the rectangle this fragment can actually see past whatever is in the way.**
@@ -1401,6 +1451,7 @@ export const MAIN_GLSL = `void main() {
1401
1451
  * is a constant nobody has measured. \`docs/IMPROVEMENTS.md\` carries the row.
1402
1452
  */
1403
1453
  float areaOccl = 1.0;
1454
+ vec3 areaGlass = vec3(1.0);
1404
1455
  #if POINT_SHADOWS
1405
1456
  /*
1406
1457
  * The sample position is pushed along the surface normal before the lookup, and it scales
@@ -1441,6 +1492,12 @@ export const MAIN_GLSL = `void main() {
1441
1492
  ),
1442
1493
  uAreaShadowWeight[a]
1443
1494
  );
1495
+ /* What the panes before the rectangle let through, faded in as its occlusion is. */
1496
+ areaGlass = mix(
1497
+ vec3(1.0),
1498
+ areaGlassTint(float(areaLayer), areaShadowFrom, uAreaShadowFar[a], uAreaShadowNear[a], right, up, halfSize),
1499
+ uAreaShadowWeight[a]
1500
+ );
1444
1501
  }
1445
1502
  int areaLiveLayer = uLiveAreaShadowLayer[a];
1446
1503
  if (areaLiveLayer >= 0) {
@@ -1458,13 +1515,28 @@ export const MAIN_GLSL = `void main() {
1458
1515
  ),
1459
1516
  uLiveAreaShadowWeight[a]
1460
1517
  );
1518
+ areaGlass *= mix(
1519
+ vec3(1.0),
1520
+ areaGlassTint(
1521
+ float(areaLiveLayer),
1522
+ areaShadowFrom,
1523
+ uLiveAreaShadowFar[a],
1524
+ uLiveAreaShadowNear[a],
1525
+ right,
1526
+ up,
1527
+ halfSize
1528
+ ),
1529
+ uLiveAreaShadowWeight[a]
1530
+ );
1461
1531
  }
1462
1532
  #endif
1463
1533
 
1534
+ glassGlow += uAreaLightColor[a] * backForm * areaOccl * areaGlass;
1535
+ if (form <= 0.0) continue;
1464
1536
  /* Held back with the point lamps', and shadowed by the same term it always was. */
1465
1537
  vec3 areaDiffuse = albedo * uAreaLightColor[a] * form * (1.0 - metal);
1466
1538
  lampOpen += areaDiffuse;
1467
- lampShadowed += areaDiffuse * areaOccl;
1539
+ lampShadowed += areaDiffuse * areaOccl * areaGlass;
1468
1540
 
1469
1541
  if (vSpecular > 0.0 || metal > 0.0) {
1470
1542
  /*
@@ -1491,7 +1563,7 @@ export const MAIN_GLSL = `void main() {
1491
1563
  vec3 areaSpec = specColor * areaDfg.x + vec3(areaDfg.y);
1492
1564
  vec3 areaHighlight = areaSpec * uAreaLightColor[a] * coverage;
1493
1565
  lampOpen += areaHighlight;
1494
- lampShadowed += areaHighlight * areaOccl;
1566
+ lampShadowed += areaHighlight * areaOccl * areaGlass;
1495
1567
  }
1496
1568
  }
1497
1569
  }
@@ -1551,6 +1623,9 @@ export const MAIN_GLSL = `void main() {
1551
1623
  * \`driftLight.ts\`.
1552
1624
  */
1553
1625
  if (driftShare > 0.0) {
1626
+ if (glassTransmission > 0.0) {
1627
+ glassGlow += driftLightIrradiance(vWorldPos, -n) * (driftShare * uDriftLight.w);
1628
+ }
1554
1629
  lit += albedo * driftLightIrradiance(vWorldPos, n) * (driftShare * uDriftLight.w) * (1.0 - metal);
1555
1630
  }
1556
1631
 
@@ -1738,14 +1813,39 @@ export const MAIN_GLSL = `void main() {
1738
1813
  * splitting the specular out before the fog, which is a larger change to the one shader every
1739
1814
  * draw uses.
1740
1815
  */
1741
- if (uRefractStrength > 0.0) {
1816
+ if (uSeeThrough.x > 0.0 || glassTransmission > 0.0) {
1742
1817
  vec3 refractN = normalize(vNormal);
1743
1818
  vec3 refractV = normalize(uCameraPos - vWorldPos);
1744
- vec2 screenUv = gl_FragCoord.xy / vec2(textureSize(uRefractScene, 0));
1745
- vec2 bent = clamp(screenUv + refractN.xy * uRefractStrength, vec2(0.0), vec2(1.0));
1746
- vec3 behind = texture(uRefractScene, bent).rgb;
1747
- float pathLength = uRefractThickness * vThickness / max(dot(refractN, refractV), 0.05);
1748
- shaded = behind * pow(uRefractTint, vec3(pathLength));
1819
+ ivec2 snapSize = textureSize(uRefractScene, 0);
1820
+ vec2 screenUv = gl_FragCoord.xy / vec2(snapSize);
1821
+ vec2 bent = clamp(screenUv + refractN.xy * uSeeThrough.x, vec2(0.0), vec2(1.0));
1822
+ /*
1823
+ * **An explicit level, and frost is what picks it** — the 2026-08-07 rule, and the reason a
1824
+ * frosted pane blurs rather than fades: the copy carries a mip chain on a frame that draws glass,
1825
+ * so a milkier pane reads a coarser level of it. Level 0 on a copy with one level is exactly what
1826
+ * \`texture()\` read here before, so plain refraction draws as it did.
1827
+ */
1828
+ /* Two short of the last level: a fully frosted pane still shows the large light and dark of what
1829
+ is behind it, where the chain's last level is one texel, the whole frame's average. */
1830
+ float levels = max(floor(log2(float(max(snapSize.x, snapSize.y)))) - 2.0, 0.0);
1831
+ vec3 behind = textureLod(uRefractScene, bent, glassFrost * levels).rgb;
1832
+ /* Unsigned: a pane passes the same light seen from either side, and so does its Fresnel. */
1833
+ float cosView = max(abs(dot(refractN, refractV)), 0.05);
1834
+ float pathLength = uSeeThrough.y * vThickness / cosView;
1835
+ vec3 seen = behind * pow(uRefractTint, vec3(pathLength));
1836
+ if (glassTransmission > 0.0) {
1837
+ /*
1838
+ * **Glass keeps its own surface and shows what is behind over it**, by how much it lets
1839
+ * through and how little it reflects at this angle — Schlick's Fresnel at 0.04, the arithmetic
1840
+ * \`glass.ts\` states. So a clear pane keeps its highlight, and a pane seen edge-on is a mirror.
1841
+ * The glow of the lights behind it is added last, by how frosted it is.
1842
+ */
1843
+ float fresnel = 0.04 + 0.96 * pow(1.0 - cosView, 5.0);
1844
+ shaded = mix(shaded, seen * uGlassTint, glassTransmission * (1.0 - fresnel)) +
1845
+ applyOutputTransform(glassGlow * uGlassTint * (glassFrost * glassTransmission) * (1.0 - fog));
1846
+ } else {
1847
+ shaded = seen;
1848
+ }
1749
1849
  }
1750
1850
  /*
1751
1851
  * The per-vertex alpha lane, folded in here rather than given an expression of its own so the
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import { MAX_SHADOW_FILTER_TAPS } from '../../renderQuality.ts';
16
16
  import { OUTPUT_TRANSFORM_GLSL } from '../outputTransform.ts';
17
+ import { LAMP_GLASS_GLSL } from './lampGlass.ts';
17
18
 
18
19
  export const POINTSHADOW_GLSL = `/**
19
20
  * How much of one tap's occlusion survives its own penumbra.
@@ -338,6 +339,7 @@ float areaShadow(
338
339
  }
339
340
  return lit / float(max(uShadowFilterTaps, 1));
340
341
  }
342
+ ${LAMP_GLASS_GLSL}
341
343
  #endif
342
344
 
343
345
  out vec4 outColor;
@@ -108,13 +108,20 @@ in float vThickness;
108
108
  * paid by every consumer whether or not they refract, against 19.8 KB for a branch -- which is the
109
109
  * price table Track D's three shipped rows measured.
110
110
  *
111
- * uRefractStrength at 0 is every draw that shipped before this existed, and it is the default, so
111
+ * uSeeThrough.x, the strength, at 0 is every draw that shipped before this existed, and it is the default, so
112
112
  * the sampler is bound to an empty texture and read nowhere on a frame that refracts nothing.
113
113
  */
114
114
  uniform sampler2D uRefractScene;
115
- uniform float uRefractStrength;
115
+ /*
116
+ * **Four numbers in one vector: refraction's strength and path thickness, glass's transmission and
117
+ * frost** (x, y, z, w). One vector rather than four, and that is the whole reason: on a device at the
118
+ * WebGL2 floor of 256 fragment uniform vectors, every loose float is a vector the light budget
119
+ * gives up, and four separate floats cost half the shadowed lights such a device can have. The tints
120
+ * stay colours of their own. Zero transmission is not glass — see glass.ts.
121
+ */
122
+ uniform vec4 uSeeThrough;
116
123
  uniform vec3 uRefractTint;
117
- uniform float uRefractThickness;
124
+ uniform vec3 uGlassTint;
118
125
  flat in int vHasTangents;
119
126
 
120
127
  /**
@@ -268,9 +275,13 @@ uniform vec3 uEmissiveScale;
268
275
  * half of the file, because a test asserts that a profile without a probe mentions them
269
276
  * nowhere — a comment is source too, and it failed that test first.
270
277
  */
271
- uniform highp sampler2D uStaticShadowMap; // wgsl:share shadow
272
- uniform highp sampler2D uPeeledShadowMap; // wgsl:share shadow
273
- uniform highp sampler2D uDynamicShadowMap; // wgsl:share shadow
278
+ /* The sun's static, moving and peeled maps, as layers of one array: see shadowMap.ts. Where glass
279
+ has cast, the last two layers are the nearest pane's depth, static then moving. */
280
+ uniform highp sampler2DArray uSunShadows; // wgsl:share shadow
281
+ /* What the sun keeps through glass, static then moving: colour times colour, and clarity (see
282
+ glassShadow.ts). A one-texel stand-in where no glass has cast, which the lookup reads as none.
283
+ Mipmapped and filtered, unlike the depths: frost reads the level its spread asks for. */
284
+ uniform highp sampler2DArray uSunGlassTints; // wgsl:share tint
274
285
  /** 0 disables directional shadows when their source is not emitting. */
275
286
  uniform float uShadowStrength;
276
287
  uniform float uShadowMapSize;
@@ -866,6 +877,10 @@ uniform float uPointShadowWeight[MAX_LIGHTS];
866
877
  * it exists.
867
878
  */
868
879
  uniform highp sampler2DArray uPointShadows; // wgsl:share shadow
880
+ /* What each light keeps through glass: one layer a light, colour times colour and clarity, beside
881
+ the array above — whose light L then keeps its depth at 2L and its nearest pane at 2L + 1 (see
882
+ pointShadowArray.ts). A one-texel stand-in where no lamp has met glass, read as none. */
883
+ uniform highp sampler2DArray uPointGlassTints; // wgsl:share tint
869
884
 
870
885
  /**
871
886
  * Moving casters are separate from persistent world maps. Two live maps exist