@driftengine/core 4.5.0 → 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +62 -0
- package/README.md +1 -1
- package/dist/geometry/boxCsg.d.ts +28 -0
- package/dist/geometry/boxCsg.js +139 -0
- package/dist/geometry/csg.d.ts +36 -0
- package/dist/geometry/csg.js +329 -0
- package/dist/geometry/meshBuilder.js +13 -0
- package/dist/geometry/meshConcat.js +1 -0
- package/dist/geometry/pixelFont.js +2 -0
- package/dist/geometry/solid.d.ts +61 -0
- package/dist/geometry/solid.js +152 -0
- package/dist/geometry/solidFlat.d.ts +35 -0
- package/dist/geometry/solidFlat.js +428 -0
- package/dist/geometry/solidHarness.d.ts +19 -0
- package/dist/geometry/solidHarness.js +66 -0
- package/dist/geometry/solidNormals.d.ts +17 -0
- package/dist/geometry/solidNormals.js +86 -0
- package/dist/geometry/solidRound.d.ts +41 -0
- package/dist/geometry/solidRound.js +323 -0
- package/dist/geometry/solidWriter.d.ts +16 -0
- package/dist/geometry/solidWriter.js +42 -0
- package/dist/index.d.ts +23 -2
- package/dist/index.js +13 -1
- package/dist/render/backend/api.d.ts +17 -3
- package/dist/render/backend/drawFog.d.ts +26 -0
- package/dist/render/backend/drawFog.js +30 -0
- package/dist/render/backend/materialChanges.d.ts +2 -1
- package/dist/render/backend/materialChanges.js +6 -1
- package/dist/render/backend/webgl2/instanced.d.ts +24 -5
- package/dist/render/backend/webgl2/instanced.js +72 -15
- package/dist/render/backend/webgl2/renderer.d.ts +122 -8
- package/dist/render/backend/webgl2/renderer.js +249 -128
- package/dist/render/backend/webgpu/buffers.d.ts +17 -0
- package/dist/render/backend/webgpu/buffers.js +34 -3
- package/dist/render/backend/webgpu/depthPass.js +5 -1
- package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +4 -1
- package/dist/render/backend/webgpu/flatPass.js +38 -9
- package/dist/render/backend/webgpu/glassTintPass.js +2 -1
- package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
- package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
- package/dist/render/backend/webgpu/instanced.d.ts +11 -1
- package/dist/render/backend/webgpu/instanced.js +21 -3
- package/dist/render/backend/webgpu/latePass.d.ts +6 -2
- package/dist/render/backend/webgpu/latePass.js +11 -4
- package/dist/render/backend/webgpu/motionPass.js +2 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +100 -10
- package/dist/render/backend/webgpu/renderer.js +478 -214
- package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +17 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +77 -38
- package/dist/render/driftLight/bake.d.ts +7 -0
- package/dist/render/driftLight/bake.js +61 -56
- package/dist/render/driftLight/denseField.d.ts +46 -0
- package/dist/render/driftLight/denseField.js +109 -0
- package/dist/render/driftLight/lightField.d.ts +10 -5
- package/dist/render/driftLight/lightField.js +25 -19
- package/dist/render/driftLight/presence.d.ts +50 -0
- package/dist/render/driftLight/presence.js +43 -0
- package/dist/render/driftLight/uniforms.d.ts +3 -3
- package/dist/render/driftLight/uniforms.js +5 -6
- package/dist/render/driftLight/worldLightField.d.ts +41 -0
- package/dist/render/driftLight/worldLightField.js +66 -0
- package/dist/render/fixtureAtlas.d.ts +20 -0
- package/dist/render/fixtureAtlas.js +49 -0
- package/dist/render/instanceCull.d.ts +63 -0
- package/dist/render/instanceCull.js +160 -0
- package/dist/render/instances.d.ts +15 -0
- package/dist/render/lightBudget.d.ts +22 -33
- package/dist/render/lightBudget.js +24 -35
- package/dist/render/lightGrid.d.ts +42 -0
- package/dist/render/lightGrid.js +87 -0
- package/dist/render/mesh.d.ts +23 -24
- package/dist/render/mesh.js +56 -68
- package/dist/render/occlusion.d.ts +10 -0
- package/dist/render/occlusion.js +36 -7
- package/dist/render/pointLightSelection.d.ts +7 -0
- package/dist/render/pointLightSelection.js +29 -4
- package/dist/render/rainField.d.ts +13 -0
- package/dist/render/rainField.js +27 -5
- package/dist/render/recon/lateDepth.d.ts +11 -0
- package/dist/render/recon/lateDepth.js +70 -0
- package/dist/render/recon/resolve.d.ts +34 -4
- package/dist/render/recon/resolve.js +161 -20
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +2 -0
- package/dist/render/shaders/depth.js +12 -9
- package/dist/render/shaders/flat/driftLight.d.ts +1 -1
- package/dist/render/shaders/flat/driftLight.js +32 -7
- package/dist/render/shaders/flat/index.js +5 -3
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +75 -23
- package/dist/render/shaders/flat/preamble.js +75 -20
- package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
- package/dist/render/shaders/flat/surfaceEffects.js +152 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +8 -8
- package/dist/render/shaders/generated/depth.wgsl.js +8 -8
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1320 -1320
- package/dist/render/shaders/generated/flat.wgsl.js +1359 -1345
- package/dist/render/shaders/generated/lightVolume.wgsl.js +18 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
- package/dist/render/shaders/generated/rush.wgsl.js +21 -16
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
- package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
- package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/text.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
- package/dist/render/shaders/recon/lateDepth.wgsl.js +38 -13
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
- package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
- package/dist/render/shaders/rush.js +11 -1
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/sdfText.js +3 -2
- package/dist/render/shaders/text.d.ts +1 -1
- package/dist/render/shaders/text.js +18 -9
- package/dist/render/surfaceEffects.d.ts +83 -0
- package/dist/render/surfaceEffects.js +75 -0
- package/dist/render/surfaceScene.d.ts +8 -0
- package/dist/render/surfaceScene.js +8 -0
- package/dist/render/surfaceTexture.d.ts +18 -2
- package/dist/render/surfaceTexture.js +68 -15
- package/dist/render/temporalAa.d.ts +25 -6
- package/dist/render/temporalAa.js +45 -15
- package/dist/render/textureSource.d.ts +30 -0
- package/dist/render/textureSource.js +53 -0
- package/dist/render/vertexDefaults.js +2 -0
- package/dist/world/hlod.d.ts +104 -0
- package/dist/world/hlod.js +214 -0
- package/package.json +6 -6
- package/src/geometry/boxCsg.ts +186 -0
- package/src/geometry/csg.ts +384 -0
- package/src/geometry/meshBuilder.ts +15 -0
- package/src/geometry/meshConcat.ts +1 -0
- package/src/geometry/pixelFont.ts +2 -0
- package/src/geometry/solid.ts +189 -0
- package/src/geometry/solidFlat.ts +522 -0
- package/src/geometry/solidHarness.ts +83 -0
- package/src/geometry/solidNormals.ts +103 -0
- package/src/geometry/solidRound.ts +443 -0
- package/src/geometry/solidWriter.ts +58 -0
- package/src/index.ts +51 -1
- package/src/render/backend/api.ts +21 -2
- package/src/render/backend/drawFog.ts +31 -0
- package/src/render/backend/materialChanges.ts +9 -2
- package/src/render/backend/webgl2/instanced.ts +80 -14
- package/src/render/backend/webgl2/renderer.ts +311 -150
- package/src/render/backend/webgpu/buffers.ts +49 -3
- package/src/render/backend/webgpu/depthPass.ts +5 -1
- package/src/render/backend/webgpu/drawCommand.ts +6 -0
- package/src/render/backend/webgpu/flatPass.ts +41 -11
- package/src/render/backend/webgpu/glassTintPass.ts +2 -1
- package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
- package/src/render/backend/webgpu/instanced.ts +21 -3
- package/src/render/backend/webgpu/latePass.ts +12 -3
- package/src/render/backend/webgpu/motionPass.ts +2 -1
- package/src/render/backend/webgpu/renderer.ts +556 -239
- package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
- package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +90 -50
- package/src/render/driftLight/bake.ts +85 -62
- package/src/render/driftLight/denseField.ts +149 -0
- package/src/render/driftLight/lightField.ts +33 -18
- package/src/render/driftLight/presence.ts +69 -0
- package/src/render/driftLight/uniforms.ts +8 -9
- package/src/render/driftLight/worldLightField.ts +83 -0
- package/src/render/fixtureAtlas.ts +63 -0
- package/src/render/instanceCull.ts +205 -0
- package/src/render/instances.ts +16 -0
- package/src/render/lightBudget.ts +25 -36
- package/src/render/lightGrid.ts +130 -0
- package/src/render/mesh.ts +64 -72
- package/src/render/occlusion.ts +51 -7
- package/src/render/pointLightSelection.ts +72 -4
- package/src/render/rainField.ts +39 -5
- package/src/render/recon/lateDepth.ts +83 -0
- package/src/render/recon/resolve.ts +206 -20
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneTarget.ts +4 -0
- package/src/render/shaders/depth.ts +12 -9
- package/src/render/shaders/flat/driftLight.ts +32 -7
- package/src/render/shaders/flat/index.ts +5 -3
- package/src/render/shaders/flat/main.ts +75 -23
- package/src/render/shaders/flat/preamble.ts +75 -20
- package/src/render/shaders/flat/surfaceEffects.ts +152 -0
- package/src/render/shaders/generated/depth.wgsl.ts +8 -8
- package/src/render/shaders/generated/flat.wgsl.ts +1361 -1345
- package/src/render/shaders/generated/lightVolume.wgsl.ts +20 -4
- package/src/render/shaders/generated/rush.wgsl.ts +21 -16
- package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
- package/src/render/shaders/generated/text.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
- package/src/render/shaders/recon/lateDepth.wgsl.ts +38 -13
- package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
- package/src/render/shaders/rush.ts +11 -1
- package/src/render/shaders/sdfText.ts +3 -2
- package/src/render/shaders/text.ts +18 -9
- package/src/render/surfaceEffects.ts +132 -0
- package/src/render/surfaceScene.ts +16 -0
- package/src/render/surfaceTexture.ts +110 -19
- package/src/render/temporalAa.ts +78 -14
- package/src/render/textureSource.ts +72 -0
- package/src/render/vertexDefaults.ts +2 -0
- package/src/world/hlod.ts +290 -0
|
@@ -34,12 +34,16 @@
|
|
|
34
34
|
* fn reconMotion(x: i32, y: i32) -> vec4<f32> // uv, previous view depth, flag
|
|
35
35
|
* fn reconHistory(x: i32, y: i32) -> vec4<f32> // output size, colour and gathered weight
|
|
36
36
|
* fn reconHistoryBilinear(p: vec2<f32>) -> vec4<f32>
|
|
37
|
+
* fn reconFlicker(x: i32, y: i32) -> vec4<f32> // output size, last frame's flicker record
|
|
38
|
+
* fn reconSwing(x: i32, y: i32) -> f32 // output size, last frame's held depth swing
|
|
37
39
|
* ```
|
|
38
40
|
*
|
|
39
41
|
* and the uniform `recon: ReconParams`, whose struct this module declares.
|
|
40
42
|
*/
|
|
41
43
|
import { wgslSceneDepthToNdc } from '../../depthConvention.js';
|
|
42
44
|
import { PLANAR_FROM, PLANAR_TO } from '../../recon/disocclusion.js';
|
|
45
|
+
import { DEPTH_SWING_WIDTH } from '../../recon/resolve.js';
|
|
46
|
+
import { FLICKER_BLEND, FLICKER_REPEAT, STILL_FROM, STILL_TO } from '../../temporalAa.js';
|
|
43
47
|
/**
|
|
44
48
|
* The parameters one resolve reads, as `ResolveFrame` minus its images.
|
|
45
49
|
*
|
|
@@ -65,10 +69,15 @@ struct ReconParams {
|
|
|
65
69
|
motionScale: f32,
|
|
66
70
|
normalFloor: f32,
|
|
67
71
|
normalCeiling: f32,
|
|
72
|
+
periodStart: u32,
|
|
73
|
+
antiFlicker: u32,
|
|
74
|
+
flickerMemory: f32,
|
|
75
|
+
flickerWidth: f32,
|
|
76
|
+
reprojection: mat4x4<f32>,
|
|
68
77
|
}
|
|
69
78
|
`;
|
|
70
79
|
/** Floats in `ReconParams`, which is what a caller sizes its uniform buffer by. */
|
|
71
|
-
export const RECON_PARAM_FLOATS =
|
|
80
|
+
export const RECON_PARAM_FLOATS = 92;
|
|
72
81
|
/** `SAMPLE_SPREAD` and `CONFIDENT_WEIGHT`, which the reference exports and this must not restate. */
|
|
73
82
|
export const RECON_SAMPLE_SPREAD = 0.47;
|
|
74
83
|
export const RECON_CONFIDENT_WEIGHT = 1;
|
|
@@ -85,8 +94,8 @@ export const RECON_WORKGROUP = 8;
|
|
|
85
94
|
*/
|
|
86
95
|
export const RECON_HISTORY_FORMAT = 'rgba16float';
|
|
87
96
|
/**
|
|
88
|
-
* The arithmetic, as text. Defines `reconResolvePixel(ox, oy) ->
|
|
89
|
-
* colour and the weight it has gathered — and `reconSharpenAt(ox, oy) -> vec3<f32>`, the picture
|
|
97
|
+
* The arithmetic, as text. Defines `reconResolvePixel(ox, oy) -> ReconResolved` — the next
|
|
98
|
+
* history's colour and the weight it has gathered, and the pixel's next flicker record — and `reconSharpenAt(ox, oy) -> vec3<f32>`, the picture
|
|
90
99
|
* shown, which reads the history the first has already written.
|
|
91
100
|
*/
|
|
92
101
|
export const RECON_RESOLVE_CORE_WGSL = /* wgsl */ `
|
|
@@ -94,6 +103,11 @@ const RECON_SAMPLE_SPREAD: f32 = ${RECON_SAMPLE_SPREAD};
|
|
|
94
103
|
const RECON_CONFIDENT_WEIGHT: f32 = ${RECON_CONFIDENT_WEIGHT}.0;
|
|
95
104
|
const RECON_PLANAR_FROM: f32 = ${PLANAR_FROM};
|
|
96
105
|
const RECON_PLANAR_TO: f32 = ${PLANAR_TO};
|
|
106
|
+
const RECON_FLICKER_BLEND: f32 = ${FLICKER_BLEND};
|
|
107
|
+
const RECON_FLICKER_REPEAT: f32 = ${FLICKER_REPEAT};
|
|
108
|
+
const RECON_STILL_FROM: f32 = ${STILL_FROM};
|
|
109
|
+
const RECON_STILL_TO: f32 = ${STILL_TO};
|
|
110
|
+
const RECON_DEPTH_SWING_WIDTH: f32 = ${DEPTH_SWING_WIDTH}.0;
|
|
97
111
|
|
|
98
112
|
fn reconClampTexel(value: i32, size: i32) -> i32 {
|
|
99
113
|
return clamp(value, 0, size - 1);
|
|
@@ -461,6 +475,10 @@ fn reconSurfaceAt(previous: bool, x: i32, y: i32, centre: f32) -> ReconSurface {
|
|
|
461
475
|
struct ReconHistory {
|
|
462
476
|
weight: f32,
|
|
463
477
|
colour: vec4<f32>,
|
|
478
|
+
/* How far the surface moved, in output pixels; read only where the weight is not zero. */
|
|
479
|
+
moved: f32,
|
|
480
|
+
/* How far, relative, last frame's depth missed the one expected there; -1 where none was held. */
|
|
481
|
+
missed: f32,
|
|
464
482
|
}
|
|
465
483
|
|
|
466
484
|
/*
|
|
@@ -468,10 +486,12 @@ struct ReconHistory {
|
|
|
468
486
|
* behind last frame's eye or off its picture, and otherwise what it gathered, scaled by whether it
|
|
469
487
|
* is the same surface.
|
|
470
488
|
*/
|
|
471
|
-
fn reconHistoryWeight(u: f32, v: f32, dx: i32, dy: i32) -> ReconHistory {
|
|
489
|
+
fn reconHistoryWeight(u: f32, v: f32, dx: i32, dy: i32, swing: f32) -> ReconHistory {
|
|
472
490
|
var out: ReconHistory;
|
|
473
491
|
out.weight = 0.0;
|
|
474
492
|
out.colour = vec4<f32>(0.0);
|
|
493
|
+
out.moved = 0.0;
|
|
494
|
+
out.missed = -1.0;
|
|
475
495
|
if (recon.hasHistory == 0u) { return out; }
|
|
476
496
|
let rw = f32(recon.renderSize.x);
|
|
477
497
|
let rh = f32(recon.renderSize.y);
|
|
@@ -484,20 +504,24 @@ fn reconHistoryWeight(u: f32, v: f32, dx: i32, dy: i32) -> ReconHistory {
|
|
|
484
504
|
mv = motion.y;
|
|
485
505
|
expected = motion.z;
|
|
486
506
|
} else {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
let
|
|
507
|
+
/* Through the one reprojection matrix the renderer multiplied: see resolve.ts. */
|
|
508
|
+
let du = (f32(dx) + 0.5 - recon.jitter.x) / rw;
|
|
509
|
+
let dv = (f32(dy) + 0.5 - recon.jitter.y) / rh;
|
|
510
|
+
let ndc = vec4<f32>(du * 2.0 - 1.0, dv * 2.0 - 1.0, reconDepth(dx, dy), 1.0);
|
|
511
|
+
let m = recon.inverseViewProj;
|
|
512
|
+
let hw = m[0][3] * ndc.x + m[1][3] * ndc.y + m[2][3] * ndc.z + m[3][3];
|
|
513
|
+
if (!(hw > 1e-12)) { return out; }
|
|
514
|
+
let clip = recon.reprojection * ndc;
|
|
490
515
|
/*
|
|
491
516
|
* A surface behind last frame's eye has a w that is not positive: the disocclusion trusts no
|
|
492
517
|
* such depth, so it needs no refusal of its own here.
|
|
493
518
|
*/
|
|
494
|
-
expected = clip.w;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
mu = (clip.x / expected) * 0.5 + 0.5 - du;
|
|
498
|
-
mv = (clip.y / expected) * 0.5 + 0.5 - dv;
|
|
519
|
+
expected = clip.w / hw;
|
|
520
|
+
mu = (clip.x / clip.w) * 0.5 + 0.5 - du;
|
|
521
|
+
mv = (clip.y / clip.w) * 0.5 + 0.5 - dv;
|
|
499
522
|
}
|
|
500
523
|
|
|
524
|
+
out.moved = length(vec2<f32>(mu * f32(recon.outputSize.x), mv * f32(recon.outputSize.y)));
|
|
501
525
|
let pu = u + mu;
|
|
502
526
|
let pv = v + mv;
|
|
503
527
|
if (!(pu >= 0.0 && pu <= 1.0 && pv >= 0.0 && pv <= 1.0)) { return out; }
|
|
@@ -544,13 +568,41 @@ fn reconHistoryWeight(u: f32, v: f32, dx: i32, dy: i32) -> ReconHistory {
|
|
|
544
568
|
normalDot = 1.0 - planar * (1.0 - dot(hereNormal.normal, thereNormal.normal));
|
|
545
569
|
}
|
|
546
570
|
}
|
|
547
|
-
|
|
571
|
+
if (held > 0.0 && expected > 0.0) { out.missed = abs(held - expected) / expected; }
|
|
572
|
+
/* A still pixel's own held depth swing: see resolve.ts. */
|
|
573
|
+
var swung = 0.0;
|
|
574
|
+
if (swing > 0.0) {
|
|
575
|
+
swung = RECON_DEPTH_SWING_WIDTH * swing *
|
|
576
|
+
(1.0 - reconSmoothstep(RECON_STILL_FROM, RECON_STILL_TO, out.moved));
|
|
577
|
+
}
|
|
578
|
+
let trust = reconDisocclusion(
|
|
579
|
+
expected,
|
|
580
|
+
held,
|
|
581
|
+
length(vec2<f32>(mu, mv)),
|
|
582
|
+
normalDot,
|
|
583
|
+
here.slope + swung,
|
|
584
|
+
);
|
|
548
585
|
out.weight = out.colour.w * trust;
|
|
549
586
|
return out;
|
|
550
587
|
}
|
|
551
588
|
|
|
552
|
-
/*
|
|
553
|
-
fn
|
|
589
|
+
/* temporalAa.ts's repeats: whether two periods' means are the same, one to zero. */
|
|
590
|
+
fn reconRepeats(mean: f32, previous: f32) -> f32 {
|
|
591
|
+
let apart = abs(mean - previous) / max(max(mean, previous), 1.0 / 255.0);
|
|
592
|
+
return 1.0 - reconSmoothstep(RECON_FLICKER_REPEAT, 2.0 * RECON_FLICKER_REPEAT, apart);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
struct ReconResolved {
|
|
596
|
+
/* The history's next colour, before sharpening, and the weight it has gathered. */
|
|
597
|
+
colour: vec4<f32>,
|
|
598
|
+
/* The pixel's next flicker record: spread, this period's sum, last period's mean, repeats. */
|
|
599
|
+
record: vec4<f32>,
|
|
600
|
+
/* And its next held depth swing. */
|
|
601
|
+
swing: f32,
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/* One output pixel: the history's next colour and weight, and its next flicker record. */
|
|
605
|
+
fn reconResolvePixel(ox: i32, oy: i32) -> ReconResolved {
|
|
554
606
|
let rw = i32(recon.renderSize.x);
|
|
555
607
|
let rh = i32(recon.renderSize.y);
|
|
556
608
|
let ow = i32(recon.outputSize.x);
|
|
@@ -598,18 +650,68 @@ fn reconResolvePixel(ox: i32, oy: i32) -> vec4<f32> {
|
|
|
598
650
|
accum = accum / gathered;
|
|
599
651
|
|
|
600
652
|
/* The history, and the weight it still deserves; a tenth of each frame is always new. */
|
|
601
|
-
|
|
602
|
-
|
|
653
|
+
var swing = 0.0;
|
|
654
|
+
if (recon.antiFlicker != 0u) { swing = reconSwing(ox, oy); }
|
|
655
|
+
let history = reconHistoryWeight(u, v, dx, dy, swing);
|
|
656
|
+
var cap = (1.0 - recon.alpha) / recon.alpha;
|
|
657
|
+
var low = box.low;
|
|
658
|
+
var high = box.high;
|
|
659
|
+
var out: ReconResolved;
|
|
660
|
+
out.record = vec4<f32>(0.0);
|
|
661
|
+
out.swing = 0.0;
|
|
662
|
+
/* The anti-flicker, operation for operation resolve.ts's: see temporalAa.ts. */
|
|
663
|
+
if (recon.antiFlicker != 0u) {
|
|
664
|
+
let seen = dot(accum, vec3<f32>(0.2126, 0.7152, 0.0722));
|
|
665
|
+
let memory = recon.flickerMemory;
|
|
666
|
+
let width = recon.flickerWidth;
|
|
667
|
+
var held = vec4<f32>(0.0, seen * memory, seen, 0.0);
|
|
668
|
+
if (history.weight > 0.0) { held = reconFlicker(ox, oy); }
|
|
669
|
+
var still = 0.0;
|
|
670
|
+
if (history.weight > 0.0) {
|
|
671
|
+
still = 1.0 - reconSmoothstep(RECON_STILL_FROM, RECON_STILL_TO, history.moved);
|
|
672
|
+
}
|
|
673
|
+
let apart = abs(seen - held.z);
|
|
674
|
+
let within = apart <= width * held.x + 1.0 / 255.0;
|
|
675
|
+
let proven = held.w > 0.75 && within;
|
|
676
|
+
var widen = 0.0;
|
|
677
|
+
let base = 1.0 - recon.alpha;
|
|
678
|
+
var keep = base;
|
|
679
|
+
if (proven) {
|
|
680
|
+
widen = width * held.x * still;
|
|
681
|
+
keep = base + (RECON_FLICKER_BLEND - base) * still;
|
|
682
|
+
}
|
|
683
|
+
low = low - vec3<f32>(widen);
|
|
684
|
+
high = high + vec3<f32>(widen);
|
|
685
|
+
cap = keep / (1.0 - keep);
|
|
686
|
+
var sum = held.y;
|
|
687
|
+
var mean = held.z;
|
|
688
|
+
var repeated = 0.0;
|
|
689
|
+
if (within) { repeated = held.w; }
|
|
690
|
+
if (recon.periodStart != 0u) {
|
|
691
|
+
if (reconRepeats(sum, mean) > 0.5) { repeated = min(1.0, repeated + 0.5); } else { repeated = 0.0; }
|
|
692
|
+
mean = sum;
|
|
693
|
+
sum = 0.0;
|
|
694
|
+
}
|
|
695
|
+
out.record = vec4<f32>(
|
|
696
|
+
held.x + (apart - held.x) * memory,
|
|
697
|
+
sum + seen * memory,
|
|
698
|
+
mean,
|
|
699
|
+
repeated * still,
|
|
700
|
+
);
|
|
701
|
+
/* The peak carried on, halving over a period, whatever the test said. */
|
|
702
|
+
if (history.missed >= 0.0) { out.swing = max(history.missed, swing * pow(0.5, memory)); }
|
|
703
|
+
}
|
|
603
704
|
let kept = min(cap, history.weight);
|
|
604
705
|
|
|
605
706
|
let total = kept + gathered;
|
|
606
707
|
var mixed = accum;
|
|
607
708
|
if (kept > 0.0) {
|
|
608
|
-
let clipped = reconClipToBox(reconToYCoCg(history.colour.xyz),
|
|
709
|
+
let clipped = reconClipToBox(reconToYCoCg(history.colour.xyz), low, high);
|
|
609
710
|
mixed = reconAccumulate(accum, reconFromYCoCg(clipped), 1.0, gathered / total);
|
|
610
711
|
}
|
|
611
712
|
let known = min(1.0, total / RECON_CONFIDENT_WEIGHT);
|
|
612
|
-
|
|
713
|
+
out.colour = vec4<f32>(sample * (1.0 - known) + mixed * known, total);
|
|
714
|
+
return out;
|
|
613
715
|
}
|
|
614
716
|
|
|
615
717
|
/*
|
|
@@ -648,8 +750,9 @@ fn reconSharpenAt(ox: i32, oy: i32) -> vec3<f32> {
|
|
|
648
750
|
* doing its own bilinear so the comparison is arithmetic rather than a tolerance on the device's
|
|
649
751
|
* sampler.
|
|
650
752
|
*
|
|
651
|
-
* Two entry points over one set of bindings. `resolveMain` writes
|
|
652
|
-
* history — and `sharpenMain` reads that history back at binding 4
|
|
753
|
+
* Two entry points over one set of bindings. `resolveMain` writes nine floats a pixel — the next
|
|
754
|
+
* history, the next flicker record and the next depth swing — and `sharpenMain` reads that history back at binding 4
|
|
755
|
+
* and writes three.
|
|
653
756
|
*/
|
|
654
757
|
export function reconResolveParityWgsl() {
|
|
655
758
|
return /* wgsl */ `
|
|
@@ -661,6 +764,16 @@ export function reconResolveParityWgsl() {
|
|
|
661
764
|
${RECON_PARAMS_WGSL}
|
|
662
765
|
@group(0) @binding(5) var<uniform> recon: ReconParams;
|
|
663
766
|
@group(0) @binding(6) var<storage, read_write> output: array<f32>;
|
|
767
|
+
@group(0) @binding(7) var flickerTexture: texture_2d<f32>;
|
|
768
|
+
@group(0) @binding(8) var swingTexture: texture_2d<f32>;
|
|
769
|
+
|
|
770
|
+
fn reconFlicker(x: i32, y: i32) -> vec4<f32> {
|
|
771
|
+
return textureLoad(flickerTexture, vec2<i32>(x, y), 0);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
fn reconSwing(x: i32, y: i32) -> f32 {
|
|
775
|
+
return textureLoad(swingTexture, vec2<i32>(x, y), 0).x;
|
|
776
|
+
}
|
|
664
777
|
|
|
665
778
|
fn reconScene(x: i32, y: i32) -> vec3<f32> {
|
|
666
779
|
return textureLoad(sceneTexture, vec2<i32>(x, y), 0).xyz;
|
|
@@ -718,11 +831,16 @@ ${RECON_RESOLVE_CORE_WGSL}
|
|
|
718
831
|
fn resolveMain(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
719
832
|
if (id.x >= recon.outputSize.x || id.y >= recon.outputSize.y) { return; }
|
|
720
833
|
let value = reconResolvePixel(i32(id.x), i32(id.y));
|
|
721
|
-
let at = (id.y * recon.outputSize.x + id.x) *
|
|
722
|
-
output[at] = value.x;
|
|
723
|
-
output[at + 1u] = value.y;
|
|
724
|
-
output[at + 2u] = value.z;
|
|
725
|
-
output[at + 3u] = value.w;
|
|
834
|
+
let at = (id.y * recon.outputSize.x + id.x) * 9u;
|
|
835
|
+
output[at] = value.colour.x;
|
|
836
|
+
output[at + 1u] = value.colour.y;
|
|
837
|
+
output[at + 2u] = value.colour.z;
|
|
838
|
+
output[at + 3u] = value.colour.w;
|
|
839
|
+
output[at + 4u] = value.record.x;
|
|
840
|
+
output[at + 5u] = value.record.y;
|
|
841
|
+
output[at + 6u] = value.record.z;
|
|
842
|
+
output[at + 7u] = value.record.w;
|
|
843
|
+
output[at + 8u] = value.swing;
|
|
726
844
|
}
|
|
727
845
|
|
|
728
846
|
@compute @workgroup_size(${RECON_WORKGROUP}, ${RECON_WORKGROUP})
|
|
@@ -772,6 +890,12 @@ ${RECON_PARAMS_WGSL}
|
|
|
772
890
|
@group(0) @binding(6) var<uniform> recon: ReconParams;
|
|
773
891
|
@group(0) @binding(7) var resolved: texture_storage_2d<rgba16float, write>;
|
|
774
892
|
/* Not "target": it is a reserved keyword in WGSL, as "meta" was in the cluster cull. */
|
|
893
|
+
/* Last frame's flicker records, and where this frame's go: a pair beside the histories. */
|
|
894
|
+
@group(0) @binding(8) var flickerTexture: texture_2d<f32>;
|
|
895
|
+
@group(0) @binding(9) var flickerOut: texture_storage_2d<rgba16float, write>;
|
|
896
|
+
/* And each pixel's held depth swing, a pair of its own. */
|
|
897
|
+
@group(0) @binding(10) var swingTexture: texture_2d<f32>;
|
|
898
|
+
@group(0) @binding(11) var swingOut: texture_storage_2d<r32float, write>;
|
|
775
899
|
|
|
776
900
|
/* Rows flip here: the reference's row 0 is the bottom one and a texture's is the top. */
|
|
777
901
|
fn reconRenderRow(y: i32) -> i32 {
|
|
@@ -804,6 +928,14 @@ fn reconHistory(x: i32, y: i32) -> vec4<f32> {
|
|
|
804
928
|
return textureLoad(historyTexture, vec2<i32>(x, reconOutputRow(y)), 0);
|
|
805
929
|
}
|
|
806
930
|
|
|
931
|
+
fn reconFlicker(x: i32, y: i32) -> vec4<f32> {
|
|
932
|
+
return textureLoad(flickerTexture, vec2<i32>(x, reconOutputRow(y)), 0);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
fn reconSwing(x: i32, y: i32) -> f32 {
|
|
936
|
+
return textureLoad(swingTexture, vec2<i32>(x, reconOutputRow(y)), 0).x;
|
|
937
|
+
}
|
|
938
|
+
|
|
807
939
|
/* A texel coordinate becomes the uv of its centre, with the row taken from the other end. */
|
|
808
940
|
fn reconSceneBilinear(p: vec2<f32>) -> vec3<f32> {
|
|
809
941
|
let size = vec2<f32>(f32(recon.renderSize.x), f32(recon.renderSize.y));
|
|
@@ -823,7 +955,9 @@ fn resolveMain(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
823
955
|
if (id.x >= recon.outputSize.x || id.y >= recon.outputSize.y) { return; }
|
|
824
956
|
let y = i32(recon.outputSize.y) - 1 - i32(id.y);
|
|
825
957
|
let value = reconResolvePixel(i32(id.x), y);
|
|
826
|
-
textureStore(resolved, vec2<i32>(i32(id.x), i32(id.y)), value);
|
|
958
|
+
textureStore(resolved, vec2<i32>(i32(id.x), i32(id.y)), value.colour);
|
|
959
|
+
textureStore(flickerOut, vec2<i32>(i32(id.x), i32(id.y)), value.record);
|
|
960
|
+
textureStore(swingOut, vec2<i32>(i32(id.x), i32(id.y)), vec4<f32>(value.swing, 0.0, 0.0, 0.0));
|
|
827
961
|
}
|
|
828
962
|
|
|
829
963
|
@compute @workgroup_size(${RECON_WORKGROUP}, ${RECON_WORKGROUP})
|
|
@@ -102,6 +102,16 @@ uniform float uMotionMax;
|
|
|
102
102
|
*/
|
|
103
103
|
uniform sampler2D uAo;
|
|
104
104
|
uniform float uAoStrength;
|
|
105
|
+
/**
|
|
106
|
+
* Where this frame's jitter put the picture the occlusion was measured from, in this pass's uv.
|
|
107
|
+
*
|
|
108
|
+
* The occlusion is measured from the frame's own depth, which a temporal resolve or a
|
|
109
|
+
* reconstruction jittered, and it is applied here to a picture those have already taken the jitter
|
|
110
|
+
* out of — so read at the pixel's own uv it moved by the jitter every frame, and every crevice
|
|
111
|
+
* shimmered by up to half a render texel. Read where the jitter put it, it holds still. Zero for an
|
|
112
|
+
* unjittered frame.
|
|
113
|
+
*/
|
|
114
|
+
uniform vec2 uAoOffset;
|
|
105
115
|
|
|
106
116
|
/**
|
|
107
117
|
* The bloom chain's top level, already thresholded, blurred and summed by its own pass.
|
|
@@ -339,7 +349,7 @@ void main() {
|
|
|
339
349
|
* strength, so the parameter reads as "how much of this occlusion", and 0 is exactly the
|
|
340
350
|
* frame that existed before the effect did.
|
|
341
351
|
*/
|
|
342
|
-
float ao = mix(1.0, textureLod(uAo, vUv, 0.0).r, uAoStrength);
|
|
352
|
+
float ao = mix(1.0, textureLod(uAo, vUv + uAoOffset, 0.0).r, uAoStrength);
|
|
343
353
|
|
|
344
354
|
/*
|
|
345
355
|
* Distance from the centre, corrected so the falloff is a circle on screen rather than
|
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
* the alpha test is not uniform control flow.
|
|
7
7
|
*/
|
|
8
8
|
export declare const SDF_TEXT_VERT = "#version 300 es\nprecision highp float;\n\nlayout(location = 0) in vec3 aPosition;\nlayout(location = 1) in vec2 aUv;\n\nuniform mat4 uViewProj;\nuniform mat4 uModel;\n\nout vec2 vUv;\n\nvoid main() {\n vUv = aUv;\n gl_Position = uViewProj * uModel * vec4(aPosition, 1.0);\n}\n";
|
|
9
|
-
export declare const SDF_TEXT_FRAG = "#version 300 es\nprecision highp float;\n\nin vec2 vUv;\n\nuniform
|
|
9
|
+
export declare const SDF_TEXT_FRAG = "#version 300 es\nprecision highp float;\n\nin vec2 vUv;\n\n/* An array of one: every surface texture is an array, and an atlas is read at layer 0. */\nuniform mediump sampler2DArray uAtlas;\nuniform vec3 uColor;\nuniform float uOpacity;\n/* Texels the field spans, from the atlas metrics. Scales the antialiasing band. */\nuniform float uDistanceRange;\nuniform vec2 uAtlasSize;\n\nout vec4 outColor;\n\n/** 0 none, 1 sRGB, 2 ACES then sRGB. See renderQuality.ts. */\nuniform int uOutputTransform;\n/** Scales the scene into the tone curve. 1 is the reference grade. See renderQuality.ts. */\nuniform float uOutputExposure;\n\n/*\n * ACES: the reference fit, not the cheap one.\n *\n * This was Narkowicz's single rational function, on the argument that the difference from\n * the reference curve is invisible next to the gain. That argument holds for a look and\n * fails for a *match*, which is what this option is for \u2014 aces exists so a world ported\n * from three.js reads the same, and three.js applies the full fit: into AP1 through\n * ACESInputMat, the RRT and ODT together, then back out through ACESOutputMat.\n *\n * The two curves are not a shade apart. Narkowicz is markedly brighter through the\n * midtones, which is exactly where an interior scene lives, and it is a large part of a\n * measured ~1.7x that survived correcting the colour space and the missing Lambert pi.\n * An approximation of a standard is the wrong thing to hold when the standard is the\n * specification.\n */\nvec3 rrtAndOdtFit(vec3 v) {\n vec3 a = v * (v + 0.0245786) - 0.000090537;\n vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;\n return a / b;\n}\n\nvec3 acesFilmic(vec3 x) {\n /*\n * Exposure first, because a tone curve is only defined relative to one.\n *\n * The reference RRT and ODT are graded for a dark cinema. Every renderer that ships ACES\n * scales its input to suit whatever it is actually shown on, and the factor each one\n * picks is a *look*, not a standard - so this engine takes no position and reads it from\n * the caller. Neutral by default, which is the reference grade unmodified. three.js's own\n * pick is 1 / 0.6, for a consumer matching one.\n */\n x *= uOutputExposure;\n // Columns, as GLSL reads them: sRGB -> XYZ -> D65_2_D60 -> AP1 -> RRT_SAT.\n const mat3 ACES_INPUT = mat3(\n 0.59719, 0.07600, 0.02840,\n 0.35458, 0.90834, 0.13383,\n 0.04823, 0.01566, 0.83777\n );\n // ODT_SAT -> XYZ -> D60_2_D65 -> sRGB.\n const mat3 ACES_OUTPUT = mat3(\n 1.60475, -0.10208, -0.00327,\n -0.53108, 1.10813, -0.07276,\n -0.07367, -0.00605, 1.07602\n );\n return clamp(ACES_OUTPUT * rrtAndOdtFit(ACES_INPUT * x), 0.0, 1.0);\n}\n\n/*\n * Linear to sRGB. The piecewise form rather than a plain pow: the linear toe near black\n * is where banding lives in an 8-bit buffer, and the cheap approximation puts it in the\n * wrong place.\n */\nvec3 linearToSrgb(vec3 c) {\n vec3 low = c * 12.92;\n vec3 high = 1.055 * pow(max(c, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055;\n return mix(high, low, step(c, vec3(0.0031308)));\n}\n\nvec3 applyOutputTransform(vec3 c) {\n if (uOutputTransform == 0) return c;\n if (uOutputTransform == 2) c = acesFilmic(c);\n return linearToSrgb(c);\n}\n\n\n/*\n * The middle of the three channels.\n *\n * A single-channel field rounds every corner off at small sizes, because one distance\n * cannot describe two edges meeting. Three channels carry the corner and the median\n * recovers it.\n */\nfloat median(vec3 rgb) {\n return max(min(rgb.r, rgb.g), min(max(rgb.r, rgb.g), rgb.b));\n}\n\nvoid main() {\n vec3 sampled = textureLod(uAtlas, vec3(vUv, 0.0), 0.0).rgb;\n float distance = median(sampled) - 0.5;\n\n /*\n * The band is one pixel wide *in screen space*, which is what lets one atlas serve every\n * size. A constant band is tuned for one size and is wrong at all the others.\n */\n vec2 texelsPerPixel = fwidth(vUv) * uAtlasSize;\n float pixelRange = max(0.5 * (texelsPerPixel.x + texelsPerPixel.y), 0.0001);\n float coverage = clamp(distance * uDistanceRange / pixelRange + 0.5, 0.0, 1.0);\n\n outColor = vec4(applyOutputTransform(uColor), coverage * uOpacity);\n}\n";
|
|
@@ -27,7 +27,8 @@ precision highp float;
|
|
|
27
27
|
|
|
28
28
|
in vec2 vUv;
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
/* An array of one: every surface texture is an array, and an atlas is read at layer 0. */
|
|
31
|
+
uniform mediump sampler2DArray uAtlas;
|
|
31
32
|
uniform vec3 uColor;
|
|
32
33
|
uniform float uOpacity;
|
|
33
34
|
/* Texels the field spans, from the atlas metrics. Scales the antialiasing band. */
|
|
@@ -49,7 +50,7 @@ float median(vec3 rgb) {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
void main() {
|
|
52
|
-
vec3 sampled = textureLod(uAtlas, vUv, 0.0).rgb;
|
|
53
|
+
vec3 sampled = textureLod(uAtlas, vec3(vUv, 0.0), 0.0).rgb;
|
|
53
54
|
float distance = median(sampled) - 0.5;
|
|
54
55
|
|
|
55
56
|
/*
|
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
* gameplay camera is doing — but it still wants perspective, or a rotating
|
|
14
14
|
* letter shears instead of turning.
|
|
15
15
|
*/
|
|
16
|
-
export declare const TEXT_VERT = "#version 300 es\nprecision highp float;\n\nlayout(location = 0) in vec3 aCorner;\nlayout(location = 1) in vec3 aNormal;\n/** Cell position in pixels, relative to the string's origin. */\nlayout(location = 2) in vec2 aCell;\n/** Which character this cell belongs to, for staggered animation. */\nlayout(location = 3) in float aCharIndex;\n\nuniform vec2 uViewport;\n/** Where the string's baseline-left sits, in pixels from the top left. */\nuniform vec2 uOrigin;\nuniform float uCellSize;\n/**
|
|
16
|
+
export declare const TEXT_VERT = "#version 300 es\nprecision highp float;\n\nlayout(location = 0) in vec3 aCorner;\nlayout(location = 1) in vec3 aNormal;\n/** Cell position in pixels, relative to the string's origin. */\nlayout(location = 2) in vec2 aCell;\n/** Which character this cell belongs to, for staggered animation. */\nlayout(location = 3) in float aCharIndex;\n\nuniform vec2 uViewport;\n/** Where the string's baseline-left sits, in pixels from the top left. */\nuniform vec2 uOrigin;\nuniform float uCellSize;\n/** How far each cell is seen from, head-on; larger flattens the perspective. */\nuniform float uDepth;\n\n/** 0 before a character has arrived, 1 once it has settled. */\nuniform float uReveal;\n/** How many characters the stagger spans \u2014 the whole line, not this piece. */\nuniform float uCharCount;\n/**\n * Where this piece starts within that line.\n *\n * A line drawn in several pieces \u2014 words around a keycap \u2014 must stagger as one\n * sentence. Without this each piece restarts at zero, so the second word begins\n * arriving before the first has finished and they sit at different heights\n * through the whole animation.\n */\nuniform float uCharOffset;\n/** Radians of entry rotation, decaying to zero as a character settles. */\nuniform float uSpin;\n/** Extra scale at the moment of arrival \u2014 the overshoot. */\nuniform float uPunch;\n/** Vertical bob amplitude in pixels, and its phase. */\nuniform float uBob;\nuniform float uTime;\n/**\n * Clip space, as the backend drawing this defines it. Identity on WebGL2.\n *\n * This draw builds its own clip position from pixels and never multiplies by a camera, so\n * the correction every other draw carries in its view-projection cannot reach it \u2014 the same\n * gap that left the sky unprojecting through the wrong space. Two things are wrong without\n * it on WebGPU, and only one of them is visible: the generated vertex shader negates Y, so\n * the message renders upside down; and z here is a narrow slice about zero, which is inside\n * OpenGL's [-1, 1] and *outside* WebGPU's [0, 1], so half of every glyph is clipped away.\n */\nuniform mat4 uClipCorrection;\n\nout vec3 vNormal;\nout float vDepth;\nout float vChar;\n\n/**\n * Spring settle: overshoots once and comes back.\n *\n * A character that eases into place reads as an element appearing; one that\n * overshoots and settles reads as an object arriving. That difference is most\n * of what makes text feel like part of a game rather than part of a page.\n */\nfloat settle(float t) {\n if (t <= 0.0) return 0.0;\n if (t >= 1.0) return 1.0;\n float d = 1.0 - t;\n return 1.0 - d * d * cos(t * 12.0) ;\n}\n\nmat2 rot(float a) {\n float s = sin(a);\n float c = cos(a);\n return mat2(c, -s, s, c);\n}\n\nvoid main() {\n // Stagger: each character starts a fraction of the reveal later than the\n // one before it, so the message assembles left to right.\n float span = max(uCharCount, 1.0);\n float start = ((aCharIndex + uCharOffset) / span) * 0.55;\n float local = clamp((uReveal - start) / max(0.45, 1.0 - start), 0.0, 1.0);\n float eased = settle(local);\n\n // Arrives spinning about its own horizontal axis and dropping into place.\n float spin = uSpin * (1.0 - eased);\n float rise = (1.0 - eased) * uCellSize * 6.0;\n float punch = 1.0 + uPunch * (1.0 - abs(eased * 2.0 - 1.0));\n\n vec3 local3 = aCorner * uCellSize * 0.5 * punch;\n local3.yz = rot(spin) * local3.yz;\n\n vec2 cell = aCell * uCellSize;\n float bob = sin(uTime * 3.1 + aCharIndex * 0.7) * uBob;\n\n // The cell's centre in pixels, y down from the top-left corner of the viewport.\n vec2 centre = uOrigin + vec2(cell.x, -cell.y) + vec2(0.0, rise - bob);\n\n // A hand-built perspective divide about the cell's own centre, so every cube\n // is seen head-on: its front face at depth zero is exactly its cell, and at\n // rest its sides are behind it wherever on the screen it is drawn.\n //\n // It divided about the viewport's top-left corner until 2026-09-30, which\n // shifted a front face by its distance from that corner times z / uDepth and\n // showed the side it uncovered. Near the corner that was nothing; across the\n // screen it widened every stroke by up to a pixel either side and closed the\n // one-cell gaps a 5x7 face reads by. What this gives up is the extruded look\n // text far from the corner had at rest; what would make it wrong is a caller\n // wanting that look, which is then one offset for every cell, not a position.\n float invDepth = uDepth / max(0.001, uDepth - local3.z);\n vec2 screen = centre + local3.xy * invDepth;\n vec2 ndc = (screen / uViewport) * 2.0 - 1.0;\n // Depth from the cube's own extrusion, mapped into a narrow slice of the\n // range so a message sorts against itself without ever reaching the scene.\n float depth = clamp(-local3.z / max(1.0, uCellSize * 2.0), -1.0, 1.0) * 0.4;\n gl_Position = uClipCorrection * vec4(ndc.x, -ndc.y, depth, 1.0);\n\n vec3 n = aNormal;\n n.yz = rot(spin) * n.yz;\n vNormal = n;\n vDepth = local3.z;\n vChar = eased;\n}";
|
|
17
17
|
export declare const TEXT_FRAG = "#version 300 es\nprecision highp float;\n\nin vec3 vNormal;\nin float vDepth;\nin float vChar;\n\nuniform vec3 uColor;\n/** Self-illumination on top of the shaded colour. */\nuniform float uGlow;\nuniform float uAlpha;\n\nout vec4 fragColor;\n\nvoid main() {\n /*\n * A fixed key light rather than the world's. Text has to stay legible at\n * midnight and at noon, and lighting it with the scene would make a\n * notification unreadable on exactly the night runs where it matters most.\n * The light only exists to give the cubes an edge.\n */\n vec3 key = normalize(vec3(-0.35, 0.7, 0.6));\n float lambert = 0.55 + 0.45 * max(dot(normalize(vNormal), key), 0.0);\n\n vec3 lit = uColor * lambert;\n // The glow rides the arrival: brightest as a character lands, settling after.\n vec3 emissive = uColor * uGlow * (0.45 + 0.55 * vChar);\n fragColor = vec4(lit + emissive, uAlpha);\n}";
|
|
@@ -27,7 +27,7 @@ uniform vec2 uViewport;
|
|
|
27
27
|
/** Where the string's baseline-left sits, in pixels from the top left. */
|
|
28
28
|
uniform vec2 uOrigin;
|
|
29
29
|
uniform float uCellSize;
|
|
30
|
-
/**
|
|
30
|
+
/** How far each cell is seen from, head-on; larger flattens the perspective. */
|
|
31
31
|
uniform float uDepth;
|
|
32
32
|
|
|
33
33
|
/** 0 before a character has arrived, 1 once it has settled. */
|
|
@@ -105,14 +105,23 @@ void main() {
|
|
|
105
105
|
vec2 cell = aCell * uCellSize;
|
|
106
106
|
float bob = sin(uTime * 3.1 + aCharIndex * 0.7) * uBob;
|
|
107
107
|
|
|
108
|
-
//
|
|
109
|
-
vec2
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
// The cell's centre in pixels, y down from the top-left corner of the viewport.
|
|
109
|
+
vec2 centre = uOrigin + vec2(cell.x, -cell.y) + vec2(0.0, rise - bob);
|
|
110
|
+
|
|
111
|
+
// A hand-built perspective divide about the cell's own centre, so every cube
|
|
112
|
+
// is seen head-on: its front face at depth zero is exactly its cell, and at
|
|
113
|
+
// rest its sides are behind it wherever on the screen it is drawn.
|
|
114
|
+
//
|
|
115
|
+
// It divided about the viewport's top-left corner until 2026-09-30, which
|
|
116
|
+
// shifted a front face by its distance from that corner times z / uDepth and
|
|
117
|
+
// showed the side it uncovered. Near the corner that was nothing; across the
|
|
118
|
+
// screen it widened every stroke by up to a pixel either side and closed the
|
|
119
|
+
// one-cell gaps a 5x7 face reads by. What this gives up is the extruded look
|
|
120
|
+
// text far from the corner had at rest; what would make it wrong is a caller
|
|
121
|
+
// wanting that look, which is then one offset for every cell, not a position.
|
|
122
|
+
float invDepth = uDepth / max(0.001, uDepth - local3.z);
|
|
123
|
+
vec2 screen = centre + local3.xy * invDepth;
|
|
124
|
+
vec2 ndc = (screen / uViewport) * 2.0 - 1.0;
|
|
116
125
|
// Depth from the cube's own extrusion, mapped into a narrow slice of the
|
|
117
126
|
// range so a message sorts against itself without ever reaching the scene.
|
|
118
127
|
float depth = clamp(-local3.z / max(1.0, uCellSize * 2.0), -1.0, 1.0) * 0.4;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each layer of a surface array does beyond its picture: rooms behind its windows, windows that
|
|
3
|
+
* light by night, wear, animation, and whether rain wets it.
|
|
4
|
+
*
|
|
5
|
+
* **Per layer, because a city region is one mesh.** Its facades are told apart by the layer each
|
|
6
|
+
* vertex carries (`MeshData.layers`), so an effect's parameters cannot be a per-draw uniform — two facades of one
|
|
7
|
+
* draw would share them. They are a table beside the array, six texels a layer, read at the vertex's
|
|
8
|
+
* layer with `texelFetch`, and they bind whenever the array does.
|
|
9
|
+
*
|
|
10
|
+
* **Everything off is all zeros**, so a layer a scene names no effect for — and every layer of an
|
|
11
|
+
* array given no table — costs the shader one size query and nothing else.
|
|
12
|
+
*
|
|
13
|
+
* Layout, six RGBA texels a layer, half floats on the GPU (every whole number here is under 2,048,
|
|
14
|
+
* which a half holds exactly):
|
|
15
|
+
* - 0: window cells across and up, glass mask (1: glass where the albedo's alpha is 0), seed;
|
|
16
|
+
* - 1: room layer (0: none; the layer plus one), room depth (0 to 1 of a cell), share of rooms lit,
|
|
17
|
+
* window glow;
|
|
18
|
+
* - 2: window colour, then 1 where rain leaves the surface dry;
|
|
19
|
+
* - 3: dust, grime, streaks, the metres over which wear fades out with distance (0: never);
|
|
20
|
+
* - 4: scroll along u and v in repeats a second, pulse rate in hertz, pulse depth;
|
|
21
|
+
* - 5: flicker, flipbook frames, flipbook frames a second, the metres past which emission fades
|
|
22
|
+
* (0: never).
|
|
23
|
+
*/
|
|
24
|
+
/** Texels a layer takes in the table. */
|
|
25
|
+
export declare const SURFACE_EFFECT_TEXELS = 6;
|
|
26
|
+
export interface SurfaceLayerEffect {
|
|
27
|
+
/**
|
|
28
|
+
* Windows across and up one repeat of the layer, for everything that works window by window:
|
|
29
|
+
* rooms, lit windows. With `glass`, a window is a hole in the albedo — glass where its alpha is 0,
|
|
30
|
+
* wall where it is 1 — which is how a facade is drawn, and the other way round would lose the
|
|
31
|
+
* wall's colour, which does not survive an upload at alpha 0.
|
|
32
|
+
*/
|
|
33
|
+
readonly windows?: {
|
|
34
|
+
readonly cells: readonly [number, number];
|
|
35
|
+
readonly glass?: boolean;
|
|
36
|
+
/** Varies which windows light and which rooms show, between facades wearing one layer. */
|
|
37
|
+
readonly seed?: number;
|
|
38
|
+
/** How bright a lit window glows, 0 for none. */
|
|
39
|
+
readonly glow?: number;
|
|
40
|
+
/**
|
|
41
|
+
* The light a window shows where there is no room behind it. **A vertex that names an emissive
|
|
42
|
+
* colour overrides it** — the window's colour, or a tint on the room where there is one — which
|
|
43
|
+
* is how buildings sharing a layer each keep their own light: a window layer's vertex emissive
|
|
44
|
+
* is otherwise unused, so the colour there, strength folded in, is free to say it.
|
|
45
|
+
*/
|
|
46
|
+
readonly colour?: readonly [number, number, number];
|
|
47
|
+
};
|
|
48
|
+
/** A room seen in parallax behind each window, drawn from another layer of the same array. */
|
|
49
|
+
readonly interior?: {
|
|
50
|
+
readonly roomLayer: number;
|
|
51
|
+
/** How deep a room is, as a share of a window's width. 0.5 unless stated. */
|
|
52
|
+
readonly depth?: number;
|
|
53
|
+
/** The share of rooms lit, 0 to 1. 1 unless stated. */
|
|
54
|
+
readonly lit?: number;
|
|
55
|
+
};
|
|
56
|
+
/** Procedural wear, each 0 to 1, fading out past `fade` metres (never, unless stated). */
|
|
57
|
+
readonly wear?: {
|
|
58
|
+
readonly dust?: number;
|
|
59
|
+
readonly grime?: number;
|
|
60
|
+
readonly streaks?: number;
|
|
61
|
+
readonly fade?: number;
|
|
62
|
+
};
|
|
63
|
+
/** Motion on the caller's clock (`Environment.surfaceTime`), so a held clock holds it. */
|
|
64
|
+
readonly animation?: {
|
|
65
|
+
/** Repeats a second along u and v. */
|
|
66
|
+
readonly scroll?: readonly [number, number];
|
|
67
|
+
/** Emission rising and falling: hertz, and how far it falls, 0 to 1. */
|
|
68
|
+
readonly pulse?: readonly [number, number];
|
|
69
|
+
/** Emission stuttering, 0 to 1. */
|
|
70
|
+
readonly flicker?: number;
|
|
71
|
+
/** Successive layers as frames: how many from this one, and how fast. */
|
|
72
|
+
readonly flipbook?: readonly [number, number];
|
|
73
|
+
/** Metres past which emission fades out, 0 for never. */
|
|
74
|
+
readonly fade?: number;
|
|
75
|
+
};
|
|
76
|
+
/** Rain leaves it dry: a covered walkway, an interior floor. */
|
|
77
|
+
readonly dry?: boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The table for `effects`, one entry a layer (`undefined` for a layer with none), as floats: six
|
|
81
|
+
* RGBA texels a layer, a row a layer.
|
|
82
|
+
*/
|
|
83
|
+
export declare function packSurfaceEffects(effects: readonly (SurfaceLayerEffect | undefined)[]): Float32Array;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each layer of a surface array does beyond its picture: rooms behind its windows, windows that
|
|
3
|
+
* light by night, wear, animation, and whether rain wets it.
|
|
4
|
+
*
|
|
5
|
+
* **Per layer, because a city region is one mesh.** Its facades are told apart by the layer each
|
|
6
|
+
* vertex carries (`MeshData.layers`), so an effect's parameters cannot be a per-draw uniform — two facades of one
|
|
7
|
+
* draw would share them. They are a table beside the array, six texels a layer, read at the vertex's
|
|
8
|
+
* layer with `texelFetch`, and they bind whenever the array does.
|
|
9
|
+
*
|
|
10
|
+
* **Everything off is all zeros**, so a layer a scene names no effect for — and every layer of an
|
|
11
|
+
* array given no table — costs the shader one size query and nothing else.
|
|
12
|
+
*
|
|
13
|
+
* Layout, six RGBA texels a layer, half floats on the GPU (every whole number here is under 2,048,
|
|
14
|
+
* which a half holds exactly):
|
|
15
|
+
* - 0: window cells across and up, glass mask (1: glass where the albedo's alpha is 0), seed;
|
|
16
|
+
* - 1: room layer (0: none; the layer plus one), room depth (0 to 1 of a cell), share of rooms lit,
|
|
17
|
+
* window glow;
|
|
18
|
+
* - 2: window colour, then 1 where rain leaves the surface dry;
|
|
19
|
+
* - 3: dust, grime, streaks, the metres over which wear fades out with distance (0: never);
|
|
20
|
+
* - 4: scroll along u and v in repeats a second, pulse rate in hertz, pulse depth;
|
|
21
|
+
* - 5: flicker, flipbook frames, flipbook frames a second, the metres past which emission fades
|
|
22
|
+
* (0: never).
|
|
23
|
+
*/
|
|
24
|
+
/** Texels a layer takes in the table. */
|
|
25
|
+
export const SURFACE_EFFECT_TEXELS = 6;
|
|
26
|
+
/**
|
|
27
|
+
* The table for `effects`, one entry a layer (`undefined` for a layer with none), as floats: six
|
|
28
|
+
* RGBA texels a layer, a row a layer.
|
|
29
|
+
*/
|
|
30
|
+
export function packSurfaceEffects(effects) {
|
|
31
|
+
const out = new Float32Array(effects.length * SURFACE_EFFECT_TEXELS * 4);
|
|
32
|
+
effects.forEach((effect, layer) => {
|
|
33
|
+
if (effect === undefined)
|
|
34
|
+
return;
|
|
35
|
+
const at = layer * SURFACE_EFFECT_TEXELS * 4;
|
|
36
|
+
const windows = effect.windows;
|
|
37
|
+
if (windows !== undefined) {
|
|
38
|
+
out[at] = windows.cells[0];
|
|
39
|
+
out[at + 1] = windows.cells[1];
|
|
40
|
+
out[at + 2] = windows.glass === true ? 1 : 0;
|
|
41
|
+
out[at + 3] = windows.seed ?? 0;
|
|
42
|
+
out[at + 7] = windows.glow ?? 0;
|
|
43
|
+
const colour = windows.colour ?? [1, 0.82, 0.55];
|
|
44
|
+
out[at + 8] = colour[0];
|
|
45
|
+
out[at + 9] = colour[1];
|
|
46
|
+
out[at + 10] = colour[2];
|
|
47
|
+
}
|
|
48
|
+
const interior = effect.interior;
|
|
49
|
+
if (interior !== undefined) {
|
|
50
|
+
out[at + 4] = interior.roomLayer + 1;
|
|
51
|
+
out[at + 5] = interior.depth ?? 0.5;
|
|
52
|
+
out[at + 6] = interior.lit ?? 1;
|
|
53
|
+
}
|
|
54
|
+
out[at + 11] = effect.dry === true ? 1 : 0;
|
|
55
|
+
const wear = effect.wear;
|
|
56
|
+
if (wear !== undefined) {
|
|
57
|
+
out[at + 12] = wear.dust ?? 0;
|
|
58
|
+
out[at + 13] = wear.grime ?? 0;
|
|
59
|
+
out[at + 14] = wear.streaks ?? 0;
|
|
60
|
+
out[at + 15] = wear.fade ?? 0;
|
|
61
|
+
}
|
|
62
|
+
const animation = effect.animation;
|
|
63
|
+
if (animation !== undefined) {
|
|
64
|
+
out[at + 16] = animation.scroll?.[0] ?? 0;
|
|
65
|
+
out[at + 17] = animation.scroll?.[1] ?? 0;
|
|
66
|
+
out[at + 18] = animation.pulse?.[0] ?? 0;
|
|
67
|
+
out[at + 19] = animation.pulse?.[1] ?? 0;
|
|
68
|
+
out[at + 20] = animation.flicker ?? 0;
|
|
69
|
+
out[at + 21] = animation.flipbook?.[0] ?? 0;
|
|
70
|
+
out[at + 22] = animation.flipbook?.[1] ?? 0;
|
|
71
|
+
out[at + 23] = animation.fade ?? 0;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scene's surface clock and weather, decided once for both backends: the four numbers the lit
|
|
3
|
+
* stage reads as `uSurfaceScene`. Each backend only uploads them, so the two cannot disagree about
|
|
4
|
+
* what a wetness of 1.4 means — it means 1, as a share of lit windows past one means all of them.
|
|
5
|
+
*/
|
|
6
|
+
import type { Environment } from './backend/webgl2/renderer.ts';
|
|
7
|
+
/** Fill `out`: seconds on the caller's clock, wetness, the lit share, the late share. */
|
|
8
|
+
export declare function resolveSurfaceScene(env: Environment, out: Float32Array): void;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const unit = (value) => Math.min(1, Math.max(0, value ?? 0));
|
|
2
|
+
/** Fill `out`: seconds on the caller's clock, wetness, the lit share, the late share. */
|
|
3
|
+
export function resolveSurfaceScene(env, out) {
|
|
4
|
+
out[0] = env.surfaceTime ?? 0;
|
|
5
|
+
out[1] = unit(env.wetness);
|
|
6
|
+
out[2] = unit(env.litWindows);
|
|
7
|
+
out[3] = unit(env.lateWindows);
|
|
8
|
+
}
|