@driftengine/core 4.4.1 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +72 -0
- package/README.md +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -0
- package/dist/render/backend/api.d.ts +17 -11
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/renderer.d.ts +82 -8
- package/dist/render/backend/webgl2/renderer.js +373 -105
- package/dist/render/backend/webgpu/buffers.d.ts +19 -1
- package/dist/render/backend/webgpu/buffers.js +27 -1
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +2 -0
- package/dist/render/backend/webgpu/flatPass.js +20 -1
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +205 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
- package/dist/render/backend/webgpu/instanced.d.ts +12 -1
- package/dist/render/backend/webgpu/instanced.js +20 -1
- package/dist/render/backend/webgpu/latePass.d.ts +26 -0
- package/dist/render/backend/webgpu/latePass.js +133 -0
- package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
- package/dist/render/backend/webgpu/motionPass.js +491 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
- package/dist/render/backend/webgpu/pipelineCache.js +11 -1
- package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
- package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
- package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
- package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
- package/dist/render/backend/webgpu/postPass.d.ts +21 -15
- package/dist/render/backend/webgpu/postPass.js +17 -4
- package/dist/render/backend/webgpu/renderer.d.ts +181 -54
- package/dist/render/backend/webgpu/renderer.js +1011 -394
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
- package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
- package/dist/render/backend/webgpu/snapshotMips.js +52 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
- package/dist/render/flameLight.js +15 -2
- package/dist/render/glass.d.ts +45 -0
- package/dist/render/glass.js +39 -0
- package/dist/render/glassCasters.d.ts +40 -0
- package/dist/render/glassCasters.js +84 -0
- package/dist/render/glassShadow.d.ts +78 -0
- package/dist/render/glassShadow.js +125 -0
- package/dist/render/globalMediumPass.d.ts +2 -3
- package/dist/render/globalMediumPass.js +2 -8
- package/dist/render/gpudriven/lit.d.ts +12 -0
- package/dist/render/gpudriven/lit.js +23 -0
- package/dist/render/gpudriven/materialTable.d.ts +15 -5
- package/dist/render/gpudriven/materialTable.js +26 -6
- package/dist/render/gpudriven/pipeline.d.ts +1 -1
- package/dist/render/gpudriven/pipeline.js +4 -3
- package/dist/render/gpudriven/sunGlass.d.ts +35 -0
- package/dist/render/gpudriven/sunGlass.js +89 -0
- package/dist/render/lightBudget.d.ts +17 -5
- package/dist/render/lightBudget.js +17 -5
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- package/dist/render/pointShadowArray.d.ts +56 -3
- package/dist/render/pointShadowArray.js +127 -16
- package/dist/render/pointShadowBudget.js +51 -41
- package/dist/render/pointShadowMap.d.ts +23 -1
- package/dist/render/pointShadowMap.js +27 -1
- package/dist/render/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -0
- package/dist/render/recon/disocclusion.d.ts +36 -3
- package/dist/render/recon/disocclusion.js +56 -4
- package/dist/render/recon/lateRouting.d.ts +14 -0
- package/dist/render/recon/lateRouting.js +18 -0
- package/dist/render/recon/mover.d.ts +57 -0
- package/dist/render/recon/mover.js +86 -0
- package/dist/render/recon/resolve.js +95 -12
- package/dist/render/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +9 -1
- package/dist/render/sceneTarget.js +59 -13
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +85 -9
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- package/dist/render/shaders/flat/glassTint.d.ts +6 -0
- package/dist/render/shaders/flat/glassTint.js +25 -0
- package/dist/render/shaders/flat/index.d.ts +13 -0
- package/dist/render/shaders/flat/index.js +4 -0
- package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
- package/dist/render/shaders/flat/lampGlass.js +183 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +115 -15
- package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -0
- package/dist/render/shaders/flat/preamble.js +21 -6
- package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
- package/dist/render/shaders/generated/depth.wgsl.js +227 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
- package/dist/render/shaders/generated/flat.wgsl.js +376 -328
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
- package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +31 -6
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +31 -6
- package/dist/render/shaders/globalMedium.js +6 -6
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
- package/dist/render/shaders/lightVolume.js +6 -6
- package/dist/render/shaders/octahedralResolve.d.ts +8 -0
- package/dist/render/shaders/octahedralResolve.js +48 -17
- package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
- package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +84 -0
- package/dist/render/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/shadowCasters.d.ts +20 -1
- package/dist/render/shadowCasters.js +15 -1
- package/dist/render/shadowMap.d.ts +54 -5
- package/dist/render/shadowMap.js +190 -36
- package/dist/render/sunGlassTint.d.ts +25 -0
- package/dist/render/sunGlassTint.js +79 -0
- package/dist/render/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/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/package.json +6 -6
- package/src/index.ts +10 -0
- package/src/render/backend/api.ts +15 -9
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/renderer.ts +422 -109
- package/src/render/backend/webgpu/buffers.ts +50 -2
- package/src/render/backend/webgpu/depthPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +22 -1
- package/src/render/backend/webgpu/glassTintPass.ts +244 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanced.ts +21 -1
- package/src/render/backend/webgpu/latePass.ts +152 -0
- package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
- package/src/render/backend/webgpu/motionPass.ts +535 -0
- package/src/render/backend/webgpu/pipelineCache.ts +12 -1
- package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
- package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
- package/src/render/backend/webgpu/postPass.ts +34 -4
- package/src/render/backend/webgpu/renderer.ts +1178 -425
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
- package/src/render/backend/webgpu/snapshotMips.ts +80 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
- package/src/render/flameLight.ts +15 -2
- package/src/render/glass.ts +74 -0
- package/src/render/glassCasters.ts +164 -0
- package/src/render/glassShadow.ts +153 -0
- package/src/render/globalMediumPass.ts +4 -11
- package/src/render/gpudriven/lit.ts +31 -0
- package/src/render/gpudriven/materialTable.ts +28 -6
- package/src/render/gpudriven/pipeline.ts +4 -3
- package/src/render/gpudriven/sunGlass.ts +117 -0
- package/src/render/lightBudget.ts +19 -5
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowBudget.ts +49 -39
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/disocclusion.ts +54 -3
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/recon/resolve.ts +137 -10
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +12 -0
- package/src/render/sceneTarget.ts +66 -12
- package/src/render/shaders/depth.ts +109 -9
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +17 -0
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +115 -15
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +21 -6
- package/src/render/shaders/generated/depth.wgsl.ts +234 -4
- package/src/render/shaders/generated/flat.wgsl.ts +376 -328
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +31 -6
- package/src/render/shaders/globalMedium.ts +6 -6
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
- package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
- package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
- package/src/render/shaders/lightVolume.ts +6 -6
- package/src/render/shaders/octahedralResolve.ts +50 -17
- package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +112 -8
- package/src/render/shaders/temporalResolve.ts +141 -5
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/temporalAa.ts +227 -0
- package/src/render/temporalPass.ts +88 -12
- package/src/render/translucentQueue.ts +20 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the sun's light keeps of itself through glass: an RGBA8 array of two layers, static and
|
|
3
|
+
* moving, beside the sun's depth array. RGB is the product of every pane's
|
|
4
|
+
* `transmission × (1 − F) × tint`, A the product of their clarities (`glassShadow.ts`).
|
|
5
|
+
*
|
|
6
|
+
* **Cleared to white and drawn with a multiplying blend and no depth test**, so every pane on a ray
|
|
7
|
+
* contributes and the order they come in cannot matter. **Mipmapped and filtered**, unlike the
|
|
8
|
+
* depths beside it: a frosted pane's colour is read at the level its spread asks for, so a wide
|
|
9
|
+
* spread is smooth rather than a scatter of taps — the chain is rebuilt after every draw of a layer.
|
|
10
|
+
* `'half'` makes it half the depth map's size and nothing else changes: the light matrix is the
|
|
11
|
+
* same, so a texel is a texel of the same place, only larger.
|
|
12
|
+
*/
|
|
13
|
+
export class SunGlassTint {
|
|
14
|
+
size;
|
|
15
|
+
texture;
|
|
16
|
+
framebuffer;
|
|
17
|
+
constructor(gl, size) {
|
|
18
|
+
this.size = size;
|
|
19
|
+
const texture = gl.createTexture();
|
|
20
|
+
const framebuffer = gl.createFramebuffer();
|
|
21
|
+
if (texture === null || framebuffer === null) {
|
|
22
|
+
throw new Error('SunGlassTint: the tint target could not be created');
|
|
23
|
+
}
|
|
24
|
+
this.texture = texture;
|
|
25
|
+
this.framebuffer = framebuffer;
|
|
26
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
|
|
27
|
+
gl.texStorage3D(gl.TEXTURE_2D_ARRAY, mipLevels(size), gl.RGBA8, size, size, 2);
|
|
28
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
|
29
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
30
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
31
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
32
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
|
|
33
|
+
/* Both layers born clear: a layer no pane has reached lets everything through. */
|
|
34
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
35
|
+
for (let layer = 0; layer < 2; layer++) {
|
|
36
|
+
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture, 0, layer);
|
|
37
|
+
gl.clearBufferfv(gl.COLOR, 0, CLEAR);
|
|
38
|
+
}
|
|
39
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
40
|
+
this.fillMips(gl);
|
|
41
|
+
}
|
|
42
|
+
/** Bind `layer` (0 static, 1 moving), clear it to white, and set the multiplying state. */
|
|
43
|
+
begin(gl, layer) {
|
|
44
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
45
|
+
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, layer);
|
|
46
|
+
gl.viewport(0, 0, this.size, this.size);
|
|
47
|
+
gl.clearBufferfv(gl.COLOR, 0, CLEAR);
|
|
48
|
+
gl.disable(gl.DEPTH_TEST);
|
|
49
|
+
gl.depthMask(false);
|
|
50
|
+
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
51
|
+
gl.enable(gl.BLEND);
|
|
52
|
+
gl.blendEquation(gl.FUNC_ADD);
|
|
53
|
+
gl.blendFuncSeparate(gl.DST_COLOR, gl.ZERO, gl.DST_ALPHA, gl.ZERO);
|
|
54
|
+
}
|
|
55
|
+
/** Put back what `begin` changed, and rebuild the mip chain the frost reads. */
|
|
56
|
+
end(gl) {
|
|
57
|
+
gl.disable(gl.BLEND);
|
|
58
|
+
gl.depthMask(true);
|
|
59
|
+
gl.enable(gl.DEPTH_TEST);
|
|
60
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
61
|
+
this.fillMips(gl);
|
|
62
|
+
}
|
|
63
|
+
/** Every level from level 0, both layers: bound on the active unit and handed back. */
|
|
64
|
+
fillMips(gl) {
|
|
65
|
+
const previous = gl.getParameter(gl.TEXTURE_BINDING_2D_ARRAY);
|
|
66
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
|
|
67
|
+
gl.generateMipmap(gl.TEXTURE_2D_ARRAY);
|
|
68
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, previous);
|
|
69
|
+
}
|
|
70
|
+
dispose(gl) {
|
|
71
|
+
gl.deleteFramebuffer(this.framebuffer);
|
|
72
|
+
gl.deleteTexture(this.texture);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const CLEAR = new Float32Array([1, 1, 1, 1]);
|
|
76
|
+
/** A full chain down to one texel. */
|
|
77
|
+
function mipLevels(size) {
|
|
78
|
+
return Math.floor(Math.log2(Math.max(1, size))) + 1;
|
|
79
|
+
}
|
|
@@ -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. */
|
|
@@ -129,6 +129,189 @@ export function clipToNeighbourhood(out, history, min, max) {
|
|
|
129
129
|
out[2] = centre[2] + offset[2] * scale;
|
|
130
130
|
return out;
|
|
131
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* **The anti-flicker: a still pixel's neighbourhood widened by its own flicker.**
|
|
134
|
+
*
|
|
135
|
+
* A thread, a crack of light round a door, a leaf finer than a pixel: the jitter catches it in some
|
|
136
|
+
* phases and misses it in others, and in a phase that misses it the nine taps round a pixel can all
|
|
137
|
+
* miss it at once. The box collapses onto the background, the history — which had settled on the
|
|
138
|
+
* thread's share of the pixel — is clipped out, and it grows back a tenth a frame until the next
|
|
139
|
+
* miss. That is a paused frame flashing at every fine edge, and the clip is doing its job: nothing
|
|
140
|
+
* in one frame says the thread is still there.
|
|
141
|
+
*
|
|
142
|
+
* What does say so is the pixel's own record. **A still camera over a still scene samples every pixel
|
|
143
|
+
* the same way every period of the jitter, exactly**, so aliasing repeats itself; a surface that moves
|
|
144
|
+
* across the pixel, a flame, a light that shimmers, does not. So each pixel keeps the mean of its
|
|
145
|
+
* samples over the last period, the sum it is gathering for this one, how many periods running have
|
|
146
|
+
* repeated, and the running mean of how far its samples land from their period's mean — and its box
|
|
147
|
+
* widens by that spread only once two periods running have repeated. Measured on the samples rather
|
|
148
|
+
* than against the history, because the history is what the clip cuts. **And a sample farther from
|
|
149
|
+
* its period's mean than the box would widen to is not the flicker the pixel has shown**: something
|
|
150
|
+
* has arrived, and the count starts again on that frame rather than at the end of the period. **Two, and a mean rather than
|
|
151
|
+
* one sample**, because one sample repeats by coincidence: stripes sliding across a pixel land on the
|
|
152
|
+
* same colour at two period starts about as often as not.
|
|
153
|
+
*
|
|
154
|
+
* **What it gives up.** A surface arriving over a pixel that was flickering is held back until the
|
|
155
|
+
* period ends and fails to repeat — at most eight frames, fading a tenth a frame meanwhile; a still
|
|
156
|
+
* pixel needs three periods before it is helped at all; and a pixel that flickers in a scene that is
|
|
157
|
+
* itself moving is not helped, since nothing repeats there. Nothing moving past a twentieth of a
|
|
158
|
+
* pixel a frame is touched, since the reprojection is then what decides.
|
|
159
|
+
*
|
|
160
|
+
* **A moving camera had a weaker proof of its own, and it was taken out.** It widened a pixel's box
|
|
161
|
+
* while its samples swung both ways about their mean, with the record carried through the
|
|
162
|
+
* reprojection. Measured on a moving courtyard it removed a twentieth of what flickered, and it broke
|
|
163
|
+
* a sun's shadow line on the floor into dashes that were not there before: the record is read at the
|
|
164
|
+
* nearest texel, so under sub-pixel motion one pixel widened and its neighbour did not. The resolve
|
|
165
|
+
* already removes nineteen twentieths of a moving frame's flicker without it. **What would make it wrong** is a jitter
|
|
166
|
+
* whose sequence does not repeat, where no period is the same twice however still the scene is.
|
|
167
|
+
*/
|
|
168
|
+
/** This frame's share of the running spread, and of a period's mean: one period of the jitter. */
|
|
169
|
+
export const FLICKER_MEMORY = 1 / JITTER_PERIOD;
|
|
170
|
+
/**
|
|
171
|
+
* How many spreads a flickering pixel's box widens by. The hardest thread is one missed in a single
|
|
172
|
+
* phase of eight: that miss needs the box to reach 7/8 of its contrast, and the running spread,
|
|
173
|
+
* worn down by seven catches that each land an eighth of the contrast from the period's mean, has
|
|
174
|
+
* fallen to a = 1/8 + (7/8 a + 7/64 − 1/8)·(7/8)⁷ of the contrast by then — 0.181, a steady state
|
|
175
|
+
* solved by hand — against the 7/32 it averages. Seven eighths over that is 4.8; six leaves room.
|
|
176
|
+
* Threads caught in a share nearer a half spread more and need fewer.
|
|
177
|
+
*/
|
|
178
|
+
export const FLICKER_WIDTH = 6;
|
|
179
|
+
/**
|
|
180
|
+
* How far apart two periods' means may be, relative to the larger, and still repeat: fully below
|
|
181
|
+
* this, not at all at twice it. Above an eight-bit record's rounding and a lamp's slow breathing; far
|
|
182
|
+
* below what anything crossing the pixel does to it.
|
|
183
|
+
*/
|
|
184
|
+
export const FLICKER_REPEAT = 0.05;
|
|
185
|
+
/**
|
|
186
|
+
* How much of the history a pixel proven to repeat keeps, where an unproven one keeps
|
|
187
|
+
* `TEMPORAL_HISTORY_BLEND`. A settled thread still breathes with the jitter by the share of each
|
|
188
|
+
* frame's sample that goes into what is shown — a tenth of its contrast at the base blend — and a
|
|
189
|
+
* pixel that has repeated for two periods has nothing to respond to, so it keeps more: three
|
|
190
|
+
* hundredths of each frame instead of a tenth. What it costs is a surface arriving inside the
|
|
191
|
+
* pixel's own swing, which fades in at that rate until the period's check lets it through.
|
|
192
|
+
*/
|
|
193
|
+
export const FLICKER_BLEND = 0.97;
|
|
194
|
+
/**
|
|
195
|
+
* How far last frame's depth may be from the depth a surface should have had there, relative, and
|
|
196
|
+
* still be that surface: two percent beyond the pixel's own depth swing, taken as the nearest of the
|
|
197
|
+
* texel where it stood and the four beside it. **The nearest of five and not the one**, because this
|
|
198
|
+
* frame's sample and last frame's can straddle an edge with nothing moved — the lesson
|
|
199
|
+
* `recon/resolve.ts` learned the expensive way.
|
|
200
|
+
*
|
|
201
|
+
* **And beyond its own swing, because depth aliases the way colour does.** A crack of light round a
|
|
202
|
+
* door is a pixel whose sample lands on the door in some phases and on what is behind it in others,
|
|
203
|
+
* so its depth jumps metres with nothing moved, and a test held to two percent reset the record of
|
|
204
|
+
* exactly the pixels the anti-flicker is for: a held frame went from 5 flickering pixels back to 774.
|
|
205
|
+
* So each pixel keeps the running mean of how far last frame's depth missed — kept whatever the test
|
|
206
|
+
* says, since resetting it would stop it ever learning the swing — and the test allows
|
|
207
|
+
* `FLICKER_WIDTH` of those, as the box does of the colour's. A flat surface's swing is none and its
|
|
208
|
+
* test stays tight; a surface revealed by the camera is a jump nothing in the pixel's past explains.
|
|
209
|
+
*/
|
|
210
|
+
export const FLICKER_SURFACE = 0.02;
|
|
211
|
+
/** Whether a record follows the surface it was kept for: see `FLICKER_SURFACE`. */
|
|
212
|
+
export function sameSurface(expected, stood, swing) {
|
|
213
|
+
return missedBy(expected, stood) <= FLICKER_WIDTH * swing + FLICKER_SURFACE * expected;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* How far the nearest of last frame's depths missed the one expected. A depth of nought is a texel
|
|
217
|
+
* with nothing recorded — the frame after a cut — and is no texel; with none, nothing is missed by
|
|
218
|
+
* any amount.
|
|
219
|
+
*/
|
|
220
|
+
export function missedBy(expected, stood) {
|
|
221
|
+
let nearest = Number.POSITIVE_INFINITY;
|
|
222
|
+
for (let i = 0; i < stood.length; i++) {
|
|
223
|
+
const depth = stood[i];
|
|
224
|
+
if (depth > 0)
|
|
225
|
+
nearest = Math.min(nearest, Math.abs(depth - expected));
|
|
226
|
+
}
|
|
227
|
+
return nearest;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The pixel's depth swing carried on: the running mean of `missedBy`, over a period, and none where
|
|
231
|
+
* there was nothing to miss — a swing learned from a cut would loosen the test for a whole period.
|
|
232
|
+
*/
|
|
233
|
+
export function nextDepthSwing(swing, missed) {
|
|
234
|
+
return Number.isFinite(missed) ? swing + (missed - swing) * FLICKER_MEMORY : 0;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Reprojected motion in pixels a frame: still below the first, moving from the second. **Small,
|
|
238
|
+
* because repetition breaks long before motion is visible**: a camera drifting a fifth of a pixel a
|
|
239
|
+
* frame shifts the grid more than a pixel a period, so nothing repeats, and a ramp to half a pixel
|
|
240
|
+
* counted that as mostly still and kept vouching for samples that had stopped repeating.
|
|
241
|
+
*/
|
|
242
|
+
export const STILL_FROM = 0.01;
|
|
243
|
+
export const STILL_TO = 0.05;
|
|
244
|
+
/**
|
|
245
|
+
* A fresh record for a surface first seen this frame: no spread and nothing repeated. **A
|
|
246
|
+
* record of another surface is no record**, so the resolve starts one over wherever last frame's
|
|
247
|
+
* depth says the surface is not the one it followed, before this frame widens by anything.
|
|
248
|
+
*/
|
|
249
|
+
export function freshFlicker(out, sample) {
|
|
250
|
+
out.spread = 0;
|
|
251
|
+
out.sum = sample * FLICKER_MEMORY;
|
|
252
|
+
out.mean = sample;
|
|
253
|
+
out.repeated = 0;
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
function smoothstep(edge0, edge1, x) {
|
|
257
|
+
const t = Math.min(1, Math.max(0, (x - edge0) / (edge1 - edge0)));
|
|
258
|
+
return t * t * (3 - 2 * t);
|
|
259
|
+
}
|
|
260
|
+
/** Rec. 709 luma, which is what the record measures in. */
|
|
261
|
+
export function luma(c) {
|
|
262
|
+
return c[0] * 0.2126 + c[1] * 0.7152 + c[2] * 0.0722;
|
|
263
|
+
}
|
|
264
|
+
/** One for a pixel whose reprojection did not move, falling to none at a twentieth of a pixel. */
|
|
265
|
+
export function stillness(motionPixels) {
|
|
266
|
+
return 1 - smoothstep(STILL_FROM, STILL_TO, motionPixels);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Whether a pixel is proven to repeat this frame: two periods running have, and this frame's sample
|
|
270
|
+
* lands within the reach of the box it would widen to. A sample beyond that reach is something
|
|
271
|
+
* arriving, and it disqualifies the frame it lands in as well as the count it carries out.
|
|
272
|
+
*/
|
|
273
|
+
export function proven(record, sample) {
|
|
274
|
+
return record.repeated > 0.75 && within(record, sample);
|
|
275
|
+
}
|
|
276
|
+
/** Whether this frame's sample lands inside the reach of the box the record would widen to. */
|
|
277
|
+
function within(record, sample) {
|
|
278
|
+
return Math.abs(sample - record.mean) <= FLICKER_WIDTH * record.spread + 1 / 255;
|
|
279
|
+
}
|
|
280
|
+
/** How far a pixel's box widens this frame: by its spread, once proven, and only while still. */
|
|
281
|
+
export function flickerWidening(record, sample, still) {
|
|
282
|
+
return proven(record, sample) ? FLICKER_WIDTH * record.spread * still : 0;
|
|
283
|
+
}
|
|
284
|
+
/** How much of the history this frame keeps: the base blend, or more once proven. */
|
|
285
|
+
export function flickerBlend(base, record, sample, still) {
|
|
286
|
+
return proven(record, sample) ? base + (FLICKER_BLEND - base) * still : base;
|
|
287
|
+
}
|
|
288
|
+
/** Whether two periods' means are the same, one to zero. */
|
|
289
|
+
export function repeats(mean, previous) {
|
|
290
|
+
const apart = Math.abs(mean - previous) / Math.max(mean, previous, 1 / 255);
|
|
291
|
+
return 1 - smoothstep(FLICKER_REPEAT, 2 * FLICKER_REPEAT, apart);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* The record carried out, from the one carried in and this frame's sample in luma. `periodStart`
|
|
295
|
+
* is the first frame of the jitter's period, where the period just gathered is compared with the
|
|
296
|
+
* one before. Forgotten in proportion to motion — a moving pixel's record would be a record of
|
|
297
|
+
* somewhere else.
|
|
298
|
+
*/
|
|
299
|
+
export function nextFlicker(out, previous, sample, still, periodStart) {
|
|
300
|
+
const apart = Math.abs(sample - previous.mean);
|
|
301
|
+
let { sum, mean, repeated } = previous;
|
|
302
|
+
if (apart > FLICKER_WIDTH * previous.spread + 1 / 255)
|
|
303
|
+
repeated = 0;
|
|
304
|
+
if (periodStart) {
|
|
305
|
+
repeated = repeats(sum, mean) > 0.5 ? Math.min(1, repeated + 0.5) : 0;
|
|
306
|
+
mean = sum;
|
|
307
|
+
sum = 0;
|
|
308
|
+
}
|
|
309
|
+
out.spread = previous.spread + (apart - previous.spread) * FLICKER_MEMORY;
|
|
310
|
+
out.sum = sum + sample * FLICKER_MEMORY;
|
|
311
|
+
out.mean = mean;
|
|
312
|
+
out.repeated = repeated * still;
|
|
313
|
+
return out;
|
|
314
|
+
}
|
|
132
315
|
/**
|
|
133
316
|
* Whether the last frame left a picture this one may sample, and where the jitter has got to.
|
|
134
317
|
*
|
|
@@ -158,6 +341,13 @@ export class TemporalHistory {
|
|
|
158
341
|
this.height = height;
|
|
159
342
|
return usable;
|
|
160
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* Whether this frame samples the first phase of the jitter's period, where the anti-flicker
|
|
346
|
+
* compares the period just gathered with the one before. One answer for both backends.
|
|
347
|
+
*/
|
|
348
|
+
get periodStart() {
|
|
349
|
+
return this.frameIndex % JITTER_PERIOD === 0;
|
|
350
|
+
}
|
|
161
351
|
/** A resolved picture is now in the history, at the size the frame was opened with. */
|
|
162
352
|
accumulated() {
|
|
163
353
|
this.picture = true;
|
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The temporal resolve on WebGL2: this frame blended into where the last one was.
|
|
3
|
-
*
|
|
4
|
-
* **Two targets and a swap, because a pass cannot read the texture it is writing.** The history is
|
|
5
|
-
* sampled at a reprojected coordinate, which is somewhere else in the same image, so reading and
|
|
6
|
-
* writing one texture is a feedback loop — undefined in WebGL2 and in practice whatever the driver
|
|
7
|
-
* left in the cache. So the resolve writes into the one it did not read and the pair swaps, which
|
|
8
|
-
* costs one more full-size texture and nothing per pixel.
|
|
9
|
-
*
|
|
10
|
-
* **What it returns is what the composite must then treat as the scene.** Bloom, occlusion and the
|
|
11
|
-
* grade all read the scene texture, and reading the unresolved one would grade a stable picture
|
|
12
|
-
* from an unstable source and put the crawl back on screen through the bloom.
|
|
13
|
-
*/
|
|
14
1
|
export declare class TemporalPass {
|
|
15
2
|
private readonly gl;
|
|
16
3
|
/**
|
|
@@ -27,6 +14,21 @@ export declare class TemporalPass {
|
|
|
27
14
|
private readonly vao;
|
|
28
15
|
/** The pair, and which of them the next resolve writes into. */
|
|
29
16
|
private textures;
|
|
17
|
+
/**
|
|
18
|
+
* Each pixel's flicker record, a pair beside the history's and swapped with it: see the
|
|
19
|
+
* anti-flicker in `temporalAa.ts`. In the history's own format, which holds what the record does —
|
|
20
|
+
* a change and a sample are colours' luma, and eight bits hold them wherever eight bits hold the
|
|
21
|
+
* colour.
|
|
22
|
+
*/
|
|
23
|
+
private records;
|
|
24
|
+
/** The rest of each pixel's record — depth, pull and recent swing — beside the first. */
|
|
25
|
+
private motions;
|
|
26
|
+
/**
|
|
27
|
+
* Whether the records can be half float, which the depth in them needs. Asked of the context
|
|
28
|
+
* rather than taken from the scene's format, since a record of an eight-bit scene still holds
|
|
29
|
+
* metres. Without it the anti-flicker is off and the resolve is the one before it existed.
|
|
30
|
+
*/
|
|
31
|
+
private readonly recordFloat;
|
|
30
32
|
private framebuffers;
|
|
31
33
|
private write;
|
|
32
34
|
private width;
|
|
@@ -49,6 +51,6 @@ export declare class TemporalPass {
|
|
|
49
51
|
* first frame, the frame after a resize and the frame after a cut pass zero, and the shader
|
|
50
52
|
* then returns this frame before it touches depth or history at all.
|
|
51
53
|
*/
|
|
52
|
-
temporalResolve(scene: WebGLTexture, depth: WebGLTexture, reprojection: Float32Array, width: number, height: number, blend: number): WebGLTexture;
|
|
54
|
+
temporalResolve(scene: WebGLTexture, depth: WebGLTexture, reprojection: Float32Array, width: number, height: number, blend: number, periodStart: boolean, depthToViewZ: Float32Array): WebGLTexture;
|
|
53
55
|
dispose(): void;
|
|
54
56
|
}
|
|
@@ -14,6 +14,8 @@ import { TEMPORAL_RESOLVE_FRAG } from './shaders/temporalResolve.js';
|
|
|
14
14
|
* grade all read the scene texture, and reading the unresolved one would grade a stable picture
|
|
15
15
|
* from an unstable source and put the crawl back on screen through the bloom.
|
|
16
16
|
*/
|
|
17
|
+
/** The resolve's three outputs: the picture and the two halves of the flicker record. */
|
|
18
|
+
const DRAW_ALL = [0x8ce0, 0x8ce1, 0x8ce2]; // COLOR_ATTACHMENT0, 1, 2
|
|
17
19
|
export class TemporalPass {
|
|
18
20
|
gl;
|
|
19
21
|
floatColor;
|
|
@@ -22,6 +24,21 @@ export class TemporalPass {
|
|
|
22
24
|
vao;
|
|
23
25
|
/** The pair, and which of them the next resolve writes into. */
|
|
24
26
|
textures = [null, null];
|
|
27
|
+
/**
|
|
28
|
+
* Each pixel's flicker record, a pair beside the history's and swapped with it: see the
|
|
29
|
+
* anti-flicker in `temporalAa.ts`. In the history's own format, which holds what the record does —
|
|
30
|
+
* a change and a sample are colours' luma, and eight bits hold them wherever eight bits hold the
|
|
31
|
+
* colour.
|
|
32
|
+
*/
|
|
33
|
+
records = [null, null];
|
|
34
|
+
/** The rest of each pixel's record — depth, pull and recent swing — beside the first. */
|
|
35
|
+
motions = [null, null];
|
|
36
|
+
/**
|
|
37
|
+
* Whether the records can be half float, which the depth in them needs. Asked of the context
|
|
38
|
+
* rather than taken from the scene's format, since a record of an eight-bit scene still holds
|
|
39
|
+
* metres. Without it the anti-flicker is off and the resolve is the one before it existed.
|
|
40
|
+
*/
|
|
41
|
+
recordFloat;
|
|
25
42
|
framebuffers = [null, null];
|
|
26
43
|
write = 0;
|
|
27
44
|
width = 0;
|
|
@@ -38,6 +55,7 @@ export class TemporalPass {
|
|
|
38
55
|
floatColor) {
|
|
39
56
|
this.gl = gl;
|
|
40
57
|
this.floatColor = floatColor;
|
|
58
|
+
this.recordFloat = gl.getExtension('EXT_color_buffer_float') !== null;
|
|
41
59
|
this.program = compileProgram(gl, FULLSCREEN_VERT, TEMPORAL_RESOLVE_FRAG, 'temporalResolve');
|
|
42
60
|
this.uniforms = uniformLocations(gl, this.program, 'temporalPass');
|
|
43
61
|
const vao = gl.createVertexArray();
|
|
@@ -54,24 +72,36 @@ export class TemporalPass {
|
|
|
54
72
|
for (let i = 0; i < 2; i++) {
|
|
55
73
|
if (this.textures[i] === null)
|
|
56
74
|
this.textures[i] = gl.createTexture();
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
gl.
|
|
75
|
+
if (this.records[i] === null)
|
|
76
|
+
this.records[i] = gl.createTexture();
|
|
77
|
+
if (this.motions[i] === null)
|
|
78
|
+
this.motions[i] = gl.createTexture();
|
|
79
|
+
for (const texture of [this.textures[i], this.records[i], this.motions[i]]) {
|
|
80
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
81
|
+
if (texture === this.textures[i] ? this.floatColor : this.recordFloat) {
|
|
82
|
+
/* The depth record is two numbers a pixel: metres, and how far they swing. */
|
|
83
|
+
const pair = texture === this.motions[i];
|
|
84
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, pair ? gl.RG16F : gl.RGBA16F, width, height, 0, pair ? gl.RG : gl.RGBA, gl.HALF_FLOAT, null);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
88
|
+
}
|
|
89
|
+
/* Linear, because the history is sampled between texels wherever the reprojection lands;
|
|
90
|
+
clamped, because a sample that wrapped would pull the far edge of the screen into the
|
|
91
|
+
corner — and the shader rejects an off-screen reprojection before it can anyway. */
|
|
92
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
93
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
94
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
95
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
63
96
|
}
|
|
64
|
-
/* Linear, because the history is sampled between texels wherever the reprojection lands;
|
|
65
|
-
clamped, because a sample that wrapped would pull the far edge of the screen into the
|
|
66
|
-
corner — and the shader rejects an off-screen reprojection before it can anyway. */
|
|
67
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
68
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
69
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
70
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
71
97
|
if (this.framebuffers[i] === null)
|
|
72
98
|
this.framebuffers[i] = gl.createFramebuffer();
|
|
73
99
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffers[i]);
|
|
74
100
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.textures[i], 0);
|
|
101
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, this.records[i], 0);
|
|
102
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.TEXTURE_2D, this.motions[i], 0);
|
|
103
|
+
/* Per framebuffer, so set once here: the resolve writes the picture and the record. */
|
|
104
|
+
gl.drawBuffers(DRAW_ALL);
|
|
75
105
|
}
|
|
76
106
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
77
107
|
}
|
|
@@ -82,7 +112,7 @@ export class TemporalPass {
|
|
|
82
112
|
* first frame, the frame after a resize and the frame after a cut pass zero, and the shader
|
|
83
113
|
* then returns this frame before it touches depth or history at all.
|
|
84
114
|
*/
|
|
85
|
-
temporalResolve(scene, depth, reprojection, width, height, blend) {
|
|
115
|
+
temporalResolve(scene, depth, reprojection, width, height, blend, periodStart, depthToViewZ) {
|
|
86
116
|
const { gl } = this;
|
|
87
117
|
this.ensureSize(width, height);
|
|
88
118
|
const target = this.write;
|
|
@@ -103,9 +133,18 @@ export class TemporalPass {
|
|
|
103
133
|
gl.activeTexture(gl.TEXTURE2);
|
|
104
134
|
gl.bindTexture(gl.TEXTURE_2D, depth);
|
|
105
135
|
gl.uniform1i(this.uniforms['uDepth'] ?? null, 2);
|
|
136
|
+
gl.activeTexture(gl.TEXTURE3);
|
|
137
|
+
gl.bindTexture(gl.TEXTURE_2D, this.records[history]);
|
|
138
|
+
gl.uniform1i(this.uniforms['uFlicker'] ?? null, 3);
|
|
139
|
+
gl.activeTexture(gl.TEXTURE4);
|
|
140
|
+
gl.bindTexture(gl.TEXTURE_2D, this.motions[history]);
|
|
141
|
+
gl.uniform1i(this.uniforms['uFlickerMotion'] ?? null, 4);
|
|
142
|
+
gl.uniform4fv(this.uniforms['uDepthToViewZ'] ?? null, depthToViewZ);
|
|
143
|
+
gl.uniform1f(this.uniforms['uAntiFlicker'] ?? null, this.recordFloat ? 1 : 0);
|
|
106
144
|
gl.uniformMatrix4fv(this.uniforms['uReprojection'] ?? null, false, reprojection);
|
|
107
145
|
gl.uniform2f(this.uniforms['uTexel'] ?? null, 1 / width, 1 / height);
|
|
108
146
|
gl.uniform1f(this.uniforms['uHistoryBlend'] ?? null, blend);
|
|
147
|
+
gl.uniform1f(this.uniforms['uPeriodStart'] ?? null, periodStart ? 1 : 0);
|
|
109
148
|
gl.bindVertexArray(this.vao);
|
|
110
149
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
111
150
|
gl.bindVertexArray(null);
|
|
@@ -122,9 +161,15 @@ export class TemporalPass {
|
|
|
122
161
|
for (let i = 0; i < 2; i++) {
|
|
123
162
|
if (this.textures[i] !== null)
|
|
124
163
|
gl.deleteTexture(this.textures[i]);
|
|
164
|
+
if (this.records[i] !== null)
|
|
165
|
+
gl.deleteTexture(this.records[i]);
|
|
166
|
+
if (this.motions[i] !== null)
|
|
167
|
+
gl.deleteTexture(this.motions[i]);
|
|
125
168
|
if (this.framebuffers[i] !== null)
|
|
126
169
|
gl.deleteFramebuffer(this.framebuffers[i]);
|
|
127
170
|
this.textures[i] = null;
|
|
171
|
+
this.records[i] = null;
|
|
172
|
+
this.motions[i] = null;
|
|
128
173
|
this.framebuffers[i] = null;
|
|
129
174
|
}
|
|
130
175
|
gl.deleteProgram(this.program);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReadonlyMat4 } from 'gl-matrix';
|
|
2
2
|
import type { TranslucentMeshOptions } from './backend/webgl2/renderer.ts';
|
|
3
|
+
import type { ResolvedGlass } from './glass.ts';
|
|
3
4
|
/**
|
|
4
5
|
* The frame's translucent draws, held so they can be submitted more than once.
|
|
5
6
|
*
|
|
@@ -46,11 +47,14 @@ export interface TranslucentDraw {
|
|
|
46
47
|
/** What survives one metre of the medium. Null where the draw named none; otherwise a copy. */
|
|
47
48
|
refractTint: Float32Array | null;
|
|
48
49
|
thicknessM: number;
|
|
50
|
+
/** Glass, resolved, or null for a draw that is not glass. See `glass.ts`. */
|
|
51
|
+
glass: ResolvedGlass | null;
|
|
49
52
|
}
|
|
50
53
|
export declare class TranslucentQueue {
|
|
51
54
|
private readonly pool;
|
|
52
55
|
private count;
|
|
53
56
|
private refracting;
|
|
57
|
+
private glassing;
|
|
54
58
|
/** How many draws this frame has recorded. */
|
|
55
59
|
get length(): number;
|
|
56
60
|
/**
|
|
@@ -58,6 +62,8 @@ export declare class TranslucentQueue {
|
|
|
58
62
|
* frame behind it first, and does not copy it for a set that never reads it.
|
|
59
63
|
*/
|
|
60
64
|
get refracts(): boolean;
|
|
65
|
+
/** Whether anything recorded this frame is glass, so the copy it reads needs its mip chain. */
|
|
66
|
+
get glassy(): boolean;
|
|
61
67
|
/** How many records are pooled, which a test uses to assert a steady scene stops allocating. */
|
|
62
68
|
get capacity(): number;
|
|
63
69
|
/**
|