@vitreajs/vitrea 0.2.0 → 0.4.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/dist/{dist-RQ4ZMA3D.js → dist-RGWZAZ6J.js} +1325 -261
- package/dist/dist-RGWZAZ6J.js.map +1 -0
- package/dist/index.d.ts +815 -189
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/dist-RQ4ZMA3D.js.map +0 -1
|
@@ -17,6 +17,7 @@ var WGSL_ANALYSIS_PASS = `struct AnalysisUniforms {
|
|
|
17
17
|
@group(0) @binding(3) var<storage, read_write> stats : array<f32>;
|
|
18
18
|
|
|
19
19
|
var<workgroup> partialLum : array<f32, ${ANALYSIS_WORKGROUP}>;
|
|
20
|
+
var<workgroup> partialTone : array<f32, ${ANALYSIS_WORKGROUP}>;
|
|
20
21
|
var<workgroup> partialSq : array<f32, ${ANALYSIS_WORKGROUP}>;
|
|
21
22
|
var<workgroup> partialEdge : array<f32, ${ANALYSIS_WORKGROUP}>;
|
|
22
23
|
|
|
@@ -32,6 +33,7 @@ fn lum_at(uv : vec2f) -> f32 {
|
|
|
32
33
|
fn cs_analysis(@builtin(local_invocation_index) lane : u32) {
|
|
33
34
|
let total = u32(au.grid.x * au.grid.y);
|
|
34
35
|
var sumLum = 0.0;
|
|
36
|
+
var sumTone = 0.0;
|
|
35
37
|
var sumSq = 0.0;
|
|
36
38
|
var sumEdge = 0.0;
|
|
37
39
|
var n = 0.0;
|
|
@@ -47,7 +49,15 @@ fn cs_analysis(@builtin(local_invocation_index) lane : u32) {
|
|
|
47
49
|
let dx = lum_at(uv + vec2f(au.texel.x, 0.0)) - lum_at(uv - vec2f(au.texel.x, 0.0));
|
|
48
50
|
let dy = lum_at(uv + vec2f(0.0, au.texel.y)) - lum_at(uv - vec2f(0.0, au.texel.y));
|
|
49
51
|
|
|
52
|
+
// The published luminance accumulates ENCODED \u2014 the W9 model (claims
|
|
53
|
+
// 5.31): the reference's tone responses track the encoded-space mean of a
|
|
54
|
+
// structured backdrop, not its linear mean. The host decodes the mean once
|
|
55
|
+
// on readback, so every consumer downstream still speaks linear light.
|
|
56
|
+
// The linear sum stays, because the variance fold below needs a mean in
|
|
57
|
+
// the same space as its squares; variance and edge density stay linear \u2014
|
|
58
|
+
// nothing tonal reads them.
|
|
50
59
|
sumLum = sumLum + c;
|
|
60
|
+
sumTone = sumTone + linear_to_srgb(vec3f(c, c, c)).x;
|
|
51
61
|
sumSq = sumSq + c * c;
|
|
52
62
|
sumEdge = sumEdge + length(vec2f(dx, dy)) * 0.5;
|
|
53
63
|
n = n + 1.0;
|
|
@@ -56,6 +66,7 @@ fn cs_analysis(@builtin(local_invocation_index) lane : u32) {
|
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
partialLum[lane] = sumLum;
|
|
69
|
+
partialTone[lane] = sumTone;
|
|
59
70
|
partialSq[lane] = sumSq;
|
|
60
71
|
partialEdge[lane] = sumEdge;
|
|
61
72
|
workgroupBarrier();
|
|
@@ -66,6 +77,7 @@ fn cs_analysis(@builtin(local_invocation_index) lane : u32) {
|
|
|
66
77
|
if (stride == 0u) { break; }
|
|
67
78
|
if (lane < stride) {
|
|
68
79
|
partialLum[lane] = partialLum[lane] + partialLum[lane + stride];
|
|
80
|
+
partialTone[lane] = partialTone[lane] + partialTone[lane + stride];
|
|
69
81
|
partialSq[lane] = partialSq[lane] + partialSq[lane + stride];
|
|
70
82
|
partialEdge[lane] = partialEdge[lane] + partialEdge[lane + stride];
|
|
71
83
|
}
|
|
@@ -76,7 +88,8 @@ fn cs_analysis(@builtin(local_invocation_index) lane : u32) {
|
|
|
76
88
|
if (lane == 0u) {
|
|
77
89
|
let count = max(f32(total), 1.0);
|
|
78
90
|
let mean = partialLum[0] / count;
|
|
79
|
-
stats[0]
|
|
91
|
+
// stats[0] is the ENCODED-space mean; the host decodes it once (W9).
|
|
92
|
+
stats[0] = partialTone[0] / count;
|
|
80
93
|
stats[1] = max(partialSq[0] / count - mean * mean, 0.0);
|
|
81
94
|
stats[2] = partialEdge[0] / count;
|
|
82
95
|
stats[3] = count;
|
|
@@ -233,7 +246,12 @@ var DEFAULT_MATERIAL_PROFILE = {
|
|
|
233
246
|
* the same 44 px span, which is the lerp-versus-multiply question C9a
|
|
234
247
|
* recorded and nobody has acted on. Claims §5.13.
|
|
235
248
|
*/
|
|
236
|
-
|
|
249
|
+
// 1.25 since W11c G1 (claims §5.41): the reference's interior over
|
|
250
|
+
// structured content is a sharp component near σ 1.25 plus a heavy one
|
|
251
|
+
// the scatter facet now supplies (`sizeScatterFloor`); the cascade's 3
|
|
252
|
+
// (claims §5.16) was the one Gaussian that best split the difference on
|
|
253
|
+
// a bed with no pitch axis to tell the two apart.
|
|
254
|
+
blurSigma: 1.25,
|
|
237
255
|
tint: srgbToLinear(SRGB_WHITE_TINT),
|
|
238
256
|
tintAlpha: 0.46,
|
|
239
257
|
rimWidth: 1.5,
|
|
@@ -330,6 +348,8 @@ var DEFAULT_MATERIAL_PROFILE = {
|
|
|
330
348
|
*/
|
|
331
349
|
sizeSpanMin: 32,
|
|
332
350
|
sizeSpanMax: 96,
|
|
351
|
+
// The inner shadow's depth gain since W12 G2 (the lens reads its own law
|
|
352
|
+
// below); the value is W2's, unchanged.
|
|
333
353
|
lensSizeGainMax: 2.6,
|
|
334
354
|
/*
|
|
335
355
|
* MEASURED (W2), each with its own status — see the fields' notes.
|
|
@@ -405,41 +425,93 @@ var DEFAULT_MATERIAL_PROFILE = {
|
|
|
405
425
|
* on. The grid 1.0/1.4/2.2 is flat to 0.2%. Carrying a fitted-looking 1.4 whose
|
|
406
426
|
* measurement no longer exists would be the worse of the two errors.
|
|
407
427
|
*/
|
|
408
|
-
|
|
428
|
+
// MEASURED (W11c G1, claims §5.41): the scatter facet off the identity, on
|
|
429
|
+
// its own curve — see `MaterialProfile.sizeScatterFloor`. The paragraph
|
|
430
|
+
// above records why the identity was the right answer while `blurSigma`
|
|
431
|
+
// was 3 and the bed had no pitch axis; the W9 probe's pitch axis is what
|
|
432
|
+
// identified the two-component interior this expresses.
|
|
433
|
+
sizeScatterGainMax: 8,
|
|
434
|
+
sizeScatterFloor: 0.4,
|
|
435
|
+
sizeScatterSpanMax: 256,
|
|
436
|
+
// The 1x three FITTED in the renderer (W13 G1's third sweep, 44 points over
|
|
437
|
+
// the calibration bed: `results/2026-09-03-w13-ramp/g1/sweep-3/g1-sweep-3.md`
|
|
438
|
+
// §3); the 2x three PROVISIONAL still, because at them the excursion is
|
|
439
|
+
// bit-exactly zero on the whole bed and a sweep cannot fit what does not
|
|
440
|
+
// move (§4). The thin anchor 0.72 is above G0's read-off of 0.637–0.642: the
|
|
441
|
+
// grid runs 0.60 / 0.64 / 0.68 and the refinement 0.72 / 0.76, and 0.72 is an
|
|
442
|
+
// interior optimum of both the interior objective and the interior gap. The
|
|
443
|
+
// thick anchor 0.52 is G0's `rrect-md` reading and is above that cell's deep
|
|
444
|
+
// sharp share of 0.481, where the joint thick fit of 0.47 would have clamped
|
|
445
|
+
// it to nothing. The 2x pair is G0's 2x readings, every one of which is BELOW
|
|
446
|
+
// its cell's deep value, so at dpr 2 the excursion is zero on this whole bed
|
|
447
|
+
// — the law evaluating to no excursion, and the 2x gap located in the deep
|
|
448
|
+
// value instead (claims §5.64 §4). See
|
|
449
|
+
// `MaterialProfile.sizeScatterRampStartThin1x`.
|
|
450
|
+
sizeScatterRampStartThin1x: 0.72,
|
|
451
|
+
sizeScatterRampStartThick1x: 0.52,
|
|
452
|
+
sizeScatterRampStartFar1x: 0.2,
|
|
453
|
+
sizeScatterRampStartThin2x: 0.46,
|
|
454
|
+
sizeScatterRampStartThick2x: 0.17,
|
|
455
|
+
sizeScatterRampStartFar2x: 0.15,
|
|
456
|
+
sizeScatterRampReach1xPx: 80,
|
|
457
|
+
sizeScatterRampReach2xPx: 100,
|
|
409
458
|
sizeOcclusionGain: 0.05,
|
|
410
459
|
sizeShadowGainMax: 1,
|
|
411
|
-
|
|
412
|
-
|
|
460
|
+
/*
|
|
461
|
+
* The lens (W12 G2, claims §5.51; Decision Log 3 of the W12 spec).
|
|
462
|
+
*
|
|
463
|
+
* MEASURED from the reference's own layer tree (§5.50): the inner refraction
|
|
464
|
+
* amount and height laws, verified on spans 32 / 44 / 48 … 112 / 128 / 160
|
|
465
|
+
* and the nested base at 130, and the ovalization's knee (0 through span 64,
|
|
466
|
+
* 0.5 from 72). FITTED on the pixels (§5.51, a 2-D band renderer validated on
|
|
467
|
+
* this renderer's own capture): the gain, the extent and the exponent, on
|
|
468
|
+
* `rrect-md` + `-ml` at 1x pitches 16 and 32 with `rrect-lg` held out at both
|
|
469
|
+
* scales; the ovalization's effective value on the box-inscribed ellipse.
|
|
470
|
+
* Predicted before the landing capture: checkerboard SSIM 1x md / ml / lg
|
|
471
|
+
* 0.954 / 0.931 / 0.929 → 0.970 / 0.946 / 0.942, 2x 0.939 / 0.902 / 0.901 →
|
|
472
|
+
* 0.950 / 0.918 / 0.918, the capsule 0.977 → 0.985.
|
|
473
|
+
*
|
|
474
|
+
* What W11c G2 recorded here — 1.6 lens depths on the square profile, fitted
|
|
475
|
+
* per depth shell — was the band's mean, not its shape (§5.48, §5.49); it is
|
|
476
|
+
* kept in git history and in claims §5.43.
|
|
477
|
+
*/
|
|
478
|
+
lensRefractionGain: 0.745,
|
|
479
|
+
lensHeightPerSpan: 0.25,
|
|
480
|
+
lensHeightMax: 20,
|
|
481
|
+
lensAmountPerSpan: 0.8,
|
|
482
|
+
lensAmountMax: 60,
|
|
483
|
+
lensThicknessReference: 8,
|
|
484
|
+
lensExtentGain: 1.337,
|
|
485
|
+
lensProfileExponent: 3.69,
|
|
486
|
+
// 0.8 by eye (W12 Decision Log 6). The pixels preferred 0.6 by a hair — every
|
|
487
|
+
// texture row that can see ω scores 0.001–0.002 lower at 0.8 (claims §5.54) —
|
|
488
|
+
// but the field's measured tilt sits at 0.8–1.0 (§5.49 §3) and the user read
|
|
489
|
+
// the 0.8 sheet as much closer to macOS; the eye overrides a margin that small,
|
|
490
|
+
// and §5.54 §1's rows are the recorded cost.
|
|
491
|
+
lensOvalization: 0.8,
|
|
492
|
+
lensOvalizationSpanMin: 64,
|
|
493
|
+
lensOvalizationSpanMax: 72,
|
|
413
494
|
reducedTransparencyFrost: 1.75,
|
|
414
495
|
increasedOcclusionLift: INCREASED_OCCLUSION_LIFT,
|
|
415
496
|
strongBorderRim: { rimWidth: 2, rimAlpha: 0.95 },
|
|
416
497
|
reducedTintAdaptation: 0.35,
|
|
417
|
-
// ADVISORY (W3). The span 0.02 … 0.65 covers most of the canonical backdrop
|
|
418
|
-
// range (0.003 … 0.891 linear), and the two ends are deliberately symmetric —
|
|
419
|
-
// 0.45 of the seed's brightness at the dark end, 0.45 of the way to white at
|
|
420
|
-
// the bright end — so the seed itself sits mid-range and the excursion reads
|
|
421
|
-
// as glass rather than as two different colours.
|
|
422
498
|
/*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
499
|
+
* MEASURED (W10, 2026-09-02) — per-pixel least squares of the reference's
|
|
500
|
+
* tinted pixel against its own untinted pixel on the five W9-probe tinted
|
|
501
|
+
* checkerboard cells (pitch 4…64 px, 17 700 px): shade = 0.5289 + 0.4886·u,
|
|
502
|
+
* RMS 0.0035. Out of sample on the canonical bed the orange cells fit at
|
|
503
|
+
* RMS 0.003 with zero bias; blue sits 0.011 darker (the second hue's
|
|
504
|
+
* residual, recorded and unmodelled). The light end extrapolates past 1 and
|
|
505
|
+
* is clamped in `tintShade`. Claims §5.36.
|
|
429
506
|
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
* its tinted interior is the declared seed EXACTLY in linear light, at a
|
|
435
|
-
* per-channel standard deviation of 0.000. Apple's tint is the author's
|
|
436
|
-
* colour, not a range of tones mapped to it; S219's wording described the
|
|
437
|
-
* inactive material's residue. Claims §5.13.
|
|
507
|
+
* §5.13's earlier "the curve is the identity" fit was made on the material's
|
|
508
|
+
* MEAN over solid backdrops, where u is either ~0.97 (shade 1.0) or the
|
|
509
|
+
* collapse has already folded the shade out — the identity was the law's
|
|
510
|
+
* two endpoints, seen without anything between them.
|
|
438
511
|
*/
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
tintToneHigh: 0.65,
|
|
512
|
+
tintShadeDark: 0.5289,
|
|
513
|
+
tintShadeLight: 1.0175,
|
|
514
|
+
tintShadeStrength: 1,
|
|
443
515
|
/*
|
|
444
516
|
* MEASURED (W7), against the settled apple-macos-26.5 bed, on the light and
|
|
445
517
|
* dark standard profiles jointly and at both scales.
|
|
@@ -472,9 +544,21 @@ var DEFAULT_MATERIAL_PROFILE = {
|
|
|
472
544
|
* is a VALIDATION cell and was not fitted to: the calibration set predicts
|
|
473
545
|
* 0.34 there and the cell reads 0.356.
|
|
474
546
|
*/
|
|
547
|
+
/*
|
|
548
|
+
* RE-SCOPED (W9, claims §5.33). The four mix constants now own TEXTURE
|
|
549
|
+
* COLLAPSE alone — the interior mean moved to the response law below — and
|
|
550
|
+
* their band contracts to the one domain the collapse is real in: full at
|
|
551
|
+
* `dark-solid` (0.0117) for thin surfaces including the 44 px capsule
|
|
552
|
+
* (byte-identical collapse, arg 0.0164 ≤ low with margin), zero by
|
|
553
|
+
* `mid-dark-solid` (0.0595), where the reference's small surface keeps a
|
|
554
|
+
* textured body at 0.4561 and the old band's partial collapse (k = 0.81)
|
|
555
|
+
* was the measured 0.1375-vs-0.4561 overshoot. `impulse__rrect-md`
|
|
556
|
+
* (arg 0.0539) keeps k ≈ 0.003 against the old band's 0.008 — the same
|
|
557
|
+
* unadapted render that cell validated.
|
|
558
|
+
*/
|
|
475
559
|
backdropToneMax: 1,
|
|
476
|
-
backdropToneLow: 0.
|
|
477
|
-
backdropToneHigh: 0.
|
|
560
|
+
backdropToneLow: 0.02,
|
|
561
|
+
backdropToneHigh: 0.055,
|
|
478
562
|
/*
|
|
479
563
|
* REFITTED 0.09 → 0.13 (2026-08-31, active bed). The law's SHAPE is untouched
|
|
480
564
|
* (Decision Log 13 stands; the two-axis rework is next wave) and `max`, `low`
|
|
@@ -493,8 +577,26 @@ var DEFAULT_MATERIAL_PROFILE = {
|
|
|
493
577
|
* estimator's algebra assumes the two colour schemes share one tint alpha,
|
|
494
578
|
* and this profile pair does not (0.46 against 0.97) — recorded in §5.13 as
|
|
495
579
|
* the reason it is no longer the primary evidence for this constant.
|
|
580
|
+
*
|
|
581
|
+
* RE-SCOPED 0.13 → 0.05 with the band above (W9): the bias's one remaining
|
|
582
|
+
* job is keeping the thick surface's collapse argument above `high` at the
|
|
583
|
+
* dark anchor (0.0117 + 0.05 = 0.0617 > 0.055) while the thin capsule's
|
|
584
|
+
* stays below `low` — the near-binary size snap the probe measured
|
|
585
|
+
* (claims §5.33). The smooth size trend on structured backdrops, which the
|
|
586
|
+
* old wide band tried and failed to carry, belongs to the response law's
|
|
587
|
+
* thickness axis now.
|
|
496
588
|
*/
|
|
497
|
-
backdropToneSizeBias: 0.
|
|
589
|
+
backdropToneSizeBias: 0.05,
|
|
590
|
+
/*
|
|
591
|
+
* MEASURED (W9 probe, claims §5.30–§5.33): the anchors are the probe bed's
|
|
592
|
+
* settled reference levels, frequency-settled over seven attested runs,
|
|
593
|
+
* under the probe's own native-mask interior. Thick rows pool the 96 px
|
|
594
|
+
* and 160 px rrects (±0.012). Not tuned; re-measured only by a new probe.
|
|
595
|
+
*/
|
|
596
|
+
backdropToneAnchorX: [0.1104, 0.2706, 0.9505],
|
|
597
|
+
backdropToneResponseThin: [0.0126, 0.4561, 0.9713],
|
|
598
|
+
backdropToneResponseThick: [0.4953, 0.5744, 0.9358],
|
|
599
|
+
backdropToneResponseStrength: 1,
|
|
498
600
|
/*
|
|
499
601
|
* FITTED (recalibration cascade, 2026-08-31). W8's geometry SURVIVES the fit
|
|
500
602
|
* unchanged — `sigmaPx` 15.55, `offsetPx` 7.95, `spreadPx` 3.10 — and only the
|
|
@@ -516,14 +618,18 @@ var DEFAULT_MATERIAL_PROFILE = {
|
|
|
516
618
|
* instrument's independent shadow axis agrees, so the geometry stands on those
|
|
517
619
|
* two and this objective is not asked to re-decide it.
|
|
518
620
|
*
|
|
519
|
-
* `reducedTransparencyOcclusion` 0.70
|
|
520
|
-
* 3.83× spread over 0.45…0.95 with a clear minimum. It also
|
|
621
|
+
* `reducedTransparencyOcclusion` 0.70 was sharp where the amplitude was flat: a
|
|
622
|
+
* 3.83× spread over 0.45…0.95 with a clear minimum. It also reconciled two
|
|
521
623
|
* routes — 0.285 × 0.70 = 0.1995 against the reference's directly measured
|
|
522
624
|
* reduce-transparency amplitude of 0.203 at a 44 px span. W8's 0.566 was the
|
|
523
|
-
* ratio of the reference's two amplitudes;
|
|
625
|
+
* ratio of the reference's two amplitudes; that was the ratio that made
|
|
524
626
|
* vitrea's shadow match the reference's under the preference, and the two
|
|
525
|
-
*
|
|
526
|
-
* is much wider in the standard profile than under reduced transparency.
|
|
627
|
+
* differed because the base amplitude is a compromise across scenes whose
|
|
628
|
+
* spread is much wider in the standard profile than under reduced transparency.
|
|
629
|
+
* **W14 G1 re-forms the constant as the LEVEL those routes were reconciling
|
|
630
|
+
* to** — 0.197 absolute, replacing both regimes rather than scaling them — for
|
|
631
|
+
* the reason its doc comment gives: a ratio and a level stopped being the same
|
|
632
|
+
* thing when the single amplitude became six unequal anchors.
|
|
527
633
|
*
|
|
528
634
|
* Extracted from the active bed's native fixtures directly —
|
|
529
635
|
* `results/2026-08-31-active-bed-stage0.json` measured the gap, these numbers
|
|
@@ -562,13 +668,33 @@ var DEFAULT_MATERIAL_PROFILE = {
|
|
|
562
668
|
* 44 px span — the dark material's shadow is nearly invisible) and lands as a
|
|
563
669
|
* profile patch, not as a branch here. Both are stated in the claims doc as the
|
|
564
670
|
* open question the cascade's fit inherits.
|
|
671
|
+
*
|
|
672
|
+
* **W14 G1 answers that open question and replaces `occlusion`.** "Over the
|
|
673
|
+
* flat near-white `light-solid` backdrop the same fit reads 0.123 rather than
|
|
674
|
+
* 0.33 … it is not a function of the backdrop's luminance" was read on a bed
|
|
675
|
+
* that had no backdrop between `hc-text` (linear 0.74) and `light-solid`
|
|
676
|
+
* (0.891): it IS a function of the backdrop's luminance, and the whole factor
|
|
677
|
+
* of 2.6 happens inside the gap the bed cannot see (claims §5.62 §5). The
|
|
678
|
+
* single amplitude becomes six anchors on two regimes — three in backdrop
|
|
679
|
+
* luminance below the knee, three in span above it — plus the lift's four
|
|
680
|
+
* constants. The three lengths are untouched; G0 re-read them free on both
|
|
681
|
+
* terms and they came back at W8's values.
|
|
565
682
|
*/
|
|
566
683
|
outerShadow: {
|
|
567
684
|
offsetPx: 7.95,
|
|
568
685
|
sigmaPx: 15.55,
|
|
569
686
|
spreadPx: 3.1,
|
|
570
|
-
|
|
571
|
-
|
|
687
|
+
thinOcclusionDark: 0,
|
|
688
|
+
thinOcclusionMid: 0.33,
|
|
689
|
+
thinOcclusionBright: 0.127,
|
|
690
|
+
thickOcclusionAt96: 0.37,
|
|
691
|
+
thickOcclusionAt128: 0.448,
|
|
692
|
+
thickOcclusionAt160: 0.479,
|
|
693
|
+
liftAmplitude: 0.01,
|
|
694
|
+
liftSpanMin: 64,
|
|
695
|
+
liftSpanFull: 118,
|
|
696
|
+
liftBlurSigmaCss: 40,
|
|
697
|
+
reducedTransparencyOcclusion: 0.197,
|
|
572
698
|
sizeGain: 0
|
|
573
699
|
},
|
|
574
700
|
lightDirection: [-0.3714, -0.9285],
|
|
@@ -587,16 +713,50 @@ var SIZE_SPAN_MIN = DEFAULT_MATERIAL_PROFILE.sizeSpanMin;
|
|
|
587
713
|
var SIZE_SPAN_MAX = DEFAULT_MATERIAL_PROFILE.sizeSpanMax;
|
|
588
714
|
var LENS_SIZE_GAIN_MAX = DEFAULT_MATERIAL_PROFILE.lensSizeGainMax;
|
|
589
715
|
var SIZE_SCATTER_GAIN_MAX = DEFAULT_MATERIAL_PROFILE.sizeScatterGainMax;
|
|
716
|
+
var SIZE_SCATTER_FLOOR = DEFAULT_MATERIAL_PROFILE.sizeScatterFloor;
|
|
717
|
+
var SIZE_SCATTER_SPAN_MAX = DEFAULT_MATERIAL_PROFILE.sizeScatterSpanMax;
|
|
718
|
+
var SIZE_SCATTER_RAMP_START_THIN_1X = DEFAULT_MATERIAL_PROFILE.sizeScatterRampStartThin1x;
|
|
719
|
+
var SIZE_SCATTER_RAMP_START_THICK_1X = DEFAULT_MATERIAL_PROFILE.sizeScatterRampStartThick1x;
|
|
720
|
+
var SIZE_SCATTER_RAMP_START_THIN_2X = DEFAULT_MATERIAL_PROFILE.sizeScatterRampStartThin2x;
|
|
721
|
+
var SIZE_SCATTER_RAMP_START_THICK_2X = DEFAULT_MATERIAL_PROFILE.sizeScatterRampStartThick2x;
|
|
722
|
+
var SIZE_SCATTER_RAMP_START_FAR_1X = DEFAULT_MATERIAL_PROFILE.sizeScatterRampStartFar1x;
|
|
723
|
+
var SIZE_SCATTER_RAMP_START_FAR_2X = DEFAULT_MATERIAL_PROFILE.sizeScatterRampStartFar2x;
|
|
724
|
+
var SIZE_SCATTER_RAMP_REACH_1X_PX = DEFAULT_MATERIAL_PROFILE.sizeScatterRampReach1xPx;
|
|
725
|
+
var SIZE_SCATTER_RAMP_REACH_2X_PX = DEFAULT_MATERIAL_PROFILE.sizeScatterRampReach2xPx;
|
|
590
726
|
var SIZE_OCCLUSION_GAIN = DEFAULT_MATERIAL_PROFILE.sizeOcclusionGain;
|
|
591
727
|
var SIZE_SHADOW_GAIN_MAX = DEFAULT_MATERIAL_PROFILE.sizeShadowGainMax;
|
|
592
|
-
var
|
|
593
|
-
var
|
|
728
|
+
var LENS_REFRACTION_GAIN = DEFAULT_MATERIAL_PROFILE.lensRefractionGain;
|
|
729
|
+
var LENS_HEIGHT_PER_SPAN = DEFAULT_MATERIAL_PROFILE.lensHeightPerSpan;
|
|
730
|
+
var LENS_HEIGHT_MAX = DEFAULT_MATERIAL_PROFILE.lensHeightMax;
|
|
731
|
+
var LENS_AMOUNT_PER_SPAN = DEFAULT_MATERIAL_PROFILE.lensAmountPerSpan;
|
|
732
|
+
var LENS_AMOUNT_MAX = DEFAULT_MATERIAL_PROFILE.lensAmountMax;
|
|
733
|
+
var LENS_THICKNESS_REFERENCE = DEFAULT_MATERIAL_PROFILE.lensThicknessReference;
|
|
734
|
+
var LENS_EXTENT_GAIN = DEFAULT_MATERIAL_PROFILE.lensExtentGain;
|
|
735
|
+
var LENS_PROFILE_EXPONENT = DEFAULT_MATERIAL_PROFILE.lensProfileExponent;
|
|
736
|
+
var LENS_OVALIZATION = DEFAULT_MATERIAL_PROFILE.lensOvalization;
|
|
594
737
|
var BACKDROP_TONE_MAX = DEFAULT_MATERIAL_PROFILE.backdropToneMax;
|
|
595
738
|
var BACKDROP_TONE_LOW = DEFAULT_MATERIAL_PROFILE.backdropToneLow;
|
|
596
739
|
var BACKDROP_TONE_HIGH = DEFAULT_MATERIAL_PROFILE.backdropToneHigh;
|
|
597
740
|
var BACKDROP_TONE_SIZE_BIAS = DEFAULT_MATERIAL_PROFILE.backdropToneSizeBias;
|
|
741
|
+
DEFAULT_MATERIAL_PROFILE.backdropToneAnchorX;
|
|
742
|
+
DEFAULT_MATERIAL_PROFILE.backdropToneResponseThin;
|
|
743
|
+
DEFAULT_MATERIAL_PROFILE.backdropToneResponseThick;
|
|
744
|
+
DEFAULT_MATERIAL_PROFILE.backdropToneResponseStrength;
|
|
598
745
|
var OUTER_SHADOW = DEFAULT_MATERIAL_PROFILE.outerShadow;
|
|
746
|
+
var RETIRED_OUTER_SHADOW_LEAVES = {
|
|
747
|
+
occlusion: "the six amplitude anchors (thinOcclusionDark, thinOcclusionMid, thinOcclusionBright, thickOcclusionAt96, thickOcclusionAt128, thickOcclusionAt160) and liftAmplitude for the second term"
|
|
748
|
+
};
|
|
749
|
+
function rejectRetiredOuterShadowLeaves(patch) {
|
|
750
|
+
if (patch === void 0)
|
|
751
|
+
return;
|
|
752
|
+
for (const [leaf, replacement] of Object.entries(RETIRED_OUTER_SHADOW_LEAVES)) {
|
|
753
|
+
if (!(leaf in patch))
|
|
754
|
+
continue;
|
|
755
|
+
throw new Error(`outerShadow.${leaf} was retired by W14 G1 (claims \xA75.62) and is replaced by ${replacement}. Applying this patch would have rendered the default shadow while recording itself as configured. It is refused rather than mapped: a single span-flat amplitude is the material the measurement retired.`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
599
758
|
function withMaterialOverrides(base, patch) {
|
|
759
|
+
rejectRetiredOuterShadowLeaves(patch.outerShadow);
|
|
600
760
|
const optics = {};
|
|
601
761
|
for (const variant of MATERIAL_VARIANTS) {
|
|
602
762
|
optics[variant] = { ...base.optics[variant], ...patch.optics?.[variant] };
|
|
@@ -616,22 +776,44 @@ function withMaterialOverrides(base, patch) {
|
|
|
616
776
|
sizeSpanMax: patch.sizeSpanMax ?? base.sizeSpanMax,
|
|
617
777
|
lensSizeGainMax: patch.lensSizeGainMax ?? base.lensSizeGainMax,
|
|
618
778
|
sizeScatterGainMax: patch.sizeScatterGainMax ?? base.sizeScatterGainMax,
|
|
779
|
+
sizeScatterFloor: patch.sizeScatterFloor ?? base.sizeScatterFloor,
|
|
780
|
+
sizeScatterSpanMax: patch.sizeScatterSpanMax ?? base.sizeScatterSpanMax,
|
|
781
|
+
sizeScatterRampStartThin1x: patch.sizeScatterRampStartThin1x ?? base.sizeScatterRampStartThin1x,
|
|
782
|
+
sizeScatterRampStartThick1x: patch.sizeScatterRampStartThick1x ?? base.sizeScatterRampStartThick1x,
|
|
783
|
+
sizeScatterRampStartThin2x: patch.sizeScatterRampStartThin2x ?? base.sizeScatterRampStartThin2x,
|
|
784
|
+
sizeScatterRampStartThick2x: patch.sizeScatterRampStartThick2x ?? base.sizeScatterRampStartThick2x,
|
|
785
|
+
sizeScatterRampStartFar1x: patch.sizeScatterRampStartFar1x ?? base.sizeScatterRampStartFar1x,
|
|
786
|
+
sizeScatterRampStartFar2x: patch.sizeScatterRampStartFar2x ?? base.sizeScatterRampStartFar2x,
|
|
787
|
+
sizeScatterRampReach1xPx: patch.sizeScatterRampReach1xPx ?? base.sizeScatterRampReach1xPx,
|
|
788
|
+
sizeScatterRampReach2xPx: patch.sizeScatterRampReach2xPx ?? base.sizeScatterRampReach2xPx,
|
|
619
789
|
sizeOcclusionGain: patch.sizeOcclusionGain ?? base.sizeOcclusionGain,
|
|
620
790
|
sizeShadowGainMax: patch.sizeShadowGainMax ?? base.sizeShadowGainMax,
|
|
621
|
-
|
|
622
|
-
|
|
791
|
+
lensRefractionGain: patch.lensRefractionGain ?? base.lensRefractionGain,
|
|
792
|
+
lensHeightPerSpan: patch.lensHeightPerSpan ?? base.lensHeightPerSpan,
|
|
793
|
+
lensHeightMax: patch.lensHeightMax ?? base.lensHeightMax,
|
|
794
|
+
lensAmountPerSpan: patch.lensAmountPerSpan ?? base.lensAmountPerSpan,
|
|
795
|
+
lensAmountMax: patch.lensAmountMax ?? base.lensAmountMax,
|
|
796
|
+
lensThicknessReference: patch.lensThicknessReference ?? base.lensThicknessReference,
|
|
797
|
+
lensExtentGain: patch.lensExtentGain ?? base.lensExtentGain,
|
|
798
|
+
lensProfileExponent: patch.lensProfileExponent ?? base.lensProfileExponent,
|
|
799
|
+
lensOvalization: patch.lensOvalization ?? base.lensOvalization,
|
|
800
|
+
lensOvalizationSpanMin: patch.lensOvalizationSpanMin ?? base.lensOvalizationSpanMin,
|
|
801
|
+
lensOvalizationSpanMax: patch.lensOvalizationSpanMax ?? base.lensOvalizationSpanMax,
|
|
623
802
|
reducedTransparencyFrost: patch.reducedTransparencyFrost ?? base.reducedTransparencyFrost,
|
|
624
803
|
increasedOcclusionLift: patch.increasedOcclusionLift ?? base.increasedOcclusionLift,
|
|
625
804
|
strongBorderRim: { ...base.strongBorderRim, ...patch.strongBorderRim },
|
|
626
805
|
reducedTintAdaptation: patch.reducedTintAdaptation ?? base.reducedTintAdaptation,
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
tintToneHigh: patch.tintToneHigh ?? base.tintToneHigh,
|
|
806
|
+
tintShadeDark: patch.tintShadeDark ?? base.tintShadeDark,
|
|
807
|
+
tintShadeLight: patch.tintShadeLight ?? base.tintShadeLight,
|
|
808
|
+
tintShadeStrength: patch.tintShadeStrength ?? base.tintShadeStrength,
|
|
631
809
|
backdropToneMax: patch.backdropToneMax ?? base.backdropToneMax,
|
|
632
810
|
backdropToneLow: patch.backdropToneLow ?? base.backdropToneLow,
|
|
633
811
|
backdropToneHigh: patch.backdropToneHigh ?? base.backdropToneHigh,
|
|
634
812
|
backdropToneSizeBias: patch.backdropToneSizeBias ?? base.backdropToneSizeBias,
|
|
813
|
+
backdropToneAnchorX: patch.backdropToneAnchorX ?? base.backdropToneAnchorX,
|
|
814
|
+
backdropToneResponseThin: patch.backdropToneResponseThin ?? base.backdropToneResponseThin,
|
|
815
|
+
backdropToneResponseThick: patch.backdropToneResponseThick ?? base.backdropToneResponseThick,
|
|
816
|
+
backdropToneResponseStrength: patch.backdropToneResponseStrength ?? base.backdropToneResponseStrength,
|
|
635
817
|
outerShadow: { ...base.outerShadow, ...patch.outerShadow },
|
|
636
818
|
lightDirection: patch.lightDirection ?? base.lightDirection,
|
|
637
819
|
sweepBandRadians: patch.sweepBandRadians ?? base.sweepBandRadians,
|
|
@@ -677,28 +859,29 @@ function tintToneAdaptation(policy, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
|
677
859
|
return 0;
|
|
678
860
|
}
|
|
679
861
|
}
|
|
680
|
-
function
|
|
681
|
-
const
|
|
682
|
-
const
|
|
862
|
+
function tintShade(materialLuminance, grip, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
863
|
+
const u = Math.min(1, Math.max(0, materialLuminance));
|
|
864
|
+
const shade = Math.min(1, Math.max(0, profile.tintShadeDark + (profile.tintShadeLight - profile.tintShadeDark) * u));
|
|
865
|
+
const k = Math.min(1, Math.max(0, grip));
|
|
866
|
+
return 1 + (shade - 1) * k;
|
|
867
|
+
}
|
|
868
|
+
function tintShadeLayer(seed, materialLuminance, grip, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
869
|
+
const shade = tintShade(materialLuminance, grip, profile);
|
|
870
|
+
return [seed[0] * shade, seed[1] * shade, seed[2] * shade];
|
|
871
|
+
}
|
|
872
|
+
function tintedMaterialColour(material, tint, grip, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
873
|
+
if (tint === void 0 || tint.strength <= 0)
|
|
874
|
+
return material;
|
|
875
|
+
const s = Math.min(1, Math.max(0, tint.strength));
|
|
876
|
+
const layer = tintShadeLayer(tint.color, relativeLuminance(material), grip, profile);
|
|
877
|
+
const clamp01 = (value) => Math.min(1, Math.max(0, value));
|
|
683
878
|
const channel = (index) => {
|
|
684
|
-
const
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
return s + (low + (high - low) * t - s) * k;
|
|
879
|
+
const from = linearToSrgbChannel(clamp01(material[index]));
|
|
880
|
+
const to = linearToSrgbChannel(clamp01(layer[index]));
|
|
881
|
+
return srgbToLinearChannel(from + (to - from) * s);
|
|
688
882
|
};
|
|
689
883
|
return [channel(0), channel(1), channel(2)];
|
|
690
884
|
}
|
|
691
|
-
function tintedTintColour(neutral, tint, backdropLuminance, toneAdaptation, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
692
|
-
if (tint === void 0)
|
|
693
|
-
return neutral;
|
|
694
|
-
const tone = tintTone(tint.color, backdropLuminance, toneAdaptation, profile);
|
|
695
|
-
const k = Math.min(1, Math.max(0, tint.strength));
|
|
696
|
-
return [
|
|
697
|
-
neutral[0] + (tone[0] - neutral[0]) * k,
|
|
698
|
-
neutral[1] + (tone[1] - neutral[1]) * k,
|
|
699
|
-
neutral[2] + (tone[2] - neutral[2]) * k
|
|
700
|
-
];
|
|
701
|
-
}
|
|
702
885
|
function backdropToneAdaptation(backdropLuminance, thickness, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
703
886
|
const x = backdropLuminance + profile.backdropToneSizeBias * Math.min(1, Math.max(0, thickness));
|
|
704
887
|
const span = Math.max(profile.backdropToneHigh - profile.backdropToneLow, 1e-6);
|
|
@@ -748,11 +931,102 @@ function lensSizeGain(spanPx, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
|
748
931
|
function lensSizeGainFromThickness(thickness, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
749
932
|
return 1 + (profile.lensSizeGainMax - 1) * thickness;
|
|
750
933
|
}
|
|
751
|
-
function
|
|
752
|
-
return
|
|
934
|
+
function shadowDepthPx(thicknessPx, spanPx, thickness, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
935
|
+
return Math.min(Math.max(thicknessPx, 0) * lensSizeGainFromThickness(thickness, profile), spanPx * 0.5);
|
|
753
936
|
}
|
|
754
|
-
function
|
|
755
|
-
return
|
|
937
|
+
function lensHeightBasePx(spanPx, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
938
|
+
return Math.min(profile.lensHeightPerSpan * Math.max(spanPx, 0), profile.lensHeightMax);
|
|
939
|
+
}
|
|
940
|
+
function lensAmountBasePx(spanPx, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
941
|
+
return Math.min(profile.lensAmountPerSpan * Math.max(spanPx, 0), profile.lensAmountMax);
|
|
942
|
+
}
|
|
943
|
+
function lensOvalizationAt(spanPx, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
944
|
+
return profile.lensOvalization * smoothstep(profile.lensOvalizationSpanMin, profile.lensOvalizationSpanMax, spanPx);
|
|
945
|
+
}
|
|
946
|
+
function lensMagnitudePx(thicknessPx, spanPx, profile = DEFAULT_MATERIAL_PROFILE, fold = 1) {
|
|
947
|
+
const thick = Math.max(thicknessPx, 0);
|
|
948
|
+
if (thick <= 0)
|
|
949
|
+
return 0;
|
|
950
|
+
const scale = thick / profile.lensThicknessReference;
|
|
951
|
+
const unclamped = thick + (lensHeightBasePx(spanPx, profile) * scale - thick) * fold;
|
|
952
|
+
const depth = lensDepthPx(thicknessPx, spanPx, profile, fold);
|
|
953
|
+
const amount = thick + (lensAmountBasePx(spanPx, profile) * scale - thick) * fold;
|
|
954
|
+
return profile.lensRefractionGain * amount * (unclamped > 0 ? depth / unclamped : 0);
|
|
955
|
+
}
|
|
956
|
+
function lensExtentPx(thicknessPx, spanPx, profile = DEFAULT_MATERIAL_PROFILE, fold = 1) {
|
|
957
|
+
return profile.lensExtentGain * lensDepthPx(thicknessPx, spanPx, profile, fold);
|
|
958
|
+
}
|
|
959
|
+
function lensDirection(normal, offset, half, ovalization) {
|
|
960
|
+
const a = Math.max(half[0], 1e-6);
|
|
961
|
+
const b = Math.max(half[1], 1e-6);
|
|
962
|
+
const ex = offset[0] / (a * a);
|
|
963
|
+
const ey = offset[1] / (b * b);
|
|
964
|
+
const r = Math.max(Math.hypot(offset[0] / a, offset[1] / b), 1e-6);
|
|
965
|
+
const scale = Math.min(a, b) / r;
|
|
966
|
+
const gx = (1 - ovalization) * normal[0] + ovalization * ex * scale;
|
|
967
|
+
const gy = (1 - ovalization) * normal[1] + ovalization * ey * scale;
|
|
968
|
+
const len = Math.hypot(gx, gy);
|
|
969
|
+
return len > 1e-9 ? [gx / len, gy / len] : [normal[0], normal[1]];
|
|
970
|
+
}
|
|
971
|
+
function sizeScatterSigma(sigmaPx, spanPx, profile = DEFAULT_MATERIAL_PROFILE, devicePixelRatio = 1, extentsCssPx) {
|
|
972
|
+
return sizeScatterSigmaAt(sigmaPx, scatterThickness(spanPx, 1, profile, devicePixelRatio, extentsCssPx), profile);
|
|
973
|
+
}
|
|
974
|
+
function scatterRampStart(devicePixelRatio, profile = DEFAULT_MATERIAL_PROFILE, spanPx = 0) {
|
|
975
|
+
const thin = rampAtScale(profile.sizeScatterRampStartThin1x, profile.sizeScatterRampStartThin2x, devicePixelRatio);
|
|
976
|
+
const thick = rampAtScale(profile.sizeScatterRampStartThick1x, profile.sizeScatterRampStartThick2x, devicePixelRatio);
|
|
977
|
+
const far = rampAtScale(profile.sizeScatterRampStartFar1x, profile.sizeScatterRampStartFar2x, devicePixelRatio);
|
|
978
|
+
const decline = smoothstep(profile.sizeSpanMax, profile.sizeScatterSpanMax, spanPx);
|
|
979
|
+
return thin + (thick - thin) * sizeThickness(spanPx, profile) + (far - thick) * decline;
|
|
980
|
+
}
|
|
981
|
+
function scatterRampReachDevicePx(devicePixelRatio, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
982
|
+
return rampAtScale(profile.sizeScatterRampReach1xPx, profile.sizeScatterRampReach2xPx, devicePixelRatio);
|
|
983
|
+
}
|
|
984
|
+
function rampAtScale(at1x, at2x, devicePixelRatio) {
|
|
985
|
+
const t = devicePixelRatio - 1;
|
|
986
|
+
if (t <= 0)
|
|
987
|
+
return at1x;
|
|
988
|
+
if (t >= 1)
|
|
989
|
+
return at2x;
|
|
990
|
+
return at1x + (at2x - at1x) * t;
|
|
991
|
+
}
|
|
992
|
+
function scatterDeepThickness(spanPx, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
993
|
+
const floor = clampUnit(profile.sizeScatterFloor);
|
|
994
|
+
return floor + (1 - floor) * smoothstep(profile.sizeSpanMin, profile.sizeScatterSpanMax, spanPx);
|
|
995
|
+
}
|
|
996
|
+
function scatterSharpShare(uDevicePx, devicePixelRatio, profile = DEFAULT_MATERIAL_PROFILE, spanPx = 0) {
|
|
997
|
+
const deepSharp = 1 - scatterDeepThickness(spanPx, profile);
|
|
998
|
+
const start = scatterRampStart(devicePixelRatio, profile, spanPx);
|
|
999
|
+
const reach = Math.max(scatterRampReachDevicePx(devicePixelRatio, profile), 1e-6);
|
|
1000
|
+
const excursion = Math.max(start - deepSharp, 0) * Math.max(1 - Math.max(uDevicePx, 0) / reach, 0);
|
|
1001
|
+
return clampUnit(deepSharp + excursion);
|
|
1002
|
+
}
|
|
1003
|
+
function scatterThickness(spanPx, fold, profile = DEFAULT_MATERIAL_PROFILE, devicePixelRatio = 1, extentsCssPx) {
|
|
1004
|
+
const floor = clampUnit(profile.sizeScatterFloor);
|
|
1005
|
+
const mean = scatterRampAreaMean(spanPx, profile, devicePixelRatio, extentsCssPx);
|
|
1006
|
+
return clampUnit(floor + (mean - floor) * fold);
|
|
1007
|
+
}
|
|
1008
|
+
function scatterRampAreaMean(spanPx, profile = DEFAULT_MATERIAL_PROFILE, devicePixelRatio = 1, extentsCssPx) {
|
|
1009
|
+
const deep = scatterDeepThickness(spanPx, profile);
|
|
1010
|
+
const amplitude = Math.max(scatterRampStart(devicePixelRatio, profile, spanPx) - (1 - deep), 0);
|
|
1011
|
+
if (amplitude <= 0)
|
|
1012
|
+
return clampUnit(deep);
|
|
1013
|
+
const width = Math.max(extentsCssPx?.[0] ?? spanPx, 0);
|
|
1014
|
+
const height = Math.max(extentsCssPx?.[1] ?? spanPx, 0);
|
|
1015
|
+
const area = width * height;
|
|
1016
|
+
if (area <= 0)
|
|
1017
|
+
return clampUnit(deep - amplitude);
|
|
1018
|
+
const reach = Math.max(scatterRampReachDevicePx(devicePixelRatio, profile) / Math.max(devicePixelRatio, 1e-3), 1e-6);
|
|
1019
|
+
const perimeter = 2 * (width + height);
|
|
1020
|
+
const limit = Math.min(reach, Math.min(width, height) / 2);
|
|
1021
|
+
const triangleMean = (perimeter * limit - 4 * limit * limit - perimeter * limit * limit / (2 * reach) + 8 * limit * limit * limit / (3 * reach)) / area;
|
|
1022
|
+
return clampUnit(deep - amplitude * triangleMean);
|
|
1023
|
+
}
|
|
1024
|
+
function sizeScatterSigmaAt(sigmaPx, scatter, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
1025
|
+
const mix = clampUnit(scatter);
|
|
1026
|
+
return sigmaPx * (1 + (profile.sizeScatterGainMax - 1) * mix);
|
|
1027
|
+
}
|
|
1028
|
+
function clampUnit(value) {
|
|
1029
|
+
return Math.min(1, Math.max(0, value));
|
|
756
1030
|
}
|
|
757
1031
|
function sizeOcclusionAlpha(alpha, spanPx, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
758
1032
|
return sizeOcclusionAlphaAt(alpha, sizeThickness(spanPx, profile), profile);
|
|
@@ -772,15 +1046,79 @@ function sizeOuterShadowOcclusion(occlusion, spanPx, profile = DEFAULT_MATERIAL_
|
|
|
772
1046
|
function sizeOuterShadowOcclusionAt(occlusion, thickness, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
773
1047
|
return Math.min(1, occlusion + profile.outerShadow.sizeGain * thickness * (1 - occlusion));
|
|
774
1048
|
}
|
|
1049
|
+
var OUTER_SHADOW_THIN_L = {
|
|
1050
|
+
/** At and below this backdrop luminance the black term is `thinOcclusionDark`. */
|
|
1051
|
+
inert: 0.02,
|
|
1052
|
+
/** From here the mid plateau holds. */
|
|
1053
|
+
midFrom: 0.06,
|
|
1054
|
+
/** To here — `hc-text`'s own linear luminance. */
|
|
1055
|
+
midTo: 0.74,
|
|
1056
|
+
/** `light-solid`'s linear luminance, where 0.127 was measured; held above. */
|
|
1057
|
+
bright: 0.891
|
|
1058
|
+
};
|
|
1059
|
+
var OUTER_SHADOW_UNMEASURED_BACKDROP_LUMINANCE = 0.3;
|
|
1060
|
+
function outerShadowThinOcclusion(backdropLuminance, shadow) {
|
|
1061
|
+
const l = backdropLuminance ?? OUTER_SHADOW_UNMEASURED_BACKDROP_LUMINANCE;
|
|
1062
|
+
const { inert, midFrom, midTo, bright } = OUTER_SHADOW_THIN_L;
|
|
1063
|
+
if (l <= inert)
|
|
1064
|
+
return shadow.thinOcclusionDark;
|
|
1065
|
+
if (l < midFrom) {
|
|
1066
|
+
const t2 = (l - inert) / (midFrom - inert);
|
|
1067
|
+
const s = t2 * t2 * (3 - 2 * t2);
|
|
1068
|
+
return shadow.thinOcclusionDark + (shadow.thinOcclusionMid - shadow.thinOcclusionDark) * s;
|
|
1069
|
+
}
|
|
1070
|
+
if (l <= midTo)
|
|
1071
|
+
return shadow.thinOcclusionMid;
|
|
1072
|
+
if (l >= bright)
|
|
1073
|
+
return shadow.thinOcclusionBright;
|
|
1074
|
+
const t = (l - midTo) / (bright - midTo);
|
|
1075
|
+
return shadow.thinOcclusionMid + (shadow.thinOcclusionBright - shadow.thinOcclusionMid) * t;
|
|
1076
|
+
}
|
|
1077
|
+
var OUTER_SHADOW_THICK_SPANS = [96, 128, 160];
|
|
1078
|
+
function outerShadowThickOcclusion(spanPx, shadow) {
|
|
1079
|
+
const [s0, s1, s2] = OUTER_SHADOW_THICK_SPANS;
|
|
1080
|
+
const y0 = shadow.thickOcclusionAt96;
|
|
1081
|
+
const y1 = shadow.thickOcclusionAt128;
|
|
1082
|
+
const y2 = shadow.thickOcclusionAt160;
|
|
1083
|
+
if (spanPx <= s0)
|
|
1084
|
+
return y0;
|
|
1085
|
+
if (spanPx >= s2)
|
|
1086
|
+
return y2;
|
|
1087
|
+
if (spanPx <= s1)
|
|
1088
|
+
return y0 + (y1 - y0) * (spanPx - s0) / (s1 - s0);
|
|
1089
|
+
return y1 + (y2 - y1) * (spanPx - s1) / (s2 - s1);
|
|
1090
|
+
}
|
|
1091
|
+
function outerShadowOcclusionAt(shadow, backdropLuminance, spanPx, thickness, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
1092
|
+
const thin = outerShadowThinOcclusion(backdropLuminance, shadow);
|
|
1093
|
+
const thick = outerShadowThickOcclusion(spanPx, shadow);
|
|
1094
|
+
const k = Math.min(1, Math.max(0, thickness));
|
|
1095
|
+
const blend = k * k * (3 - 2 * k);
|
|
1096
|
+
return sizeOuterShadowOcclusionAt(thin + (thick - thin) * blend, thickness, profile);
|
|
1097
|
+
}
|
|
1098
|
+
function outerShadowLiftRise(spanPx, shadow) {
|
|
1099
|
+
return smoothstep(shadow.liftSpanMin, shadow.liftSpanFull, spanPx);
|
|
1100
|
+
}
|
|
775
1101
|
function outerShadowUnderPolicy(policy, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
776
1102
|
const shadow = profile.outerShadow;
|
|
777
1103
|
if (policy.glass === "none" || policy.frost === "none")
|
|
778
|
-
return
|
|
1104
|
+
return flatOuterShadow(shadow, 0);
|
|
779
1105
|
if (policy.frost === "increased") {
|
|
780
|
-
return
|
|
1106
|
+
return flatOuterShadow(shadow, shadow.reducedTransparencyOcclusion);
|
|
781
1107
|
}
|
|
782
1108
|
return shadow;
|
|
783
1109
|
}
|
|
1110
|
+
function flatOuterShadow(shadow, amplitude) {
|
|
1111
|
+
return {
|
|
1112
|
+
...shadow,
|
|
1113
|
+
thinOcclusionDark: amplitude,
|
|
1114
|
+
thinOcclusionMid: amplitude,
|
|
1115
|
+
thinOcclusionBright: amplitude,
|
|
1116
|
+
thickOcclusionAt96: amplitude,
|
|
1117
|
+
thickOcclusionAt128: amplitude,
|
|
1118
|
+
thickOcclusionAt160: amplitude,
|
|
1119
|
+
liftAmplitude: 0
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
784
1122
|
var SRGB_ENCODING_EXPONENT = 2.4;
|
|
785
1123
|
function outerShadowFalloff(signedDistancePx, sigmaPx) {
|
|
786
1124
|
const x = -signedDistancePx / Math.max(sigmaPx, 1e-4);
|
|
@@ -790,8 +1128,8 @@ function outerShadowAlpha(occlusion) {
|
|
|
790
1128
|
const occ = Math.min(1, Math.max(0, occlusion));
|
|
791
1129
|
return 1 - Math.pow(1 - occ, 1 / SRGB_ENCODING_EXPONENT);
|
|
792
1130
|
}
|
|
793
|
-
function outerShadowReachPx(shadow) {
|
|
794
|
-
const alpha = outerShadowAlpha(
|
|
1131
|
+
function outerShadowReachPx(shadow, occlusion) {
|
|
1132
|
+
const alpha = outerShadowAlpha(occlusion);
|
|
795
1133
|
if (!(alpha > 0))
|
|
796
1134
|
return 0;
|
|
797
1135
|
const cutoff = 1 / 255 / alpha;
|
|
@@ -809,12 +1147,18 @@ function outerShadowReachPx(shadow) {
|
|
|
809
1147
|
}
|
|
810
1148
|
return Math.max(0, hi + Math.abs(shadow.offsetPx) + shadow.spreadPx);
|
|
811
1149
|
}
|
|
812
|
-
function lensDepthPx(thicknessPx, spanPx, profile = DEFAULT_MATERIAL_PROFILE) {
|
|
813
|
-
const
|
|
814
|
-
|
|
1150
|
+
function lensDepthPx(thicknessPx, spanPx, profile = DEFAULT_MATERIAL_PROFILE, fold = 1) {
|
|
1151
|
+
const thick = Math.max(thicknessPx, 0);
|
|
1152
|
+
const scale = thick / profile.lensThicknessReference;
|
|
1153
|
+
const depth = thick + (lensHeightBasePx(spanPx, profile) * scale - thick) * fold;
|
|
1154
|
+
return Math.min(Math.max(depth, 0), spanPx * 0.5);
|
|
815
1155
|
}
|
|
816
|
-
function
|
|
817
|
-
|
|
1156
|
+
function lensDisplacementPx(depthPx, spanPx, thicknessPx, refractionScale, profile = DEFAULT_MATERIAL_PROFILE, fold = 1) {
|
|
1157
|
+
const extent = lensExtentPx(thicknessPx, spanPx, profile, fold);
|
|
1158
|
+
if (extent <= 0)
|
|
1159
|
+
return 0;
|
|
1160
|
+
const t = Math.max(0, 1 - Math.max(depthPx, 0) / extent);
|
|
1161
|
+
return lensMagnitudePx(thicknessPx, spanPx, profile, fold) * Math.pow(t, profile.lensProfileExponent) * refractionScale;
|
|
818
1162
|
}
|
|
819
1163
|
|
|
820
1164
|
// ../renderer-webgpu/dist/analysis.js
|
|
@@ -826,7 +1170,11 @@ var ZERO_STATS = {
|
|
|
826
1170
|
};
|
|
827
1171
|
function statsFromBuffer(values) {
|
|
828
1172
|
return {
|
|
829
|
-
|
|
1173
|
+
// The reduction publishes the ENCODED-space mean (the W9 model, claims
|
|
1174
|
+
// §5.31) and this is its one decode: every consumer downstream — the
|
|
1175
|
+
// drivers, `adaptiveTint`'s fitted thresholds — still speaks linear light,
|
|
1176
|
+
// and a solid backdrop reads identically under either convention.
|
|
1177
|
+
luminance: srgbToLinearChannel(values[0] ?? 0),
|
|
830
1178
|
variance: values[1] ?? 0,
|
|
831
1179
|
edgeDensity: values[2] ?? 0,
|
|
832
1180
|
sampleCount: values[3] ?? 0
|
|
@@ -1300,6 +1648,44 @@ function createAppTextureProvider(options) {
|
|
|
1300
1648
|
};
|
|
1301
1649
|
}
|
|
1302
1650
|
|
|
1651
|
+
// ../renderer-webgpu/dist/backdrop-fit.js
|
|
1652
|
+
var IDENTITY_FIT = [1, 1, 0, 0];
|
|
1653
|
+
function isUsablePlacement(placement) {
|
|
1654
|
+
return placement !== void 0 && Number.isFinite(placement.x) && Number.isFinite(placement.y) && placement.width > 0 && placement.height > 0;
|
|
1655
|
+
}
|
|
1656
|
+
function coverFit(sourceWidth, sourceHeight, viewportWidthCss, viewportHeightCss) {
|
|
1657
|
+
const viewportAspect = viewportHeightCss > 0 ? viewportWidthCss / viewportHeightCss : 1;
|
|
1658
|
+
const sourceAspect = sourceHeight > 0 ? sourceWidth / sourceHeight : 1;
|
|
1659
|
+
if (sourceAspect > viewportAspect) {
|
|
1660
|
+
const scaleX = viewportAspect / sourceAspect;
|
|
1661
|
+
return [scaleX, 1, (1 - scaleX) / 2, 0];
|
|
1662
|
+
}
|
|
1663
|
+
const scaleY = sourceAspect / viewportAspect;
|
|
1664
|
+
return [1, scaleY, 0, (1 - scaleY) / 2];
|
|
1665
|
+
}
|
|
1666
|
+
function placementFit(placement, viewportWidthCss, viewportHeightCss) {
|
|
1667
|
+
return [
|
|
1668
|
+
viewportWidthCss / placement.width,
|
|
1669
|
+
viewportHeightCss / placement.height,
|
|
1670
|
+
-placement.x / placement.width,
|
|
1671
|
+
-placement.y / placement.height
|
|
1672
|
+
];
|
|
1673
|
+
}
|
|
1674
|
+
function backdropFit(sourceWidth, sourceHeight, placement, viewportWidthCss, viewportHeightCss) {
|
|
1675
|
+
return isUsablePlacement(placement) ? placementFit(placement, viewportWidthCss, viewportHeightCss) : coverFit(sourceWidth, sourceHeight, viewportWidthCss, viewportHeightCss);
|
|
1676
|
+
}
|
|
1677
|
+
function texelsPerCssPx(sourceWidth, sourceHeight, placement, viewportWidthCss, viewportHeightCss) {
|
|
1678
|
+
if (isUsablePlacement(placement)) {
|
|
1679
|
+
return sourceWidth > 0 ? sourceWidth / placement.width : 1;
|
|
1680
|
+
}
|
|
1681
|
+
return viewportWidthCss > 0 && viewportHeightCss > 0 ? Math.max(sourceWidth / viewportWidthCss, sourceHeight / viewportHeightCss) : 1;
|
|
1682
|
+
}
|
|
1683
|
+
function samePlacement(a, b) {
|
|
1684
|
+
if (a === void 0 || b === void 0)
|
|
1685
|
+
return a === b;
|
|
1686
|
+
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1303
1689
|
// ../renderer-webgpu/dist/device.js
|
|
1304
1690
|
function createDeviceHost(options = {}) {
|
|
1305
1691
|
let status = {
|
|
@@ -1610,7 +1996,8 @@ var poolKey = {
|
|
|
1610
1996
|
backdropBodyScratch: (sourceId) => `backdrop:${sourceId}:body-scratch`,
|
|
1611
1997
|
backdropUpload: (sourceId) => `backdrop:${sourceId}:upload`,
|
|
1612
1998
|
groupField: (groupId) => `group:${groupId}:field`,
|
|
1613
|
-
groupAux: (groupId) => `group:${groupId}:aux
|
|
1999
|
+
groupAux: (groupId) => `group:${groupId}:aux`,
|
|
2000
|
+
groupAux2: (groupId) => `group:${groupId}:aux2`
|
|
1614
2001
|
};
|
|
1615
2002
|
|
|
1616
2003
|
// ../renderer-webgpu/dist/gpu-context.js
|
|
@@ -1767,7 +2154,7 @@ function resolveSurfaces(group, family, profile = DEFAULT_MATERIAL_PROFILE, poli
|
|
|
1767
2154
|
centre: [fieldSource.channels.center[0], fieldSource.channels.center[1]],
|
|
1768
2155
|
channels,
|
|
1769
2156
|
spanPx,
|
|
1770
|
-
lensDepthPx:
|
|
2157
|
+
lensDepthPx: lensDepthPx(shape.channels.thickness, spanPx, profile, profile.refractionScale[accessibilityRefractionCap(policy)]),
|
|
1771
2158
|
sizeThickness: thickness,
|
|
1772
2159
|
tintStrength: Math.min(1, Math.max(0, surface.tint?.strength ?? 0))
|
|
1773
2160
|
};
|
|
@@ -1832,9 +2219,9 @@ function packInstances(surfaces, origin, into) {
|
|
|
1832
2219
|
data[o + 11] = s.shape.channels.thickness;
|
|
1833
2220
|
data[o + 12] = s.channels.press;
|
|
1834
2221
|
data[o + 13] = s.channels.glow;
|
|
1835
|
-
data[o + 14] = s.
|
|
2222
|
+
data[o + 14] = Math.max(s.shape.channels.thickness, 0) * Math.min(1, Math.max(0, s.channels.lensStrength));
|
|
1836
2223
|
data[o + 15] = s.tintStrength;
|
|
1837
|
-
data[o + 16] = s.
|
|
2224
|
+
data[o + 16] = s.spanPx;
|
|
1838
2225
|
data[o + 17] = 0;
|
|
1839
2226
|
}
|
|
1840
2227
|
return { data, count: surfaces.length };
|
|
@@ -2110,9 +2497,9 @@ var WGSL_INSTANCE_STRUCT = `struct Instance {
|
|
|
2110
2497
|
thick : f32, // 44 material thickness, CSS px
|
|
2111
2498
|
press : f32, // 48 interaction channel value, 0..1
|
|
2112
2499
|
glow : f32, // 52 interaction channel value, 0..1
|
|
2113
|
-
|
|
2500
|
+
lensThick : f32, // 56 authored thickness in CSS px times the lensStrength channel (W12 G2): both depths are evaluated from it per pixel
|
|
2114
2501
|
tintK : f32, // 60 author tint strength, 0..1 \u2014 the seed itself is a group uniform
|
|
2115
|
-
|
|
2502
|
+
span : f32, // 64 the surface's shorter extent, CSS px \u2014 the size law's input, both curves (W11c)
|
|
2116
2503
|
_pad : f32, // 68 stride padding: vec2f aligns the struct to 8, so 72 is the next legal size
|
|
2117
2504
|
};`;
|
|
2118
2505
|
var WGSL_FIELD_SAMPLE = `struct FieldSample {
|
|
@@ -2278,7 +2665,8 @@ var WGSL_FIELD_PASS = `struct FieldUniforms {
|
|
|
2278
2665
|
// field but its members are not one size, and carrying them per pixel is what
|
|
2279
2666
|
// lets a 40 px button and a 320 px platter share a field pass and still lens by
|
|
2280
2667
|
// their own depth \u2014 parent acceptance #2 inside a GlassEffectContainer.
|
|
2281
|
-
// aux
|
|
2668
|
+
// aux = (lensThickPx, glow, spanPx, tintStrength)
|
|
2669
|
+
// aux2 = (p \u2212 centre, half.x, half.y) \u2014 W12 G2, group-local CSS px
|
|
2282
2670
|
//
|
|
2283
2671
|
// The third and fourth slots both changed hands, and neither of the values they
|
|
2284
2672
|
// used to carry was ever read by a fragment stage. 'thick' left because
|
|
@@ -2292,23 +2680,45 @@ var WGSL_FIELD_PASS = `struct FieldUniforms {
|
|
|
2292
2680
|
// because a group is one field pass and one optics pass while its members are
|
|
2293
2681
|
// not one thing:
|
|
2294
2682
|
//
|
|
2295
|
-
// '
|
|
2296
|
-
// 320 px platter in one container
|
|
2297
|
-
// shadow by their own size.
|
|
2683
|
+
// 'spanPx' \u2014 the size law's input (W2): the surface's shorter extent in
|
|
2684
|
+
// CSS px. A 40 px button and a 320 px platter in one container
|
|
2685
|
+
// lens, scatter, occlude and shadow by their own size. Until
|
|
2686
|
+
// W11c this slot carried the folded thickness factor itself;
|
|
2687
|
+
// the scatter facet now rides a curve with a different band
|
|
2688
|
+
// top, so the span travels and each consumer evaluates its own
|
|
2689
|
+
// curve from it with the bands and the fold as uniforms.
|
|
2298
2690
|
// 'tintStrength' \u2014 the author tint's strength (W3). One emphasised control
|
|
2299
2691
|
// among plain ones is the composition Apple's guidance
|
|
2300
2692
|
// describes; the seed itself is a group uniform.
|
|
2301
2693
|
//
|
|
2302
2694
|
// They compose rather than compete: the size law moves what the material does to
|
|
2303
2695
|
// the backdrop, the tint moves what colour it does it in.
|
|
2696
|
+
//
|
|
2697
|
+
// One consequence of carrying the span rather than the thickness, stated so it
|
|
2698
|
+
// is not mistaken for a drift: the union below blends 'aux' linearly between two
|
|
2699
|
+
// members, so a pixel in the neck between a button and a platter now blends
|
|
2700
|
+
// their SPANS and evaluates the curves on the blend, where it used to blend the
|
|
2701
|
+
// evaluated thicknesses. Inside either member the two are identical; only the
|
|
2702
|
+
// smooth-min neck differs, and nothing measured claims a value there.
|
|
2703
|
+
//
|
|
2704
|
+
// 'aux2' (W12 G2) carries the pixel's offset from the owning surface's centre
|
|
2705
|
+
// and that surface's half-extents, which
|
|
2706
|
+
// the optics pass needs for the lens's direction: the reference displaces along
|
|
2707
|
+
// the gradient of its SDF blended toward the oval inscribed in the shape's box
|
|
2708
|
+
// (claims \xA75.50's 'gradientOvalization'), and an oval is a property of the box,
|
|
2709
|
+
// not of the distance field. Blended by the same weight as 'aux' in a neck, so
|
|
2710
|
+
// two merged members share one oval there \u2014 a value nothing measured either.
|
|
2711
|
+
// For a concentric child the box is the parent's (the child is the parent's
|
|
2712
|
+
// level set), which is the same centre and a slightly larger oval.
|
|
2304
2713
|
|
|
2305
2714
|
@group(0) @binding(0) var<uniform> fu : FieldUniforms;
|
|
2306
2715
|
@group(0) @binding(1) var<storage, read> instances : array<Instance>;
|
|
2307
2716
|
|
|
2308
2717
|
struct Member {
|
|
2309
|
-
d
|
|
2310
|
-
g
|
|
2311
|
-
aux
|
|
2718
|
+
d : f32,
|
|
2719
|
+
g : vec2f,
|
|
2720
|
+
aux : vec4f,
|
|
2721
|
+
aux2 : vec4f,
|
|
2312
2722
|
};
|
|
2313
2723
|
|
|
2314
2724
|
fn eval_instance(i : u32, p : vec2f) -> Member {
|
|
@@ -2321,13 +2731,17 @@ fn eval_instance(i : u32, p : vec2f) -> Member {
|
|
|
2321
2731
|
var m : Member;
|
|
2322
2732
|
m.d = f.d + s.inset;
|
|
2323
2733
|
m.g = f.g;
|
|
2324
|
-
m.aux = vec4f(s.
|
|
2734
|
+
m.aux = vec4f(s.lensThick, s.glow, s.span, s.tintK);
|
|
2735
|
+
// The pixel relative to the surface's centre, and the half-extents: what the
|
|
2736
|
+
// oval's gradient needs, written here where the group-local position is exact.
|
|
2737
|
+
m.aux2 = vec4f(p - s.centre, s.half);
|
|
2325
2738
|
return m;
|
|
2326
2739
|
}
|
|
2327
2740
|
|
|
2328
2741
|
struct FieldOut {
|
|
2329
2742
|
@location(0) field : vec4f,
|
|
2330
2743
|
@location(1) aux : vec4f,
|
|
2744
|
+
@location(2) aux2 : vec4f,
|
|
2331
2745
|
};
|
|
2332
2746
|
|
|
2333
2747
|
@fragment
|
|
@@ -2345,6 +2759,7 @@ fn fs_field(in : FullscreenOut) -> FieldOut {
|
|
|
2345
2759
|
// coverage downstream is not reliably zero.
|
|
2346
2760
|
out.field = vec4f(65000.0, 0.0, -1.0, 0.0);
|
|
2347
2761
|
out.aux = vec4f(0.0);
|
|
2762
|
+
out.aux2 = vec4f(0.0);
|
|
2348
2763
|
return out;
|
|
2349
2764
|
}
|
|
2350
2765
|
|
|
@@ -2357,6 +2772,7 @@ fn fs_field(in : FullscreenOut) -> FieldOut {
|
|
|
2357
2772
|
acc.d = blend.x;
|
|
2358
2773
|
acc.g = mix(s.g, acc.g, h);
|
|
2359
2774
|
acc.aux = mix(s.aux, acc.aux, h);
|
|
2775
|
+
acc.aux2 = mix(s.aux2, acc.aux2, h);
|
|
2360
2776
|
nearest = min(nearest, s.d);
|
|
2361
2777
|
}
|
|
2362
2778
|
// One clamp at the end of the fold, so |union - min| <= maxBulge holds for any
|
|
@@ -2369,6 +2785,7 @@ fn fs_field(in : FullscreenOut) -> FieldOut {
|
|
|
2369
2785
|
|
|
2370
2786
|
out.field = vec4f(acc.d, normal.x, normal.y, coverage);
|
|
2371
2787
|
out.aux = acc.aux;
|
|
2788
|
+
out.aux2 = acc.aux2;
|
|
2372
2789
|
return out;
|
|
2373
2790
|
}`;
|
|
2374
2791
|
function fieldPassSource(family) {
|
|
@@ -2409,7 +2826,10 @@ var WGSL_HIGHLIGHT_PASS = `struct HighlightUniforms {
|
|
|
2409
2826
|
/// accessibility refraction cap, and the resolved strength \u2014 zero where there
|
|
2410
2827
|
/// is no measured backdrop tone, which stands the fade down
|
|
2411
2828
|
toneAdapt : vec4f,
|
|
2412
|
-
/// the backdrop source's own average luminance (x)
|
|
2829
|
+
/// the backdrop source's own average luminance (x), and the size law's band
|
|
2830
|
+
/// and fold \u2014 sizeSpanMin (y), sizeSpanMax (z), the accessibility fold (w) \u2014
|
|
2831
|
+
/// so this pass evaluates the thickness curve off the per-pixel span exactly
|
|
2832
|
+
/// as the optics pass does (W11c: the span rides the field, not the factor)
|
|
2413
2833
|
toneLevel : vec4f,
|
|
2414
2834
|
/// this pass's uv into the FIELD texture's uv: scale (xy), offset (zw).
|
|
2415
2835
|
///
|
|
@@ -2489,7 +2909,14 @@ fn fs_highlight(in : FullscreenOut) -> @location(0) vec4f {
|
|
|
2489
2909
|
* around a surface the optics pass had made invisible \u2014 measured at 60 pixels
|
|
2490
2910
|
* past 2/255, peaking at 97/255, on exactly that cell.
|
|
2491
2911
|
*/
|
|
2492
|
-
|
|
2912
|
+
// The thickness curve off the per-pixel span, written out as the optics pass
|
|
2913
|
+
// writes it, so the two passes fade on one curve and not on two copies.
|
|
2914
|
+
let spanT = clamp(
|
|
2915
|
+
(aux.z - hu.toneLevel.y) / max(hu.toneLevel.z - hu.toneLevel.y, 1e-6),
|
|
2916
|
+
0.0,
|
|
2917
|
+
1.0,
|
|
2918
|
+
);
|
|
2919
|
+
let sizeK = clamp(spanT * spanT * (3.0 - 2.0 * spanT) * hu.toneLevel.w, 0.0, 1.0);
|
|
2493
2920
|
var toneAdapt = 0.0;
|
|
2494
2921
|
if (hu.toneAdapt.w > 0.0) {
|
|
2495
2922
|
let toneX = hu.toneLevel.x + hu.toneAdapt.z * sizeK;
|
|
@@ -2514,7 +2941,9 @@ var WGSL_OPTICS_PASS = `struct OpticsUniforms {
|
|
|
2514
2941
|
screen : vec4f,
|
|
2515
2942
|
/// backdrop uv transform on viewport-normalised coords: scale (xy), offset (zw)
|
|
2516
2943
|
fit : vec4f,
|
|
2517
|
-
/// refractionScale,
|
|
2944
|
+
/// refractionScale, lensRefractionGain (W12 G2: the gain on the reference's
|
|
2945
|
+
/// amount law), the inner shadow's depth gain (W2's lensSizeGainMax, which the
|
|
2946
|
+
/// shadow keeps), chainMaxLod
|
|
2518
2947
|
lens : vec4f,
|
|
2519
2948
|
/// fixed tint colour, linear light (xyz), tint alpha (w)
|
|
2520
2949
|
tint : vec4f,
|
|
@@ -2522,7 +2951,10 @@ var WGSL_OPTICS_PASS = `struct OpticsUniforms {
|
|
|
2522
2951
|
adapt : vec4f,
|
|
2523
2952
|
/// author tint seed, linear light (xyz), tone adaptation under the contrast regime (w)
|
|
2524
2953
|
seed : vec4f,
|
|
2525
|
-
///
|
|
2954
|
+
/// the author tint's shade law (W10): tintShadeDark, tintShadeLight,
|
|
2955
|
+
/// tintShadeStrength (the profile's provenance gate); and (w) the size law's
|
|
2956
|
+
/// accessibility fold \u2014 the refraction ladder read at the preference's cap,
|
|
2957
|
+
/// which every facet's span-dependent rise is multiplied by (W11c)
|
|
2526
2958
|
tone : vec4f,
|
|
2527
2959
|
/// rimWidthPx, rimAlpha, specularPower, specularGain
|
|
2528
2960
|
rim : vec4f,
|
|
@@ -2542,18 +2974,68 @@ var WGSL_OPTICS_PASS = `struct OpticsUniforms {
|
|
|
2542
2974
|
toneAdapt : vec4f,
|
|
2543
2975
|
/// the backdrop source's own average colour, linear (xyz), and its luminance (w)
|
|
2544
2976
|
toneColour : vec4f,
|
|
2545
|
-
/// the outer shadow (W8
|
|
2546
|
-
///
|
|
2547
|
-
///
|
|
2548
|
-
///
|
|
2549
|
-
///
|
|
2550
|
-
///
|
|
2977
|
+
/// the outer shadow (W8, amplitude re-read by W14 G1): the THIN regime's peak
|
|
2978
|
+
/// LINEAR occlusion, already resolved at this group's backdrop luminance and
|
|
2979
|
+
/// already folded under the accessibility policy (x) \u2014 a linear occlusion and
|
|
2980
|
+
/// no longer a compositing alpha, because the shader now blends two regimes
|
|
2981
|
+
/// before it converts; the blur's sigma in group-local CSS px (y), the
|
|
2982
|
+
/// silhouette's outward spread in the same units (z), and the downward offset
|
|
2983
|
+
/// expressed in field-texture UV (w) \u2014 a UV so the offset silhouette is one
|
|
2984
|
+
/// texture read rather than a gradient extrapolation, which is exact only on a
|
|
2985
|
+
/// straight edge and a capsule is mostly not one
|
|
2551
2986
|
shadow : vec4f,
|
|
2552
2987
|
/// the outer shadow's size-law gain (x), read against the casting surface's own
|
|
2553
2988
|
/// thickness, and the field rect's height in CSS px (y) \u2014 the conversion the
|
|
2554
|
-
/// shadow's shift needs when it lands outside the texture;
|
|
2989
|
+
/// shadow's shift needs when it lands outside the texture; the body depth
|
|
2990
|
+
/// ramp's THICK start (z, W13 G1's third form \u2014 the thin end is scatter.y and
|
|
2991
|
+
/// this pass mixes the two by the pixel's own sizeThickness), and (w) the
|
|
2555
2992
|
/// padding a uniform's vec4 alignment requires
|
|
2556
2993
|
shadowSize : vec4f,
|
|
2994
|
+
/// the backdrop tone response's anchors (W9): the three solid anchors'
|
|
2995
|
+
/// ENCODED-space means (xyz), and the backdrop's linear-space mean (w) \u2014 the
|
|
2996
|
+
/// quantity the response solve composites against
|
|
2997
|
+
toneAnchor : vec4f,
|
|
2998
|
+
/// the response law's thin row: the reference's settled interior levels at the
|
|
2999
|
+
/// three anchors for a surface of sizeThickness 0 (xyz); w is the law's
|
|
3000
|
+
/// per-profile authority \u2014 0 on dark profiles, whose response is unmeasured
|
|
3001
|
+
toneRowThin : vec4f,
|
|
3002
|
+
/// the thick row (sizeThickness saturated), same layout
|
|
3003
|
+
toneRowThick : vec4f,
|
|
3004
|
+
/// the size law's bands: the scatter facet's floor (x) and the depth ramp's
|
|
3005
|
+
/// THIN start s0 at the ratio this group draws at (y, W13 G1 \u2014 the CPU has
|
|
3006
|
+
/// already interpolated it between the profile's 1x and 2x anchors; the thick
|
|
3007
|
+
/// end is shadowSize.z), and the
|
|
3008
|
+
/// thickness curve's sizeSpanMin (z) and sizeSpanMax (w); the fold both
|
|
3009
|
+
/// multiply by is tone.w. The scatter span curve's own band top is lensOval.w,
|
|
3010
|
+
/// and it starts at the same z.
|
|
3011
|
+
scatter : vec4f,
|
|
3012
|
+
/// the lens law (W12 G2): the reference's height law (lensHeightPerSpan,
|
|
3013
|
+
/// lensHeightMax) and amount law (lensAmountPerSpan, lensAmountMax)
|
|
3014
|
+
lensLaw : vec4f,
|
|
3015
|
+
/// the lens profile (W12 G2): lensExtentGain, lensProfileExponent,
|
|
3016
|
+
/// lensOvalization, lensThicknessReference
|
|
3017
|
+
lensShape : vec4f,
|
|
3018
|
+
/// the ovalization's knee (W12 G2): spanMin (x), spanMax (y); the body depth
|
|
3019
|
+
/// ramp's reach in group-local CSS px (z, W13 G1) \u2014 the profile names it in
|
|
3020
|
+
/// device px and the CPU divides by the ratio, so that this shader can read
|
|
3021
|
+
/// the field's own CSS-px depth without a second conversion; the scatter span
|
|
3022
|
+
/// curve's band top sizeScatterSpanMax (w, W11c), which W13 G1 keeps
|
|
3023
|
+
/// underneath the ramp as its deep value
|
|
3024
|
+
lensOval : vec4f,
|
|
3025
|
+
/// the outer shadow's THICK regime (W14 G1): the composite's peak linear
|
|
3026
|
+
/// occlusion at casting spans 96, 128 and 160 CSS px (xyz), already folded
|
|
3027
|
+
/// under the accessibility policy, piecewise-linear between and held outside;
|
|
3028
|
+
/// and (w) the body depth ramp's FAR start (W13 G1's fourth form) \u2014 the start
|
|
3029
|
+
/// at span >= sizeScatterSpanMax, which the ramp declines to from the thick
|
|
3030
|
+
/// end along the scatter span curve, in the slot W14 left free
|
|
3031
|
+
shadowThick : vec4f,
|
|
3032
|
+
/// the lift (W14 G1), the shadow's second term and GPU-tier only: its peak
|
|
3033
|
+
/// amplitude in LINEAR light as a fraction of the blurred backdrop's own
|
|
3034
|
+
/// luminance (x), the span rise's foot and top in CSS px (yz), and the chain
|
|
3035
|
+
/// LOD whose blur is the profile's liftBlurSigmaCss \u2014 resolved on the CPU,
|
|
3036
|
+
/// which is the only place the CSS-px-to-texel conversion is knowable, and
|
|
3037
|
+
/// already clamped to chainMaxLod (w)
|
|
3038
|
+
shadowLift : vec4f,
|
|
2557
3039
|
};
|
|
2558
3040
|
|
|
2559
3041
|
@group(0) @binding(0) var<uniform> ou : OpticsUniforms;
|
|
@@ -2563,6 +3045,45 @@ var WGSL_OPTICS_PASS = `struct OpticsUniforms {
|
|
|
2563
3045
|
@group(0) @binding(4) var backdropChain : texture_2d<f32>;
|
|
2564
3046
|
@group(0) @binding(5) var backdropBody : texture_2d<f32>;
|
|
2565
3047
|
@group(0) @binding(6) var fieldSampler : sampler;
|
|
3048
|
+
/// The owning surface's box per pixel (W12 G2): the pixel's offset from its
|
|
3049
|
+
/// centre (xy) and its half-extents (zw), group-local CSS px \u2014 the lens's oval.
|
|
3050
|
+
@group(0) @binding(7) var aux2Texture : texture_2d<f32>;
|
|
3051
|
+
|
|
3052
|
+
/// One encoded sRGB channel from a linear one \u2014 the space the backdrop tone
|
|
3053
|
+
/// response's anchors live in (W9). Mirrors material.ts's 'linearToSrgbChannel'.
|
|
3054
|
+
fn srgb_encode(c : f32) -> f32 {
|
|
3055
|
+
let x = clamp(c, 0.0, 1.0);
|
|
3056
|
+
return select(1.055 * pow(x, 1.0 / 2.4) - 0.055, x * 12.92, x <= 0.0031308);
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
/// The backdrop tone response R(encodedInput, sizeK) (W9): monotone
|
|
3060
|
+
/// (Fritsch\u2013Carlson) interpolation through the three anchors, clamped to their
|
|
3061
|
+
/// span; smoothstep between the thin and thick rows. Mirrors material.ts's
|
|
3062
|
+
/// 'backdropToneResponse' term for term \u2014 the constants are authored there.
|
|
3063
|
+
fn tone_response(x : f32, sizeK : f32) -> f32 {
|
|
3064
|
+
let f = sizeK * sizeK * (3.0 - 2.0 * sizeK);
|
|
3065
|
+
let ys = mix(ou.toneRowThin.xyz, ou.toneRowThick.xyz, vec3f(f));
|
|
3066
|
+
let xs = ou.toneAnchor.xyz;
|
|
3067
|
+
let xc = clamp(x, xs.x, xs.z);
|
|
3068
|
+
let h0 = max(xs.y - xs.x, 1e-4);
|
|
3069
|
+
let h1 = max(xs.z - xs.y, 1e-4);
|
|
3070
|
+
let d0 = (ys.y - ys.x) / h0;
|
|
3071
|
+
let d1 = (ys.z - ys.y) / h1;
|
|
3072
|
+
// The interior slope: the harmonic mean where the secants agree, 0 across a
|
|
3073
|
+
// sign change \u2014 what keeps the curve monotone between monotone anchors.
|
|
3074
|
+
var m1 = 0.0;
|
|
3075
|
+
if (d0 * d1 > 0.0) { m1 = 2.0 * d0 * d1 / (d0 + d1); }
|
|
3076
|
+
var h = h0; var t = (xc - xs.x) / h0;
|
|
3077
|
+
var y0 = ys.x; var y1 = ys.y; var s0 = d0; var s1 = m1;
|
|
3078
|
+
if (xc > xs.y) {
|
|
3079
|
+
h = h1; t = (xc - xs.y) / h1;
|
|
3080
|
+
y0 = ys.y; y1 = ys.z; s0 = m1; s1 = d1;
|
|
3081
|
+
}
|
|
3082
|
+
return y0 * (1.0 + 2.0 * t) * (1.0 - t) * (1.0 - t)
|
|
3083
|
+
+ s0 * h * t * (1.0 - t) * (1.0 - t)
|
|
3084
|
+
+ y1 * t * t * (3.0 - 2.0 * t)
|
|
3085
|
+
+ s1 * h * t * t * (t - 1.0);
|
|
3086
|
+
}
|
|
2566
3087
|
|
|
2567
3088
|
/// Rim proximity: 1 exactly on the contour, falling to 0 by 'width' on either
|
|
2568
3089
|
/// side. Symmetric, so the rim is a band on the boundary rather than a plateau
|
|
@@ -2582,7 +3103,33 @@ fn outer_shadow_falloff(signedDistance : f32, sigma : f32) -> f32 {
|
|
|
2582
3103
|
return 0.5 * (1.0 + tanh(0.7978845608028654 * (x + 0.044715 * x * x * x)));
|
|
2583
3104
|
}
|
|
2584
3105
|
|
|
2585
|
-
///
|
|
3106
|
+
/// What one pixel's outer shadow is made of: the compositing-space alpha the
|
|
3107
|
+
/// black term composites at, the falloff BOTH terms ride, and the span of the
|
|
3108
|
+
/// surface that cast it \u2014 which the lift's own rise is read from.
|
|
3109
|
+
///
|
|
3110
|
+
/// One struct rather than three calls because there is one falloff (W14 G0,
|
|
3111
|
+
/// claims \xA75.62 \xA74): the black term's free fit and the lift's free fit return
|
|
3112
|
+
/// the same sigma, offset and spread, so evaluating a second geometry for the
|
|
3113
|
+
/// second term would be asserting a difference the measurement denies.
|
|
3114
|
+
struct ShadowSample {
|
|
3115
|
+
alpha : f32,
|
|
3116
|
+
falloff : f32,
|
|
3117
|
+
castSpanCss : f32,
|
|
3118
|
+
};
|
|
3119
|
+
|
|
3120
|
+
/// The composite's peak linear occlusion above the knee, at a casting span \u2014
|
|
3121
|
+
/// piecewise linear through the three measured anchors (96 / 128 / 160 CSS px)
|
|
3122
|
+
/// and held flat outside them. Mirrors material.ts's
|
|
3123
|
+
/// 'outerShadowThickOcclusion'.
|
|
3124
|
+
fn outer_shadow_thick(spanCss : f32) -> f32 {
|
|
3125
|
+
let y = ou.shadowThick.xyz;
|
|
3126
|
+
if (spanCss <= 96.0) { return y.x; }
|
|
3127
|
+
if (spanCss >= 160.0) { return y.z; }
|
|
3128
|
+
if (spanCss <= 128.0) { return y.x + (y.y - y.x) * (spanCss - 96.0) / 32.0; }
|
|
3129
|
+
return y.y + (y.z - y.y) * (spanCss - 128.0) / 32.0;
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
/// The outer shadow's alpha at this pixel (W8, amplitude W14 G1).
|
|
2586
3133
|
///
|
|
2587
3134
|
/// The shadow is the group's OWN field, translated down by 'shadow.w' in field
|
|
2588
3135
|
/// UV and outset by 'shadow.z', then blurred. Reading the offset silhouette from
|
|
@@ -2593,9 +3140,13 @@ fn outer_shadow_falloff(signedDistance : f32, sigma : f32) -> f32 {
|
|
|
2593
3140
|
/// The value is an ALPHA on pure black, so what it composites to is the backdrop
|
|
2594
3141
|
/// times '1 - alpha' \u2014 multiplicative occlusion, and exactly zero over black,
|
|
2595
3142
|
/// with no branch for it.
|
|
2596
|
-
fn
|
|
2597
|
-
|
|
2598
|
-
|
|
3143
|
+
fn outer_shadow(uv : vec2f, upsampled : f32, fieldSize : vec2f) -> ShadowSample {
|
|
3144
|
+
var out : ShadowSample;
|
|
3145
|
+
out.alpha = 0.0;
|
|
3146
|
+
out.falloff = 0.0;
|
|
3147
|
+
out.castSpanCss = 0.0;
|
|
3148
|
+
if (ou.shadow.x <= 0.0 && ou.shadowThick.x <= 0.0) {
|
|
3149
|
+
return out;
|
|
2599
3150
|
}
|
|
2600
3151
|
/*
|
|
2601
3152
|
* The shift can leave the field texture, and clamping alone reads a lie there.
|
|
@@ -2631,19 +3182,78 @@ fn outer_shadow_alpha(uv : vec2f, upsampled : f32, fieldSize : vec2f) -> f32 {
|
|
|
2631
3182
|
}
|
|
2632
3183
|
|
|
2633
3184
|
// The size law reaches the amplitude and nothing else \u2014 the reference's three
|
|
2634
|
-
// lengths are span-invariant across 32\u2026160 px.
|
|
2635
|
-
// the
|
|
2636
|
-
// position rather than the one under the pixel
|
|
3185
|
+
// lengths are span-invariant across 32\u2026160 px. Every span-keyed quantity here
|
|
3186
|
+
// rides the thickness and the span of the surface that CAST the shadow, which
|
|
3187
|
+
// is the one read at the offset position rather than the one under the pixel
|
|
3188
|
+
// being shaded.
|
|
2637
3189
|
//
|
|
2638
|
-
//
|
|
2639
|
-
// the
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
3190
|
+
// The thickness curve off the casting surface's span (W11c: the span rides
|
|
3191
|
+
// the field), written as the body's own is below.
|
|
3192
|
+
let castT = clamp(
|
|
3193
|
+
(shadowAux.z - ou.scatter.z) / max(ou.scatter.w - ou.scatter.z, 1e-6),
|
|
3194
|
+
0.0,
|
|
3195
|
+
1.0,
|
|
3196
|
+
);
|
|
3197
|
+
let sizeK = clamp(castT * castT * (3.0 - 2.0 * castT) * clamp(ou.tone.w, 0.0, 1.0), 0.0, 1.0);
|
|
3198
|
+
|
|
3199
|
+
// The two regimes and the knee between them (W14 G1). The thin amplitude is
|
|
3200
|
+
// resolved on the CPU because its key \u2014 the backdrop's luminance \u2014 is one
|
|
3201
|
+
// number for the whole group; the thick one is a span law and has to be per
|
|
3202
|
+
// pixel. The blend is the smoothstep of 'sizeK', which is exactly the curve
|
|
3203
|
+
// 'tone_response' blends its own thin and thick rows across: one knee for the
|
|
3204
|
+
// face and the shadow, which is the charter's third binding rule read from
|
|
3205
|
+
// the other side.
|
|
3206
|
+
let blend = sizeK * sizeK * (3.0 - 2.0 * sizeK);
|
|
3207
|
+
let regime = mix(ou.shadow.x, outer_shadow_thick(shadowAux.z), blend);
|
|
3208
|
+
// The size gain, on the relative form 'sizeOuterShadowOcclusionAt' uses, in
|
|
3209
|
+
// LINEAR light; then the one conversion to the canvas's compositing space,
|
|
3210
|
+
// which is where the black term is actually composited.
|
|
3211
|
+
let occ = clamp(regime + ou.shadowSize.x * sizeK * (1.0 - regime), 0.0, 1.0);
|
|
3212
|
+
out.alpha = 1.0 - pow(1.0 - occ, 1.0 / 2.4);
|
|
3213
|
+
out.falloff = outer_shadow_falloff(shadowField.x + clampedOffCss - ou.shadow.z, ou.shadow.y);
|
|
3214
|
+
out.castSpanCss = shadowAux.z;
|
|
3215
|
+
return out;
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
/// The lift (W14 G1) \u2014 the shadow's second term, in LINEAR light, premultiplied
|
|
3219
|
+
/// by nothing yet: a blurred copy of the backdrop's own light, at an amplitude
|
|
3220
|
+
/// that is zero below the knee and saturating above it, on the SAME falloff the
|
|
3221
|
+
/// black term rides.
|
|
3222
|
+
///
|
|
3223
|
+
/// 'V' is the backdrop chain read at the pixel's OWN position \u2014 the copy is of
|
|
3224
|
+
/// the backdrop beneath the shadow, not of the backdrop under the surface \u2014 at
|
|
3225
|
+
/// the LOD whose blur is the profile's 40 CSS px. The chain's uv transform is on
|
|
3226
|
+
/// viewport-normalised coordinates, so every pixel this pass draws has a valid
|
|
3227
|
+
/// sample: unlike the field texture, which the offset shift can walk off the top
|
|
3228
|
+
/// of, the chain covers the whole viewport and there is nothing to reconstruct.
|
|
3229
|
+
///
|
|
3230
|
+
/// Zero over black by construction (V = 0), zero below the knee by the rise, and
|
|
3231
|
+
/// zero where the group has no backdrop to copy \u2014 so 'dark-solid', 'impulse',
|
|
3232
|
+
/// every thin cell and every unsampled group stay byte-for-byte what they were.
|
|
3233
|
+
fn outer_shadow_lift(viewport01 : vec2f, shadow : ShadowSample) -> vec3f {
|
|
3234
|
+
if (ou.flags.x <= 0.5 || ou.shadowLift.x <= 0.0 || shadow.falloff <= 0.0) {
|
|
3235
|
+
return vec3f(0.0);
|
|
3236
|
+
}
|
|
3237
|
+
// Written out rather than through WGSL's 'smoothstep', which is undefined
|
|
3238
|
+
// where the two edges coincide \u2014 a profile is entitled to set the rise's foot
|
|
3239
|
+
// and top to one number, and material.ts's own 'smoothstep' degrades that to a
|
|
3240
|
+
// step rather than to a NaN. Same rule as the tone band's divide above.
|
|
3241
|
+
let riseT = clamp(
|
|
3242
|
+
(shadow.castSpanCss - ou.shadowLift.y) / max(ou.shadowLift.z - ou.shadowLift.y, 1e-6),
|
|
3243
|
+
0.0,
|
|
3244
|
+
1.0,
|
|
3245
|
+
);
|
|
3246
|
+
let rise = riseT * riseT * (3.0 - 2.0 * riseT);
|
|
3247
|
+
if (rise <= 0.0) {
|
|
3248
|
+
return vec3f(0.0);
|
|
3249
|
+
}
|
|
3250
|
+
let uv = clamp(viewport01 * ou.fit.xy + ou.fit.zw, vec2f(0.0), vec2f(1.0));
|
|
3251
|
+
let chainSample = textureSampleLevel(backdropChain, backdropSampler, uv, ou.shadowLift.w);
|
|
3252
|
+
// Premultiplied linear in, straight colour out \u2014 the same unpremultiply the
|
|
3253
|
+
// body's own samples take, for the same reason: a partially transparent
|
|
3254
|
+
// backdrop must not darken what the material adds back.
|
|
3255
|
+
let v = chainSample.rgb / max(chainSample.a, 1e-6);
|
|
3256
|
+
return v * (ou.shadowLift.x * rise * shadow.falloff);
|
|
2647
3257
|
}
|
|
2648
3258
|
|
|
2649
3259
|
@fragment
|
|
@@ -2655,13 +3265,16 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
|
|
|
2655
3265
|
// contour to the coarse grid and the rim would step along it.
|
|
2656
3266
|
var field : vec4f;
|
|
2657
3267
|
var aux : vec4f;
|
|
3268
|
+
var aux2 : vec4f;
|
|
2658
3269
|
if (ou.flags.w > 0.5) {
|
|
2659
3270
|
field = textureSampleLevel(fieldTexture, fieldSampler, in.uv, 0.0);
|
|
2660
3271
|
aux = textureSampleLevel(auxTexture, fieldSampler, in.uv, 0.0);
|
|
3272
|
+
aux2 = textureSampleLevel(aux2Texture, fieldSampler, in.uv, 0.0);
|
|
2661
3273
|
} else {
|
|
2662
3274
|
let texel = vec2i(in.uv * ou.flags.yz);
|
|
2663
3275
|
field = textureLoad(fieldTexture, texel, 0);
|
|
2664
3276
|
aux = textureLoad(auxTexture, texel, 0);
|
|
3277
|
+
aux2 = textureLoad(aux2Texture, texel, 0);
|
|
2665
3278
|
}
|
|
2666
3279
|
|
|
2667
3280
|
let d = field.x;
|
|
@@ -2677,61 +3290,202 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
|
|
|
2677
3290
|
* is clipped out of its own border box, and the two meet across the coverage
|
|
2678
3291
|
* ramp with no seam because it is one expression.
|
|
2679
3292
|
*/
|
|
2680
|
-
let
|
|
3293
|
+
let shadowSample = outer_shadow(in.uv, ou.flags.w, ou.flags.yz);
|
|
3294
|
+
let shadowAlpha = shadowSample.alpha * shadowSample.falloff;
|
|
3295
|
+
let viewport01 = in.position.xy / ou.screen.xy;
|
|
3296
|
+
// Resolved once, for both sides of the coverage ramp: the lift fills exactly
|
|
3297
|
+
// what the surface's coverage leaves, on the same rule and in the same
|
|
3298
|
+
// expression as the black term's alpha at the bottom of this function, so the
|
|
3299
|
+
// two meet across the ramp with no seam. At full coverage it is not computed
|
|
3300
|
+
// at all and the chain read is not taken.
|
|
3301
|
+
var liftEncoded = vec3f(0.0);
|
|
3302
|
+
if (coverage < 1.0) {
|
|
3303
|
+
let lift = outer_shadow_lift(viewport01, shadowSample);
|
|
3304
|
+
liftEncoded = min(linear_to_srgb(max(lift, vec3f(0.0))), vec3f(shadowAlpha));
|
|
3305
|
+
}
|
|
2681
3306
|
if (coverage <= 0.0) {
|
|
2682
|
-
|
|
3307
|
+
/*
|
|
3308
|
+
* The lift (W14 G1) makes this a premultiplied COMPOSITE rather than an
|
|
3309
|
+
* alpha on black: the compositor produces 'page\xB7(1 \u2212 shadowAlpha) + lift',
|
|
3310
|
+
* which is the reference's own two-term form outside the coverage.
|
|
3311
|
+
*
|
|
3312
|
+
* The added light is computed in linear light and encoded on its own before
|
|
3313
|
+
* it is emitted, because it IS its own light \u2014 over the checkerboard's black
|
|
3314
|
+
* squares, where the multiply is inert, what the reference adds is exactly
|
|
3315
|
+
* this term and nothing else, and encoding it alone reproduces G0's measured
|
|
3316
|
+
* +0.048 encoded at span 160 from its +0.0038 linear (claims \xA75.62 \xA72, \xA73).
|
|
3317
|
+
* Where the backdrop is bright the encoding of a sum and the sum of the
|
|
3318
|
+
* encodings differ, and this term is the second: the residual is bounded by
|
|
3319
|
+
* the term's own size and is well inside the bed's \xB14/255, on the same
|
|
3320
|
+
* honest-floor argument 'outerShadowAlpha' makes for the black term's space.
|
|
3321
|
+
*
|
|
3322
|
+
* A premultiplied layer may not carry a channel above its own alpha, so the
|
|
3323
|
+
* emitted light is capped at 'shadowAlpha'. At the profile's constants the
|
|
3324
|
+
* cap is inert everywhere on the falloff: both terms are proportional to the
|
|
3325
|
+
* same F, and the lift's encoded value stays at about a sixth of the black
|
|
3326
|
+
* term's alpha from the deepest ring out to where the shadow stops moving a
|
|
3327
|
+
* code. It is here so that a swept amplitude produces a valid layer rather
|
|
3328
|
+
* than an implementation-defined one.
|
|
3329
|
+
*/
|
|
3330
|
+
return vec4f(liftEncoded, shadowAlpha);
|
|
2683
3331
|
}
|
|
2684
3332
|
|
|
2685
|
-
let viewport01 = in.position.xy / ou.screen.xy;
|
|
2686
3333
|
let viewportCss = ou.screen.xy * ou.screen.z;
|
|
2687
3334
|
|
|
2688
|
-
// Per-pixel, unioned through the field pass. See the module note.
|
|
2689
|
-
|
|
2690
|
-
//
|
|
2691
|
-
|
|
2692
|
-
//
|
|
2693
|
-
|
|
2694
|
-
//
|
|
2695
|
-
//
|
|
2696
|
-
//
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
let
|
|
3335
|
+
// Per-pixel, unioned through the field pass. See the module note. 'aux.x' is
|
|
3336
|
+
// the authored thickness times the lensStrength channel (W12 G2); the lens's
|
|
3337
|
+
// depth and the inner shadow's are both evaluated from it and the span below.
|
|
3338
|
+
let lensThick = max(aux.x, 0.0);
|
|
3339
|
+
// The thickness curve off the span of whichever surface owns this pixel
|
|
3340
|
+
// (W11c). 'sizeK' is the thickness factor, 0..1: zero on anything at or below
|
|
3341
|
+
// the profile's 'sizeSpanMin', saturated at 'sizeSpanMax', folded under the
|
|
3342
|
+
// preference \u2014 the occlusion, the inner shadow and the tone response multiply
|
|
3343
|
+
// by it, so a small control takes the pre-law path exactly. Written out as a
|
|
3344
|
+
// smoothstep with a guarded denominator, so a profile that collapses the band
|
|
3345
|
+
// degrades to a step rather than to NaN.
|
|
3346
|
+
let span = aux.z;
|
|
3347
|
+
let fold = clamp(ou.tone.w, 0.0, 1.0);
|
|
3348
|
+
let thickT = clamp((span - ou.scatter.z) / max(ou.scatter.w - ou.scatter.z, 1e-6), 0.0, 1.0);
|
|
3349
|
+
// The unfolded curve, which the depth ramp's start grades along below, and the
|
|
3350
|
+
// folded factor every other facet multiplies by. One smoothstep, two readings.
|
|
3351
|
+
let sizeThick = thickT * thickT * (3.0 - 2.0 * thickT);
|
|
3352
|
+
let sizeK = clamp(sizeThick * fold, 0.0, 1.0);
|
|
3353
|
+
/*
|
|
3354
|
+
* The body's mix is the span curve W11c fitted with a ramp in DEPTH riding on
|
|
3355
|
+
* top of it near the contour (W13 G1, from the measurement of claims \xA75.61 \xA72
|
|
3356
|
+
* and the re-forming its first runtime sweep forced):
|
|
3357
|
+
*
|
|
3358
|
+
* kDeep = floor + (1 - floor) * smoothstep(sizeSpanMin, sizeScatterSpanMax, span)
|
|
3359
|
+
* s0 = startThin + (startThick - startThin) * sizeThickness(span)
|
|
3360
|
+
* + (startFar - startThick) * smoothstep(sizeSpanMax, sizeScatterSpanMax, span)
|
|
3361
|
+
* s(u) = (1 - kDeep) + max(0, s0 - (1 - kDeep)) * max(0, 1 - u / U)
|
|
3362
|
+
* k(u) = 1 - s(u)
|
|
3363
|
+
*
|
|
3364
|
+
* The first form of the ramp replaced the span curve outright and its sweep
|
|
3365
|
+
* measured what that cost: the ramp's own projection onto one number per
|
|
3366
|
+
* surface runs 0.43-0.56 where the curve it replaced runs 0.41-1.00, so the
|
|
3367
|
+
* family is nearly span-flat where the bed is strongly span-graded and no
|
|
3368
|
+
* point in 81 reached the wave's stops. So the curve stays as the deep value
|
|
3369
|
+
* and the ramp is what it always measured as - a near-contour excursion.
|
|
3370
|
+
*
|
|
3371
|
+
* The SECOND form gave that excursion one start per scale, and its own sweep
|
|
3372
|
+
* refuted that arithmetically (claims \xA75.64 \xA72): the smallest cell's span is
|
|
3373
|
+
* exactly sizeSpanMin, so its deep sharp share is exactly 1 - floor and no
|
|
3374
|
+
* start at or below it can touch that cell, while the next span up only
|
|
3375
|
+
* improves BELOW a start lower still. So the start grades with span too, and
|
|
3376
|
+
* along the material's own thin/thick curve rather than a new one - the same
|
|
3377
|
+
* smoothstep 'sizeK' is built from above, unfolded, because s0 is a share the
|
|
3378
|
+
* reference has and not a rise a preference removes. The fold below still
|
|
3379
|
+
* applies once, on the composed mix.
|
|
3380
|
+
*
|
|
3381
|
+
* The THIRD form stopped there and its holdout failed on one row for the
|
|
3382
|
+
* form's own arithmetic (claims \xA75.67 \xA74): sizeThickness saturates at
|
|
3383
|
+
* sizeSpanMax, so every thick span got the same start while the reference's
|
|
3384
|
+
* start keeps FALLING across the thick spans, and because kDeep keeps rising
|
|
3385
|
+
* to sizeScatterSpanMax the excursion grew with span where the reference's
|
|
3386
|
+
* shrinks. So the FOURTH form declines the start past the knee along the
|
|
3387
|
+
* scatter span curve itself \u2014 the same smoothstep 'deepT' is built from, on
|
|
3388
|
+
* the band above the thickness knee \u2014 from the thick anchor to startFar at
|
|
3389
|
+
* sizeScatterSpanMax. Same curve read twice, no new span statistic.
|
|
3390
|
+
*
|
|
3391
|
+
* '-d' is the depth, in group-local CSS px and unclamped - the same quantity
|
|
3392
|
+
* the lens's 'lensT' and the inner shadow's 'shadowT' are evaluated from,
|
|
3393
|
+
* except that those two divide it by a depth of their own while this reads it
|
|
3394
|
+
* absolutely, because the reach measured as a LENGTH rather than as a fraction
|
|
3395
|
+
* of the span. The reach reaches this shader already divided by the device
|
|
3396
|
+
* ratio ('lensOval.z'), so the ratio 'u / U' is the same number in CSS px as
|
|
3397
|
+
* it is in the device px the profile names.
|
|
3398
|
+
*
|
|
3399
|
+
* 'max(s0 - sDeep, 0)' rather than a signed difference: the excursion is the
|
|
3400
|
+
* band the reference has ABOVE the body, and a span whose deep sharp share
|
|
3401
|
+
* already exceeds s0 has nothing to add rather than something to subtract.
|
|
3402
|
+
*
|
|
3403
|
+
* The fold keeps its W11c semantics on the composed law: the floor is the
|
|
3404
|
+
* frost the material has at any size and is not folded, everything above it -
|
|
3405
|
+
* the span curve's rise and the ramp's excursion alike - is depth and is, so
|
|
3406
|
+
* fold 1 renders the law and fold 0 sits at the floor exactly.
|
|
3407
|
+
*/
|
|
3408
|
+
let scatterFloor = clamp(ou.scatter.x, 0.0, 1.0);
|
|
3409
|
+
let deepT = clamp((span - ou.scatter.z) / max(ou.lensOval.w - ou.scatter.z, 1e-6), 0.0, 1.0);
|
|
3410
|
+
let kDeep = scatterFloor + (1.0 - scatterFloor) * deepT * deepT * (3.0 - 2.0 * deepT);
|
|
3411
|
+
let sDeep = 1.0 - kDeep;
|
|
3412
|
+
let rampT = max(1.0 - max(-d, 0.0) / max(ou.lensOval.z, 1e-6), 0.0);
|
|
3413
|
+
let farT = clamp((span - ou.scatter.w) / max(ou.lensOval.w - ou.scatter.w, 1e-6), 0.0, 1.0);
|
|
3414
|
+
let farS = farT * farT * (3.0 - 2.0 * farT);
|
|
3415
|
+
let rampStart = ou.scatter.y + (ou.shadowSize.z - ou.scatter.y) * sizeThick
|
|
3416
|
+
+ (ou.shadowThick.w - ou.shadowSize.z) * farS;
|
|
3417
|
+
let sharpShare = clamp(sDeep + max(rampStart - sDeep, 0.0) * rampT, 0.0, 1.0);
|
|
3418
|
+
let kScatter = clamp(scatterFloor + ((1.0 - sharpShare) - scatterFloor) * fold, 0.0, 1.0);
|
|
3419
|
+
// The inner shadow's depth and profile: W2's law, byte for byte \u2014 the
|
|
3420
|
+
// thickness times the size gain (folded through 'sizeK'), clamped to the
|
|
3421
|
+
// shorter half extent, and a square on it. The lens ran on this until W12 G2;
|
|
3422
|
+
// the occlusion keeps it, because nothing measured it as wrong, and every
|
|
3423
|
+
// solid-backdrop cell renders exactly as it did.
|
|
3424
|
+
let shadowLensDepth = max(min(lensThick * (1.0 + (ou.lens.z - 1.0) * sizeK), span * 0.5), 1e-4);
|
|
3425
|
+
let shadowT = clamp(-d / shadowLensDepth, 0.0, 1.0);
|
|
3426
|
+
let shadowProfile = (1.0 - shadowT) * (1.0 - shadowT);
|
|
3427
|
+
|
|
3428
|
+
// The lens (W12 G2, claims \xA75.51) \u2014 see the module note. The reference's
|
|
3429
|
+
// height and amount laws off the span, scaled by the thickness over the
|
|
3430
|
+
// reference's unit, folded like the W2 law (at fold 0 the depth is the
|
|
3431
|
+
// authored thickness and nothing more), and clamped to the shorter half
|
|
3432
|
+
// extent with the magnitude clamped by the same ratio.
|
|
3433
|
+
let thickScale = lensThick / max(ou.lensShape.w, 1e-4);
|
|
3434
|
+
let heightBase = min(ou.lensLaw.x * span, ou.lensLaw.y);
|
|
3435
|
+
let amountBase = min(ou.lensLaw.z * span, ou.lensLaw.w);
|
|
3436
|
+
let depthUnclamped = mix(lensThick, heightBase * thickScale, fold);
|
|
3437
|
+
let lensDepth = clamp(depthUnclamped, 0.0, span * 0.5);
|
|
3438
|
+
let clampRatio = select(0.0, lensDepth / depthUnclamped, depthUnclamped > 1e-6);
|
|
3439
|
+
let magnitude = ou.lens.y * mix(lensThick, amountBase * thickScale, fold) * clampRatio;
|
|
3440
|
+
let extent = max(ou.lensShape.x * lensDepth, 1e-4);
|
|
3441
|
+
// One steep power over the extent; '-d' is depth inside the surface.
|
|
3442
|
+
let lensT = max(1.0 - max(-d, 0.0) / extent, 0.0);
|
|
3443
|
+
let displacementCss = magnitude * pow(lensT, ou.lensShape.y) * ou.lens.x;
|
|
3444
|
+
|
|
3445
|
+
// The direction: the field's gradient blended toward the oval inscribed in
|
|
3446
|
+
// the surface's box (the reference's 'gradientOvalization'), on from the
|
|
3447
|
+
// knee, with the magnitude fixed \u2014 which tilts the displacement toward the
|
|
3448
|
+
// edge's midpoint and magnifies the band along the edge.
|
|
3449
|
+
let ovalT = clamp((span - ou.lensOval.x) / max(ou.lensOval.y - ou.lensOval.x, 1e-6), 0.0, 1.0);
|
|
3450
|
+
let omega = ou.lensShape.z * ovalT * ovalT * (3.0 - 2.0 * ovalT);
|
|
3451
|
+
let halfExt = max(aux2.zw, vec2f(1e-4));
|
|
3452
|
+
let rel = aux2.xy;
|
|
3453
|
+
let unitR = max(length(rel / halfExt), 1e-6);
|
|
3454
|
+
let ovalGrad = (rel / (halfExt * halfExt)) * (min(halfExt.x, halfExt.y) / unitR);
|
|
3455
|
+
let blended = (1.0 - omega) * normal + omega * ovalGrad;
|
|
3456
|
+
let blendLen = length(blended);
|
|
3457
|
+
let direction = select(normal, blended / blendLen, blendLen > 1e-6);
|
|
3458
|
+
let displaceCss = -direction * displacementCss;
|
|
2701
3459
|
let refracted01 = viewport01 + displaceCss / viewportCss;
|
|
2702
3460
|
|
|
2703
|
-
let straightUv = clamp(viewport01 * ou.fit.xy + ou.fit.zw, vec2f(0.0), vec2f(1.0));
|
|
2704
3461
|
let refractedUv = clamp(refracted01 * ou.fit.xy + ou.fit.zw, vec2f(0.0), vec2f(1.0));
|
|
2705
3462
|
|
|
2706
|
-
let bodyLod = clamp(lensDepth * ou.lens.y, 0.0, ou.lens.w);
|
|
2707
|
-
let lod = clamp(bodyLod - ou.lens.z * profile, 0.0, ou.lens.w);
|
|
2708
|
-
|
|
2709
3463
|
/*
|
|
2710
3464
|
* The scattering facet of the size law: "a softer scattering of light".
|
|
2711
3465
|
*
|
|
2712
3466
|
* The body texture is one blur for the whole backdrop source \u2014 it is built once
|
|
2713
3467
|
* per source per frame, so it cannot be per-surface. The chain beside it can:
|
|
2714
3468
|
* 'size.w' is the chain level whose blur already matches that body, so
|
|
2715
|
-
* 'size.w + log2(
|
|
2716
|
-
* wider
|
|
2717
|
-
*
|
|
2718
|
-
*
|
|
3469
|
+
* 'size.w + log2(sizeScatterGainMax)' is the level whose blur is the gain times
|
|
3470
|
+
* wider \u2014 the HEAVY component, at one fixed level \u2014 and lerping the body toward
|
|
3471
|
+
* it by 'kScatter' is what the reference's interior measures (W11c, claims
|
|
3472
|
+
* \xA75.41): a sharp component near the body's \u03C3 and a heavy one near \u03C3 10, mixed
|
|
3473
|
+
* by a share that is \u2248 0.4 on a small control and rises with the span. The
|
|
3474
|
+
* mix, not the level, is what the span moves.
|
|
2719
3475
|
*/
|
|
2720
|
-
let
|
|
2721
|
-
let scatterLod = clamp(ou.size.w + log2(max(scatterGain, 1e-4)), 0.0, ou.lens.w);
|
|
3476
|
+
let scatterLod = clamp(ou.size.w + log2(max(ou.size.x, 1e-4)), 0.0, ou.lens.w);
|
|
2722
3477
|
|
|
2723
3478
|
var backdrop = vec3f(0.0);
|
|
2724
3479
|
if (ou.flags.x > 0.5) {
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
let
|
|
3480
|
+
// Both components at the refracted position (W11c G2): the band and the
|
|
3481
|
+
// interior are one body, and the displacement alone is the lens.
|
|
3482
|
+
let bodySample = textureSampleLevel(backdropBody, backdropSampler, refractedUv, 0.0);
|
|
3483
|
+
let scatterSample = textureSampleLevel(backdropChain, backdropSampler, refractedUv, scatterLod);
|
|
2728
3484
|
// Premultiplied linear in, straight colour out: the material composites over
|
|
2729
3485
|
// whatever is behind it, so a partially transparent backdrop must not darken
|
|
2730
3486
|
// the glass.
|
|
2731
|
-
let lensColour = lensSample.rgb / max(lensSample.a, 1e-6);
|
|
2732
3487
|
let scatterColour = scatterSample.rgb / max(scatterSample.a, 1e-6);
|
|
2733
|
-
|
|
2734
|
-
backdrop = mix(bodyColour, lensColour, profile);
|
|
3488
|
+
backdrop = mix(bodySample.rgb / max(bodySample.a, 1e-6), scatterColour, kScatter);
|
|
2735
3489
|
}
|
|
2736
3490
|
|
|
2737
3491
|
// Adaptive tint. 'adapt.w' is the strength the accessibility policy and the
|
|
@@ -2739,8 +3493,6 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
|
|
|
2739
3493
|
// is what a 'hint' or 'none' group gets.
|
|
2740
3494
|
let neutral = mix(ou.tint.rgb, ou.adapt.rgb, ou.adapt.w);
|
|
2741
3495
|
|
|
2742
|
-
let backdropLuma = dot(backdrop, vec3f(0.2126, 0.7152, 0.0722));
|
|
2743
|
-
|
|
2744
3496
|
/*
|
|
2745
3497
|
* Backdrop tone adaptation (W7) \u2014 step two of the composition contract, between
|
|
2746
3498
|
* the colour scheme's neutral and the author's tint.
|
|
@@ -2785,57 +3537,148 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
|
|
|
2785
3537
|
* and 'adaptedTintAlpha' in material.ts.
|
|
2786
3538
|
*/
|
|
2787
3539
|
let sizedAlpha = ou.tint.w + ou.size.y * sizeK * (1.0 - ou.tint.w);
|
|
2788
|
-
|
|
2789
|
-
|
|
3540
|
+
|
|
3541
|
+
/*
|
|
3542
|
+
* The backdrop tone response solve (W9) \u2014 the law that owns the interior
|
|
3543
|
+
* MEAN, where the collapse below owns texture and nothing else (claims
|
|
3544
|
+
* \xA75.33). The composite under this mechanism reduces exactly to
|
|
3545
|
+
* mean = (1 \u2212 k)\xB7M\u2080 + k\xB7toneLuma with M\u2080 = (1 \u2212 \u03B1)\xB7bgLinear + \u03B1\xB7L(neutral),
|
|
3546
|
+
* so the neutral's tone is solved in closed form: shift its luma so the
|
|
3547
|
+
* post-collapse mean lands on R(encodedInput, sizeK), the reference's own
|
|
3548
|
+
* measured response. Chroma is untouched \u2014 the shift is achromatic \u2014 and
|
|
3549
|
+
* the author tint still displaces the result per the composition contract.
|
|
3550
|
+
*
|
|
3551
|
+
* Three stand-downs, each measured rather than defensive: the whole axis is
|
|
3552
|
+
* off where no backdrop tone was measured (same gate as the collapse); the
|
|
3553
|
+
* solve's authority fades to zero below the dark anchor, where the only
|
|
3554
|
+
* evidence is the impulse cell the collapse constants were fitted on; and
|
|
3555
|
+
* at k \u2192 1 the collapse owns the pixel outright, so the solve's
|
|
3556
|
+
* extrapolation is never evaluated against a vanishing (1 \u2212 k).
|
|
3557
|
+
*/
|
|
3558
|
+
var solvedNeutral = neutral;
|
|
3559
|
+
var solvedAlpha = sizedAlpha;
|
|
3560
|
+
if (ou.flags.x > 0.5 && ou.toneAdapt.w > 0.0 && ou.toneRowThin.w > 0.0 &&
|
|
3561
|
+
sizedAlpha > 1e-3 && toneAdapt < 0.995) {
|
|
3562
|
+
let encodedInput = srgb_encode(ou.toneColour.w);
|
|
3563
|
+
let anchor = max(ou.toneAnchor.x, 1e-4);
|
|
3564
|
+
let authority =
|
|
3565
|
+
smoothstep(anchor * 0.5, anchor, encodedInput) * clamp(ou.toneRowThin.w, 0.0, 1.0);
|
|
3566
|
+
if (authority > 0.0) {
|
|
3567
|
+
let response = tone_response(encodedInput, sizeK);
|
|
3568
|
+
// The collapse's mean pull is toward L(toneColour.rgb) \u2014 the LINEAR
|
|
3569
|
+
// mean, which toneAnchor.w carries \u2014 not toward the encoded level.
|
|
3570
|
+
let preCollapse = (response - toneAdapt * ou.toneAnchor.w) / (1.0 - toneAdapt);
|
|
3571
|
+
let neutralLuma = dot(neutral, vec3f(0.2126, 0.7152, 0.0722));
|
|
3572
|
+
let nominal = (1.0 - sizedAlpha) * ou.toneAnchor.w + sizedAlpha * neutralLuma;
|
|
3573
|
+
let shift = (preCollapse - nominal) / sizedAlpha * authority * ou.toneAdapt.w;
|
|
3574
|
+
solvedNeutral = clamp(neutral + vec3f(shift), vec3f(0.0), vec3f(1.0));
|
|
3575
|
+
/*
|
|
3576
|
+
* The light attractor needs OPACITY. The light scheme's neutral is
|
|
3577
|
+
* already at white, so an upward shift clamps to nothing \u2014 and the
|
|
3578
|
+
* reference's light-adapted state is the material gone opaque-bright,
|
|
3579
|
+
* the same "an adapting material stops transmitting" the collapse's
|
|
3580
|
+
* alpha half was measured on. Whatever the clamp truncated is carried
|
|
3581
|
+
* by the alpha, solved against the same composite and folded by the
|
|
3582
|
+
* same authority. One-sided by design: darkward opacity is the
|
|
3583
|
+
* collapse's own axis with its own fitted constants.
|
|
3584
|
+
*/
|
|
3585
|
+
let solvedLuma = dot(solvedNeutral, vec3f(0.2126, 0.7152, 0.0722));
|
|
3586
|
+
let achieved = (1.0 - sizedAlpha) * ou.toneAnchor.w + sizedAlpha * solvedLuma;
|
|
3587
|
+
if (preCollapse > achieved + 1e-4 && solvedLuma > ou.toneAnchor.w + 1e-3) {
|
|
3588
|
+
let alphaTarget = clamp(
|
|
3589
|
+
(preCollapse - ou.toneAnchor.w) / (solvedLuma - ou.toneAnchor.w),
|
|
3590
|
+
sizedAlpha,
|
|
3591
|
+
1.0,
|
|
3592
|
+
);
|
|
3593
|
+
solvedAlpha = mix(sizedAlpha, alphaTarget, authority * ou.toneAdapt.w);
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
let adaptedAlpha = solvedAlpha + toneAdapt * (1.0 - solvedAlpha);
|
|
3599
|
+
var adapted = solvedNeutral;
|
|
2790
3600
|
if (toneAdapt > 0.0 && adaptedAlpha > 0.0) {
|
|
2791
3601
|
adapted =
|
|
2792
|
-
(
|
|
3602
|
+
(solvedNeutral * ((1.0 - toneAdapt) * solvedAlpha) + ou.toneColour.rgb * toneAdapt) /
|
|
3603
|
+
adaptedAlpha;
|
|
2793
3604
|
}
|
|
2794
3605
|
|
|
2795
|
-
// The
|
|
2796
|
-
//
|
|
2797
|
-
//
|
|
2798
|
-
//
|
|
2799
|
-
//
|
|
2800
|
-
//
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
3606
|
+
// The material, untinted: the adapted neutral over what this pixel looks
|
|
3607
|
+
// through, at the material's own occlusion. That alpha is what reduced
|
|
3608
|
+
// transparency lifts and what the size law thickens ("a larger size is more
|
|
3609
|
+
// opaque" is a statement about how much material there is), and the backdrop
|
|
3610
|
+
// adaptation moved it above. The author's colour never touches it \u2014 but not
|
|
3611
|
+
// for the reason the composition contract first gave (claims \xA75.36).
|
|
3612
|
+
var colour = mix(backdrop, adapted, adaptedAlpha);
|
|
3613
|
+
/*
|
|
3614
|
+
* How much of this pixel the SURFACE owns, as the canvas will composite it.
|
|
3615
|
+
*
|
|
3616
|
+
* With a backdrop the composite above is the whole pixel \u2014 the pass sampled
|
|
3617
|
+
* what is behind the surface and mixed it in, so the output is opaque inside
|
|
3618
|
+
* the contour and the page beneath the canvas never shows. With no backdrop
|
|
3619
|
+
* the composite is not here to make: a 'css-backdrop' group's blurred
|
|
3620
|
+
* backdrop is a DOM proxy under this canvas, a 'none' group's is the page
|
|
3621
|
+
* itself, and the browser is the compositor. So the material leaves as a
|
|
3622
|
+
* LAYER \u2014 the adapted colour at the material's own alpha, premultiplied on
|
|
3623
|
+
* the way out \u2014 and every term below that would have shaped the composite
|
|
3624
|
+
* shapes the layer instead, in the form that composites to the same thing.
|
|
3625
|
+
*
|
|
3626
|
+
* Before W11a this path mixed the material over a black backdrop and wrote
|
|
3627
|
+
* it opaque: a nested surface over glass rendered as a flat 0.468 where the
|
|
3628
|
+
* reference reads 0.89 (claims \xA75.38 \xA75). The rows that floored were that
|
|
3629
|
+
* grey. The pair the layer is written at is the host's
|
|
3630
|
+
* ('GroupRenderInput.unsampledMaterial'): the browser composites this canvas
|
|
3631
|
+
* in encoded sRGB, so the alpha is the CSS tier's, not the linear profile's.
|
|
3632
|
+
*/
|
|
3633
|
+
var bodyAlpha = 1.0;
|
|
3634
|
+
if (ou.flags.x <= 0.5) {
|
|
3635
|
+
colour = adapted;
|
|
3636
|
+
bodyAlpha = adaptedAlpha;
|
|
2824
3637
|
}
|
|
2825
3638
|
|
|
2826
|
-
// The tint
|
|
2827
|
-
//
|
|
2828
|
-
//
|
|
2829
|
-
//
|
|
3639
|
+
// The author tint (W10). 'aux.w' is the per-pixel strength, unioned in the
|
|
3640
|
+
// field pass, so a toolbar can carry one tinted control among plain ones; the
|
|
3641
|
+
// seed is a group uniform. At strength 0 this is the identity and the material
|
|
3642
|
+
// is the one the calibration bed measures, byte for byte.
|
|
2830
3643
|
//
|
|
2831
|
-
//
|
|
2832
|
-
//
|
|
2833
|
-
//
|
|
2834
|
-
// the
|
|
2835
|
-
//
|
|
3644
|
+
// Apple's mechanism, measured per pixel: the tinted material is an OPAQUE
|
|
3645
|
+
// layer of the seed at a shade that is linear in the luminance the untinted
|
|
3646
|
+
// material shows at this pixel \u2014 about half the seed's light over black
|
|
3647
|
+
// content, the seed itself over white, the seed's chromaticity intact
|
|
3648
|
+
// throughout. That layer composites over the material at the AUTHOR's
|
|
3649
|
+
// opacity in the encoded space, which is what a CALayer with 'opacity' does
|
|
3650
|
+
// and how the reference's half-strength cell measures. So a tinted surface
|
|
3651
|
+
// over a checkerboard shows the checker as light and dark ORANGE, not as
|
|
3652
|
+
// orange glass with the checker behind it \u2014 the material's own alpha is not
|
|
3653
|
+
// what a tinted surface shows.
|
|
2836
3654
|
//
|
|
2837
|
-
//
|
|
2838
|
-
|
|
3655
|
+
// 'seed.w' is the contrast regime's grip on the excursion, never on the hue;
|
|
3656
|
+
// 'tone.z' is the profile's provenance gate (the dark scheme renders the pure
|
|
3657
|
+
// seed); and '1 \u2212 toneAdapt' folds the shade out where the collapse has made
|
|
3658
|
+
// the material a dark body, where the reference renders the pure seed too.
|
|
3659
|
+
// At zero grip the layer is the bare seed \u2014 the author's colour, flat.
|
|
3660
|
+
if (aux.w > 0.0) {
|
|
3661
|
+
// The untinted material's luminance at this pixel. Over a backdrop that is
|
|
3662
|
+
// the composite; as a layer it is the layer over the tone the host measured
|
|
3663
|
+
// for the group \u2014 zero where nothing was measured, the same reference-level
|
|
3664
|
+
// convention the CSS tier's 'materialLuminance' takes (within its 0.02).
|
|
3665
|
+
let u = bodyAlpha * dot(colour, vec3f(0.2126, 0.7152, 0.0722)) + (1.0 - bodyAlpha) * ou.toneColour.w;
|
|
3666
|
+
let grip = clamp(ou.seed.w, 0.0, 1.0) * clamp(ou.tone.z, 0.0, 1.0) * (1.0 - toneAdapt);
|
|
3667
|
+
let shade = mix(1.0, clamp(mix(ou.tone.x, ou.tone.y, clamp(u, 0.0, 1.0)), 0.0, 1.0), grip);
|
|
3668
|
+
let layer = ou.seed.rgb * shade;
|
|
3669
|
+
let s = clamp(aux.w, 0.0, 1.0);
|
|
3670
|
+
let encodedMaterial = linear_to_srgb(clamp(colour, vec3f(0.0), vec3f(1.0)));
|
|
3671
|
+
let encodedLayer = linear_to_srgb(layer);
|
|
3672
|
+
if (ou.flags.x > 0.5) {
|
|
3673
|
+
colour = srgb_to_linear(mix(encodedMaterial, encodedLayer, vec3f(s)));
|
|
3674
|
+
} else {
|
|
3675
|
+
// The opaque layer at the author's opacity over the material's own layer,
|
|
3676
|
+
// premultiplied: the fold 'tintedCssOptics' makes into one rgba.
|
|
3677
|
+
let premultiplied = (1.0 - s) * bodyAlpha * encodedMaterial + s * encodedLayer;
|
|
3678
|
+
bodyAlpha = 1.0 - (1.0 - s) * (1.0 - bodyAlpha);
|
|
3679
|
+
colour = srgb_to_linear(premultiplied / max(bodyAlpha, 1e-6));
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
2839
3682
|
|
|
2840
3683
|
/*
|
|
2841
3684
|
* Everything below is the surface's own APPEARANCE \u2014 the marks that say a
|
|
@@ -2861,21 +3704,51 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
|
|
|
2861
3704
|
// strongest, which is what makes a thicker surface read as heavier \u2014 and the
|
|
2862
3705
|
// shadow facet of the size law deepens it further with the span.
|
|
2863
3706
|
let shadowDepth = ou.light.z * (1.0 + (ou.size.z - 1.0) * sizeK);
|
|
2864
|
-
|
|
3707
|
+
// A multiplicative occlusion of the whole composite. As a layer that is the
|
|
3708
|
+
// colour scaled and the alpha raised \u2014 (k\xB7a\xB7c, 1 \u2212 k\xB7(1 \u2212 a)) composites to
|
|
3709
|
+
// k times what (a\xB7c, a) would \u2014 and at bodyAlpha 1 it is the plain product.
|
|
3710
|
+
let shadowKeep = 1.0 - shadowProfile * shadowDepth * ou.light.w * present;
|
|
3711
|
+
let shadowedAlpha = 1.0 - shadowKeep * (1.0 - bodyAlpha);
|
|
3712
|
+
colour = colour * (shadowKeep * bodyAlpha / max(shadowedAlpha, 1e-6));
|
|
3713
|
+
bodyAlpha = shadowedAlpha;
|
|
2865
3714
|
|
|
2866
3715
|
// Rim and specular from the gradient. The rim is unlit ambient edge brightness;
|
|
2867
3716
|
// the specular term is the same edge lit from 'light.xy'.
|
|
2868
3717
|
let rw = rim_weight(d, ou.rim.x);
|
|
2869
3718
|
let facing = dot(normal, ou.light.xy);
|
|
2870
3719
|
let spec = pow(clamp(facing, 0.0, 1.0), max(ou.rim.z, 1e-3)) * ou.rim.w;
|
|
2871
|
-
|
|
3720
|
+
let rim = rw * (ou.rim.y + spec) * present;
|
|
3721
|
+
if (ou.flags.x > 0.5) {
|
|
3722
|
+
colour = colour + vec3f(rim);
|
|
3723
|
+
} else {
|
|
3724
|
+
// Added light has no premultiplied form of its own \u2014 a canvas colour may
|
|
3725
|
+
// not exceed its alpha \u2014 so the layer carries the light in its opacity:
|
|
3726
|
+
// (a\xB7c + rim, a + rim) composites to dst\xB7(1 \u2212 a) + a\xB7c + rim \u2212 rim\xB7dst,
|
|
3727
|
+
// the additive term short of rim \xD7 dst. Exact wherever the layer is
|
|
3728
|
+
// opaque (an author tint at full strength, or a lit contour pixel whose
|
|
3729
|
+
// alpha the rim fills), and never further off than the rim times what
|
|
3730
|
+
// shows through \u2014 where the mix form ("white over the layer at the rim's
|
|
3731
|
+
// weight") is short by the rim times the whole composite, which halved
|
|
3732
|
+
// the rim on a tinted panel.
|
|
3733
|
+
let carried = clamp(bodyAlpha + rim, 0.0, 1.0);
|
|
3734
|
+
colour = min((colour * bodyAlpha + vec3f(rim)) / max(carried, 1e-6), vec3f(1.0));
|
|
3735
|
+
bodyAlpha = carried;
|
|
3736
|
+
}
|
|
2872
3737
|
|
|
2873
3738
|
// The material over its own shadow over the page, in one premultiplied vector:
|
|
2874
|
-
// the colour is the surface's, weighted by its coverage
|
|
2875
|
-
// the
|
|
2876
|
-
// the shadow
|
|
2877
|
-
|
|
2878
|
-
|
|
3739
|
+
// the colour is the surface's, weighted by its coverage and by however much of
|
|
3740
|
+
// the pixel the layer owns, and the alpha is what the two of them together
|
|
3741
|
+
// leave the page. The shadow fills only what the surface's COVERAGE leaves \u2014
|
|
3742
|
+
// clipped out of the silhouette exactly as a 'box-shadow' is clipped out of
|
|
3743
|
+
// its border box \u2014 never what the layer's own transparency leaves: a
|
|
3744
|
+
// translucent surface shows the page through it, not its own shadow. (With
|
|
3745
|
+
// an opaque body the two are the same quantity, which is how the shadow was
|
|
3746
|
+
// first written and why the difference only surfaced with the layer form.)
|
|
3747
|
+
let body = encode_output(max(colour, vec3f(0.0)), coverage * bodyAlpha);
|
|
3748
|
+
return vec4f(
|
|
3749
|
+
body.rgb + liftEncoded * (1.0 - coverage),
|
|
3750
|
+
body.a + shadowAlpha * (1.0 - coverage),
|
|
3751
|
+
);
|
|
2879
3752
|
}`;
|
|
2880
3753
|
|
|
2881
3754
|
// ../renderer-webgpu/dist/wgsl/prelude.js
|
|
@@ -3003,7 +3876,11 @@ function createPassRunner(context) {
|
|
|
3003
3876
|
fragment: {
|
|
3004
3877
|
module,
|
|
3005
3878
|
entryPoint: "fs_field",
|
|
3006
|
-
targets: [
|
|
3879
|
+
targets: [
|
|
3880
|
+
{ format: WORKING_TEXTURE_FORMAT },
|
|
3881
|
+
{ format: WORKING_TEXTURE_FORMAT },
|
|
3882
|
+
{ format: WORKING_TEXTURE_FORMAT }
|
|
3883
|
+
]
|
|
3007
3884
|
},
|
|
3008
3885
|
primitive: { topology: "triangle-list" }
|
|
3009
3886
|
};
|
|
@@ -3067,6 +3944,13 @@ function createPassRunner(context) {
|
|
|
3067
3944
|
usage: fieldUsage(),
|
|
3068
3945
|
label: `vitrea:group:${args.groupId}:aux`
|
|
3069
3946
|
});
|
|
3947
|
+
const aux2 = pool.acquire(poolKey.groupAux2(args.groupId), {
|
|
3948
|
+
width,
|
|
3949
|
+
height,
|
|
3950
|
+
format: WORKING_TEXTURE_FORMAT,
|
|
3951
|
+
usage: fieldUsage(),
|
|
3952
|
+
label: `vitrea:group:${args.groupId}:aux2`
|
|
3953
|
+
});
|
|
3070
3954
|
const slot = uniformSlot(`field:${args.groupId}`, 12);
|
|
3071
3955
|
slot.data[0] = rectWidth;
|
|
3072
3956
|
slot.data[1] = rectHeight;
|
|
@@ -3100,6 +3984,12 @@ function createPassRunner(context) {
|
|
|
3100
3984
|
loadOp: "clear",
|
|
3101
3985
|
storeOp: "store",
|
|
3102
3986
|
clearValue: { r: 0, g: 0, b: 0, a: 0 }
|
|
3987
|
+
},
|
|
3988
|
+
{
|
|
3989
|
+
view: aux2.createView(),
|
|
3990
|
+
loadOp: "clear",
|
|
3991
|
+
storeOp: "store",
|
|
3992
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 }
|
|
3103
3993
|
}
|
|
3104
3994
|
]
|
|
3105
3995
|
});
|
|
@@ -3116,13 +4006,14 @@ function createPassRunner(context) {
|
|
|
3116
4006
|
return {
|
|
3117
4007
|
field,
|
|
3118
4008
|
aux,
|
|
4009
|
+
aux2,
|
|
3119
4010
|
width,
|
|
3120
4011
|
height,
|
|
3121
4012
|
upsampled: width !== rectWidth || height !== rectHeight
|
|
3122
4013
|
};
|
|
3123
4014
|
},
|
|
3124
4015
|
opticsPass(encoder, args) {
|
|
3125
|
-
const slot = uniformSlot(`optics:${args.groupId}`,
|
|
4016
|
+
const slot = uniformSlot(`optics:${args.groupId}`, 96);
|
|
3126
4017
|
const d = slot.data;
|
|
3127
4018
|
d[0] = args.viewportDevice[0];
|
|
3128
4019
|
d[1] = args.viewportDevice[1];
|
|
@@ -3133,8 +4024,8 @@ function createPassRunner(context) {
|
|
|
3133
4024
|
d[6] = args.fit[2];
|
|
3134
4025
|
d[7] = args.fit[3];
|
|
3135
4026
|
d[8] = args.refractionScale;
|
|
3136
|
-
d[9] = args.
|
|
3137
|
-
d[10] = args.
|
|
4027
|
+
d[9] = args.lensRefractionGain;
|
|
4028
|
+
d[10] = args.shadowDepthSizeGainMax;
|
|
3138
4029
|
d[11] = args.chainMaxLod;
|
|
3139
4030
|
d[12] = args.tint[0];
|
|
3140
4031
|
d[13] = args.tint[1];
|
|
@@ -3148,10 +4039,10 @@ function createPassRunner(context) {
|
|
|
3148
4039
|
d[21] = args.tintSeed[1];
|
|
3149
4040
|
d[22] = args.tintSeed[2];
|
|
3150
4041
|
d[23] = args.tintToneAdaptation;
|
|
3151
|
-
d[24] = args.
|
|
3152
|
-
d[25] = args.
|
|
3153
|
-
d[26] = args.
|
|
3154
|
-
d[27] =
|
|
4042
|
+
d[24] = args.tintShade[0];
|
|
4043
|
+
d[25] = args.tintShade[1];
|
|
4044
|
+
d[26] = args.tintShade[2];
|
|
4045
|
+
d[27] = args.sizeFold;
|
|
3155
4046
|
d[28] = args.rimWidth;
|
|
3156
4047
|
d[29] = args.rimAlpha;
|
|
3157
4048
|
d[30] = args.specularPower;
|
|
@@ -3182,8 +4073,44 @@ function createPassRunner(context) {
|
|
|
3182
4073
|
d[55] = args.outerShadow[3];
|
|
3183
4074
|
d[56] = args.outerShadowSizeGain;
|
|
3184
4075
|
d[57] = args.outerShadowRectCssHeight;
|
|
3185
|
-
d[58] =
|
|
4076
|
+
d[58] = args.sizeScatterRampStartThick;
|
|
3186
4077
|
d[59] = 0;
|
|
4078
|
+
d[60] = args.backdropToneAnchorX[0];
|
|
4079
|
+
d[61] = args.backdropToneAnchorX[1];
|
|
4080
|
+
d[62] = args.backdropToneAnchorX[2];
|
|
4081
|
+
d[63] = args.backdropToneLinearMean;
|
|
4082
|
+
d[64] = args.backdropToneResponseThin[0];
|
|
4083
|
+
d[65] = args.backdropToneResponseThin[1];
|
|
4084
|
+
d[66] = args.backdropToneResponseThin[2];
|
|
4085
|
+
d[67] = args.backdropToneResponseStrength;
|
|
4086
|
+
d[68] = args.backdropToneResponseThick[0];
|
|
4087
|
+
d[69] = args.backdropToneResponseThick[1];
|
|
4088
|
+
d[70] = args.backdropToneResponseThick[2];
|
|
4089
|
+
d[71] = 0;
|
|
4090
|
+
d[72] = args.sizeScatterFloor;
|
|
4091
|
+
d[73] = args.sizeScatterRampStartThin;
|
|
4092
|
+
d[74] = args.sizeSpanMin;
|
|
4093
|
+
d[75] = args.sizeSpanMax;
|
|
4094
|
+
d[76] = args.lensHeightPerSpan;
|
|
4095
|
+
d[77] = args.lensHeightMax;
|
|
4096
|
+
d[78] = args.lensAmountPerSpan;
|
|
4097
|
+
d[79] = args.lensAmountMax;
|
|
4098
|
+
d[80] = args.lensExtentGain;
|
|
4099
|
+
d[81] = args.lensProfileExponent;
|
|
4100
|
+
d[82] = args.lensOvalization;
|
|
4101
|
+
d[83] = args.lensThicknessReference;
|
|
4102
|
+
d[84] = args.lensOvalizationSpanMin;
|
|
4103
|
+
d[85] = args.lensOvalizationSpanMax;
|
|
4104
|
+
d[86] = args.sizeScatterRampReachCssPx;
|
|
4105
|
+
d[87] = args.sizeScatterSpanMax;
|
|
4106
|
+
d[88] = args.outerShadowThick[0];
|
|
4107
|
+
d[89] = args.outerShadowThick[1];
|
|
4108
|
+
d[90] = args.outerShadowThick[2];
|
|
4109
|
+
d[91] = args.sizeScatterRampStartFar;
|
|
4110
|
+
d[92] = args.outerShadowLift[0];
|
|
4111
|
+
d[93] = args.outerShadowLift[1];
|
|
4112
|
+
d[94] = args.outerShadowLift[2];
|
|
4113
|
+
d[95] = args.outerShadowLift[3];
|
|
3187
4114
|
slot.write();
|
|
3188
4115
|
const chain = args.backdrop?.chain ?? placeholderView;
|
|
3189
4116
|
const body = args.backdrop?.body ?? placeholderView;
|
|
@@ -3206,7 +4133,8 @@ function createPassRunner(context) {
|
|
|
3206
4133
|
{ binding: 5, resource: body },
|
|
3207
4134
|
// Linear, no mips: the field targets have one level, and this is only
|
|
3208
4135
|
// read at all when the governor shrank them below the group's rect.
|
|
3209
|
-
{ binding: 6, resource: context.flatSampler }
|
|
4136
|
+
{ binding: 6, resource: context.flatSampler },
|
|
4137
|
+
{ binding: 7, resource: args.fields.aux2.createView() }
|
|
3210
4138
|
]
|
|
3211
4139
|
}));
|
|
3212
4140
|
pass.draw(3);
|
|
@@ -3240,9 +4168,9 @@ function createPassRunner(context) {
|
|
|
3240
4168
|
d[22] = args.backdropTone[2];
|
|
3241
4169
|
d[23] = args.backdropTone[3];
|
|
3242
4170
|
d[24] = args.backdropToneLevel;
|
|
3243
|
-
d[25] =
|
|
3244
|
-
d[26] =
|
|
3245
|
-
d[27] =
|
|
4171
|
+
d[25] = args.sizeSpanMin;
|
|
4172
|
+
d[26] = args.sizeSpanMax;
|
|
4173
|
+
d[27] = args.sizeFold;
|
|
3246
4174
|
const fieldRect = args.fieldRectDevice;
|
|
3247
4175
|
d[28] = args.rectDevice.width / Math.max(fieldRect.width, 1e-6);
|
|
3248
4176
|
d[29] = args.rectDevice.height / Math.max(fieldRect.height, 1e-6);
|
|
@@ -3289,6 +4217,7 @@ function createPassRunner(context) {
|
|
|
3289
4217
|
forget(groupId) {
|
|
3290
4218
|
pool.release(poolKey.groupField(groupId));
|
|
3291
4219
|
pool.release(poolKey.groupAux(groupId));
|
|
4220
|
+
pool.release(poolKey.groupAux2(groupId));
|
|
3292
4221
|
storages.get(groupId)?.destroy();
|
|
3293
4222
|
storages.delete(groupId);
|
|
3294
4223
|
for (const key of [`field:${groupId}`, `optics:${groupId}`, `highlight:${groupId}`]) {
|
|
@@ -3433,6 +4362,9 @@ function createRebuildLedger() {
|
|
|
3433
4362
|
|
|
3434
4363
|
// ../renderer-webgpu/dist/pyramid.js
|
|
3435
4364
|
var chainUsage = () => GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING;
|
|
4365
|
+
var densityOf = (source, request) => texelsPerCssPx(source.width, source.height, request.placement, request.viewportCss[0], request.viewportCss[1]);
|
|
4366
|
+
var same = (next, existing) => Math.abs(next - existing) <= 1e-6 * Math.max(1, Math.abs(existing));
|
|
4367
|
+
var sameBody = (existing, request) => same(densityOf({ width: existing.sourceWidth, height: existing.sourceHeight }, request), existing.texelsPerCss) && same(request.bodySigmaCss, existing.bodySigmaCss);
|
|
3436
4368
|
function createPyramidStore(context) {
|
|
3437
4369
|
const { device, pool, cache } = context;
|
|
3438
4370
|
const resources = /* @__PURE__ */ new Map();
|
|
@@ -3508,7 +4440,7 @@ function createPyramidStore(context) {
|
|
|
3508
4440
|
return target;
|
|
3509
4441
|
};
|
|
3510
4442
|
const mipView = (texture, level) => texture.createView({ baseMipLevel: level, mipLevelCount: 1, dimension: "2d" });
|
|
3511
|
-
function allocate(sourceId, plan, bodyLevel, sizeEpoch, builtEpoch, bodySigmaTexels) {
|
|
4443
|
+
function allocate(sourceId, plan, bodyLevel, sizeEpoch, builtEpoch, bodySigmaTexels, density, bodySigmaCss) {
|
|
3512
4444
|
const existing = resources.get(sourceId);
|
|
3513
4445
|
const bodyWidth = (plan.levels[bodyLevel] ?? plan.levels[0]).width;
|
|
3514
4446
|
const bodyHeight = (plan.levels[bodyLevel] ?? plan.levels[0]).height;
|
|
@@ -3546,7 +4478,11 @@ function createPyramidStore(context) {
|
|
|
3546
4478
|
stats,
|
|
3547
4479
|
sizeEpoch,
|
|
3548
4480
|
builtEpoch,
|
|
3549
|
-
bodySigmaTexels
|
|
4481
|
+
bodySigmaTexels,
|
|
4482
|
+
texelsPerCss: density.texelsPerCss,
|
|
4483
|
+
sourceWidth: density.sourceWidth,
|
|
4484
|
+
sourceHeight: density.sourceHeight,
|
|
4485
|
+
bodySigmaCss
|
|
3550
4486
|
};
|
|
3551
4487
|
resources.set(sourceId, next);
|
|
3552
4488
|
return next;
|
|
@@ -3734,7 +4670,7 @@ function createPyramidStore(context) {
|
|
|
3734
4670
|
},
|
|
3735
4671
|
build(request, provider, encoder) {
|
|
3736
4672
|
const existing = liveResources(request.sourceId);
|
|
3737
|
-
if (existing !== void 0 && existing.builtEpoch >= request.epoch && !provider.isDirty()) {
|
|
4673
|
+
if (existing !== void 0 && existing.builtEpoch >= request.epoch && !provider.isDirty() && sameBody(existing, request)) {
|
|
3738
4674
|
ledger.recordClean();
|
|
3739
4675
|
return { status: "clean", resources: existing };
|
|
3740
4676
|
}
|
|
@@ -3752,12 +4688,11 @@ function createPyramidStore(context) {
|
|
|
3752
4688
|
}
|
|
3753
4689
|
pendingRelease.push(provider);
|
|
3754
4690
|
const plan = planPyramid(frame.width, frame.height, request.resolution);
|
|
3755
|
-
const
|
|
3756
|
-
const cover = viewportW > 0 && viewportH > 0 ? Math.max(frame.width / viewportW, frame.height / viewportH) : 1;
|
|
4691
|
+
const texelsPerCss = densityOf(frame, request);
|
|
3757
4692
|
const planScale = frame.width > 0 ? plan.width / frame.width : 1;
|
|
3758
|
-
const bodySigmaTexels = request.bodySigmaCss *
|
|
4693
|
+
const bodySigmaTexels = request.bodySigmaCss * texelsPerCss * planScale;
|
|
3759
4694
|
const bodyPlan = bodyBlurPlan(bodySigmaTexels, plan);
|
|
3760
|
-
const target = allocate(request.sourceId, plan, bodyPlan.level, frame.sizeEpoch, request.epoch, bodySigmaTexels);
|
|
4695
|
+
const target = allocate(request.sourceId, plan, bodyPlan.level, frame.sizeEpoch, request.epoch, bodySigmaTexels, { texelsPerCss, sourceWidth: frame.width, sourceHeight: frame.height }, request.bodySigmaCss);
|
|
3761
4696
|
runImport(encoder, request.sourceId, frame, target.chain);
|
|
3762
4697
|
runChain(encoder, request.sourceId, plan, target.chain);
|
|
3763
4698
|
runBodyBlur(encoder, request.sourceId, plan, target.chain, target.body, bodyPlan.level, bodyPlan.residualSigmaTexels);
|
|
@@ -3873,8 +4808,11 @@ var RENDERER_PASS_IDS = [
|
|
|
3873
4808
|
OPTICS_PASS_ID,
|
|
3874
4809
|
HIGHLIGHT_PASS_ID
|
|
3875
4810
|
];
|
|
4811
|
+
var STANDALONE_RESOLUTION = { scale: 1, maxDimension: 2048 };
|
|
3876
4812
|
function createWebGPURenderer(options = {}) {
|
|
3877
4813
|
const providers = /* @__PURE__ */ new Map();
|
|
4814
|
+
const placements = /* @__PURE__ */ new Map();
|
|
4815
|
+
const lastResolution = /* @__PURE__ */ new Map();
|
|
3878
4816
|
const groups = /* @__PURE__ */ new Map();
|
|
3879
4817
|
const adaptation = /* @__PURE__ */ new Map();
|
|
3880
4818
|
const lastReadbackAt = /* @__PURE__ */ new Map();
|
|
@@ -3963,18 +4901,48 @@ function createWebGPURenderer(options = {}) {
|
|
|
3963
4901
|
}
|
|
3964
4902
|
return state;
|
|
3965
4903
|
};
|
|
3966
|
-
const
|
|
3967
|
-
const
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
4904
|
+
const fitFor = (sourceId, pyramid) => {
|
|
4905
|
+
const placement = placements.get(sourceId);
|
|
4906
|
+
return isUsablePlacement(placement) ? placementFit(placement, viewport.widthCss, viewport.heightCss) : coverFit(pyramid.plan.width, pyramid.plan.height, viewport.widthCss, viewport.heightCss);
|
|
4907
|
+
};
|
|
4908
|
+
const liftChainLod = (sigmaCss, pyramid) => {
|
|
4909
|
+
if (pyramid === void 0 || !(sigmaCss > 0))
|
|
4910
|
+
return 0;
|
|
4911
|
+
const planScale = pyramid.sourceWidth > 0 ? pyramid.plan.width / pyramid.sourceWidth : 1;
|
|
4912
|
+
const sigmaTexels = sigmaCss * pyramid.texelsPerCss * planScale;
|
|
4913
|
+
return Math.min(chainLodForSigma(sigmaTexels), pyramid.plan.maxLod);
|
|
4914
|
+
};
|
|
4915
|
+
const staleBodyRebuilds = (explicit, pyramids) => {
|
|
4916
|
+
const requests = [];
|
|
4917
|
+
const named = new Set(explicit.map((request) => request.sourceId));
|
|
4918
|
+
for (const sourceId of providers.keys()) {
|
|
4919
|
+
if (named.has(sourceId))
|
|
4920
|
+
continue;
|
|
4921
|
+
const existing = pyramids.resources(sourceId);
|
|
4922
|
+
if (existing === void 0)
|
|
4923
|
+
continue;
|
|
4924
|
+
const next = texelsPerCssPx(existing.sourceWidth, existing.sourceHeight, placements.get(sourceId), viewport.widthCss, viewport.heightCss);
|
|
4925
|
+
const sameDensity = Math.abs(next - existing.texelsPerCss) <= 1e-6 * Math.max(1, existing.texelsPerCss);
|
|
4926
|
+
const sigmaCss = bodySigmaCssFor(sourceId);
|
|
4927
|
+
const sameSigma = Math.abs(sigmaCss - existing.bodySigmaCss) <= 1e-6 * Math.max(1, existing.bodySigmaCss);
|
|
4928
|
+
if (sameDensity && sameSigma)
|
|
4929
|
+
continue;
|
|
4930
|
+
requests.push({
|
|
4931
|
+
sourceId,
|
|
4932
|
+
epoch: existing.builtEpoch,
|
|
4933
|
+
resolution: lastResolution.get(sourceId) ?? STANDALONE_RESOLUTION,
|
|
4934
|
+
groupIds: [...groups.values()].filter((entry) => entry.input.backdropSourceId === sourceId).map((entry) => entry.input.groupId)
|
|
4935
|
+
});
|
|
3972
4936
|
}
|
|
3973
|
-
|
|
3974
|
-
return [1, scaleY, 0, (1 - scaleY) / 2];
|
|
4937
|
+
return requests;
|
|
3975
4938
|
};
|
|
3976
4939
|
const unionOf = (input) => input.union ?? DEFAULT_GROUP_UNION;
|
|
3977
4940
|
const variantOf = (input) => input.variant ?? "regular";
|
|
4941
|
+
const sourceVariant = (sourceId) => {
|
|
4942
|
+
const input = [...groups.values()].find((entry) => entry.input.backdropSourceId === sourceId)?.input;
|
|
4943
|
+
return input === void 0 ? "regular" : variantOf(input);
|
|
4944
|
+
};
|
|
4945
|
+
const bodySigmaCssFor = (sourceId) => opticsUnderPolicy(material.optics[sourceVariant(sourceId)], accessibility, material).blurSigma;
|
|
3978
4946
|
const groupTintSeed = (input) => input.surfaces.find((surface) => surface.fieldReferenceOnly !== true && (surface.tint?.strength ?? 0) > 0)?.tint?.color;
|
|
3979
4947
|
const stateOf = (input, resolution) => {
|
|
3980
4948
|
const resolved = resolution?.groups.find((group) => group.groupId === input.groupId);
|
|
@@ -3987,8 +4955,12 @@ function createWebGPURenderer(options = {}) {
|
|
|
3987
4955
|
};
|
|
3988
4956
|
};
|
|
3989
4957
|
function rebuildRequests(explicit) {
|
|
3990
|
-
|
|
3991
|
-
|
|
4958
|
+
const base = explicit ?? standaloneRequests();
|
|
4959
|
+
const { store: pyramids } = ensureContext();
|
|
4960
|
+
const stale = staleBodyRebuilds(base, pyramids);
|
|
4961
|
+
return stale.length === 0 ? base : [...base, ...stale];
|
|
4962
|
+
}
|
|
4963
|
+
function standaloneRequests() {
|
|
3992
4964
|
const sampled = /* @__PURE__ */ new Set();
|
|
3993
4965
|
for (const entry of groups.values()) {
|
|
3994
4966
|
const id = entry.input.backdropSourceId;
|
|
@@ -4006,7 +4978,7 @@ function createWebGPURenderer(options = {}) {
|
|
|
4006
4978
|
// keeping the books, so a monotonically rising number is the honest
|
|
4007
4979
|
// stand-in and it keeps `build`'s clean-skip from firing spuriously.
|
|
4008
4980
|
epoch: framesDrawn + 1,
|
|
4009
|
-
resolution:
|
|
4981
|
+
resolution: STANDALONE_RESOLUTION,
|
|
4010
4982
|
groupIds: [...groups.values()].filter((entry) => entry.input.backdropSourceId === sourceId).map((entry) => entry.input.groupId)
|
|
4011
4983
|
});
|
|
4012
4984
|
}
|
|
@@ -4022,14 +4994,18 @@ function createWebGPURenderer(options = {}) {
|
|
|
4022
4994
|
unbuilt.push(request.sourceId);
|
|
4023
4995
|
continue;
|
|
4024
4996
|
}
|
|
4025
|
-
|
|
4026
|
-
const
|
|
4997
|
+
lastResolution.set(request.sourceId, request.resolution);
|
|
4998
|
+
const placement = placements.get(request.sourceId);
|
|
4027
4999
|
const outcome = pyramids.build({
|
|
4028
5000
|
sourceId: request.sourceId,
|
|
4029
5001
|
epoch: request.epoch,
|
|
4030
5002
|
resolution: request.resolution,
|
|
4031
|
-
|
|
4032
|
-
|
|
5003
|
+
// The body's sharp width in CSS px at every device scale (W13 Decision
|
|
5004
|
+
// Log 8); the heavy component rides the same conversion — its level
|
|
5005
|
+
// is this one plus log2(sizeScatterGainMax) octaves.
|
|
5006
|
+
bodySigmaCss: bodySigmaCssFor(request.sourceId),
|
|
5007
|
+
viewportCss: [viewport.widthCss, viewport.heightCss],
|
|
5008
|
+
...isUsablePlacement(placement) ? { placement } : {}
|
|
4033
5009
|
}, provider, encoder);
|
|
4034
5010
|
if (outcome.status === "built")
|
|
4035
5011
|
built += 1;
|
|
@@ -4078,11 +5054,12 @@ function createWebGPURenderer(options = {}) {
|
|
|
4078
5054
|
}
|
|
4079
5055
|
const union = unionOf(input);
|
|
4080
5056
|
const shadow = outerShadowUnderPolicy(policy, material);
|
|
4081
|
-
|
|
5057
|
+
const shadowBackdropLuminance = input.backdropTone === void 0 ? void 0 : input.backdropToneLevel ?? relativeLuminance(input.backdropTone);
|
|
5058
|
+
let reachOcclusion = outerShadowThinOcclusion(shadowBackdropLuminance, shadow);
|
|
4082
5059
|
for (const surface of surfaces) {
|
|
4083
|
-
reachOcclusion = Math.max(reachOcclusion,
|
|
5060
|
+
reachOcclusion = Math.max(reachOcclusion, outerShadowOcclusionAt(shadow, shadowBackdropLuminance, surface.spanPx, surface.sizeThickness, material));
|
|
4084
5061
|
}
|
|
4085
|
-
const shadowReachPx = outerShadowReachPx(
|
|
5062
|
+
const shadowReachPx = outerShadowReachPx(shadow, reachOcclusion);
|
|
4086
5063
|
const snapped = snapRectToDevicePixels(groupFieldRect(surfaces, union, void 0, shadowReachPx), dpr);
|
|
4087
5064
|
const rectDevice = clipFieldRectToCanvas(snapped, dpr, viewportDevice);
|
|
4088
5065
|
if (rectDevice === void 0)
|
|
@@ -4107,19 +5084,24 @@ function createWebGPURenderer(options = {}) {
|
|
|
4107
5084
|
});
|
|
4108
5085
|
const state = stateOf(input, resolution);
|
|
4109
5086
|
const variant = variantOf(input);
|
|
4110
|
-
const
|
|
5087
|
+
const sourceId = input.backdropSourceId;
|
|
5088
|
+
const pyramid = sourceId === void 0 ? void 0 : pyramids.resources(sourceId);
|
|
5089
|
+
const adapt = sourceId === void 0 ? void 0 : adaptationFor(sourceId).values;
|
|
5090
|
+
const nominalOptics = material.optics[variant];
|
|
5091
|
+
const optics = opticsUnderPolicy(pyramid === void 0 && input.unsampledMaterial !== void 0 ? {
|
|
5092
|
+
...nominalOptics,
|
|
5093
|
+
tint: input.unsampledMaterial.tint,
|
|
5094
|
+
tintAlpha: input.unsampledMaterial.tintAlpha
|
|
5095
|
+
} : nominalOptics, policy, material);
|
|
4111
5096
|
const backdropTone = [
|
|
4112
5097
|
material.backdropToneLow,
|
|
4113
5098
|
material.backdropToneHigh,
|
|
4114
5099
|
backdropToneSizeBiasUnderPolicy(policy, material),
|
|
4115
5100
|
input.backdropTone === void 0 ? 0 : backdropToneUnderPolicy(policy, material) * material.backdropToneMax
|
|
4116
5101
|
];
|
|
4117
|
-
const backdropToneLevel =
|
|
5102
|
+
const backdropToneLevel = shadowBackdropLuminance ?? 0;
|
|
4118
5103
|
const refraction = effectiveRefraction(accessibilityRefractionCap(policy), state.refraction);
|
|
4119
5104
|
const refractionScale = material.refractionScale[refraction];
|
|
4120
|
-
const sourceId = input.backdropSourceId;
|
|
4121
|
-
const pyramid = sourceId === void 0 ? void 0 : pyramids.resources(sourceId);
|
|
4122
|
-
const adapt = sourceId === void 0 ? void 0 : adaptationFor(sourceId).values;
|
|
4123
5105
|
passes.opticsPass(encoder, {
|
|
4124
5106
|
groupId: input.groupId,
|
|
4125
5107
|
target: active.optics,
|
|
@@ -4129,10 +5111,22 @@ function createWebGPURenderer(options = {}) {
|
|
|
4129
5111
|
viewportDevice,
|
|
4130
5112
|
cssPerDevice,
|
|
4131
5113
|
coverageRampCss: cssPerDevice,
|
|
4132
|
-
fit: pyramid === void 0 ? [1, 1, 0, 0] :
|
|
5114
|
+
fit: pyramid === void 0 || sourceId === void 0 ? [1, 1, 0, 0] : fitFor(sourceId, pyramid),
|
|
4133
5115
|
refractionScale,
|
|
4134
|
-
|
|
4135
|
-
|
|
5116
|
+
lensRefractionGain: material.lensRefractionGain,
|
|
5117
|
+
// The lens law (W12 G2) and the inner shadow's own depth gain, both
|
|
5118
|
+
// evaluated per pixel in the shader from the thickness and the span.
|
|
5119
|
+
lensHeightPerSpan: material.lensHeightPerSpan,
|
|
5120
|
+
lensHeightMax: material.lensHeightMax,
|
|
5121
|
+
lensAmountPerSpan: material.lensAmountPerSpan,
|
|
5122
|
+
lensAmountMax: material.lensAmountMax,
|
|
5123
|
+
lensThicknessReference: material.lensThicknessReference,
|
|
5124
|
+
lensExtentGain: material.lensExtentGain,
|
|
5125
|
+
lensProfileExponent: material.lensProfileExponent,
|
|
5126
|
+
lensOvalization: material.lensOvalization,
|
|
5127
|
+
lensOvalizationSpanMin: material.lensOvalizationSpanMin,
|
|
5128
|
+
lensOvalizationSpanMax: material.lensOvalizationSpanMax,
|
|
5129
|
+
shadowDepthSizeGainMax: material.lensSizeGainMax,
|
|
4136
5130
|
chainMaxLod: pyramid?.plan.maxLod ?? 0,
|
|
4137
5131
|
tint: optics.tint,
|
|
4138
5132
|
tintAlpha: optics.tintAlpha,
|
|
@@ -4152,12 +5146,7 @@ function createWebGPURenderer(options = {}) {
|
|
|
4152
5146
|
*/
|
|
4153
5147
|
tintSeed: (policy.glass === "none" ? void 0 : groupTintSeed(input)) ?? optics.tint,
|
|
4154
5148
|
tintToneAdaptation: tintToneAdaptation(policy, material),
|
|
4155
|
-
|
|
4156
|
-
material.tintToneFloor,
|
|
4157
|
-
material.tintToneCeilMix,
|
|
4158
|
-
material.tintToneLow,
|
|
4159
|
-
material.tintToneHigh
|
|
4160
|
-
],
|
|
5149
|
+
tintShade: [material.tintShadeDark, material.tintShadeLight, material.tintShadeStrength],
|
|
4161
5150
|
rimWidth: optics.rimWidth,
|
|
4162
5151
|
rimAlpha: optics.rimAlpha,
|
|
4163
5152
|
specularPower: optics.specularPower,
|
|
@@ -4174,6 +5163,28 @@ function createWebGPURenderer(options = {}) {
|
|
|
4174
5163
|
sizeOcclusionGain: material.sizeOcclusionGain,
|
|
4175
5164
|
sizeShadowGainMax: material.sizeShadowGainMax,
|
|
4176
5165
|
bodyChainLod: chainLodForSigma(pyramid?.bodySigmaTexels ?? 0),
|
|
5166
|
+
// The size law's curves (W11c): the shader evaluates the thickness and
|
|
5167
|
+
// the scatter mix per pixel from the span the field carries, under the
|
|
5168
|
+
// same fold `sizeThicknessUnderPolicy` applies on the CPU.
|
|
5169
|
+
sizeSpanMin: material.sizeSpanMin,
|
|
5170
|
+
sizeSpanMax: material.sizeSpanMax,
|
|
5171
|
+
sizeScatterFloor: material.sizeScatterFloor,
|
|
5172
|
+
sizeScatterSpanMax: material.sizeScatterSpanMax,
|
|
5173
|
+
// The body's depth ramp (W13 G1, claims §5.61 §2, §5.64 §5), resolved
|
|
5174
|
+
// from the viewport's own ratio: the profile anchors the start's thin
|
|
5175
|
+
// and thick ends and the reach at dpr 1 and dpr 2, and the CPU
|
|
5176
|
+
// interpolates all three in dpr. The start's SPAN grading stays in the
|
|
5177
|
+
// shader, because a group's members are not one size and the span
|
|
5178
|
+
// arrives per pixel on the field's own channel — so the two anchors go
|
|
5179
|
+
// over and `sizeThickness` is evaluated there. The reach is handed over
|
|
5180
|
+
// in CSS px because the field's depth is in CSS px.
|
|
5181
|
+
sizeScatterRampStartThin: scatterRampStart(dpr, material, 0),
|
|
5182
|
+
sizeScatterRampStartThick: scatterRampStart(dpr, material, material.sizeSpanMax),
|
|
5183
|
+
// The fourth form's far end, at the scatter span curve's top; the shader
|
|
5184
|
+
// declines from the thick end to it along that curve.
|
|
5185
|
+
sizeScatterRampStartFar: scatterRampStart(dpr, material, material.sizeScatterSpanMax),
|
|
5186
|
+
sizeScatterRampReachCssPx: scatterRampReachDevicePx(dpr, material) / dpr,
|
|
5187
|
+
sizeFold: material.refractionScale[accessibilityRefractionCap(policy)],
|
|
4177
5188
|
backdropTone,
|
|
4178
5189
|
backdropToneColour: [
|
|
4179
5190
|
input.backdropTone?.[0] ?? 0,
|
|
@@ -4181,8 +5192,31 @@ function createWebGPURenderer(options = {}) {
|
|
|
4181
5192
|
input.backdropTone?.[2] ?? 0,
|
|
4182
5193
|
backdropToneLevel
|
|
4183
5194
|
],
|
|
5195
|
+
// The response law's anchors (W9) and the linear backdrop mean its
|
|
5196
|
+
// solve composites against. The shader re-derives the encoded input
|
|
5197
|
+
// from `backdropToneLevel`, so the fourth slot carries the linear
|
|
5198
|
+
// mean — the one quantity the solve needs that the tone colour does
|
|
5199
|
+
// not already hold.
|
|
5200
|
+
backdropToneAnchorX: material.backdropToneAnchorX,
|
|
5201
|
+
backdropToneResponseThin: material.backdropToneResponseThin,
|
|
5202
|
+
backdropToneResponseThick: material.backdropToneResponseThick,
|
|
5203
|
+
// The response law rides only the UN-DEGRADED regime, for the same
|
|
5204
|
+
// provenance reason the dark profiles set its strength to 0: the
|
|
5205
|
+
// anchors are standard-light-reference measurements, and the
|
|
5206
|
+
// accessibility references behave differently (nearly opaque, flat in
|
|
5207
|
+
// span — W2). Measured before this gate existed: reduced-transparency
|
|
5208
|
+
// photo ΔE p95 0.007 → 0.048 and cross-tier ratio 0.99 → 0.86. Where
|
|
5209
|
+
// any policy fold touches the tone axis, the solve stands down and the
|
|
5210
|
+
// collapse alone runs, exactly the pre-W9 behaviour those profiles
|
|
5211
|
+
// were fitted on.
|
|
5212
|
+
backdropToneResponseStrength: backdropToneUnderPolicy(policy, material) >= 0.999 ? material.backdropToneResponseStrength : 0,
|
|
5213
|
+
backdropToneLinearMean: input.backdropToneLinearLuminance ?? backdropToneLevel,
|
|
4184
5214
|
outerShadow: [
|
|
4185
|
-
|
|
5215
|
+
// The thin regime's LINEAR occlusion at this group's backdrop (W14
|
|
5216
|
+
// G1). The conversion to the compositing space moved into the shader
|
|
5217
|
+
// with the regimes: it is not linear, so converting one regime here
|
|
5218
|
+
// and blending in the shader would not be the same law.
|
|
5219
|
+
outerShadowThinOcclusion(shadowBackdropLuminance, shadow),
|
|
4186
5220
|
shadow.sigmaPx,
|
|
4187
5221
|
shadow.spreadPx,
|
|
4188
5222
|
// The offset in field-texture UV. The field spans exactly this rect, so
|
|
@@ -4190,6 +5224,19 @@ function createWebGPURenderer(options = {}) {
|
|
|
4190
5224
|
// scaled the rasterisation.
|
|
4191
5225
|
shadow.offsetPx / Math.max(rectDevice.height * cssPerDevice, 1e-6)
|
|
4192
5226
|
],
|
|
5227
|
+
outerShadowThick: [
|
|
5228
|
+
shadow.thickOcclusionAt96,
|
|
5229
|
+
shadow.thickOcclusionAt128,
|
|
5230
|
+
shadow.thickOcclusionAt160
|
|
5231
|
+
],
|
|
5232
|
+
outerShadowLift: [
|
|
5233
|
+
// The lift needs a chain to copy; without one there is nothing to add,
|
|
5234
|
+
// and `hasBackdrop` in the shader says the same thing a second time.
|
|
5235
|
+
pyramid === void 0 || policy.glass === "none" ? 0 : shadow.liftAmplitude,
|
|
5236
|
+
shadow.liftSpanMin,
|
|
5237
|
+
shadow.liftSpanFull,
|
|
5238
|
+
liftChainLod(shadow.liftBlurSigmaCss, pyramid)
|
|
5239
|
+
],
|
|
4193
5240
|
outerShadowSizeGain: shadow.sizeGain,
|
|
4194
5241
|
outerShadowRectCssHeight: rectDevice.height * cssPerDevice,
|
|
4195
5242
|
backdrop: pyramid === void 0 || policy.glass === "none" ? void 0 : {
|
|
@@ -4219,9 +5266,13 @@ function createWebGPURenderer(options = {}) {
|
|
|
4219
5266
|
glowGain: material.glowGain,
|
|
4220
5267
|
colour: optics.highlight,
|
|
4221
5268
|
// The same four numbers the optics pass took, so the two passes fade
|
|
4222
|
-
// the surface on one curve rather than on two copies of it
|
|
5269
|
+
// the surface on one curve rather than on two copies of it — and the
|
|
5270
|
+
// same band and fold, since the curve is evaluated off the span.
|
|
4223
5271
|
backdropTone,
|
|
4224
|
-
backdropToneLevel
|
|
5272
|
+
backdropToneLevel,
|
|
5273
|
+
sizeSpanMin: material.sizeSpanMin,
|
|
5274
|
+
sizeSpanMax: material.sizeSpanMax,
|
|
5275
|
+
sizeFold: material.refractionScale[accessibilityRefractionCap(policy)]
|
|
4225
5276
|
});
|
|
4226
5277
|
}
|
|
4227
5278
|
drawn += 1;
|
|
@@ -4286,6 +5337,8 @@ function createWebGPURenderer(options = {}) {
|
|
|
4286
5337
|
providers.set(provider.id, provider);
|
|
4287
5338
|
},
|
|
4288
5339
|
unregisterBackdrop(sourceId) {
|
|
5340
|
+
placements.delete(sourceId);
|
|
5341
|
+
lastResolution.delete(sourceId);
|
|
4289
5342
|
const provider = providers.get(sourceId);
|
|
4290
5343
|
if (provider === void 0)
|
|
4291
5344
|
return;
|
|
@@ -4298,6 +5351,17 @@ function createWebGPURenderer(options = {}) {
|
|
|
4298
5351
|
backdrop(sourceId) {
|
|
4299
5352
|
return providers.get(sourceId);
|
|
4300
5353
|
},
|
|
5354
|
+
setBackdropPlacement(sourceId, placement) {
|
|
5355
|
+
if (samePlacement(placements.get(sourceId), placement))
|
|
5356
|
+
return;
|
|
5357
|
+
if (placement === void 0)
|
|
5358
|
+
placements.delete(sourceId);
|
|
5359
|
+
else
|
|
5360
|
+
placements.set(sourceId, { ...placement });
|
|
5361
|
+
},
|
|
5362
|
+
backdropPlacement(sourceId) {
|
|
5363
|
+
return placements.get(sourceId);
|
|
5364
|
+
},
|
|
4301
5365
|
setViewport(next) {
|
|
4302
5366
|
const changed = next.widthCss !== viewport.widthCss || next.heightCss !== viewport.heightCss || next.devicePixelRatio !== viewport.devicePixelRatio;
|
|
4303
5367
|
viewport = next;
|
|
@@ -4454,6 +5518,6 @@ function createWebGPURenderer(options = {}) {
|
|
|
4454
5518
|
};
|
|
4455
5519
|
}
|
|
4456
5520
|
|
|
4457
|
-
export { ADAPTIVE_LUMINANCE_HIGH, ADAPTIVE_LUMINANCE_LOW, ADAPTIVE_TINT_DARK, ADAPTIVE_TINT_LIGHT, ANALYSIS_DISPATCH, ANALYSIS_GRID, ANALYSIS_PASS_ID, ANALYSIS_STATS_FLOATS, ANALYSIS_TARGET_EXTENT, ANALYSIS_WORKGROUP, BACKDROP_ALPHA_MODES, BACKDROP_COLOR_SPACES, BACKDROP_KINDS, BACKDROP_PASS_ID, BACKDROP_TONE_HIGH, BACKDROP_TONE_LOW, BACKDROP_TONE_MAX, BACKDROP_TONE_SIZE_BIAS, CANVAS_FORMAT, CHAIN_SIGMA_AT_LEVEL_1, CROSS_CHECK_SHAPE_FLOATS, CROSS_CHECK_WORKGROUP, DEFAULT_MATERIAL_PROFILE, FAMILY_C_CROSS_CHECK, FIELD_PASS_ID, GOVERNOR_LADDER, HIGHLIGHT_PASS_ID, IDLE_CHANNELS, INCREASED_OCCLUSION_LIFT, INSTANCE_BYTES, INSTANCE_FLOATS,
|
|
4458
|
-
//# sourceMappingURL=dist-
|
|
4459
|
-
//# sourceMappingURL=dist-
|
|
5521
|
+
export { ADAPTIVE_LUMINANCE_HIGH, ADAPTIVE_LUMINANCE_LOW, ADAPTIVE_TINT_DARK, ADAPTIVE_TINT_LIGHT, ANALYSIS_DISPATCH, ANALYSIS_GRID, ANALYSIS_PASS_ID, ANALYSIS_STATS_FLOATS, ANALYSIS_TARGET_EXTENT, ANALYSIS_WORKGROUP, BACKDROP_ALPHA_MODES, BACKDROP_COLOR_SPACES, BACKDROP_KINDS, BACKDROP_PASS_ID, BACKDROP_TONE_HIGH, BACKDROP_TONE_LOW, BACKDROP_TONE_MAX, BACKDROP_TONE_SIZE_BIAS, CANVAS_FORMAT, CHAIN_SIGMA_AT_LEVEL_1, CROSS_CHECK_SHAPE_FLOATS, CROSS_CHECK_WORKGROUP, DEFAULT_MATERIAL_PROFILE, FAMILY_C_CROSS_CHECK, FIELD_PASS_ID, GOVERNOR_LADDER, HIGHLIGHT_PASS_ID, IDENTITY_FIT, IDLE_CHANNELS, INCREASED_OCCLUSION_LIFT, INSTANCE_BYTES, INSTANCE_FLOATS, LENS_AMOUNT_MAX, LENS_AMOUNT_PER_SPAN, LENS_EXTENT_GAIN, LENS_HEIGHT_MAX, LENS_HEIGHT_PER_SPAN, LENS_OVALIZATION, LENS_PROFILE_EXPONENT, LENS_REFRACTION_GAIN, LENS_SIZE_GAIN_MAX, LENS_THICKNESS_REFERENCE, LUMINANCE_WEIGHTS, MATERIAL_OPTICS, MATERIAL_VARIANTS, MAX_CHAIN_LEVELS, MIN_LEVEL_EXTENT, NOMINAL_GOVERNOR, NOMINAL_MATERIAL_POLICY, OPTICS_PASS_ID, OUTER_SHADOW, OUTER_SHADOW_THICK_SPANS, OUTER_SHADOW_THIN_L, OUTER_SHADOW_UNMEASURED_BACKDROP_LUMINANCE, OUTPUT_TEXTURE_FORMAT, PASS_LABEL, REFRACTION_LADDER, REFRACTION_SCALE, RENDERER_ERROR_CODES, RENDERER_PASS_IDS, RendererError, SIZE_OCCLUSION_GAIN, SIZE_SCATTER_FLOOR, SIZE_SCATTER_GAIN_MAX, SIZE_SCATTER_RAMP_REACH_1X_PX, SIZE_SCATTER_RAMP_REACH_2X_PX, SIZE_SCATTER_RAMP_START_FAR_1X, SIZE_SCATTER_RAMP_START_FAR_2X, SIZE_SCATTER_RAMP_START_THICK_1X, SIZE_SCATTER_RAMP_START_THICK_2X, SIZE_SCATTER_RAMP_START_THIN_1X, SIZE_SCATTER_RAMP_START_THIN_2X, SIZE_SCATTER_SPAN_MAX, SIZE_SHADOW_GAIN_MAX, SIZE_SPAN_MAX, SIZE_SPAN_MIN, SRGB_ENCODING_EXPONENT, SUPPORTED_APP_TEXTURE_FORMATS, WGSL_ANALYSIS_PASS, WGSL_CROSS_CHECK_PASS, WGSL_DOWNSAMPLE_PASS, WGSL_FIELD_KERNELS, WGSL_FIELD_PASS, WGSL_FIELD_SAMPLE, WGSL_HIGHLIGHT_PASS, WGSL_IMPORT_PASS, WGSL_INSTANCE_STRUCT, WGSL_OPTICS_PASS, WGSL_PRELUDE, WGSL_RSUPN_GRAD, WGSL_RSUP_GRAD, WGSL_SMOOTH_UNION, WORKING_TEXTURE_FORMAT, ZERO_STATS, accessibilityRefractionCap, adaptationStrength, adaptedTintAlpha, adaptedTintColour, adaptiveTint, allShaderSource, alphaNormalisationMode, analysisModule, backdropFit, backdropToneAdaptation, backdropToneSizeBiasUnderPolicy, backdropToneUnderPolicy, bodyBlurPlan, chainModule, clipFieldRectToCanvas, coverFit, createAdaptationState, createAppTextureProvider, createCopyProvider, createDeviceHost, createGovernor, createGpuContext, createGradientProvider, createPassRunner, createPipelineCache, createPyramidStore, createRebuildLedger, createStorageSlot, createTexturePool, createTimingCollector, createUniformSlot, createVideoProvider, createWebGPURenderer, crossCheckKernelModule, displayP3ToSrgbLinear, effectiveRefraction, encodeOutput, encodeOutputBytes, fieldModule, fieldPassSource, groupFieldRect, highlightModule, importColorMatrix, importModule, importPassSource, isUsablePlacement, lensAmountBasePx, lensDepthPx, lensDirection, lensDisplacementPx, lensExtentPx, lensHeightBasePx, lensMagnitudePx, lensOvalizationAt, lensSizeGain, lensSizeGainFromThickness, linearGradientStops, linearToSrgb, linearToSrgbChannel, occlusionAlphaUnderPolicy, opticsModule, opticsUnderPolicy, outerShadowAlpha, outerShadowFalloff, outerShadowLiftRise, outerShadowOcclusionAt, outerShadowReachPx, outerShadowThickOcclusion, outerShadowThinOcclusion, outerShadowUnderPolicy, packInstances, pipelineKey, placementFit, planPyramid, poolKey, readbackDue, refractionRank, relativeLuminance, rendererError, resolveSurfaces, samePlacement, scatterDeepThickness, scatterRampAreaMean, scatterRampReachDevicePx, scatterRampStart, scatterSharpShare, scatterThickness, shadowDepthPx, sizeOcclusionAlpha, sizeOcclusionAlphaAt, sizeOuterShadowOcclusion, sizeOuterShadowOcclusionAt, sizeScatterSigma, sizeScatterSigmaAt, sizeShadowDepth, sizeShadowDepthAt, sizeThickness, sizeThicknessUnderPolicy, snapRectToDevicePixels, srgbToLinear, srgbToLinearChannel, statsFromBuffer, supportsTimestamps, texelsPerCssPx, tintShade, tintShadeLayer, tintToneAdaptation, tintedMaterialColour, validateAppTexture, withMaterialOverrides };
|
|
5522
|
+
//# sourceMappingURL=dist-RGWZAZ6J.js.map
|
|
5523
|
+
//# sourceMappingURL=dist-RGWZAZ6J.js.map
|