@driftengine/core 4.4.1 → 4.4.2
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 +26 -0
- package/README.md +1 -1
- package/dist/render/backend/webgl2/renderer.d.ts +3 -0
- package/dist/render/backend/webgl2/renderer.js +13 -0
- package/dist/render/backend/webgpu/postPass.d.ts +19 -3
- package/dist/render/backend/webgpu/postPass.js +17 -4
- package/dist/render/backend/webgpu/renderer.d.ts +10 -0
- package/dist/render/backend/webgpu/renderer.js +67 -4
- package/dist/render/flameLight.js +15 -2
- package/dist/render/pointShadowBudget.js +51 -41
- package/dist/render/recon/disocclusion.d.ts +36 -3
- package/dist/render/recon/disocclusion.js +56 -4
- package/dist/render/recon/resolve.js +95 -12
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +1 -1
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +31 -6
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +31 -6
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/resolve.wgsl.js +112 -8
- package/dist/render/shaders/temporalResolve.js +133 -5
- package/dist/render/temporalAa.d.ts +145 -0
- package/dist/render/temporalAa.js +190 -0
- package/dist/render/temporalPass.d.ts +16 -14
- package/dist/render/temporalPass.js +59 -14
- package/package.json +6 -6
- package/src/render/backend/webgl2/renderer.ts +21 -1
- package/src/render/backend/webgpu/postPass.ts +34 -4
- package/src/render/backend/webgpu/renderer.ts +70 -4
- package/src/render/flameLight.ts +15 -2
- package/src/render/pointShadowBudget.ts +49 -39
- package/src/render/recon/disocclusion.ts +54 -3
- package/src/render/recon/resolve.ts +137 -10
- package/src/render/sceneTarget.ts +8 -1
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +31 -6
- package/src/render/shaders/recon/resolve.wgsl.ts +112 -8
- package/src/render/shaders/temporalResolve.ts +141 -5
- package/src/render/temporalAa.ts +227 -0
- package/src/render/temporalPass.ts +88 -12
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
* and the uniform `recon: ReconParams`, whose struct this module declares.
|
|
40
40
|
*/
|
|
41
41
|
import { wgslSceneDepthToNdc } from '../../depthConvention.js';
|
|
42
|
+
import { PLANAR_FROM, PLANAR_TO } from '../../recon/disocclusion.js';
|
|
42
43
|
/**
|
|
43
44
|
* The parameters one resolve reads, as `ResolveFrame` minus its images.
|
|
44
45
|
*
|
|
@@ -91,6 +92,8 @@ export const RECON_HISTORY_FORMAT = 'rgba16float';
|
|
|
91
92
|
export const RECON_RESOLVE_CORE_WGSL = /* wgsl */ `
|
|
92
93
|
const RECON_SAMPLE_SPREAD: f32 = ${RECON_SAMPLE_SPREAD};
|
|
93
94
|
const RECON_CONFIDENT_WEIGHT: f32 = ${RECON_CONFIDENT_WEIGHT}.0;
|
|
95
|
+
const RECON_PLANAR_FROM: f32 = ${PLANAR_FROM};
|
|
96
|
+
const RECON_PLANAR_TO: f32 = ${PLANAR_TO};
|
|
94
97
|
|
|
95
98
|
fn reconClampTexel(value: i32, size: i32) -> i32 {
|
|
96
99
|
return clamp(value, 0, size - 1);
|
|
@@ -322,10 +325,11 @@ fn reconDisocclusion(
|
|
|
322
325
|
histDepth: f32,
|
|
323
326
|
motionLength: f32,
|
|
324
327
|
normalDot: f32,
|
|
328
|
+
slope: f32,
|
|
325
329
|
) -> f32 {
|
|
326
330
|
if (!(currDepth > 0.0) || !(histDepth > 0.0) || !(motionLength >= 0.0)) { return 0.0; }
|
|
327
331
|
let disagreement = abs(currDepth - histDepth) / currDepth;
|
|
328
|
-
let tolerance = recon.depthTolerance + recon.motionScale * motionLength;
|
|
332
|
+
let tolerance = recon.depthTolerance + recon.motionScale * motionLength + slope;
|
|
329
333
|
let depth = 1.0 - reconSmoothstep(tolerance, 2.0 * tolerance, disagreement);
|
|
330
334
|
let normal = reconSmoothstep(recon.normalFloor, recon.normalCeiling, normalDot);
|
|
331
335
|
return depth * normal;
|
|
@@ -386,6 +390,74 @@ fn reconNormalAt(previous: bool, x: i32, y: i32) -> ReconNormal {
|
|
|
386
390
|
return reconNormalFrom(centre.position, right.position, up.position, eye);
|
|
387
391
|
}
|
|
388
392
|
|
|
393
|
+
/* resolve.ts's viewDepthAt. */
|
|
394
|
+
fn reconViewDepthAt(previous: bool, x: i32, y: i32) -> f32 {
|
|
395
|
+
let w = i32(recon.renderSize.x);
|
|
396
|
+
let h = i32(recon.renderSize.y);
|
|
397
|
+
if (x < 0 || y < 0 || x >= w || y >= h) { return 0.0; }
|
|
398
|
+
var jitter = recon.jitter;
|
|
399
|
+
var m = recon.inverseViewProj;
|
|
400
|
+
var z = 0.0;
|
|
401
|
+
if (previous) {
|
|
402
|
+
jitter = recon.previousJitter;
|
|
403
|
+
m = recon.previousInverseViewProj;
|
|
404
|
+
z = reconPreviousDepth(x, y);
|
|
405
|
+
} else {
|
|
406
|
+
z = reconDepth(x, y);
|
|
407
|
+
}
|
|
408
|
+
let u = (f32(x) + 0.5 - jitter.x) / f32(w);
|
|
409
|
+
let v = (f32(y) + 0.5 - jitter.y) / f32(h);
|
|
410
|
+
let cw = m[0][3] * (u * 2.0 - 1.0) + m[1][3] * (v * 2.0 - 1.0) + m[2][3] * z + m[3][3];
|
|
411
|
+
if (!(abs(cw) > 1e-12)) { return 0.0; }
|
|
412
|
+
return 1.0 / cw;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/* disocclusion.ts's planarity. */
|
|
416
|
+
fn reconPlanarity(before: f32, centre: f32, after: f32) -> f32 {
|
|
417
|
+
if (!(before > 0.0) || !(centre > 0.0) || !(after > 0.0)) { return 1.0; }
|
|
418
|
+
let a = 1.0 / after - 1.0 / centre;
|
|
419
|
+
let b = 1.0 / before - 1.0 / centre;
|
|
420
|
+
let spread = abs(a) + abs(b);
|
|
421
|
+
if (!(spread > 0.0)) { return 1.0; }
|
|
422
|
+
return 1.0 - reconSmoothstep(RECON_PLANAR_FROM, RECON_PLANAR_TO, abs(a + b) / spread);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* disocclusion.ts's surfaceSlope. */
|
|
426
|
+
fn reconSurfaceSlope(before: f32, centre: f32, after: f32) -> f32 {
|
|
427
|
+
let hasAfter = after > 0.0;
|
|
428
|
+
let hasBefore = before > 0.0;
|
|
429
|
+
if (!hasAfter) {
|
|
430
|
+
if (!hasBefore) { return 0.0; }
|
|
431
|
+
return abs(before - centre) / centre;
|
|
432
|
+
}
|
|
433
|
+
if (!hasBefore) { return abs(after - centre) / centre; }
|
|
434
|
+
let a = after - centre;
|
|
435
|
+
let b = before - centre;
|
|
436
|
+
if (a * b > 0.0) { return 0.0; }
|
|
437
|
+
return min(abs(a), abs(b)) / centre;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
struct ReconSurface {
|
|
441
|
+
planar: f32,
|
|
442
|
+
slope: f32,
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/* resolve.ts's surfaceAt. */
|
|
446
|
+
fn reconSurfaceAt(previous: bool, x: i32, y: i32, centre: f32) -> ReconSurface {
|
|
447
|
+
var out: ReconSurface;
|
|
448
|
+
out.planar = 1.0;
|
|
449
|
+
out.slope = 0.0;
|
|
450
|
+
for (var axis = 0; axis < 2; axis = axis + 1) {
|
|
451
|
+
let ax = 1 - axis;
|
|
452
|
+
let ay = axis;
|
|
453
|
+
let after = reconViewDepthAt(previous, x + ax, y + ay);
|
|
454
|
+
let before = reconViewDepthAt(previous, x - ax, y - ay);
|
|
455
|
+
out.planar = out.planar * reconPlanarity(before, centre, after);
|
|
456
|
+
out.slope = max(out.slope, reconSurfaceSlope(before, centre, after));
|
|
457
|
+
}
|
|
458
|
+
return out;
|
|
459
|
+
}
|
|
460
|
+
|
|
389
461
|
struct ReconHistory {
|
|
390
462
|
weight: f32,
|
|
391
463
|
colour: vec4<f32>,
|
|
@@ -433,14 +505,46 @@ fn reconHistoryWeight(u: f32, v: f32, dx: i32, dy: i32) -> ReconHistory {
|
|
|
433
505
|
pu * f32(recon.outputSize.x) - 0.5,
|
|
434
506
|
pv * f32(recon.outputSize.y) - 0.5,
|
|
435
507
|
);
|
|
436
|
-
|
|
437
|
-
let
|
|
438
|
-
let
|
|
508
|
+
/* Last frame's depth, searched around where the dilated texel was: see resolve.ts. */
|
|
509
|
+
let su = (f32(dx) + 0.5 - recon.jitter.x) / rw + mu;
|
|
510
|
+
let sv = (f32(dy) + 0.5 - recon.jitter.y) / rh + mv;
|
|
511
|
+
let cx = reconClampTexel(i32(reconRound(su * rw + recon.previousJitter.x - 0.5)), i32(rw));
|
|
512
|
+
let cy = reconClampTexel(i32(reconRound(sv * rh + recon.previousJitter.y - 0.5)), i32(rh));
|
|
513
|
+
var held = reconViewDepthAt(true, cx, cy);
|
|
514
|
+
var px = cx;
|
|
515
|
+
var py = cy;
|
|
516
|
+
if (!(held > 0.0 && abs(held - expected) <= recon.depthTolerance * expected)) {
|
|
517
|
+
held = 0.0;
|
|
518
|
+
var nearest = 3.4e38;
|
|
519
|
+
for (var j = -1; j <= 1; j = j + 1) {
|
|
520
|
+
for (var i = -1; i <= 1; i = i + 1) {
|
|
521
|
+
let tx = reconClampTexel(cx + i, i32(rw));
|
|
522
|
+
let ty = reconClampTexel(cy + j, i32(rh));
|
|
523
|
+
let depth = reconViewDepthAt(true, tx, ty);
|
|
524
|
+
if (!(depth > 0.0)) { continue; }
|
|
525
|
+
let off = abs(depth - expected);
|
|
526
|
+
if (off < nearest) {
|
|
527
|
+
nearest = off;
|
|
528
|
+
held = depth;
|
|
529
|
+
px = tx;
|
|
530
|
+
py = ty;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
let here = reconSurfaceAt(false, dx, dy, reconViewDepthAt(false, dx, dy));
|
|
537
|
+
var planar = here.planar;
|
|
538
|
+
if (planar > 0.0) { planar = planar * reconSurfaceAt(true, px, py, held).planar; }
|
|
439
539
|
var normalDot = 1.0;
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
540
|
+
if (planar > 0.0) {
|
|
541
|
+
let hereNormal = reconNormalAt(false, dx, dy);
|
|
542
|
+
let thereNormal = reconNormalAt(true, px, py);
|
|
543
|
+
if (hereNormal.ok && thereNormal.ok) {
|
|
544
|
+
normalDot = 1.0 - planar * (1.0 - dot(hereNormal.normal, thereNormal.normal));
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
let trust = reconDisocclusion(expected, held, length(vec2<f32>(mu, mv)), normalDot, here.slope);
|
|
444
548
|
out.weight = out.colour.w * trust;
|
|
445
549
|
return out;
|
|
446
550
|
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* The vertex stage is `FULLSCREEN_VERT`, shared with the occlusion pass, its blur and the rush.
|
|
21
21
|
*/
|
|
22
22
|
import { glslSceneDepthToNdc } from '../depthConvention.js';
|
|
23
|
+
import { FLICKER_BLEND, FLICKER_MEMORY, FLICKER_REPEAT, FLICKER_SURFACE, FLICKER_WIDTH, STILL_FROM, STILL_TO, } from '../temporalAa.js';
|
|
23
24
|
export const TEMPORAL_RESOLVE_FRAG = `#version 300 es
|
|
24
25
|
precision highp float;
|
|
25
26
|
|
|
@@ -29,6 +30,22 @@ in vec2 vUv;
|
|
|
29
30
|
uniform sampler2D uScene;
|
|
30
31
|
/** What this pass wrote last frame: resolved, and conceptually unjittered. */
|
|
31
32
|
uniform sampler2D uHistory;
|
|
33
|
+
/**
|
|
34
|
+
* Each pixel's flicker record from last frame, the anti-flicker in temporalAa.ts: its spread about
|
|
35
|
+
* its period's mean, the sum this period has gathered, the last period's mean, and how many periods
|
|
36
|
+
* running have repeated. highp for the reason the depth's is: a luma in a half-float scene is not a
|
|
37
|
+
* value from 0 to 1.
|
|
38
|
+
*/
|
|
39
|
+
uniform highp sampler2D uFlicker;
|
|
40
|
+
/**
|
|
41
|
+
* The rest of the record: the view depth the pixel's sample stood at, so the next frame can tell
|
|
42
|
+
* whether it follows the same surface, and the pixel's own depth swing, which that test allows for.
|
|
43
|
+
*/
|
|
44
|
+
uniform highp sampler2D uFlickerMotion;
|
|
45
|
+
/** The inverse projection's depth row and w row, as the other passes read a depth into metres. */
|
|
46
|
+
uniform vec4 uDepthToViewZ;
|
|
47
|
+
/** One where the records can hold what they hold, zero where they cannot: then 4.4.1's resolve. */
|
|
48
|
+
uniform float uAntiFlicker;
|
|
32
49
|
/**
|
|
33
50
|
* The frame's depth.
|
|
34
51
|
*
|
|
@@ -52,6 +69,8 @@ uniform highp sampler2D uDepth;
|
|
|
52
69
|
uniform mat4 uReprojection;
|
|
53
70
|
/** One texel of the target, for walking the neighbourhood. */
|
|
54
71
|
uniform vec2 uTexel;
|
|
72
|
+
/** One on the first frame of the jitter's period, where the anti-flicker compares two periods. */
|
|
73
|
+
uniform float uPeriodStart;
|
|
55
74
|
/**
|
|
56
75
|
* How much of the clipped history to keep, 0 to 1.
|
|
57
76
|
*
|
|
@@ -62,7 +81,41 @@ uniform vec2 uTexel;
|
|
|
62
81
|
*/
|
|
63
82
|
uniform float uHistoryBlend;
|
|
64
83
|
|
|
65
|
-
out vec4 fragColor;
|
|
84
|
+
layout(location = 0) out vec4 fragColor;
|
|
85
|
+
/** The record carried out, in the texture uFlicker reads next frame. */
|
|
86
|
+
layout(location = 1) out vec4 flickerOut;
|
|
87
|
+
/** And the rest of it, in the texture uFlickerMotion reads. */
|
|
88
|
+
layout(location = 2) out vec4 flickerMotionOut;
|
|
89
|
+
|
|
90
|
+
const float FLICKER_MEMORY = ${FLICKER_MEMORY.toFixed(3)};
|
|
91
|
+
const float FLICKER_WIDTH = ${FLICKER_WIDTH.toFixed(1)};
|
|
92
|
+
const float FLICKER_REPEAT = ${FLICKER_REPEAT.toFixed(2)};
|
|
93
|
+
const float FLICKER_BLEND = ${FLICKER_BLEND.toFixed(2)};
|
|
94
|
+
const float FLICKER_SURFACE = ${FLICKER_SURFACE.toFixed(2)};
|
|
95
|
+
const float STILL_FROM = ${STILL_FROM.toFixed(2)};
|
|
96
|
+
const float STILL_TO = ${STILL_TO.toFixed(2)};
|
|
97
|
+
|
|
98
|
+
float luma(vec3 c) {
|
|
99
|
+
return dot(c, vec3(0.2126, 0.7152, 0.0722));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** A record starting over: no spread, and nothing repeated yet. */
|
|
103
|
+
vec4 freshRecord(vec3 current) {
|
|
104
|
+
float seen = luma(current);
|
|
105
|
+
return vec4(0.0, seen * FLICKER_MEMORY, seen, 0.0);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Metres in front of the camera, from a depth sample, as depthOfField.ts reads one. */
|
|
109
|
+
float viewDepthOf(float depth) {
|
|
110
|
+
float z = ${glslSceneDepthToNdc('depth')};
|
|
111
|
+
return -(uDepthToViewZ.x * z + uDepthToViewZ.y) / (uDepthToViewZ.z * z + uDepthToViewZ.w);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Whether two periods' means are the same, one to zero. */
|
|
115
|
+
float repeats(float mean, float previous) {
|
|
116
|
+
float apart = abs(mean - previous) / max(max(mean, previous), 1.0 / 255.0);
|
|
117
|
+
return 1.0 - smoothstep(FLICKER_REPEAT, 2.0 * FLICKER_REPEAT, apart);
|
|
118
|
+
}
|
|
66
119
|
|
|
67
120
|
/**
|
|
68
121
|
* The colours actually present around this pixel this frame, as a box.
|
|
@@ -154,19 +207,45 @@ void main() {
|
|
|
154
207
|
/* Nothing to blend towards: the first frame, a resize, a cut, or the effect switched off. */
|
|
155
208
|
if (uHistoryBlend <= 0.0) {
|
|
156
209
|
fragColor = vec4(current, 1.0);
|
|
210
|
+
flickerOut = freshRecord(current);
|
|
211
|
+
flickerMotionOut = vec4(0.0);
|
|
157
212
|
return;
|
|
158
213
|
}
|
|
159
214
|
|
|
215
|
+
/*
|
|
216
|
+
* **The motion is the nearest surface's among the nine**, the dilation every temporal resolve
|
|
217
|
+
* uses: a pixel at a thin thing samples it in some frames and what is behind it in others, and the
|
|
218
|
+
* two move apart as the camera does, so reprojecting each frame by whichever it caught sends its
|
|
219
|
+
* history and its record somewhere different every frame. A crack of light round a door moves with
|
|
220
|
+
* the door that frames it, and so does its pixel. The depth kept is that surface's too, so the record
|
|
221
|
+
* keeps and next frame tests the depth the reprojection followed.
|
|
222
|
+
*/
|
|
223
|
+
vec2 nearestAt = vUv;
|
|
160
224
|
float depth = textureLod(uDepth, vUv, 0.0).r;
|
|
161
|
-
|
|
225
|
+
float metres = viewDepthOf(depth);
|
|
226
|
+
for (int y = -1; y <= 1; y++) {
|
|
227
|
+
for (int x = -1; x <= 1; x++) {
|
|
228
|
+
vec2 at = vUv + vec2(float(x), float(y)) * uTexel;
|
|
229
|
+
float tap = textureLod(uDepth, at, 0.0).r;
|
|
230
|
+
float tapMetres = viewDepthOf(tap);
|
|
231
|
+
if (tapMetres < metres) {
|
|
232
|
+
metres = tapMetres;
|
|
233
|
+
depth = tap;
|
|
234
|
+
nearestAt = at;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
vec4 clip = vec4(nearestAt * 2.0 - 1.0, ${glslSceneDepthToNdc('depth')}, 1.0);
|
|
162
239
|
vec4 previous = uReprojection * clip;
|
|
163
240
|
/* Behind the previous eye: there is no last-frame position to sample. */
|
|
164
241
|
if (previous.w <= 0.0) {
|
|
165
242
|
fragColor = vec4(current, 1.0);
|
|
243
|
+
flickerOut = freshRecord(current);
|
|
244
|
+
flickerMotionOut = vec4(metres, 0.0, 0.0, 0.0);
|
|
166
245
|
return;
|
|
167
246
|
}
|
|
168
247
|
|
|
169
|
-
vec2 wasUv = (previous.xy / previous.w) * 0.5 + 0.5;
|
|
248
|
+
vec2 wasUv = vUv + ((previous.xy / previous.w) * 0.5 + 0.5 - nearestAt);
|
|
170
249
|
/*
|
|
171
250
|
* **Off the edge of the last frame is a disocclusion**, and the honest answer is this frame's
|
|
172
251
|
* colour rather than a clamped sample of the border, which would drag one row of pixels inward
|
|
@@ -174,6 +253,8 @@ void main() {
|
|
|
174
253
|
*/
|
|
175
254
|
if (wasUv.x < 0.0 || wasUv.x > 1.0 || wasUv.y < 0.0 || wasUv.y > 1.0) {
|
|
176
255
|
fragColor = vec4(current, 1.0);
|
|
256
|
+
flickerOut = freshRecord(current);
|
|
257
|
+
flickerMotionOut = vec4(metres, 0.0, 0.0, 0.0);
|
|
177
258
|
return;
|
|
178
259
|
}
|
|
179
260
|
|
|
@@ -181,8 +262,55 @@ void main() {
|
|
|
181
262
|
vec3 hi;
|
|
182
263
|
neighbourhood(lo, hi);
|
|
183
264
|
vec3 history = historyCatmullRom(wasUv);
|
|
184
|
-
vec3 bounded = clipToNeighbourhood(history, lo, hi);
|
|
185
265
|
|
|
186
|
-
|
|
266
|
+
/*
|
|
267
|
+
* The anti-flicker, operation for operation the one in temporalAa.ts. The record is read at the
|
|
268
|
+
* texel where the surface was rather than filtered, so two surfaces' records never mix at an
|
|
269
|
+
* edge; it is the surface's own if last frame's depth there, or at one of the texels beside it,
|
|
270
|
+
* is the depth this surface should have had — the nearest of five, because this frame's sample
|
|
271
|
+
* and last frame's can straddle an edge with nothing moved. Otherwise it is no record.
|
|
272
|
+
*/
|
|
273
|
+
ivec2 size = textureSize(uFlicker, 0);
|
|
274
|
+
ivec2 was = clamp(ivec2(wasUv * vec2(size)), ivec2(0), size - 1);
|
|
275
|
+
float nearest = 1e9;
|
|
276
|
+
for (int n = 0; n < 5; n++) {
|
|
277
|
+
ivec2 at = was + (n == 1 ? ivec2(1, 0) : n == 2 ? ivec2(-1, 0) : n == 3 ? ivec2(0, 1) : n == 4 ? ivec2(0, -1) : ivec2(0));
|
|
278
|
+
float stood = texelFetch(uFlickerMotion, clamp(at, ivec2(0), size - 1), 0).r;
|
|
279
|
+
if (stood > 0.0) nearest = min(nearest, abs(stood - previous.w));
|
|
280
|
+
}
|
|
281
|
+
/* The swing is the pixel's whatever the test says: resetting it would stop it learning one. */
|
|
282
|
+
vec4 motionThere = texelFetch(uFlickerMotion, was, 0);
|
|
283
|
+
bool missedAll = nearest > 1e8;
|
|
284
|
+
bool same = uAntiFlicker > 0.5 && !missedAll &&
|
|
285
|
+
nearest <= FLICKER_WIDTH * motionThere.g + FLICKER_SURFACE * previous.w;
|
|
286
|
+
float swingOut = missedAll ? 0.0 : motionThere.g + (nearest - motionThere.g) * FLICKER_MEMORY;
|
|
287
|
+
float seen = luma(current);
|
|
288
|
+
vec4 held = same ? texelFetch(uFlicker, was, 0) : freshRecord(current);
|
|
289
|
+
|
|
290
|
+
float still = 1.0 - smoothstep(STILL_FROM, STILL_TO, length((wasUv - vUv) / uTexel));
|
|
291
|
+
float apart = abs(seen - held.b);
|
|
292
|
+
float reach = FLICKER_WIDTH * held.r + 1.0 / 255.0;
|
|
293
|
+
bool within = apart <= reach;
|
|
294
|
+
bool proven = held.a > 0.75 && within;
|
|
295
|
+
float widen = proven ? FLICKER_WIDTH * held.r * still : 0.0;
|
|
296
|
+
float blend = proven ? mix(uHistoryBlend, FLICKER_BLEND, still) : uHistoryBlend;
|
|
297
|
+
vec3 bounded = clipToNeighbourhood(history, lo - widen, hi + widen);
|
|
298
|
+
|
|
299
|
+
float sum = held.g;
|
|
300
|
+
float mean = held.b;
|
|
301
|
+
float repeated = within ? held.a : 0.0;
|
|
302
|
+
if (uPeriodStart > 0.5) {
|
|
303
|
+
repeated = repeats(sum, mean) > 0.5 ? min(1.0, repeated + 0.5) : 0.0;
|
|
304
|
+
mean = sum;
|
|
305
|
+
sum = 0.0;
|
|
306
|
+
}
|
|
307
|
+
flickerOut = vec4(
|
|
308
|
+
held.r + (apart - held.r) * FLICKER_MEMORY,
|
|
309
|
+
sum + seen * FLICKER_MEMORY,
|
|
310
|
+
mean,
|
|
311
|
+
repeated * still
|
|
312
|
+
);
|
|
313
|
+
flickerMotionOut = vec4(metres, swingOut, 0.0, 0.0);
|
|
314
|
+
fragColor = vec4(mix(current, bounded, blend), 1.0);
|
|
187
315
|
}
|
|
188
316
|
`;
|
|
@@ -76,6 +76,146 @@ export type Rgb = [number, number, number];
|
|
|
76
76
|
* spells it this way.
|
|
77
77
|
*/
|
|
78
78
|
export declare function clipToNeighbourhood(out: Rgb, history: readonly [number, number, number], min: readonly [number, number, number], max: readonly [number, number, number]): Rgb;
|
|
79
|
+
/**
|
|
80
|
+
* **The anti-flicker: a still pixel's neighbourhood widened by its own flicker.**
|
|
81
|
+
*
|
|
82
|
+
* A thread, a crack of light round a door, a leaf finer than a pixel: the jitter catches it in some
|
|
83
|
+
* phases and misses it in others, and in a phase that misses it the nine taps round a pixel can all
|
|
84
|
+
* miss it at once. The box collapses onto the background, the history — which had settled on the
|
|
85
|
+
* thread's share of the pixel — is clipped out, and it grows back a tenth a frame until the next
|
|
86
|
+
* miss. That is a paused frame flashing at every fine edge, and the clip is doing its job: nothing
|
|
87
|
+
* in one frame says the thread is still there.
|
|
88
|
+
*
|
|
89
|
+
* What does say so is the pixel's own record. **A still camera over a still scene samples every pixel
|
|
90
|
+
* the same way every period of the jitter, exactly**, so aliasing repeats itself; a surface that moves
|
|
91
|
+
* across the pixel, a flame, a light that shimmers, does not. So each pixel keeps the mean of its
|
|
92
|
+
* samples over the last period, the sum it is gathering for this one, how many periods running have
|
|
93
|
+
* repeated, and the running mean of how far its samples land from their period's mean — and its box
|
|
94
|
+
* widens by that spread only once two periods running have repeated. Measured on the samples rather
|
|
95
|
+
* than against the history, because the history is what the clip cuts. **And a sample farther from
|
|
96
|
+
* its period's mean than the box would widen to is not the flicker the pixel has shown**: something
|
|
97
|
+
* has arrived, and the count starts again on that frame rather than at the end of the period. **Two, and a mean rather than
|
|
98
|
+
* one sample**, because one sample repeats by coincidence: stripes sliding across a pixel land on the
|
|
99
|
+
* same colour at two period starts about as often as not.
|
|
100
|
+
*
|
|
101
|
+
* **What it gives up.** A surface arriving over a pixel that was flickering is held back until the
|
|
102
|
+
* period ends and fails to repeat — at most eight frames, fading a tenth a frame meanwhile; a still
|
|
103
|
+
* pixel needs three periods before it is helped at all; and a pixel that flickers in a scene that is
|
|
104
|
+
* itself moving is not helped, since nothing repeats there. Nothing moving past a twentieth of a
|
|
105
|
+
* pixel a frame is touched, since the reprojection is then what decides.
|
|
106
|
+
*
|
|
107
|
+
* **A moving camera had a weaker proof of its own, and it was taken out.** It widened a pixel's box
|
|
108
|
+
* while its samples swung both ways about their mean, with the record carried through the
|
|
109
|
+
* reprojection. Measured on a moving courtyard it removed a twentieth of what flickered, and it broke
|
|
110
|
+
* a sun's shadow line on the floor into dashes that were not there before: the record is read at the
|
|
111
|
+
* nearest texel, so under sub-pixel motion one pixel widened and its neighbour did not. The resolve
|
|
112
|
+
* already removes nineteen twentieths of a moving frame's flicker without it. **What would make it wrong** is a jitter
|
|
113
|
+
* whose sequence does not repeat, where no period is the same twice however still the scene is.
|
|
114
|
+
*/
|
|
115
|
+
/** This frame's share of the running spread, and of a period's mean: one period of the jitter. */
|
|
116
|
+
export declare const FLICKER_MEMORY: number;
|
|
117
|
+
/**
|
|
118
|
+
* How many spreads a flickering pixel's box widens by. The hardest thread is one missed in a single
|
|
119
|
+
* phase of eight: that miss needs the box to reach 7/8 of its contrast, and the running spread,
|
|
120
|
+
* worn down by seven catches that each land an eighth of the contrast from the period's mean, has
|
|
121
|
+
* fallen to a = 1/8 + (7/8 a + 7/64 − 1/8)·(7/8)⁷ of the contrast by then — 0.181, a steady state
|
|
122
|
+
* solved by hand — against the 7/32 it averages. Seven eighths over that is 4.8; six leaves room.
|
|
123
|
+
* Threads caught in a share nearer a half spread more and need fewer.
|
|
124
|
+
*/
|
|
125
|
+
export declare const FLICKER_WIDTH = 6;
|
|
126
|
+
/**
|
|
127
|
+
* How far apart two periods' means may be, relative to the larger, and still repeat: fully below
|
|
128
|
+
* this, not at all at twice it. Above an eight-bit record's rounding and a lamp's slow breathing; far
|
|
129
|
+
* below what anything crossing the pixel does to it.
|
|
130
|
+
*/
|
|
131
|
+
export declare const FLICKER_REPEAT = 0.05;
|
|
132
|
+
/**
|
|
133
|
+
* How much of the history a pixel proven to repeat keeps, where an unproven one keeps
|
|
134
|
+
* `TEMPORAL_HISTORY_BLEND`. A settled thread still breathes with the jitter by the share of each
|
|
135
|
+
* frame's sample that goes into what is shown — a tenth of its contrast at the base blend — and a
|
|
136
|
+
* pixel that has repeated for two periods has nothing to respond to, so it keeps more: three
|
|
137
|
+
* hundredths of each frame instead of a tenth. What it costs is a surface arriving inside the
|
|
138
|
+
* pixel's own swing, which fades in at that rate until the period's check lets it through.
|
|
139
|
+
*/
|
|
140
|
+
export declare const FLICKER_BLEND = 0.97;
|
|
141
|
+
/**
|
|
142
|
+
* How far last frame's depth may be from the depth a surface should have had there, relative, and
|
|
143
|
+
* still be that surface: two percent beyond the pixel's own depth swing, taken as the nearest of the
|
|
144
|
+
* texel where it stood and the four beside it. **The nearest of five and not the one**, because this
|
|
145
|
+
* frame's sample and last frame's can straddle an edge with nothing moved — the lesson
|
|
146
|
+
* `recon/resolve.ts` learned the expensive way.
|
|
147
|
+
*
|
|
148
|
+
* **And beyond its own swing, because depth aliases the way colour does.** A crack of light round a
|
|
149
|
+
* door is a pixel whose sample lands on the door in some phases and on what is behind it in others,
|
|
150
|
+
* so its depth jumps metres with nothing moved, and a test held to two percent reset the record of
|
|
151
|
+
* exactly the pixels the anti-flicker is for: a held frame went from 5 flickering pixels back to 774.
|
|
152
|
+
* So each pixel keeps the running mean of how far last frame's depth missed — kept whatever the test
|
|
153
|
+
* says, since resetting it would stop it ever learning the swing — and the test allows
|
|
154
|
+
* `FLICKER_WIDTH` of those, as the box does of the colour's. A flat surface's swing is none and its
|
|
155
|
+
* test stays tight; a surface revealed by the camera is a jump nothing in the pixel's past explains.
|
|
156
|
+
*/
|
|
157
|
+
export declare const FLICKER_SURFACE = 0.02;
|
|
158
|
+
/** Whether a record follows the surface it was kept for: see `FLICKER_SURFACE`. */
|
|
159
|
+
export declare function sameSurface(expected: number, stood: ArrayLike<number>, swing: number): boolean;
|
|
160
|
+
/**
|
|
161
|
+
* How far the nearest of last frame's depths missed the one expected. A depth of nought is a texel
|
|
162
|
+
* with nothing recorded — the frame after a cut — and is no texel; with none, nothing is missed by
|
|
163
|
+
* any amount.
|
|
164
|
+
*/
|
|
165
|
+
export declare function missedBy(expected: number, stood: ArrayLike<number>): number;
|
|
166
|
+
/**
|
|
167
|
+
* The pixel's depth swing carried on: the running mean of `missedBy`, over a period, and none where
|
|
168
|
+
* there was nothing to miss — a swing learned from a cut would loosen the test for a whole period.
|
|
169
|
+
*/
|
|
170
|
+
export declare function nextDepthSwing(swing: number, missed: number): number;
|
|
171
|
+
/**
|
|
172
|
+
* Reprojected motion in pixels a frame: still below the first, moving from the second. **Small,
|
|
173
|
+
* because repetition breaks long before motion is visible**: a camera drifting a fifth of a pixel a
|
|
174
|
+
* frame shifts the grid more than a pixel a period, so nothing repeats, and a ramp to half a pixel
|
|
175
|
+
* counted that as mostly still and kept vouching for samples that had stopped repeating.
|
|
176
|
+
*/
|
|
177
|
+
export declare const STILL_FROM = 0.01;
|
|
178
|
+
export declare const STILL_TO = 0.05;
|
|
179
|
+
/**
|
|
180
|
+
* A pixel's flicker record, in luma: the running spread of its samples about their period's mean,
|
|
181
|
+
* the sum this period has gathered so far, the last period's mean, and how many periods running have
|
|
182
|
+
* repeated — none, one or two, as 0, 0.5 and 1.
|
|
183
|
+
*/
|
|
184
|
+
export interface Flicker {
|
|
185
|
+
spread: number;
|
|
186
|
+
sum: number;
|
|
187
|
+
mean: number;
|
|
188
|
+
repeated: number;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* A fresh record for a surface first seen this frame: no spread and nothing repeated. **A
|
|
192
|
+
* record of another surface is no record**, so the resolve starts one over wherever last frame's
|
|
193
|
+
* depth says the surface is not the one it followed, before this frame widens by anything.
|
|
194
|
+
*/
|
|
195
|
+
export declare function freshFlicker(out: Flicker, sample: number): Flicker;
|
|
196
|
+
/** Rec. 709 luma, which is what the record measures in. */
|
|
197
|
+
export declare function luma(c: readonly [number, number, number]): number;
|
|
198
|
+
/** One for a pixel whose reprojection did not move, falling to none at a twentieth of a pixel. */
|
|
199
|
+
export declare function stillness(motionPixels: number): number;
|
|
200
|
+
/**
|
|
201
|
+
* Whether a pixel is proven to repeat this frame: two periods running have, and this frame's sample
|
|
202
|
+
* lands within the reach of the box it would widen to. A sample beyond that reach is something
|
|
203
|
+
* arriving, and it disqualifies the frame it lands in as well as the count it carries out.
|
|
204
|
+
*/
|
|
205
|
+
export declare function proven(record: Flicker, sample: number): boolean;
|
|
206
|
+
/** How far a pixel's box widens this frame: by its spread, once proven, and only while still. */
|
|
207
|
+
export declare function flickerWidening(record: Flicker, sample: number, still: number): number;
|
|
208
|
+
/** How much of the history this frame keeps: the base blend, or more once proven. */
|
|
209
|
+
export declare function flickerBlend(base: number, record: Flicker, sample: number, still: number): number;
|
|
210
|
+
/** Whether two periods' means are the same, one to zero. */
|
|
211
|
+
export declare function repeats(mean: number, previous: number): number;
|
|
212
|
+
/**
|
|
213
|
+
* The record carried out, from the one carried in and this frame's sample in luma. `periodStart`
|
|
214
|
+
* is the first frame of the jitter's period, where the period just gathered is compared with the
|
|
215
|
+
* one before. Forgotten in proportion to motion — a moving pixel's record would be a record of
|
|
216
|
+
* somewhere else.
|
|
217
|
+
*/
|
|
218
|
+
export declare function nextFlicker(out: Flicker, previous: Flicker, sample: number, still: number, periodStart: boolean): Flicker;
|
|
79
219
|
/**
|
|
80
220
|
* Whether the last frame left a picture this one may sample, and where the jitter has got to.
|
|
81
221
|
*
|
|
@@ -97,6 +237,11 @@ export declare class TemporalHistory {
|
|
|
97
237
|
* and not two.
|
|
98
238
|
*/
|
|
99
239
|
openFrame(width: number, height: number): boolean;
|
|
240
|
+
/**
|
|
241
|
+
* Whether this frame samples the first phase of the jitter's period, where the anti-flicker
|
|
242
|
+
* compares the period just gathered with the one before. One answer for both backends.
|
|
243
|
+
*/
|
|
244
|
+
get periodStart(): boolean;
|
|
100
245
|
/** A resolved picture is now in the history, at the size the frame was opened with. */
|
|
101
246
|
accumulated(): void;
|
|
102
247
|
/** Forget it — a cut, a teleport, or anything else that makes the last frame a different scene. */
|