@driftengine/core 4.3.0 → 4.4.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 +282 -0
- package/README.md +1 -1
- package/dist/cinematic/cameraPath.d.ts +53 -0
- package/dist/cinematic/cameraPath.js +118 -0
- package/dist/environment/daylight.d.ts +77 -0
- package/dist/environment/daylight.js +77 -0
- package/dist/geometry/meshConcat.d.ts +9 -0
- package/dist/geometry/meshConcat.js +101 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +14 -1
- package/dist/render/ambientOcclusionPass.js +5 -1
- package/dist/render/backend/createRenderer.d.ts +13 -1
- package/dist/render/backend/createRenderer.js +6 -5
- package/dist/render/backend/webgl2/renderer.d.ts +147 -1
- package/dist/render/backend/webgl2/renderer.js +402 -23
- package/dist/render/backend/webgpu/buffers.js +46 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +81 -3
- package/dist/render/backend/webgpu/depthPass.js +75 -14
- package/dist/render/backend/webgpu/exposurePass.d.ts +39 -0
- package/dist/render/backend/webgpu/exposurePass.js +190 -0
- package/dist/render/backend/webgpu/fieldCompose.d.ts +11 -0
- package/dist/render/backend/webgpu/fieldCompose.js +13 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +3 -1
- package/dist/render/backend/webgpu/flatPass.js +12 -5
- package/dist/render/backend/webgpu/particlePass.d.ts +8 -0
- package/dist/render/backend/webgpu/particlePass.js +1 -0
- package/dist/render/backend/webgpu/postPass.d.ts +13 -3
- package/dist/render/backend/webgpu/probeBake.d.ts +59 -3
- package/dist/render/backend/webgpu/probeBake.js +97 -7
- package/dist/render/backend/webgpu/probeTargets.d.ts +65 -0
- package/dist/render/backend/webgpu/probeTargets.js +180 -0
- package/dist/render/backend/webgpu/renderer.d.ts +174 -11
- package/dist/render/backend/webgpu/renderer.js +682 -252
- package/dist/render/backend/webgpu/scatterPass.js +7 -7
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +9 -0
- package/dist/render/backend/webgpu/skyPass.js +5 -1
- package/dist/render/backend/webgpu/uniformRing.d.ts +31 -1
- package/dist/render/backend/webgpu/uniformRing.js +53 -3
- package/dist/render/bloomChain.d.ts +6 -0
- package/dist/render/bloomChain.js +8 -0
- package/dist/render/clusteredLights.d.ts +19 -11
- package/dist/render/clusteredLights.js +96 -45
- package/dist/render/cutoutCaster.d.ts +23 -0
- package/dist/render/cutoutCaster.js +20 -0
- package/dist/render/driftLight/bake.d.ts +34 -0
- package/dist/render/driftLight/bake.js +95 -0
- package/dist/render/driftLight/falloff.d.ts +16 -0
- package/dist/render/driftLight/falloff.js +23 -0
- package/dist/render/driftLight/layout.d.ts +59 -0
- package/dist/render/driftLight/layout.js +138 -0
- package/dist/render/driftLight/lightField.d.ts +108 -0
- package/dist/render/driftLight/lightField.js +179 -0
- package/dist/render/driftLight/uniforms.d.ts +28 -0
- package/dist/render/driftLight/uniforms.js +41 -0
- package/dist/render/driftLight/visibility.d.ts +18 -0
- package/dist/render/driftLight/visibility.js +47 -0
- package/dist/render/emptyTexture.d.ts +6 -0
- package/dist/render/emptyTexture.js +19 -0
- package/dist/render/exposurePass.d.ts +48 -0
- package/dist/render/exposurePass.js +172 -0
- package/dist/render/flameLight.d.ts +71 -0
- package/dist/render/flameLight.js +63 -0
- package/dist/render/gi/bounceLights.d.ts +34 -0
- package/dist/render/gi/bounceLights.js +66 -0
- package/dist/render/gi/fieldReach.d.ts +12 -0
- package/dist/render/gi/fieldReach.js +62 -0
- package/dist/render/gi/globalField.d.ts +8 -0
- package/dist/render/gi/globalField.js +31 -0
- package/dist/render/gi/probeTrace.d.ts +3 -1
- package/dist/render/gi/probeTrace.js +3 -1
- package/dist/render/gpuCapability.js +19 -4
- package/dist/render/lightBudget.d.ts +11 -0
- package/dist/render/lightBudget.js +11 -0
- package/dist/render/pointLightSelection.d.ts +16 -0
- package/dist/render/pointLightSelection.js +178 -83
- package/dist/render/pointShadowBudget.d.ts +12 -2
- package/dist/render/pointShadowBudget.js +16 -10
- package/dist/render/probeGrid.d.ts +11 -1
- package/dist/render/probeGrid.js +5 -2
- package/dist/render/probeSweeps.d.ts +45 -0
- package/dist/render/probeSweeps.js +77 -0
- package/dist/render/reflectionProbe.d.ts +43 -1
- package/dist/render/reflectionProbe.js +17 -3
- package/dist/render/renderQuality.d.ts +3 -1
- package/dist/render/rendererHarness.d.ts +9 -0
- package/dist/render/rendererHarness.js +18 -3
- package/dist/render/sceneTarget.d.ts +20 -0
- package/dist/render/sceneTarget.js +62 -1
- package/dist/render/shaders/ambientOcclusion.d.ts +63 -6
- package/dist/render/shaders/ambientOcclusion.js +188 -71
- package/dist/render/shaders/cutoutCoverage.d.ts +31 -0
- package/dist/render/shaders/cutoutCoverage.js +40 -0
- package/dist/render/shaders/depth.d.ts +25 -0
- package/dist/render/shaders/depth.js +69 -3
- package/dist/render/shaders/exposure.d.ts +52 -0
- package/dist/render/shaders/exposure.js +124 -0
- package/dist/render/shaders/filmLook.d.ts +47 -0
- package/dist/render/shaders/filmLook.js +98 -0
- package/dist/render/shaders/flat/directionalShadow.js +35 -4
- package/dist/render/shaders/flat/driftLight.d.ts +29 -0
- package/dist/render/shaders/flat/driftLight.js +75 -0
- package/dist/render/shaders/flat/index.js +5 -0
- package/dist/render/shaders/flat/lobes.js +13 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +68 -9
- package/dist/render/shaders/flat/preamble.js +23 -5
- package/dist/render/shaders/flat/probeGrid.js +46 -3
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +8 -3
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +8 -3
- package/dist/render/shaders/generated/depth.wgsl.d.ts +100 -0
- package/dist/render/shaders/generated/depth.wgsl.js +100 -0
- package/dist/render/shaders/generated/exposure.wgsl.d.ts +42 -0
- package/dist/render/shaders/generated/exposure.wgsl.js +48 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1800 -1344
- package/dist/render/shaders/generated/flat.wgsl.js +1816 -1360
- package/dist/render/shaders/generated/localExposure.wgsl.d.ts +18 -0
- package/dist/render/shaders/generated/localExposure.wgsl.js +24 -0
- package/dist/render/shaders/generated/rush.wgsl.d.ts +40 -10
- package/dist/render/shaders/generated/rush.wgsl.js +40 -10
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +16 -0
- package/dist/render/shaders/gi/driftLight.wgsl.js +49 -0
- package/dist/render/shaders/gi/probeBake.wgsl.d.ts +29 -4
- package/dist/render/shaders/gi/probeBake.wgsl.js +190 -20
- package/dist/render/shaders/globalMedium.js +4 -0
- package/dist/render/shaders/localExposure.d.ts +35 -0
- package/dist/render/shaders/localExposure.js +138 -0
- package/dist/render/shaders/prefilter.d.ts +1 -1
- package/dist/render/shaders/prefilter.js +6 -0
- package/dist/render/shaders/rush.js +33 -5
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +15 -3
- package/dist/render/shaders/temporalResolve.js +39 -1
- package/dist/render/shadowCasters.d.ts +5 -3
- package/dist/render/shadowMap.js +13 -0
- package/dist/render/surfaceTexture.d.ts +5 -0
- package/package.json +6 -6
- package/scripts/gpuCompute.mjs +24 -3
- package/scripts/png.mjs +23 -12
- package/src/cinematic/cameraPath.ts +178 -0
- package/src/environment/daylight.ts +152 -0
- package/src/geometry/meshConcat.ts +100 -0
- package/src/index.ts +32 -0
- package/src/render/ambientOcclusionPass.ts +5 -1
- package/src/render/backend/createRenderer.ts +22 -6
- package/src/render/backend/webgl2/renderer.ts +439 -20
- package/src/render/backend/webgpu/buffers.ts +51 -8
- package/src/render/backend/webgpu/depthPass.ts +78 -10
- package/src/render/backend/webgpu/exposurePass.ts +234 -0
- package/src/render/backend/webgpu/fieldCompose.ts +15 -0
- package/src/render/backend/webgpu/flatPass.ts +10 -2
- package/src/render/backend/webgpu/particlePass.ts +9 -0
- package/src/render/backend/webgpu/probeBake.ts +143 -9
- package/src/render/backend/webgpu/probeTargets.ts +211 -0
- package/src/render/backend/webgpu/renderer.ts +808 -273
- package/src/render/backend/webgpu/scatterPass.ts +7 -7
- package/src/render/backend/webgpu/skinPaletteRing.ts +10 -0
- package/src/render/backend/webgpu/skyPass.ts +5 -1
- package/src/render/backend/webgpu/uniformRing.ts +49 -3
- package/src/render/bloomChain.ts +9 -0
- package/src/render/clusteredLights.ts +108 -63
- package/src/render/cutoutCaster.ts +44 -0
- package/src/render/driftLight/bake.ts +125 -0
- package/src/render/driftLight/falloff.ts +26 -0
- package/src/render/driftLight/layout.ts +177 -0
- package/src/render/driftLight/lightField.ts +227 -0
- package/src/render/driftLight/uniforms.ts +61 -0
- package/src/render/driftLight/visibility.ts +58 -0
- package/src/render/emptyTexture.ts +41 -0
- package/src/render/exposurePass.ts +186 -0
- package/src/render/flameLight.ts +135 -0
- package/src/render/gi/bounceLights.ts +68 -0
- package/src/render/gi/fieldReach.ts +74 -0
- package/src/render/gi/globalField.ts +39 -0
- package/src/render/gi/probeTrace.ts +3 -1
- package/src/render/gpuCapability.ts +18 -5
- package/src/render/lightBudget.ts +12 -0
- package/src/render/pointLightSelection.ts +191 -79
- package/src/render/pointShadowBudget.ts +16 -8
- package/src/render/probeGrid.ts +13 -2
- package/src/render/probeSweeps.ts +80 -0
- package/src/render/reflectionProbe.ts +54 -2
- package/src/render/renderQuality.ts +3 -1
- package/src/render/rendererHarness.ts +25 -3
- package/src/render/sceneTarget.ts +75 -1
- package/src/render/shaders/ambientOcclusion.ts +205 -71
- package/src/render/shaders/cutoutCoverage.ts +43 -0
- package/src/render/shaders/depth.ts +80 -3
- package/src/render/shaders/exposure.ts +128 -0
- package/src/render/shaders/filmLook.ts +111 -0
- package/src/render/shaders/flat/directionalShadow.ts +35 -4
- package/src/render/shaders/flat/driftLight.ts +75 -0
- package/src/render/shaders/flat/index.ts +5 -0
- package/src/render/shaders/flat/lobes.ts +13 -0
- package/src/render/shaders/flat/main.ts +68 -9
- package/src/render/shaders/flat/preamble.ts +23 -5
- package/src/render/shaders/flat/probeGrid.ts +46 -3
- package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +8 -3
- package/src/render/shaders/generated/depth.wgsl.ts +103 -0
- package/src/render/shaders/generated/exposure.wgsl.ts +51 -0
- package/src/render/shaders/generated/flat.wgsl.ts +1816 -1360
- package/src/render/shaders/generated/localExposure.wgsl.ts +26 -0
- package/src/render/shaders/generated/rush.wgsl.ts +40 -10
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +49 -0
- package/src/render/shaders/gi/probeBake.wgsl.ts +191 -20
- package/src/render/shaders/globalMedium.ts +4 -0
- package/src/render/shaders/localExposure.ts +154 -0
- package/src/render/shaders/prefilter.ts +6 -0
- package/src/render/shaders/rush.ts +33 -5
- package/src/render/shaders/tangentFrame.ts +15 -3
- package/src/render/shaders/temporalResolve.ts +39 -1
- package/src/render/shadowCasters.ts +5 -3
- package/src/render/shadowMap.ts +14 -0
- package/src/render/surfaceTexture.ts +5 -0
package/dist/render/probeGrid.js
CHANGED
|
@@ -46,14 +46,16 @@ export function sameGrid(a, b) {
|
|
|
46
46
|
if (a.spacing[axis] !== b.spacing[axis])
|
|
47
47
|
return false;
|
|
48
48
|
}
|
|
49
|
-
return
|
|
49
|
+
return a.crossfade === b.crossfade;
|
|
50
50
|
}
|
|
51
51
|
export class ProbeGrid {
|
|
52
52
|
origin;
|
|
53
53
|
spacing;
|
|
54
54
|
counts;
|
|
55
|
-
/** Probes in total
|
|
55
|
+
/** Probes in total: the array's layer count, or a third of it for a crossfading grid. */
|
|
56
56
|
layers;
|
|
57
|
+
/** Whether sweeps blend into one another. See `ProbeGridOptions.crossfade`. */
|
|
58
|
+
crossfade;
|
|
57
59
|
/** The reciprocal of the spacing, because the shader multiplies and so does everything here. */
|
|
58
60
|
invSpacing;
|
|
59
61
|
constructor(options) {
|
|
@@ -91,6 +93,7 @@ export class ProbeGrid {
|
|
|
91
93
|
this.invSpacing = [1 / this.spacing[0], 1 / this.spacing[1], 1 / this.spacing[2]];
|
|
92
94
|
this.counts = counts;
|
|
93
95
|
this.layers = layers;
|
|
96
|
+
this.crossfade = options.crossfade === true;
|
|
94
97
|
}
|
|
95
98
|
/**
|
|
96
99
|
* The layer a probe at whole lattice coordinates occupies.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A probe grid's sweeps: which layer a bake writes, when a sweep is whole, and how far the shading
|
|
3
|
+
* has moved from the last whole sweep toward the newest.
|
|
4
|
+
*
|
|
5
|
+
* **A grid re-baked a probe a frame changes in steps.** Each probe holds the light of the moment it
|
|
6
|
+
* was baked until its turn comes round again, a sweep later, and then jumps to the light of that
|
|
7
|
+
* moment. Under a moving sun the jump is a pass of light across the scene once a sweep: reported as
|
|
8
|
+
* "a pass every second or two", which at sixty-four probes a frame at 60 Hz is exactly one sweep.
|
|
9
|
+
*
|
|
10
|
+
* **So a crossfading grid keeps three sets of layers**: the last whole sweep, the newest whole
|
|
11
|
+
* sweep, and the one being baked. The shading blends the first toward the second by how much of the
|
|
12
|
+
* third has been baked, so it reaches the newest sweep at the moment the next one is whole, and the
|
|
13
|
+
* light moves a sixty-fourth of a sweep a frame rather than a probe's worth at once. The set being
|
|
14
|
+
* written is never one the shading reads.
|
|
15
|
+
*
|
|
16
|
+
* What it gives up is three times the layers, and a sweep of lag: the light shown is a blend of what
|
|
17
|
+
* the grid saw one and two sweeps ago. What would make it wrong is a light that must follow a change
|
|
18
|
+
* faster than a sweep, which a burst of bakes serves, since the blend follows the bakes rather than
|
|
19
|
+
* the clock. A grid that does not crossfade is one set, ready once every probe is baked, as before.
|
|
20
|
+
*
|
|
21
|
+
* Backend-neutral, per the rule that a decision lives in one place and only the binding is per
|
|
22
|
+
* backend: both renderers ask this where to write and what to upload.
|
|
23
|
+
*/
|
|
24
|
+
export declare class ProbeSweeps {
|
|
25
|
+
/** Sets of layers the array holds: three for a crossfading grid, one otherwise. */
|
|
26
|
+
readonly sets: 1 | 3;
|
|
27
|
+
private readonly layers;
|
|
28
|
+
/** Which probes the sweep being baked has reached, and how many. */
|
|
29
|
+
private readonly reached;
|
|
30
|
+
private reachedCount;
|
|
31
|
+
private from;
|
|
32
|
+
private to;
|
|
33
|
+
private write;
|
|
34
|
+
/** Whether a whole sweep has been rotated in. */
|
|
35
|
+
private whole;
|
|
36
|
+
constructor(layers: number, crossfade: boolean);
|
|
37
|
+
/** The array layer a bake of probe `layer` writes. */
|
|
38
|
+
writeLayer(layer: number): number;
|
|
39
|
+
/** Record that probe `layer` was baked into the set being written; a whole sweep rotates. */
|
|
40
|
+
baked(layer: number): void;
|
|
41
|
+
/** Whether the shading may read the grid: once a sweep is whole. */
|
|
42
|
+
get ready(): boolean;
|
|
43
|
+
/** The layer offsets of the two sets shown and the blend between them, into `out`. */
|
|
44
|
+
uniforms(out: Float32Array): Float32Array;
|
|
45
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A probe grid's sweeps: which layer a bake writes, when a sweep is whole, and how far the shading
|
|
3
|
+
* has moved from the last whole sweep toward the newest.
|
|
4
|
+
*
|
|
5
|
+
* **A grid re-baked a probe a frame changes in steps.** Each probe holds the light of the moment it
|
|
6
|
+
* was baked until its turn comes round again, a sweep later, and then jumps to the light of that
|
|
7
|
+
* moment. Under a moving sun the jump is a pass of light across the scene once a sweep: reported as
|
|
8
|
+
* "a pass every second or two", which at sixty-four probes a frame at 60 Hz is exactly one sweep.
|
|
9
|
+
*
|
|
10
|
+
* **So a crossfading grid keeps three sets of layers**: the last whole sweep, the newest whole
|
|
11
|
+
* sweep, and the one being baked. The shading blends the first toward the second by how much of the
|
|
12
|
+
* third has been baked, so it reaches the newest sweep at the moment the next one is whole, and the
|
|
13
|
+
* light moves a sixty-fourth of a sweep a frame rather than a probe's worth at once. The set being
|
|
14
|
+
* written is never one the shading reads.
|
|
15
|
+
*
|
|
16
|
+
* What it gives up is three times the layers, and a sweep of lag: the light shown is a blend of what
|
|
17
|
+
* the grid saw one and two sweeps ago. What would make it wrong is a light that must follow a change
|
|
18
|
+
* faster than a sweep, which a burst of bakes serves, since the blend follows the bakes rather than
|
|
19
|
+
* the clock. A grid that does not crossfade is one set, ready once every probe is baked, as before.
|
|
20
|
+
*
|
|
21
|
+
* Backend-neutral, per the rule that a decision lives in one place and only the binding is per
|
|
22
|
+
* backend: both renderers ask this where to write and what to upload.
|
|
23
|
+
*/
|
|
24
|
+
export class ProbeSweeps {
|
|
25
|
+
/** Sets of layers the array holds: three for a crossfading grid, one otherwise. */
|
|
26
|
+
sets;
|
|
27
|
+
layers;
|
|
28
|
+
/** Which probes the sweep being baked has reached, and how many. */
|
|
29
|
+
reached;
|
|
30
|
+
reachedCount = 0;
|
|
31
|
+
from = 0;
|
|
32
|
+
to = 0;
|
|
33
|
+
write = 0;
|
|
34
|
+
/** Whether a whole sweep has been rotated in. */
|
|
35
|
+
whole = false;
|
|
36
|
+
constructor(layers, crossfade) {
|
|
37
|
+
this.layers = Math.max(1, Math.trunc(layers));
|
|
38
|
+
this.sets = crossfade ? 3 : 1;
|
|
39
|
+
this.reached = new Uint8Array(this.layers);
|
|
40
|
+
}
|
|
41
|
+
/** The array layer a bake of probe `layer` writes. */
|
|
42
|
+
writeLayer(layer) {
|
|
43
|
+
return this.write * this.layers + layer;
|
|
44
|
+
}
|
|
45
|
+
/** Record that probe `layer` was baked into the set being written; a whole sweep rotates. */
|
|
46
|
+
baked(layer) {
|
|
47
|
+
if (layer < 0 || layer >= this.layers || this.reached[layer] === 1)
|
|
48
|
+
return;
|
|
49
|
+
this.reached[layer] = 1;
|
|
50
|
+
this.reachedCount++;
|
|
51
|
+
if (this.reachedCount < this.layers)
|
|
52
|
+
return;
|
|
53
|
+
this.reached.fill(0);
|
|
54
|
+
this.reachedCount = 0;
|
|
55
|
+
if (this.sets === 1) {
|
|
56
|
+
this.whole = true;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
/* The newest is shown from now on; the first whole sweep is shown alone, blending into itself. */
|
|
60
|
+
this.from = this.whole ? this.to : this.write;
|
|
61
|
+
this.to = this.write;
|
|
62
|
+
this.whole = true;
|
|
63
|
+
/* The set neither end of the blend holds. */
|
|
64
|
+
this.write = this.from === this.to ? (this.to + 1) % 3 : 3 - this.from - this.to;
|
|
65
|
+
}
|
|
66
|
+
/** Whether the shading may read the grid: once a sweep is whole. */
|
|
67
|
+
get ready() {
|
|
68
|
+
return this.whole;
|
|
69
|
+
}
|
|
70
|
+
/** The layer offsets of the two sets shown and the blend between them, into `out`. */
|
|
71
|
+
uniforms(out) {
|
|
72
|
+
out[0] = this.from * this.layers;
|
|
73
|
+
out[1] = this.to * this.layers;
|
|
74
|
+
out[2] = this.sets === 1 ? 0 : this.reachedCount / this.layers;
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -59,7 +59,46 @@ export interface ProbeBakeOptions {
|
|
|
59
59
|
* nothing gets the frame it already had.
|
|
60
60
|
*/
|
|
61
61
|
readonly irradiance?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Whether what the probe sees is lit by the grid's last bake, so each bake adds a bounce.
|
|
64
|
+
*
|
|
65
|
+
* Defaults to **false**, which is what every bake did before this existed. Then everything a
|
|
66
|
+
* probe sees is lit by the sun and by the hemispheric ambient, so the grid holds one bounce. An
|
|
67
|
+
* enclosed space is mostly lit at the second bounce and later. A courtyard's arcades are the
|
|
68
|
+
* case this was built for: one bounce came out about two stops under a reference render.
|
|
69
|
+
*
|
|
70
|
+
* With it on, the surfaces in each face take their ambient from the grid instead. Round-robin
|
|
71
|
+
* baking then converges on the space's full interreflection, about a bounce each time the grid
|
|
72
|
+
* is swept, and a scene holding one moment bakes the grid two or three times. It costs no pass
|
|
73
|
+
* and no memory. The faces are drawn into their own cube and only the resolve writes the array,
|
|
74
|
+
* so the array is never read and written in one pass.
|
|
75
|
+
*
|
|
76
|
+
* **Nothing changes until every probe has been baked once**, because until then the grid is not
|
|
77
|
+
* readable; the first sweep lights by the ambient as before. What it gives up: light that leaks
|
|
78
|
+
* through a wall into a probe is bounced again, so a leak grows with each sweep. What would make
|
|
79
|
+
* it wrong is a space with near-white walls, where a sweep adds almost as much as the last and
|
|
80
|
+
* convergence takes many sweeps.
|
|
81
|
+
*/
|
|
82
|
+
readonly bounce?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Which of the six faces this call draws, as `[first, count]`: all six when absent.
|
|
85
|
+
*
|
|
86
|
+
* **A bake is six draws of the scene, and a scene of millions of triangles makes it the most
|
|
87
|
+
* expensive thing in a frame.** Measured on a bought courtyard of eleven million: 6.4 ms of a
|
|
88
|
+
* 14 ms frame, on the frames it ran, which is a frame rate no display can hold steady. Spread over
|
|
89
|
+
* calls, each draws only its faces into the capture, and the convolution into the layer, which
|
|
90
|
+
* reads all six, runs when the sixth is drawn. Faces in the order a cube numbers them,
|
|
91
|
+
* `+X, -X, +Y, -Y, +Z, -Z`.
|
|
92
|
+
*
|
|
93
|
+
* **The caller finishes one probe before starting the next**, because the capture the faces are
|
|
94
|
+
* drawn into is one for the whole grid: faces of two probes interleaved make a cube of neither.
|
|
95
|
+
* What it gives up is a probe whose faces were drawn a few frames apart, under a light that moved
|
|
96
|
+
* a little between them.
|
|
97
|
+
*/
|
|
98
|
+
readonly faces?: readonly [first: number, count: number];
|
|
62
99
|
}
|
|
100
|
+
/** The faces a bake draws, `[first, end)`, from `ProbeBakeOptions.faces`. All six when absent. */
|
|
101
|
+
export declare function probeFaceRange(options: ProbeBakeOptions | undefined): [number, number];
|
|
63
102
|
export declare class ReflectionProbe {
|
|
64
103
|
/**
|
|
65
104
|
* The captured cube, box-filtered, which the convolution reads and the projection reads.
|
|
@@ -188,8 +227,11 @@ export declare class ReflectionProbe {
|
|
|
188
227
|
* Returns whether the bake happened. A false means the allocation was refused and the caller
|
|
189
228
|
* should carry on without a probe, which is a picture with a gradient in its paint rather
|
|
190
229
|
* than a room.
|
|
230
|
+
*
|
|
231
|
+
* `first` and `end` draw only those faces, and the chain the roughness reads is built when the
|
|
232
|
+
* sixth is drawn. See `ProbeBakeOptions.faces`.
|
|
191
233
|
*/
|
|
192
|
-
bake(gl: WebGL2RenderingContext, origin: Vec3, clearColor: Vec3, drawFace: (camera: Camera) => void): boolean;
|
|
234
|
+
bake(gl: WebGL2RenderingContext, origin: Vec3, clearColor: Vec3, drawFace: (camera: Camera) => void, first?: number, end?: number): boolean;
|
|
193
235
|
/**
|
|
194
236
|
* Read six faces back off the GPU, at one mip level, for the irradiance projection.
|
|
195
237
|
*
|
|
@@ -34,6 +34,15 @@ export function cubeFaceProjection(out, viewProj) {
|
|
|
34
34
|
}
|
|
35
35
|
return out;
|
|
36
36
|
}
|
|
37
|
+
/** The faces a bake draws, `[first, end)`, from `ProbeBakeOptions.faces`. All six when absent. */
|
|
38
|
+
export function probeFaceRange(options) {
|
|
39
|
+
const asked = options?.faces;
|
|
40
|
+
if (asked === undefined)
|
|
41
|
+
return [0, 6];
|
|
42
|
+
const first = Math.min(Math.max(Math.trunc(asked[0]), 0), 5);
|
|
43
|
+
const count = Math.max(Math.trunc(asked[1]), 1);
|
|
44
|
+
return [first, Math.min(first + count, 6)];
|
|
45
|
+
}
|
|
37
46
|
/**
|
|
38
47
|
* The six directions of a cubemap, as the yaw and pitch a `Camera` aims with.
|
|
39
48
|
*
|
|
@@ -336,8 +345,11 @@ export class ReflectionProbe {
|
|
|
336
345
|
* Returns whether the bake happened. A false means the allocation was refused and the caller
|
|
337
346
|
* should carry on without a probe, which is a picture with a gradient in its paint rather
|
|
338
347
|
* than a room.
|
|
348
|
+
*
|
|
349
|
+
* `first` and `end` draw only those faces, and the chain the roughness reads is built when the
|
|
350
|
+
* sixth is drawn. See `ProbeBakeOptions.faces`.
|
|
339
351
|
*/
|
|
340
|
-
bake(gl, origin, clearColor, drawFace) {
|
|
352
|
+
bake(gl, origin, clearColor, drawFace, first = 0, end = FACES.length) {
|
|
341
353
|
if (this.unusable)
|
|
342
354
|
return false;
|
|
343
355
|
const camera = this.camera;
|
|
@@ -351,12 +363,12 @@ export class ReflectionProbe {
|
|
|
351
363
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
352
364
|
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, this.depth);
|
|
353
365
|
gl.viewport(0, 0, this.size, this.size);
|
|
354
|
-
for (let face =
|
|
366
|
+
for (let face = first; face < end; face++) {
|
|
355
367
|
const aim = FACES[face];
|
|
356
368
|
if (aim === undefined)
|
|
357
369
|
continue;
|
|
358
370
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, this.texture, 0);
|
|
359
|
-
if (face ===
|
|
371
|
+
if (face === first && gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
360
372
|
this.unusable = true;
|
|
361
373
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
362
374
|
console.warn('ReflectionProbe: this driver will not render to a cubemap face, so reflective ' +
|
|
@@ -376,6 +388,8 @@ export class ReflectionProbe {
|
|
|
376
388
|
drawFace(camera);
|
|
377
389
|
}
|
|
378
390
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
391
|
+
if (end < FACES.length)
|
|
392
|
+
return true;
|
|
379
393
|
/* The blur the roughness reads. Once per bake, not once per frame. */
|
|
380
394
|
gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.texture);
|
|
381
395
|
gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
|
|
@@ -552,7 +552,9 @@ export interface RenderQuality {
|
|
|
552
552
|
*/
|
|
553
553
|
readonly reconstruction: number;
|
|
554
554
|
/**
|
|
555
|
-
* Light a scene from what bounces, rather than from what was baked
|
|
555
|
+
* Light a scene from what bounces, rather than from what was baked: **DriftRay**, this engine's
|
|
556
|
+
* ray tracing, rays marched through the declared distance fields in GPU compute rather than on
|
|
557
|
+
* the dedicated ray-tracing units WebGPU does not expose. **Off by default.**
|
|
556
558
|
*
|
|
557
559
|
* On, the probe grid the shading already samples is filled by tracing `render/gi/`'s three-level
|
|
558
560
|
* chain — the world distance field, and behind it the probes themselves, which is what makes the
|
|
@@ -29,6 +29,15 @@ export declare function recordingGl(options?: {
|
|
|
29
29
|
* `Infinity` by default, so a link only ever fails where a test asked for it.
|
|
30
30
|
*/
|
|
31
31
|
readonly refuseLinkAbove?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Uniforms every program reports as active, each located by a handle carrying its name.
|
|
34
|
+
*
|
|
35
|
+
* **None by default**, which ends every reflection loop at once and leaves every upload aimed
|
|
36
|
+
* at `null`. A test about what a frame *uploads* names the uniform here and then finds the
|
|
37
|
+
* calls whose location says that name, rather than guessing at an upload's position in the
|
|
38
|
+
* recording.
|
|
39
|
+
*/
|
|
40
|
+
readonly uniforms?: readonly string[];
|
|
32
41
|
}): {
|
|
33
42
|
gl: WebGL2RenderingContext;
|
|
34
43
|
canvas: HTMLCanvasElement;
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
import { countUniformVectors } from './uniformVectorBudget.js';
|
|
19
19
|
export function recordingGl(options = {}) {
|
|
20
20
|
const extensions = new Set(options.extensions ?? []);
|
|
21
|
+
const uniforms = options.uniforms ?? [];
|
|
22
|
+
/* What `clearDepth` last set, so a module that borrows the value can be seen to put it back. */
|
|
23
|
+
let depthClear = 1;
|
|
21
24
|
const reportedVectors = options.fragmentUniformVectors ?? 1024;
|
|
22
25
|
const enforcedVectors = options.refuseLinkAbove ?? Number.POSITIVE_INFINITY;
|
|
23
26
|
/** Fragment sources by shader handle, so a refusal can be decided from what was compiled. */
|
|
@@ -37,6 +40,7 @@ export function recordingGl(options = {}) {
|
|
|
37
40
|
MAX_TEXTURE_IMAGE_UNITS: 0x8872,
|
|
38
41
|
MAX_VERTEX_UNIFORM_VECTORS: 0x8dfb,
|
|
39
42
|
MAX_FRAGMENT_UNIFORM_VECTORS: 0x8dfd,
|
|
43
|
+
DEPTH_CLEAR_VALUE: 0x0b73,
|
|
40
44
|
/*
|
|
41
45
|
* Enums a test needs to compare against, rather than merely to pass through.
|
|
42
46
|
*
|
|
@@ -97,6 +101,8 @@ export function recordingGl(options = {}) {
|
|
|
97
101
|
return reportedVectors;
|
|
98
102
|
if (pname === constants.MAX_VERTEX_UNIFORM_VECTORS)
|
|
99
103
|
return 1024;
|
|
104
|
+
if (pname === constants.DEPTH_CLEAR_VALUE)
|
|
105
|
+
return depthClear;
|
|
100
106
|
/* Generous by default: every remaining limit is a ceiling something is checked against,
|
|
101
107
|
and a small answer is read as a device that cannot do the thing. */
|
|
102
108
|
return 4096;
|
|
@@ -129,7 +135,9 @@ export function recordingGl(options = {}) {
|
|
|
129
135
|
/* Linked yes; zero active uniforms and attributes, which ends every reflection loop at once
|
|
130
136
|
and is why this harness does not have to know a single uniform name. */
|
|
131
137
|
getProgramParameter: (program, pname) => {
|
|
132
|
-
if (pname === constants.ACTIVE_UNIFORMS
|
|
138
|
+
if (pname === constants.ACTIVE_UNIFORMS)
|
|
139
|
+
return uniforms.length;
|
|
140
|
+
if (pname === constants.ACTIVE_ATTRIBUTES)
|
|
133
141
|
return 0;
|
|
134
142
|
if (pname !== constants.LINK_STATUS)
|
|
135
143
|
return true;
|
|
@@ -140,9 +148,16 @@ export function recordingGl(options = {}) {
|
|
|
140
148
|
const source = programFragments.get(program);
|
|
141
149
|
return source === undefined || countUniformVectors(source) <= enforcedVectors;
|
|
142
150
|
},
|
|
143
|
-
getActiveUniform: () => ({
|
|
151
|
+
getActiveUniform: (_program, index) => ({
|
|
152
|
+
name: uniforms[Number(index)] ?? '',
|
|
153
|
+
size: 1,
|
|
154
|
+
type: 0,
|
|
155
|
+
}),
|
|
144
156
|
getActiveAttrib: () => ({ name: '', size: 1, type: 0 }),
|
|
145
|
-
getUniformLocation: () => ({}),
|
|
157
|
+
getUniformLocation: (_program, name) => ({ name }),
|
|
158
|
+
clearDepth: (value) => {
|
|
159
|
+
depthClear = Number(value);
|
|
160
|
+
},
|
|
146
161
|
getAttribLocation: () => -1,
|
|
147
162
|
checkFramebufferStatus: () => constants.FRAMEBUFFER_COMPLETE,
|
|
148
163
|
getError: () => constants.NO_ERROR,
|
|
@@ -85,6 +85,16 @@ export declare class SceneTarget {
|
|
|
85
85
|
private gradeSource;
|
|
86
86
|
private gradeSize;
|
|
87
87
|
private gradeStrength;
|
|
88
|
+
/** The lens and the print, held like the grade. See `filmLook.ts`. */
|
|
89
|
+
private vignette;
|
|
90
|
+
private grain;
|
|
91
|
+
private grainSeed;
|
|
92
|
+
/** Eye adaptation: how far, and this frame's time step. See `Renderer.setAutoExposure`. */
|
|
93
|
+
private autoExposure;
|
|
94
|
+
private autoExposureDt;
|
|
95
|
+
/** How far each region is brought toward the frame. See `Renderer.setLocalExposure`. */
|
|
96
|
+
private localExposure;
|
|
97
|
+
private exposure;
|
|
88
98
|
private width;
|
|
89
99
|
private height;
|
|
90
100
|
/**
|
|
@@ -244,6 +254,16 @@ export declare class SceneTarget {
|
|
|
244
254
|
* `null`, or a strength at or below zero, is exactly the frame that existed before the effect
|
|
245
255
|
* did: the shader's branch is on a uniform, so no fetch happens at all.
|
|
246
256
|
*/
|
|
257
|
+
/** The lens's corner falloff, held until changed. See `Renderer.setVignette`. */
|
|
258
|
+
setVignette(strength: number): void;
|
|
259
|
+
/** The print's grain and this frame's seed, held until changed. See `Renderer.setFilmGrain`. */
|
|
260
|
+
setFilmGrain(strength: number, seed: number): void;
|
|
261
|
+
/** How far the eye adapts, and the time since the last frame. See `Renderer.setAutoExposure`. */
|
|
262
|
+
setAutoExposure(strength: number, dtSec: number): void;
|
|
263
|
+
/** How far each region is brought toward the frame. See `Renderer.setLocalExposure`. */
|
|
264
|
+
setLocalExposure(strength: number): void;
|
|
265
|
+
/** A new shot: the eye meters it afresh rather than easing in from the last one. */
|
|
266
|
+
cutExposure(): void;
|
|
247
267
|
setColourGrade(lut: ColourGradeLut | null, strength: number): void;
|
|
248
268
|
/**
|
|
249
269
|
* Put a table on the device, replacing whatever was there.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { glSceneDepthFormat } from './depthConvention.js';
|
|
2
|
+
import { clampGrain, clampVignette, grainSeed } from './shaders/filmLook.js';
|
|
2
3
|
import { AmbientOcclusionPass } from './ambientOcclusionPass.js';
|
|
4
|
+
import { ExposurePass } from './exposurePass.js';
|
|
5
|
+
import { clampAutoExposure } from './shaders/exposure.js';
|
|
6
|
+
import { clampLocalExposure } from './shaders/localExposure.js';
|
|
3
7
|
import { BloomPass } from './bloomPass.js';
|
|
4
8
|
import { GRADE_PLACEHOLDER_SIZE, identityGradeLut, validateGradeLut, } from './colourGrade.js';
|
|
5
9
|
import { compileProgram, uniformLocations } from './shader.js';
|
|
@@ -85,6 +89,16 @@ export class SceneTarget {
|
|
|
85
89
|
gradeSource = null;
|
|
86
90
|
gradeSize = GRADE_PLACEHOLDER_SIZE;
|
|
87
91
|
gradeStrength = 0;
|
|
92
|
+
/** The lens and the print, held like the grade. See `filmLook.ts`. */
|
|
93
|
+
vignette = 0;
|
|
94
|
+
grain = 0;
|
|
95
|
+
grainSeed = 0;
|
|
96
|
+
/** Eye adaptation: how far, and this frame's time step. See `Renderer.setAutoExposure`. */
|
|
97
|
+
autoExposure = 0;
|
|
98
|
+
autoExposureDt = 0;
|
|
99
|
+
/** How far each region is brought toward the frame. See `Renderer.setLocalExposure`. */
|
|
100
|
+
localExposure = 0;
|
|
101
|
+
exposure = null;
|
|
88
102
|
width = 0;
|
|
89
103
|
height = 0;
|
|
90
104
|
/**
|
|
@@ -520,6 +534,28 @@ export class SceneTarget {
|
|
|
520
534
|
* `null`, or a strength at or below zero, is exactly the frame that existed before the effect
|
|
521
535
|
* did: the shader's branch is on a uniform, so no fetch happens at all.
|
|
522
536
|
*/
|
|
537
|
+
/** The lens's corner falloff, held until changed. See `Renderer.setVignette`. */
|
|
538
|
+
setVignette(strength) {
|
|
539
|
+
this.vignette = clampVignette(strength);
|
|
540
|
+
}
|
|
541
|
+
/** The print's grain and this frame's seed, held until changed. See `Renderer.setFilmGrain`. */
|
|
542
|
+
setFilmGrain(strength, seed) {
|
|
543
|
+
this.grain = clampGrain(strength);
|
|
544
|
+
this.grainSeed = grainSeed(seed);
|
|
545
|
+
}
|
|
546
|
+
/** How far the eye adapts, and the time since the last frame. See `Renderer.setAutoExposure`. */
|
|
547
|
+
setAutoExposure(strength, dtSec) {
|
|
548
|
+
this.autoExposure = clampAutoExposure(strength);
|
|
549
|
+
this.autoExposureDt = dtSec;
|
|
550
|
+
}
|
|
551
|
+
/** How far each region is brought toward the frame. See `Renderer.setLocalExposure`. */
|
|
552
|
+
setLocalExposure(strength) {
|
|
553
|
+
this.localExposure = clampLocalExposure(strength);
|
|
554
|
+
}
|
|
555
|
+
/** A new shot: the eye meters it afresh rather than easing in from the last one. */
|
|
556
|
+
cutExposure() {
|
|
557
|
+
this.exposure?.cut();
|
|
558
|
+
}
|
|
523
559
|
setColourGrade(lut, strength) {
|
|
524
560
|
this.gradeStrength = lut === null ? 0 : Math.max(0, Math.min(1, strength));
|
|
525
561
|
if (lut === null || lut === this.gradeSource)
|
|
@@ -682,6 +718,16 @@ export class SceneTarget {
|
|
|
682
718
|
this.bloom ??= new BloomPass(gl);
|
|
683
719
|
bloomTexture = this.bloom.run(scene, this.width, this.height, bloom.threshold);
|
|
684
720
|
}
|
|
721
|
+
/*
|
|
722
|
+
* Eye adaptation, metered from the same resolved scene bloom reads, and after it for the same
|
|
723
|
+
* reason: the composite consumes it. Off at zero strength, where nothing is allocated or drawn.
|
|
724
|
+
*/
|
|
725
|
+
let heldExposure = null;
|
|
726
|
+
const local = this.localExposure > 0;
|
|
727
|
+
if ((this.autoExposure > 0 || local) && scene !== null) {
|
|
728
|
+
this.exposure ??= new ExposurePass(gl);
|
|
729
|
+
heldExposure = this.exposure.run(scene, this.autoExposureDt, local);
|
|
730
|
+
}
|
|
685
731
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
686
732
|
gl.viewport(0, 0, this.width, this.height);
|
|
687
733
|
gl.disable(gl.DEPTH_TEST);
|
|
@@ -767,6 +813,18 @@ export class SceneTarget {
|
|
|
767
813
|
gl.uniform1i(this.uniforms['uGradeLut'] ?? null, 4);
|
|
768
814
|
gl.uniform1f(this.uniforms['uGradeStrength'] ?? null, this.gradeStrength);
|
|
769
815
|
gl.uniform1f(this.uniforms['uGradeSize'] ?? null, this.gradeSize);
|
|
816
|
+
/* The held brightness on unit 5, and the scene as the placeholder where there is none, for
|
|
817
|
+
the reason the occlusion's placeholder gives. Zero strength is the whole of the off path. */
|
|
818
|
+
gl.activeTexture(gl.TEXTURE5);
|
|
819
|
+
gl.bindTexture(gl.TEXTURE_2D, heldExposure ?? scene);
|
|
820
|
+
gl.uniform1i(this.uniforms['uExposureHeld'] ?? null, 5);
|
|
821
|
+
gl.uniform1f(this.uniforms['uAutoExposure'] ?? null, heldExposure === null ? 0 : this.autoExposure);
|
|
822
|
+
/* Local exposure's grid on unit 6, with the same placeholder and the same off path. */
|
|
823
|
+
const localGrid = local && heldExposure !== null ? (this.exposure?.localTexture ?? null) : null;
|
|
824
|
+
gl.activeTexture(gl.TEXTURE6);
|
|
825
|
+
gl.bindTexture(gl.TEXTURE_2D, localGrid ?? scene);
|
|
826
|
+
gl.uniform1i(this.uniforms['uExposureLocal'] ?? null, 6);
|
|
827
|
+
gl.uniform1f(this.uniforms['uLocalExposure'] ?? null, localGrid === null ? 0 : this.localExposure);
|
|
770
828
|
gl.activeTexture(gl.TEXTURE0);
|
|
771
829
|
/* No unit and no texture: a flat colour, not a sample. Uploaded unconditionally — two
|
|
772
830
|
floats and a triplet cost nothing next to the triangle this pass already draws — and it
|
|
@@ -774,6 +832,8 @@ export class SceneTarget {
|
|
|
774
832
|
mix it does not need. */
|
|
775
833
|
gl.uniform3fv(this.uniforms['uVeilColor'] ?? null, veilColor);
|
|
776
834
|
gl.uniform1f(this.uniforms['uVeilAlpha'] ?? null, veilAlpha);
|
|
835
|
+
gl.uniform1f(this.uniforms['uVignette'] ?? null, this.vignette);
|
|
836
|
+
gl.uniform2f(this.uniforms['uGrain'] ?? null, this.grain, this.grainSeed);
|
|
777
837
|
gl.bindVertexArray(this.vao);
|
|
778
838
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
779
839
|
gl.bindVertexArray(null);
|
|
@@ -788,7 +848,7 @@ export class SceneTarget {
|
|
|
788
848
|
* own framebuffers on the next frame, and that is how this was found: 120 pixels of a frame
|
|
789
849
|
* that should have been identical, and a warning per draw explaining why.
|
|
790
850
|
*/
|
|
791
|
-
for (let unit = 0; unit <=
|
|
851
|
+
for (let unit = 0; unit <= 5; unit++) {
|
|
792
852
|
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
793
853
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
794
854
|
}
|
|
@@ -815,6 +875,7 @@ export class SceneTarget {
|
|
|
815
875
|
dispose() {
|
|
816
876
|
const { gl } = this;
|
|
817
877
|
this.ao?.dispose();
|
|
878
|
+
this.exposure?.dispose();
|
|
818
879
|
this.ao = null;
|
|
819
880
|
this.bloom?.dispose();
|
|
820
881
|
this.bloom = null;
|
|
@@ -24,13 +24,23 @@
|
|
|
24
24
|
* wherever the depth test cuts the window short, as a dotted line along every edge.
|
|
25
25
|
*/
|
|
26
26
|
/**
|
|
27
|
-
* The estimate. Writes one channel
|
|
27
|
+
* The estimate. Writes one channel at `AO_STORE`: that is open sky, 0 is fully enclosed, and the
|
|
28
|
+
* second axis of the blur decodes it.
|
|
28
29
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
30
|
+
* **Horizons, not neighbours: ground-truth ambient occlusion (Jimenez et al. 2016).** Each pixel
|
|
31
|
+
* walks two lines across the screen, both ways, and keeps the highest thing it can see along each
|
|
32
|
+
* half: the horizon. The sky left between the two horizons of a line, weighted by the cosine to the
|
|
33
|
+
* surface's own normal, is that line's visibility in closed form, and two lines a pixel with the
|
|
34
|
+
* rotation tile below turning them is the hemisphere. It replaced a sum over twelve neighbours that
|
|
35
|
+
* counted anything within the radius as a blocker, however thin: cloth hanging a hand in front of a
|
|
36
|
+
* column darkened the column behind it, which read as a halo round every curtain in a courtyard.
|
|
37
|
+
*
|
|
38
|
+
* **What stops the halo is two rules about the horizon**, both the paper's. A sample fades out of
|
|
39
|
+
* the horizon as it approaches the radius, so a far wall does not shade a floor; and once the walk
|
|
40
|
+
* has passed the highest sample, the horizon sinks back toward what it sees next, so a thin thing
|
|
41
|
+
* in front raises it only while the walk is behind that thing. What that gives up is the shade under
|
|
42
|
+
* a thick object seen edge on, which reads as a thin one; the thickness is a constant because depth
|
|
43
|
+
* alone cannot say how deep anything is.
|
|
34
44
|
*/
|
|
35
45
|
export declare const AO_FRAG: string;
|
|
36
46
|
/**
|
|
@@ -43,3 +53,50 @@ export declare const AO_FRAG: string;
|
|
|
43
53
|
* added to remove.
|
|
44
54
|
*/
|
|
45
55
|
export declare const AO_BLUR_FRAG: string;
|
|
56
|
+
/**
|
|
57
|
+
* The estimate's closed form, written out once more so a test can pin it to hand-derived values:
|
|
58
|
+
* the cosine-weighted sky between horizon angles `h0 < 0 < h1` for a normal at `normal`, all in
|
|
59
|
+
* radians from the eye within one line's plane.
|
|
60
|
+
*/
|
|
61
|
+
export declare function gtaoArc(h0: number, h1: number, normal: number): number;
|
|
62
|
+
/** One step of a horizon: up to a higher sample, or a fifth of the way down toward a lower one. */
|
|
63
|
+
export declare function nextHorizon(horizon: number, cosine: number): number;
|
|
64
|
+
/** The shader's thickness heuristic. See `AO_THIN` inside `AO_FRAG`. */
|
|
65
|
+
export declare const AO_THIN = 0.2;
|
|
66
|
+
/**
|
|
67
|
+
* What the estimate is multiplied by before it is stored, and divided by once the blur is done.
|
|
68
|
+
*
|
|
69
|
+
* **Half, because an unblurred estimate is allowed past one.** A pixel's two lines see a plane
|
|
70
|
+
* turned away from the eye from two directions, and the closed form credits one of them more than
|
|
71
|
+
* the whole sky and the other less: it is the average over every turn that is exactly one, and the
|
|
72
|
+
* average is what the blur is for. Stored as it came, an eight-bit target clipped every turn above
|
|
73
|
+
* one before the blur could average it, and every surface seen edge on came back a few percent
|
|
74
|
+
* shaded with nothing on it. A line reaches π/2 at most, so half fits the whole range.
|
|
75
|
+
*
|
|
76
|
+
* What it gives up is one bit of the intermediate: steps of 1/128 rather than 1/256 until the
|
|
77
|
+
* second axis decodes, which the eight taps a pixel averages carry below what a frame can show.
|
|
78
|
+
*/
|
|
79
|
+
export declare const AO_STORE = 0.5;
|
|
80
|
+
/** How far a neighbour may sit from this pixel's surface, as a fraction of its distance. */
|
|
81
|
+
export declare const AO_BLUR_DEPTH_TOLERANCE = 0.02;
|
|
82
|
+
/**
|
|
83
|
+
* The blur's weight for a neighbour `offset` pixels along the axis, from view depths: this pixel's
|
|
84
|
+
* own, the two either side of it, and the neighbour's. The shader's lines written out again.
|
|
85
|
+
*
|
|
86
|
+
* **The neighbour is compared with this pixel's plane, not with this pixel's depth.** Comparing
|
|
87
|
+
* depths refused every neighbour of a surface seen edge on: a floor forty metres out changes its
|
|
88
|
+
* depth by four percent a row, twice the tolerance, so the pass down the frame kept the centre alone
|
|
89
|
+
* and left the rotation tile's rows standing — four-pixel stripes across every ramp and far floor,
|
|
90
|
+
* reported as strips on a track. On a plane 1/z is affine in the screen, so one step of it predicts
|
|
91
|
+
* the plane's depth at every tap exactly, and a tap is refused only for leaving the plane.
|
|
92
|
+
*
|
|
93
|
+
* The step is taken from whichever neighbour is closer to the centre in 1/z. At a silhouette that is
|
|
94
|
+
* the one on this pixel's own side, so the far surface cannot bend the prediction toward it; on a
|
|
95
|
+
* plane the two agree to a quantisation step, which moves a weight by nothing a frame shows. What it
|
|
96
|
+
* gives up is a crease: past the fold between a floor and a wall the prediction runs on along the
|
|
97
|
+
* floor, so the wall's taps fade out over the tolerance rather than being kept.
|
|
98
|
+
*
|
|
99
|
+
* **Written here rather than in the shader**, because a comment inside the GLSL is part of the string
|
|
100
|
+
* a consumer ships.
|
|
101
|
+
*/
|
|
102
|
+
export declare function blurWeight(centre: number, before: number, after: number, neighbour: number, offset: number): number;
|