@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
@@ -1,4 +1,5 @@
1
1
  import { resolveConditionals } from './conditionals.ts';
2
+ import { SUN_STATIC_LAYER } from '../shadowMap.ts';
2
3
  import { CUTOUT_COVERAGE_GLSL } from './cutoutCoverage.ts';
3
4
  import { SKINNING_GLSL } from './skinning.ts';
4
5
  import { CHANNEL_ATTRIBUTE, CHANNEL_BEND } from './vertexChannel.ts';
@@ -73,6 +74,11 @@ out vec4 vLightPosition;
73
74
  out vec2 vUv;
74
75
  flat out float vAlphaCutout;
75
76
  #endif
77
+ #if GLASS
78
+ /* Where on the pane this is, for the tint's own normal and its angle to the light. Last, so the
79
+ outputs before it keep the locations the plain variants give them. */
80
+ out vec3 vGlassWorld;
81
+ #endif
76
82
 
77
83
  void main() {
78
84
  #if SKINNED
@@ -100,20 +106,23 @@ void main() {
100
106
  vUv = aUv * uUvScale;
101
107
  vAlphaCutout = uAlphaCutout;
102
108
  #endif
109
+ #if GLASS
110
+ vGlassWorld = bent;
111
+ #endif
103
112
  }
104
113
  `;
105
114
 
106
115
  /** The rigid variant: the world, a prop, a mesh with no rig behind it. */
107
116
  export const DEPTH_VERT = resolveConditionals(
108
117
  DEPTH_VERT_SOURCE,
109
- { SKINNED: false, INSTANCED: false, CUTOUT: false },
118
+ { SKINNED: false, INSTANCED: false, CUTOUT: false, GLASS: false },
110
119
  'depth',
111
120
  );
112
121
 
113
122
  /** The skinned variant, which reads a joint palette and moves the vertex by it. */
114
123
  export const DEPTH_SKINNED_VERT = resolveConditionals(
115
124
  DEPTH_VERT_SOURCE,
116
- { SKINNED: true, INSTANCED: false, CUTOUT: false },
125
+ { SKINNED: true, INSTANCED: false, CUTOUT: false, GLASS: false },
117
126
  'depth-skinned',
118
127
  );
119
128
 
@@ -127,7 +136,7 @@ export const DEPTH_SKINNED_VERT = resolveConditionals(
127
136
  */
128
137
  export const DEPTH_INSTANCED_VERT = resolveConditionals(
129
138
  DEPTH_VERT_SOURCE,
130
- { SKINNED: false, INSTANCED: true, CUTOUT: false },
139
+ { SKINNED: false, INSTANCED: true, CUTOUT: false, GLASS: false },
131
140
  'depth-instanced',
132
141
  );
133
142
 
@@ -143,13 +152,13 @@ export const DEPTH_INSTANCED_VERT = resolveConditionals(
143
152
  */
144
153
  export const DEPTH_CUTOUT_VERT = resolveConditionals(
145
154
  DEPTH_VERT_SOURCE,
146
- { SKINNED: false, INSTANCED: false, CUTOUT: true },
155
+ { SKINNED: false, INSTANCED: false, CUTOUT: true, GLASS: false },
147
156
  'depth-cutout',
148
157
  );
149
158
 
150
159
  export const DEPTH_INSTANCED_CUTOUT_VERT = resolveConditionals(
151
160
  DEPTH_VERT_SOURCE,
152
- { SKINNED: false, INSTANCED: true, CUTOUT: true },
161
+ { SKINNED: false, INSTANCED: true, CUTOUT: true, GLASS: false },
153
162
  'depth-instanced-cutout',
154
163
  );
155
164
 
@@ -157,14 +166,14 @@ export const DEPTH_FRAG = `#version 300 es
157
166
  precision highp float;
158
167
  in vec4 vLightPosition;
159
168
 
160
- uniform highp sampler2D uPreviousShadowMap;
169
+ uniform highp sampler2DArray uPreviousShadowMap;
161
170
  uniform int uPeelShadowLayer;
162
171
 
163
172
  void main() {
164
173
  if (uPeelShadowLayer != 0) {
165
174
  vec3 p = vLightPosition.xyz / vLightPosition.w;
166
175
  vec2 uv = p.xy * 0.5 + 0.5;
167
- float previousDepth = texture(uPreviousShadowMap, uv).r;
176
+ float previousDepth = texture(uPreviousShadowMap, vec3(uv, ${SUN_STATIC_LAYER}.0)).r;
168
177
  // Discard the already-recorded surface and everything in front of it. The
169
178
  // ordinary depth test then stores the next independently fading occluder.
170
179
  if (gl_FragCoord.z <= previousDepth + 0.00001) discard;
@@ -190,7 +199,7 @@ in vec4 vLightPosition;
190
199
  in vec2 vUv;
191
200
  flat in float vAlphaCutout;
192
201
 
193
- uniform highp sampler2D uPreviousShadowMap;
202
+ uniform highp sampler2DArray uPreviousShadowMap;
194
203
  uniform int uPeelShadowLayer;
195
204
  uniform highp sampler2D uCutoutMap;
196
205
  ${CUTOUT_COVERAGE_GLSL}
@@ -200,9 +209,100 @@ void main() {
200
209
  if (uPeelShadowLayer != 0) {
201
210
  vec3 p = vLightPosition.xyz / vLightPosition.w;
202
211
  vec2 uv = p.xy * 0.5 + 0.5;
203
- float previousDepth = textureLod(uPreviousShadowMap, uv, 0.0).r;
212
+ float previousDepth = textureLod(uPreviousShadowMap, vec3(uv, ${SUN_STATIC_LAYER}.0), 0.0).r;
204
213
  if (gl_FragCoord.z <= previousDepth + 0.00001) discard;
205
214
  }
206
215
  if (alpha < vAlphaCutout) discard;
207
216
  }
208
217
  `;
218
+
219
+ /**
220
+ * The glass tint's vertex stages: every way a caster is drawn, with the world position handed on.
221
+ *
222
+ * **The same five as the depth pass, and for the same reason**: a pane must cast its colour from
223
+ * exactly where it casts its depth, deformed and placed the same way, or the coloured patch parts
224
+ * from the pane's own shadow edge. The only addition is `vGlassWorld`.
225
+ */
226
+ export const GLASS_TINT_VERT = resolveConditionals(
227
+ DEPTH_VERT_SOURCE,
228
+ { SKINNED: false, INSTANCED: false, CUTOUT: false, GLASS: true },
229
+ 'glass-tint',
230
+ );
231
+ export const GLASS_TINT_SKINNED_VERT = resolveConditionals(
232
+ DEPTH_VERT_SOURCE,
233
+ { SKINNED: true, INSTANCED: false, CUTOUT: false, GLASS: true },
234
+ 'glass-tint-skinned',
235
+ );
236
+ export const GLASS_TINT_INSTANCED_VERT = resolveConditionals(
237
+ DEPTH_VERT_SOURCE,
238
+ { SKINNED: false, INSTANCED: true, CUTOUT: false, GLASS: true },
239
+ 'glass-tint-instanced',
240
+ );
241
+ export const GLASS_TINT_CUTOUT_VERT = resolveConditionals(
242
+ DEPTH_VERT_SOURCE,
243
+ { SKINNED: false, INSTANCED: false, CUTOUT: true, GLASS: true },
244
+ 'glass-tint-cutout',
245
+ );
246
+ export const GLASS_TINT_INSTANCED_CUTOUT_VERT = resolveConditionals(
247
+ DEPTH_VERT_SOURCE,
248
+ { SKINNED: false, INSTANCED: true, CUTOUT: true, GLASS: true },
249
+ 'glass-tint-instanced-cutout',
250
+ );
251
+
252
+ /**
253
+ * What a pane lets through: its transmission and colour, less what it reflects toward the light,
254
+ * and how clear it is — `glassShadow.ts`'s `paneTexel`, stated for the device. Drawn with a
255
+ * multiplying blend and no depth test, so every pane on a ray contributes whatever order they
256
+ * come in.
257
+ *
258
+ * **The pane's normal is its triangle's**, from the derivatives of its world position, first and in
259
+ * uniform control flow: a pane is flat, a sign does not matter to a cosine taken absolutely, and it
260
+ * spares the five vertex stages a normal they would carry for this alone. What it gives up is a
261
+ * curved pane, whose Fresnel steps per triangle; what would change it is glass that is mostly bulbs.
262
+ */
263
+ const GLASS_TINT_BODY = /* glsl */ `
264
+ vec3 n = normalize(cross(dFdx(vGlassWorld), dFdy(vGlassWorld)));
265
+ vec3 toLight = uGlassLight.w > 0.5 ? normalize(uGlassLight.xyz - vGlassWorld) : uGlassLight.xyz;
266
+ float cosLight = abs(dot(n, toLight));
267
+ float reflected = 0.04 + 0.96 * pow(1.0 - cosLight, 5.0);
268
+ outTint = vec4(uGlassPane.rgb * (1.0 - reflected), uGlassPane.a);
269
+ `;
270
+
271
+ export const GLASS_TINT_FRAG = `#version 300 es
272
+ precision highp float;
273
+ in vec4 vLightPosition;
274
+ in vec3 vGlassWorld;
275
+
276
+ /* rgb: transmission times tint; a: clarity, one less the frost. */
277
+ uniform vec4 uGlassPane;
278
+ /* xyz: the light's position (w 1) or the direction toward it (w 0). */
279
+ uniform vec4 uGlassLight;
280
+
281
+ out vec4 outTint;
282
+
283
+ void main() {
284
+ ${GLASS_TINT_BODY}
285
+ }
286
+ `;
287
+
288
+ /** A cut-out pane: its colour lands only where its depth does, by the depth cutout's own test. */
289
+ export const GLASS_TINT_CUTOUT_FRAG = `#version 300 es
290
+ precision highp float;
291
+ in vec4 vLightPosition;
292
+ in vec2 vUv;
293
+ flat in float vAlphaCutout;
294
+ in vec3 vGlassWorld;
295
+
296
+ uniform vec4 uGlassPane;
297
+ uniform vec4 uGlassLight;
298
+ uniform highp sampler2D uCutoutMap;
299
+ ${CUTOUT_COVERAGE_GLSL}
300
+ out vec4 outTint;
301
+
302
+ void main() {
303
+ /* First, in uniform control flow, as the depth cutout samples it. */
304
+ float alpha = cutoutAlpha(texture(uCutoutMap, vUv).a, vUv * vec2(textureSize(uCutoutMap, 0)));
305
+ ${GLASS_TINT_BODY}
306
+ if (alpha < vAlphaCutout) discard;
307
+ }
308
+ `;
@@ -1,5 +1,13 @@
1
1
  /** Filtering the directional cascade, with receiver-plane depth gradient compensation. */
2
2
  import { DIRECTIONAL_SHADOW_FADE_START, MAX_SHADOW_FILTER_TAPS } from '../../renderQuality.ts';
3
+ import {
4
+ SUN_DYNAMIC_LAYER,
5
+ SUN_GLASS_MOVING_LAYER,
6
+ SUN_GLASS_STATIC_LAYER,
7
+ SUN_PEELED_LAYER,
8
+ SUN_STATIC_LAYER,
9
+ } from '../../shadowMap.ts';
10
+ import { FROST_RADIUS_CAP, FROST_SPREAD } from '../../glassShadow.ts';
3
11
 
4
12
  export const DIRECTIONALSHADOW_GLSL = `vec2 receiverPlaneDepthGradient(vec3 p) {
5
13
  vec3 dx = dFdx(p);
@@ -127,10 +135,10 @@ float shadowFactor(float directionalNdl) {
127
135
  */
128
136
  float coarseCompare = p.z - 1.0 / uShadowDepthSpan;
129
137
  float coarse =
130
- directionalVisibility(p.z, coarseCompare, textureLod(uStaticShadowMap, p.xy, 0.0).r) *
131
- directionalVisibility(p.z, coarseCompare, textureLod(uDynamicShadowMap, p.xy, 0.0).r);
138
+ directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_STATIC_LAYER}.0), 0.0).r) *
139
+ directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_DYNAMIC_LAYER}.0), 0.0).r);
132
140
  if (uPeeledShadowEnabled != 0) {
133
- coarse *= directionalVisibility(p.z, coarseCompare, textureLod(uPeeledShadowMap, p.xy, 0.0).r);
141
+ coarse *= directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_PEELED_LAYER}.0), 0.0).r);
134
142
  }
135
143
  float strength = uShadowStrength * edgeFade * lowElevationFade;
136
144
  if (receiverFade <= 0.0) return mix(1.0, coarse, strength);
@@ -191,11 +199,11 @@ float shadowFactor(float directionalNdl) {
191
199
  * \`shadowMap.ts\` allocates these with one storage level and \`NEAREST\` filtering, so
192
200
  * there is no mip to select and level zero is the only thing \`texture\` could have read.
193
201
  */
194
- float staticDepth = textureLod(uStaticShadowMap, sampleUv, 0.0).r;
195
- float dynamicDepth = textureLod(uDynamicShadowMap, sampleUv, 0.0).r;
202
+ float staticDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_STATIC_LAYER}.0), 0.0).r;
203
+ float dynamicDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
196
204
  staticLit += directionalVisibility(tapReceiverDepth, compare, staticDepth);
197
205
  if (uPeeledShadowEnabled != 0) {
198
- float peeledDepth = textureLod(uPeeledShadowMap, sampleUv, 0.0).r;
206
+ float peeledDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_PEELED_LAYER}.0), 0.0).r;
199
207
  peeledLit += directionalVisibility(tapReceiverDepth, compare, peeledDepth);
200
208
  } else {
201
209
  peeledLit += 1.0;
@@ -215,6 +223,89 @@ float shadowFactor(float directionalNdl) {
215
223
 
216
224
  return mix(1.0, mix(coarse, lit, receiverFade), strength);
217
225
  }
226
+
227
+ /*
228
+ * One of the sun's two glass layers at receiver p: how milky the nearest pane is and how far behind
229
+ * it this receiver stands, the spread that follows (glassShadow.ts's frostRadius, capped), and the
230
+ * tint's level for that spread. Then glassShadow.ts's spreadTint: the outline from taps at the base
231
+ * radius — where the clear pane's patch ends — and the colour from the spread taps, each read coarse,
232
+ * unmixed from the ground around it and weighed by the pane's share of it, so a frosted patch mixes
233
+ * its panes' colours and keeps the light the clear one lets through. What it gives up is a pane lying
234
+ * behind the receiver lending it colour within the spread, which only a receiver between two panes
235
+ * meets. One function for both layers, because two copies were most of the lit shaders' growth.
236
+ */
237
+ vec3 sunGlassLayer(vec3 p, float paneLayer, float tintLayer, float bias, float texel, float uvPerMetre) {
238
+ float tintSize = float(textureSize(uSunGlassTints, 0).x);
239
+ float clarity = textureLod(uSunGlassTints, vec3(p.xy, tintLayer), 0.0).a;
240
+ float behind =
241
+ max(p.z - textureLod(uSunShadows, vec3(p.xy, paneLayer), 0.0).r, 0.0) * uShadowDepthSpan;
242
+ float radius = clamp(
243
+ (1.0 - clarity) * ${FROST_SPREAD.toFixed(6)} * behind * uvPerMetre,
244
+ texel,
245
+ texel * ${FROST_RADIUS_CAP.toFixed(1)}
246
+ );
247
+ float lod = log2(max(radius * tintSize, 1.0));
248
+ float cover = 0.0;
249
+ vec3 colour = vec3(0.0);
250
+ float found = 0.0;
251
+ for (int i = 0; i < ${MAX_SHADOW_FILTER_TAPS}; i++) {
252
+ if (i >= uShadowFilterTaps) break;
253
+ vec2 offset = DIRECTIONAL_PCF_OFFSETS[i];
254
+ vec2 edgeUv = p.xy + offset * texel;
255
+ cover += p.z > textureLod(uSunShadows, vec3(edgeUv, paneLayer), 0.0).r + bias ? 1.0 : 0.0;
256
+ /* The spread taps weigh by the pane's unmixed share rather than a depth test: continuous, so a
257
+ colour crossing between two panes blends rather than stepping through sixteen bands. */
258
+ vec4 pane = glassUnmix(textureLod(uSunGlassTints, vec3(p.xy + offset * radius, tintLayer), lod), clarity);
259
+ colour += pane.rgb * pane.a;
260
+ found += pane.a;
261
+ }
262
+ float taps = float(max(uShadowFilterTaps, 1));
263
+ return mix(vec3(1.0), found > 0.0 ? colour / found : vec3(1.0), cover / taps);
264
+ }
265
+
266
+ /*
267
+ * What the sun's light keeps of itself through glass at this receiver: glassShadow.ts's tap rule
268
+ * and frost spread, over shadowFactor's own taps. A tap counts a pane only when the receiver is
269
+ * farther from the sun than that pane (the glass depth layers), so a surface on the sun's side of a
270
+ * pane, and the pane itself, keep the sun's own colour; the taps average with equal weight, so the
271
+ * spread moves light rather than losing it. Frost widens the taps by how far behind the nearest pane
272
+ * the receiver is, capped, with the tap count unchanged. What it gives up is spread past the edge of
273
+ * an opaque frame's shadow, which this lookup never sees. Faded with the opaque shadow's own fades.
274
+ */
275
+ vec3 sunGlassLookup(float directionalNdl) {
276
+ vec3 p = vLightPos.xyz / vLightPos.w;
277
+ p = p * 0.5 + 0.5;
278
+ /* Map units per metre along the receiver, from derivatives taken first in uniform control flow,
279
+ so a frost spread stated in metres lands the same whatever size of light matrix a consumer built. */
280
+ float uvPerMetre = length(dFdx(p.xy)) / max(length(dFdx(vWorldPos)), 1e-6);
281
+ if (uShadowStrength <= 0.0 || textureSize(uSunGlassTints, 0).x <= 1) return vec3(1.0);
282
+ if (directionalNdl <= 0.0) return vec3(1.0);
283
+ 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);
284
+ vec2 fromCentre = abs(p.xy - 0.5) * 2.0;
285
+ float edgeFade = 1.0 - smoothstep(0.72, 0.98, max(fromCentre.x, fromCentre.y));
286
+ edgeFade *= 1.0 - smoothstep(0.90, 1.0, p.z);
287
+ float shadowSlope = length(uDirectionalDir.xz) / max(uDirectionalDir.y, 0.001);
288
+ float lowElevationFade = 1.0 - smoothstep(
289
+ uShadowMaxSlope * ${DIRECTIONAL_SHADOW_FADE_START.toFixed(2)},
290
+ uShadowMaxSlope,
291
+ shadowSlope
292
+ );
293
+ float strength = uShadowStrength * edgeFade * lowElevationFade;
294
+ if (strength <= 0.0) return vec3(1.0);
295
+
296
+ float bias = 0.14 / uShadowDepthSpan;
297
+ float texel = 1.35 / uShadowMapSize;
298
+ vec3 staticTint = sunGlassLayer(p, ${SUN_GLASS_STATIC_LAYER}.0, 0.0, bias, texel, uvPerMetre);
299
+ vec3 movingTint = sunGlassLayer(p, ${SUN_GLASS_MOVING_LAYER}.0, 1.0, bias, texel, uvPerMetre);
300
+ return mix(vec3(1.0), staticTint * movingTint, strength);
301
+ }
302
+
303
+ /* The way in, asking first whether this build reads glass at all (GLASS_SHADOWS). A constant, so
304
+ the lookup's derivatives are still taken in uniform control flow. */
305
+ vec3 sunGlassTint(float directionalNdl) {
306
+ if (GLASS_SHADOWS) return sunGlassLookup(directionalNdl);
307
+ return vec3(1.0);
308
+ }
218
309
  #endif
219
310
 
220
311
  /**
@@ -0,0 +1,25 @@
1
+ /**
2
+ * What the lit stage shares between every glass tint it reads: whether it reads any, and
3
+ * `glassShadow.ts`'s `unmixTint`, stated once for the sun's lookup and the lamps'. At depth zero,
4
+ * outside every conditional, so each lookup can call it whichever shadows a permutation compiles.
5
+ */
6
+ export function glassTintGlsl(glassShadows: boolean): string {
7
+ return /* glsl */ `
8
+ /*
9
+ * Whether this build reads glass at all, which every way into the lookups asks first — and as a
10
+ * branch's own condition, never negated: see \`overridableConstants\` in scripts/wgsl/transform.mjs.
11
+ */
12
+ const bool GLASS_SHADOWS = ${glassShadows ? 'true' : 'false'}; // wgsl:override
13
+ /*
14
+ * A coarse level of a glass tint with the open ground's white taken back out, by how milky the pane
15
+ * at the centre is: rgb the pane's colour, a its share of the texel (glassShadow.ts's unmixTint).
16
+ * A clear centre is never read coarse, so it is returned as it stands.
17
+ */
18
+ vec4 glassUnmix(vec4 mip, float centreClarity) {
19
+ if (centreClarity >= 1.0) return vec4(mip.rgb, 1.0);
20
+ float share = clamp((1.0 - mip.a) / (1.0 - centreClarity), 0.0, 1.0);
21
+ if (share <= 0.0) return vec4(1.0, 1.0, 1.0, 0.0);
22
+ return vec4(clamp((mip.rgb - vec3(1.0 - share)) / share, 0.0, 1.0), share);
23
+ }
24
+ `;
25
+ }
@@ -7,6 +7,7 @@ import { preambleGlsl } from './preamble.ts';
7
7
  import { LOBES_GLSL } from './lobes.ts';
8
8
  import { POINTSHADOW_GLSL } from './pointShadow.ts';
9
9
  import { DIRECTIONALSHADOW_GLSL } from './directionalShadow.ts';
10
+ import { glassTintGlsl } from './glassTint.ts';
10
11
  import { MORPH_GLSL } from '../morph.ts';
11
12
  import { SKINNING_GLSL } from '../skinning.ts';
12
13
  import { SURFACE_GLSL } from './surface.ts';
@@ -364,6 +365,19 @@ export interface FlatShaderOptions {
364
365
  * for its instructions not to be there.
365
366
  */
366
367
  readonly nightEmissive: boolean;
368
+ /**
369
+ * Whether this build reads glass in its shadows at all: `glassShadows` other than `'off'`. True
370
+ * when absent, which is what the generated WGSL is built with.
371
+ *
372
+ * **Not a permutation axis, and the payload is why.** Every lit permutation carries the glass
373
+ * lookups, and a second copy of each without them would be most of the generated file again. So
374
+ * it is one switch, `GLASS_SHADOWS`, that every way into the lookups branches on first: written
375
+ * into this source for WebGL2, and a pipeline-overridable constant on the generated WGSL, which
376
+ * the device compiles away as it would under `#if`. That matters because the lookups returning
377
+ * at their first line still cost the lit pass its registers — 0.42 ms at 720p on WebGL2 on the
378
+ * courtyard at night, measured, and nothing once compiled out.
379
+ */
380
+ readonly glassShadows?: boolean;
367
381
  /**
368
382
  * How many point lights this build declares room for. `MAX_POINT_LIGHTS` when absent.
369
383
  *
@@ -466,6 +480,9 @@ export function flatFrag(options: FlatShaderOptions): string {
466
480
  * is at depth zero, and it is still declared before `pointShadow` uses it.
467
481
  */
468
482
  OCTAHEDRAL_GLSL,
483
+ /* Depth zero as well: both glass lookups, sun and lamps, call it, and every permutation
484
+ declares its switch, which the pipelines set whether the permutation reads it or not. */
485
+ glassTintGlsl(options.glassShadows ?? true),
469
486
  LOBES_GLSL,
470
487
  POINTSHADOW_GLSL,
471
488
  DIRECTIONALSHADOW_GLSL,
@@ -0,0 +1,185 @@
1
+ /**
2
+ * What a lamp's light keeps through glass at this receiver: `glassShadow.ts`'s rules over the point
3
+ * filter's own taps, for a round lamp and for a rectangle.
4
+ *
5
+ * **The sun's lookup, in the lamp's geometry** (`directionalShadow.ts`'s `sunGlassLookup`). The
6
+ * outline comes from depth-tested taps on the opaque filter's own disk — a tap counts a pane only
7
+ * where the receiver is farther from the lamp than that pane, by the opaque bias, and fades with
8
+ * the opaque shadow's own reach and penumbra — and the colour from spread taps, read coarse,
9
+ * unmixed from what lies around the pane and weighed by its share, so a frosted patch mixes its
10
+ * panes and keeps the light a clear one passes.
11
+ *
12
+ * **Where a lamp differs is the frost.** Light scattered at a pane spreads in a cone behind it, so
13
+ * at a receiver `behind` metres past the pane the colour comes from pane points within
14
+ * `(1 − clarity) × tan 30° × behind` of the ray. Seen from the lamp that is an angle over the pane's
15
+ * distance, and in the filter's units — offsets added to the ray at the receiver's distance — it is
16
+ * that spread times `dist / pane`. Capped at `FROST_RADIUS_CAP` base radii, the tap count unchanged.
17
+ *
18
+ * **What it costs** is one coarse tint fetch per shadowed lamp on a receiver with no glass near its
19
+ * filter — the tint is white there, and the lookup stops — and two tap loops where there is. **What
20
+ * it gives up** is what the sun's gives up, plus the octahedral map's fold: a coarse level averages
21
+ * across the map's outer edges, so a wide frost spread lying across one borrows a little colour from
22
+ * the other side of the sphere.
23
+ */
24
+ import { FROST_RADIUS_CAP, FROST_SPREAD } from '../../glassShadow.ts';
25
+ import { MAX_SHADOW_FILTER_TAPS } from '../../renderQuality.ts';
26
+
27
+ /** Octahedral texels per radian at edge 1: the sphere's 4π over `edge²` texels, as a side. */
28
+ const TEXELS_PER_RADIAN = 1 / Math.sqrt(4 * Math.PI);
29
+
30
+ export const LAMP_GLASS_GLSL = `
31
+ /*
32
+ * Whether any glass lies within \`radius\` of this ray: the tint is exactly white wherever no pane
33
+ * was drawn, and any pane passes at most 0.96 of itself, so one coarse fetch answers for the disk.
34
+ */
35
+ bool lampGlassNear(float tintLayer, vec3 toFrag, float dist, float radius) {
36
+ float texels = radius / dist * float(textureSize(uPointGlassTints, 0).x) * ${TEXELS_PER_RADIAN.toFixed(6)};
37
+ vec4 around = textureLod(uPointGlassTints, vec3(octEncode(toFrag), tintLayer), log2(max(texels, 1.0)) + 1.0);
38
+ return min(min(around.r, around.g), min(around.b, around.a)) < 0.998;
39
+ }
40
+
41
+ /*
42
+ * The spread half: the colour of the glass the frost disk finds, read at \`lod\`, unmixed from what
43
+ * lies around it and weighed by its share (glassShadow.ts's spreadTint). Clear when it finds none.
44
+ */
45
+ vec3 lampGlassColour(
46
+ float tintLayer,
47
+ vec3 toFrag,
48
+ vec3 tu,
49
+ vec3 tv,
50
+ float offset,
51
+ float lod,
52
+ float clarity,
53
+ float ca,
54
+ float sa
55
+ ) {
56
+ vec3 colour = vec3(0.0);
57
+ float found = 0.0;
58
+ for (int i = 0; i < ${MAX_SHADOW_FILTER_TAPS}; i++) {
59
+ if (i >= uShadowFilterTaps) break;
60
+ vec2 disk = PCF_OFFSETS[i];
61
+ vec2 turned = vec2(disk.x * ca - disk.y * sa, disk.x * sa + disk.y * ca);
62
+ vec3 spreadDir = toFrag + (tu * turned.x + tv * turned.y) * offset;
63
+ vec4 pane = glassUnmix(textureLod(uPointGlassTints, vec3(octEncode(spreadDir), tintLayer), lod), clarity);
64
+ colour += pane.rgb * pane.a;
65
+ found += pane.a;
66
+ }
67
+ return found > 0.0 ? colour / found : vec3(1.0);
68
+ }
69
+
70
+ /* One outline tap: how much of the pane on this ray lies between the lamp and the receiver. */
71
+ float lampGlassTap(vec3 sampleDir, float glassLayer, float dist, float bias, float far, float size) {
72
+ float stored = textureLod(uPointShadows, vec3(octEncode(sampleDir), glassLayer), 0.0).r;
73
+ if (stored >= 0.9999) return 0.0;
74
+ float storedDistance = stored * far;
75
+ if (dist - bias <= storedDistance) return 0.0;
76
+ return shadowReach(max(dist - storedDistance, 0.0), far) * tapStrength(dist, storedDistance, size);
77
+ }
78
+
79
+ /*
80
+ * The frost's offset in the filter's units, the tint level that reads it as an average, and the
81
+ * pane's clarity: the cone behind the nearest pane on the ray, as the lamp sees it, capped at
82
+ * \`base\` radii.
83
+ */
84
+ vec3 lampGlassFrost(float tintLayer, vec2 centreUv, float dist, float paneDistance, float base) {
85
+ float clarity = textureLod(uPointGlassTints, vec3(centreUv, tintLayer), 0.0).a;
86
+ float behind = max(dist - paneDistance, 0.0);
87
+ float frost = (1.0 - clarity) * ${FROST_SPREAD.toFixed(6)} * behind * dist / max(paneDistance, 0.05);
88
+ float offset = clamp(frost, base, base * ${FROST_RADIUS_CAP.toFixed(1)});
89
+ float texels = offset / dist * float(textureSize(uPointGlassTints, 0).x) * ${TEXELS_PER_RADIAN.toFixed(6)};
90
+ return vec3(offset, log2(max(texels, 1.0)), clarity);
91
+ }
92
+
93
+ /*
94
+ * **One lookup for both kinds of lamp**, a rectangle's being the round one with its outline taps on
95
+ * areaShadow's ellipse: a zero \`halfSize\` is a round lamp of \`sourceRadius\`. One function rather
96
+ * than two because every lit permutation carries it, and two copies of the same eighty lines were
97
+ * thirty-five kilobytes of the generated shaders.
98
+ */
99
+ vec3 lampGlassTint(
100
+ float layer,
101
+ vec3 toFrag,
102
+ float far,
103
+ float near,
104
+ vec3 right,
105
+ vec3 up,
106
+ vec2 halfSize,
107
+ float sourceRadius
108
+ ) {
109
+ if (textureSize(uPointGlassTints, 0).x <= 1) return vec3(1.0);
110
+ float dist = length(toFrag);
111
+ if (dist <= near) return vec3(1.0);
112
+ float glassLayer = layer + 1.0;
113
+ float tintLayer = layer * 0.5;
114
+ /* The opaque filter's own widest disk, so the check covers every tap it could take. */
115
+ if (!lampGlassNear(tintLayer, toFrag, dist, MAX_FILTER_RADIUS + 0.012)) return vec3(1.0);
116
+
117
+ float bias = 0.04 + dist * 0.015;
118
+ vec2 centreUv = octEncode(toFrag);
119
+ float paneStored = textureLod(uPointShadows, vec3(centreUv, glassLayer), 0.0).r;
120
+ float paneDistance = paneStored >= 0.9999 ? dist : paneStored * far;
121
+ float spread = max(dist - paneDistance, 0.0) / max(paneDistance, 0.05);
122
+
123
+ vec3 fwd = toFrag / dist;
124
+ vec3 lift = abs(fwd.y) < 0.9 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
125
+ vec3 tu = normalize(cross(lift, fwd));
126
+ vec3 tv = cross(fwd, tu);
127
+ /* A round lamp: the opaque filter's radius, from the pane as its occluder, so a large lamp's
128
+ tint is soft too. A rectangle: its two axes across the ray, each with its own width. */
129
+ vec3 alongX = tu;
130
+ vec3 alongY = tv;
131
+ float radius = clamp(sourceRadius * spread, 0.01, MAX_FILTER_RADIUS) + 0.012;
132
+ float widthX = radius;
133
+ float widthY = radius;
134
+ float penumbra = sourceRadius;
135
+ if (halfSize.x > 0.0) {
136
+ vec3 acrossX = right - fwd * dot(right, fwd);
137
+ vec3 acrossY = up - fwd * dot(up, fwd);
138
+ float seenX = length(acrossX);
139
+ float seenY = length(acrossY);
140
+ alongX = seenX > 1e-5 ? acrossX / seenX : vec3(0.0);
141
+ alongY = seenY > 1e-5 ? acrossY / seenY : vec3(0.0);
142
+ float halfX = halfSize.x * seenX;
143
+ float halfY = halfSize.y * seenY;
144
+ widthX = min(halfX * spread, MAX_FILTER_RADIUS) + 0.012;
145
+ widthY = min(halfY * spread, MAX_FILTER_RADIUS) + 0.012;
146
+ penumbra = min(halfX, halfY);
147
+ }
148
+ /* pointShadow's column-pair turn, so main's pair resolve averages these taps as it does those. */
149
+ float angle = float(int(gl_FragCoord.x) & 1) * PCF_PAIR_TURN;
150
+ float ca = cos(angle);
151
+ float sa = sin(angle);
152
+
153
+ float cover = 0.0;
154
+ for (int i = 0; i < ${MAX_SHADOW_FILTER_TAPS}; i++) {
155
+ if (i >= uShadowFilterTaps) break;
156
+ vec2 disk = PCF_OFFSETS[i];
157
+ vec2 turned = vec2(disk.x * ca - disk.y * sa, disk.x * sa + disk.y * ca);
158
+ vec3 sampleDir = toFrag + alongX * (turned.x * widthX) + alongY * (turned.y * widthY);
159
+ cover += lampGlassTap(sampleDir, glassLayer, dist, bias, far, penumbra);
160
+ }
161
+ if (cover <= 0.0) return vec3(1.0);
162
+ vec3 frost = lampGlassFrost(tintLayer, centreUv, dist, paneDistance, max(widthX, widthY));
163
+ vec3 colour = lampGlassColour(tintLayer, toFrag, tu, tv, frost.x, frost.y, frost.z, ca, sa);
164
+ return mix(vec3(1.0), colour, cover / float(max(uShadowFilterTaps, 1)));
165
+ }
166
+
167
+ /* The two ways in, each asking first whether this build reads glass at all (GLASS_SHADOWS). */
168
+ vec3 pointGlassTint(float layer, vec3 toFrag, float far, float near, float sourceRadius) {
169
+ if (GLASS_SHADOWS) return lampGlassTint(layer, toFrag, far, near, vec3(0.0), vec3(0.0), vec2(0.0), sourceRadius);
170
+ return vec3(1.0);
171
+ }
172
+
173
+ vec3 areaGlassTint(
174
+ float layer,
175
+ vec3 toFrag,
176
+ float far,
177
+ float near,
178
+ vec3 right,
179
+ vec3 up,
180
+ vec2 halfSize
181
+ ) {
182
+ if (GLASS_SHADOWS) return lampGlassTint(layer, toFrag, far, near, right, up, halfSize, 0.0);
183
+ return vec3(1.0);
184
+ }
185
+ `;