@driftengine/core 4.4.2 → 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.
- package/CHANGELOG.json +46 -0
- package/README.md +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -0
- package/dist/render/backend/api.d.ts +17 -11
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/renderer.d.ts +79 -8
- package/dist/render/backend/webgl2/renderer.js +360 -105
- package/dist/render/backend/webgpu/buffers.d.ts +19 -1
- package/dist/render/backend/webgpu/buffers.js +27 -1
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +2 -0
- package/dist/render/backend/webgpu/flatPass.js +20 -1
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +205 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
- package/dist/render/backend/webgpu/instanced.d.ts +12 -1
- package/dist/render/backend/webgpu/instanced.js +20 -1
- package/dist/render/backend/webgpu/latePass.d.ts +26 -0
- package/dist/render/backend/webgpu/latePass.js +133 -0
- package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
- package/dist/render/backend/webgpu/motionPass.js +491 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
- package/dist/render/backend/webgpu/pipelineCache.js +11 -1
- package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
- package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
- package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
- package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
- package/dist/render/backend/webgpu/postPass.d.ts +2 -12
- package/dist/render/backend/webgpu/renderer.d.ts +171 -54
- package/dist/render/backend/webgpu/renderer.js +944 -390
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
- package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
- package/dist/render/backend/webgpu/snapshotMips.js +52 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
- package/dist/render/glass.d.ts +45 -0
- package/dist/render/glass.js +39 -0
- package/dist/render/glassCasters.d.ts +40 -0
- package/dist/render/glassCasters.js +84 -0
- package/dist/render/glassShadow.d.ts +78 -0
- package/dist/render/glassShadow.js +125 -0
- package/dist/render/globalMediumPass.d.ts +2 -3
- package/dist/render/globalMediumPass.js +2 -8
- package/dist/render/gpudriven/lit.d.ts +12 -0
- package/dist/render/gpudriven/lit.js +23 -0
- package/dist/render/gpudriven/materialTable.d.ts +15 -5
- package/dist/render/gpudriven/materialTable.js +26 -6
- package/dist/render/gpudriven/pipeline.d.ts +1 -1
- package/dist/render/gpudriven/pipeline.js +4 -3
- package/dist/render/gpudriven/sunGlass.d.ts +35 -0
- package/dist/render/gpudriven/sunGlass.js +89 -0
- package/dist/render/lightBudget.d.ts +17 -5
- package/dist/render/lightBudget.js +17 -5
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- package/dist/render/pointShadowArray.d.ts +56 -3
- package/dist/render/pointShadowArray.js +127 -16
- package/dist/render/pointShadowMap.d.ts +23 -1
- package/dist/render/pointShadowMap.js +27 -1
- package/dist/render/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -0
- package/dist/render/recon/lateRouting.d.ts +14 -0
- package/dist/render/recon/lateRouting.js +18 -0
- package/dist/render/recon/mover.d.ts +57 -0
- package/dist/render/recon/mover.js +86 -0
- package/dist/render/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +7 -1
- package/dist/render/sceneTarget.js +58 -12
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +85 -9
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- package/dist/render/shaders/flat/glassTint.d.ts +6 -0
- package/dist/render/shaders/flat/glassTint.js +25 -0
- package/dist/render/shaders/flat/index.d.ts +13 -0
- package/dist/render/shaders/flat/index.js +4 -0
- package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
- package/dist/render/shaders/flat/lampGlass.js +183 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +115 -15
- package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -0
- package/dist/render/shaders/flat/preamble.js +21 -6
- package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
- package/dist/render/shaders/generated/depth.wgsl.js +227 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
- package/dist/render/shaders/generated/flat.wgsl.js +376 -328
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
- package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
- package/dist/render/shaders/globalMedium.js +6 -6
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
- package/dist/render/shaders/lightVolume.js +6 -6
- package/dist/render/shaders/octahedralResolve.d.ts +8 -0
- package/dist/render/shaders/octahedralResolve.js +48 -17
- package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
- package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +84 -0
- package/dist/render/shadowCasters.d.ts +20 -1
- package/dist/render/shadowCasters.js +15 -1
- package/dist/render/shadowMap.d.ts +54 -5
- package/dist/render/shadowMap.js +190 -36
- package/dist/render/sunGlassTint.d.ts +25 -0
- package/dist/render/sunGlassTint.js +79 -0
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/package.json +6 -6
- package/src/index.ts +10 -0
- package/src/render/backend/api.ts +15 -9
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/renderer.ts +401 -108
- package/src/render/backend/webgpu/buffers.ts +50 -2
- package/src/render/backend/webgpu/depthPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +22 -1
- package/src/render/backend/webgpu/glassTintPass.ts +244 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanced.ts +21 -1
- package/src/render/backend/webgpu/latePass.ts +152 -0
- package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
- package/src/render/backend/webgpu/motionPass.ts +535 -0
- package/src/render/backend/webgpu/pipelineCache.ts +12 -1
- package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
- package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
- package/src/render/backend/webgpu/renderer.ts +1108 -421
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
- package/src/render/backend/webgpu/snapshotMips.ts +80 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
- package/src/render/glass.ts +74 -0
- package/src/render/glassCasters.ts +164 -0
- package/src/render/glassShadow.ts +153 -0
- package/src/render/globalMediumPass.ts +4 -11
- package/src/render/gpudriven/lit.ts +31 -0
- package/src/render/gpudriven/materialTable.ts +28 -6
- package/src/render/gpudriven/pipeline.ts +4 -3
- package/src/render/gpudriven/sunGlass.ts +117 -0
- package/src/render/lightBudget.ts +19 -5
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +12 -0
- package/src/render/sceneTarget.ts +58 -11
- package/src/render/shaders/depth.ts +109 -9
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +17 -0
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +115 -15
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +21 -6
- package/src/render/shaders/generated/depth.wgsl.ts +234 -4
- package/src/render/shaders/generated/flat.wgsl.ts +376 -328
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- package/src/render/shaders/globalMedium.ts +6 -6
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
- package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
- package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
- package/src/render/shaders/lightVolume.ts +6 -6
- package/src/render/shaders/octahedralResolve.ts +50 -17
- package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/translucentQueue.ts +20 -1
|
@@ -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(
|
|
131
|
-
directionalVisibility(p.z, coarseCompare, textureLod(
|
|
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(
|
|
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(
|
|
195
|
-
float dynamicDepth = textureLod(
|
|
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(
|
|
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
|
+
`;
|
|
@@ -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
|
-
|
|
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 +
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
1816
|
+
if (uSeeThrough.x > 0.0 || glassTransmission > 0.0) {
|
|
1742
1817
|
vec3 refractN = normalize(vNormal);
|
|
1743
1818
|
vec3 refractV = normalize(uCameraPos - vWorldPos);
|
|
1744
|
-
|
|
1745
|
-
vec2
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
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;
|