@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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local exposure: each region of the frame brought part of the way toward the frame's own
|
|
3
|
+
* brightness, so shade and sun can both be read in one picture.
|
|
4
|
+
*
|
|
5
|
+
* **A tone curve maps one range, and a sunlit courtyard is two.** Measured in a bought courtyard at
|
|
6
|
+
* noon, the shaded arcades hold a fiftieth of the light the sunlit paving does, which is what the
|
|
7
|
+
* place is: a canyon lit mostly by its own walls. Exposed for the paving the arcades go to black,
|
|
8
|
+
* exposed for the arcades the paving goes to white, and no curve fixes both, because a curve is a
|
|
9
|
+
* function of a pixel's brightness and the same brightness has to mean shade in one place and sun in
|
|
10
|
+
* another. A photographer brackets; an eye adapts by region. This is the second.
|
|
11
|
+
*
|
|
12
|
+
* **A bilateral grid over the exposure meter's tiles.** The grid pass writes, for each of the
|
|
13
|
+
* meter's 32 × 32 tiles and each of ten two-stop bands of log luminance, the sum and the share of
|
|
14
|
+
* the tile's taps that fell in that band, and in an eleventh block the tile's mean. The composite
|
|
15
|
+
* reads the grid at the pixel's place and in the pixel's own band, filtered in both, so a shaded
|
|
16
|
+
* pixel beside sunlit ones takes the shade's level rather than the tile's: that is what keeps the
|
|
17
|
+
* lift from haloing across the edge of a shadow. The band's level is blended 0.4 toward the tile's
|
|
18
|
+
* mean, which is what stops a texture's own light and dark being flattened into one another.
|
|
19
|
+
*
|
|
20
|
+
* **The gain is `strength · (held − local)` stops**, clamped to three: at 0.5 a region five stops
|
|
21
|
+
* under the frame is lifted two and a half, and one two over is brought down one. The held value is
|
|
22
|
+
* eye adaptation's, so the frame as a whole still adapts and regions are moved relative to it.
|
|
23
|
+
*
|
|
24
|
+
* What it gives up is some of the contrast between regions, which is the point, and a halo a tile
|
|
25
|
+
* wide where two regions share a band and differ in brightness. What would make it wrong is a scene
|
|
26
|
+
* whose drama is the contrast itself: a single shaft into a dark room wants the room dark, and the
|
|
27
|
+
* strength is the caller's for that reason.
|
|
28
|
+
*/
|
|
29
|
+
import { METER_GRID, METER_MAX_LOG2, METER_MIN_LOG2, METER_TAPS } from './exposure.ts';
|
|
30
|
+
|
|
31
|
+
/** Bands of log luminance, and how many stops each is wide. */
|
|
32
|
+
export const LOCAL_BINS = 10;
|
|
33
|
+
export const LOCAL_BIN_STOPS = (METER_MAX_LOG2 - METER_MIN_LOG2) / LOCAL_BINS;
|
|
34
|
+
/** The grid's width in texels: a block of tiles per band, and one for the tiles' means. */
|
|
35
|
+
export const LOCAL_GRID_WIDTH = METER_GRID * (LOCAL_BINS + 1);
|
|
36
|
+
/** How far a pixel's band level is blended toward its tile's mean. */
|
|
37
|
+
export const LOCAL_BLURRED_BLEND = 0.4;
|
|
38
|
+
/** The furthest a region is moved either way, in stops. */
|
|
39
|
+
export const LOCAL_RANGE_STOPS = 3;
|
|
40
|
+
|
|
41
|
+
const BLOCKS = (LOCAL_BINS + 1).toFixed(1);
|
|
42
|
+
const GRID = METER_GRID.toFixed(1);
|
|
43
|
+
const MIN = METER_MIN_LOG2.toFixed(1);
|
|
44
|
+
const MAX = METER_MAX_LOG2.toFixed(1);
|
|
45
|
+
const STOPS = LOCAL_BIN_STOPS.toFixed(1);
|
|
46
|
+
const LAST = (LOCAL_BINS - 1).toFixed(1);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The grid: one texel a tile a band. Each reads its tile's 8 × 8 taps, as the meter does, and keeps
|
|
50
|
+
* the ones in its band; the last block keeps them all. Sum and count are both divided by the taps,
|
|
51
|
+
* so the filtered pair in the composite is still a weighted mean.
|
|
52
|
+
*/
|
|
53
|
+
export const EXPOSURE_LOCAL_FRAG = `#version 300 es
|
|
54
|
+
precision highp float;
|
|
55
|
+
|
|
56
|
+
in vec2 vUv;
|
|
57
|
+
|
|
58
|
+
/** The finished scene, in scene light, before any exposure or curve. */
|
|
59
|
+
uniform highp sampler2D uScene;
|
|
60
|
+
|
|
61
|
+
out vec2 fragColor;
|
|
62
|
+
|
|
63
|
+
void main() {
|
|
64
|
+
float uvBlock = vUv.x * ${BLOCKS};
|
|
65
|
+
float band = floor(uvBlock);
|
|
66
|
+
vec2 corner = vec2(fract(uvBlock), vUv.y) - vec2(0.5 / ${GRID});
|
|
67
|
+
float sum = 0.0;
|
|
68
|
+
float count = 0.0;
|
|
69
|
+
for (int y = 0; y < ${METER_TAPS}; y++) {
|
|
70
|
+
for (int x = 0; x < ${METER_TAPS}; x++) {
|
|
71
|
+
vec2 uv = corner + (vec2(float(x), float(y)) + 0.5) / ${(METER_GRID * METER_TAPS).toFixed(1)};
|
|
72
|
+
vec3 c = textureLod(uScene, uv, 0.0).rgb;
|
|
73
|
+
float level = clamp(log2(max(dot(c, vec3(0.2126, 0.7152, 0.0722)), 1e-8)), ${MIN}, ${MAX});
|
|
74
|
+
float tapBand = floor(clamp((level - ${MIN}) / ${STOPS}, 0.0, ${LAST}));
|
|
75
|
+
float kept = band > ${LAST} || tapBand == band ? 1.0 : 0.0;
|
|
76
|
+
sum += level * kept;
|
|
77
|
+
count += kept;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
fragColor = vec2(sum, count) / ${(METER_TAPS * METER_TAPS).toFixed(1)};
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The composite's half. Needs `AUTO_EXPOSURE_GLSL` before it, for the held brightness.
|
|
86
|
+
*
|
|
87
|
+
* A block is read clamped half a texel inside its own tiles, so the filter never reaches into the
|
|
88
|
+
* neighbouring band's block. Every fetch is at level zero: the grid has one level, and the branch
|
|
89
|
+
* on the strength above them is uniform but the pixel's band is not.
|
|
90
|
+
*/
|
|
91
|
+
export const LOCAL_EXPOSURE_GLSL = `
|
|
92
|
+
/** How far each region is brought toward the frame, 0 to 1. 0 is off and the whole of the off path. */
|
|
93
|
+
uniform float uLocalExposure;
|
|
94
|
+
/** The bilateral grid: a block of tiles per band of brightness, and one of the tiles' means. */
|
|
95
|
+
uniform highp sampler2D uExposureLocal;
|
|
96
|
+
|
|
97
|
+
vec2 localBlock(vec2 uv, float block) {
|
|
98
|
+
vec2 inside = clamp(uv, vec2(0.5 / ${GRID}), vec2(1.0 - 0.5 / ${GRID}));
|
|
99
|
+
return textureLod(uExposureLocal, vec2((block + inside.x) / ${BLOCKS}, inside.y), 0.0).rg;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
float localExposureGain(vec2 uv, vec3 light) {
|
|
103
|
+
if (uLocalExposure <= 0.0) return 1.0;
|
|
104
|
+
float held = texelFetch(uExposureHeld, ivec2(0, 0), 0).r;
|
|
105
|
+
float pixel = clamp(log2(max(dot(light, vec3(0.2126, 0.7152, 0.0722)), 1e-8)), ${MIN}, ${MAX});
|
|
106
|
+
float b = (pixel - ${MIN}) / ${STOPS} - 0.5;
|
|
107
|
+
float lower = clamp(floor(b), 0.0, ${LAST});
|
|
108
|
+
float upper = min(lower + 1.0, ${LAST});
|
|
109
|
+
vec2 banded = mix(localBlock(uv, lower), localBlock(uv, upper), clamp(b - lower, 0.0, 1.0));
|
|
110
|
+
float tileMean = localBlock(uv, ${LOCAL_BINS.toFixed(1)}).r;
|
|
111
|
+
float bilateral = banded.y > 1e-3 ? banded.x / banded.y : tileMean;
|
|
112
|
+
float local = mix(bilateral, tileMean, ${LOCAL_BLURRED_BLEND.toFixed(1)});
|
|
113
|
+
float stops = uLocalExposure * (held - local);
|
|
114
|
+
return exp2(clamp(stops, -${LOCAL_RANGE_STOPS.toFixed(1)}, ${LOCAL_RANGE_STOPS.toFixed(1)}));
|
|
115
|
+
}
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
const clamp = (value: number, low: number, high: number): number =>
|
|
119
|
+
Math.min(Math.max(value, low), high);
|
|
120
|
+
|
|
121
|
+
/** The two bands a pixel at `pixelLog2` reads, and how much of the upper, as the shader finds them. */
|
|
122
|
+
export function localBin(pixelLog2: number): { lower: number; upper: number; weight: number } {
|
|
123
|
+
const pixel = clamp(pixelLog2, METER_MIN_LOG2, METER_MAX_LOG2);
|
|
124
|
+
const b = (pixel - METER_MIN_LOG2) / LOCAL_BIN_STOPS - 0.5;
|
|
125
|
+
const lower = clamp(Math.floor(b), 0, LOCAL_BINS - 1);
|
|
126
|
+
return { lower, upper: Math.min(lower + 1, LOCAL_BINS - 1), weight: clamp(b - lower, 0, 1) };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** A pixel's local level from its two bands' sums and counts and its tile's mean, as the shader. */
|
|
130
|
+
export function sliceLocal(
|
|
131
|
+
sumLower: number,
|
|
132
|
+
countLower: number,
|
|
133
|
+
sumUpper: number,
|
|
134
|
+
countUpper: number,
|
|
135
|
+
weight: number,
|
|
136
|
+
tileMean: number,
|
|
137
|
+
): number {
|
|
138
|
+
const sum = sumLower + (sumUpper - sumLower) * weight;
|
|
139
|
+
const count = countLower + (countUpper - countLower) * weight;
|
|
140
|
+
const bilateral = count > 1e-3 ? sum / count : tileMean;
|
|
141
|
+
return bilateral + (tileMean - bilateral) * LOCAL_BLURRED_BLEND;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A region's gain for a local level and the frame's held one at `strength`, as the composite. */
|
|
145
|
+
export function localExposureGain(localLog2: number, heldLog2: number, strength: number): number {
|
|
146
|
+
if (!(strength > 0)) return 1;
|
|
147
|
+
const stops = strength * (heldLog2 - localLog2);
|
|
148
|
+
return 2 ** clamp(stops, -LOCAL_RANGE_STOPS, LOCAL_RANGE_STOPS);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** A local exposure strength as the shader takes it: 0 to 1, anything else off. */
|
|
152
|
+
export function clampLocalExposure(strength: number): number {
|
|
153
|
+
return Number.isFinite(strength) ? clamp(strength, 0, 1) : 0;
|
|
154
|
+
}
|
|
@@ -39,6 +39,12 @@ import { OCTAHEDRAL_GLSL } from './octahedral.ts';
|
|
|
39
39
|
*/
|
|
40
40
|
export const PREFILTER_FRAG = `#version 300 es
|
|
41
41
|
precision highp float;
|
|
42
|
+
/*
|
|
43
|
+
* highp int as well, because the radical inverse below is a 32-bit bit reversal and the fragment
|
|
44
|
+
* default for int and uint is mediump. A driver that computes mediump in sixteen bits returns 0
|
|
45
|
+
* for every sample, and every cosine sample of the diffuse level lands on its own normal.
|
|
46
|
+
*/
|
|
47
|
+
precision highp int;
|
|
42
48
|
|
|
43
49
|
${OCTAHEDRAL_GLSL}
|
|
44
50
|
|
|
@@ -22,9 +22,17 @@
|
|
|
22
22
|
/* The vertex stage is `FULLSCREEN_VERT`, shared with the occlusion pass and its blur. */
|
|
23
23
|
import { glslSceneDepthToNdc } from '../depthConvention.ts';
|
|
24
24
|
import { DEPTH_OF_FIELD_GLSL } from './depthOfField.ts';
|
|
25
|
+
import { AUTO_EXPOSURE_GLSL } from './exposure.ts';
|
|
26
|
+
import { LOCAL_EXPOSURE_GLSL } from './localExposure.ts';
|
|
27
|
+
import { FILM_LOOK_GLSL } from './filmLook.ts';
|
|
25
28
|
|
|
26
29
|
export const RUSH_FRAG = `#version 300 es
|
|
27
30
|
precision highp float;
|
|
31
|
+
/*
|
|
32
|
+
* highp int as well, for the film grain's hash, which multiplies and shifts uints. The fragment
|
|
33
|
+
* default is mediump, and a sixteen-bit driver would hand back a hash of sixteen bits.
|
|
34
|
+
*/
|
|
35
|
+
precision highp int;
|
|
28
36
|
|
|
29
37
|
in vec2 vUv;
|
|
30
38
|
|
|
@@ -135,6 +143,11 @@ uniform float uBloomStrength;
|
|
|
135
143
|
*/
|
|
136
144
|
uniform vec3 uVeilColor;
|
|
137
145
|
uniform float uVeilAlpha;
|
|
146
|
+
${FILM_LOOK_GLSL}
|
|
147
|
+
|
|
148
|
+
/* Eye adaptation's held brightness and the gain it asks for. See exposure.ts. */
|
|
149
|
+
${AUTO_EXPOSURE_GLSL}
|
|
150
|
+
${LOCAL_EXPOSURE_GLSL}
|
|
138
151
|
|
|
139
152
|
/**
|
|
140
153
|
* The colour grade: a lookup table over display values, sampled after the tone map.
|
|
@@ -293,6 +306,24 @@ vec3 cameraBlur(vec3 scene) {
|
|
|
293
306
|
return sum / 9.0;
|
|
294
307
|
}
|
|
295
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Everything after the light is complete, in the order the header of each step argues: the lens
|
|
311
|
+
* darkens its corners in scene light, the curve and the grade turn light into a picture, the veil
|
|
312
|
+
* fades it, and the print's grain sits on what is left. One function, so the two exits of \`main\`
|
|
313
|
+
* cannot finish a frame differently.
|
|
314
|
+
*/
|
|
315
|
+
vec3 finish(vec3 light) {
|
|
316
|
+
/*
|
|
317
|
+
* The eye's adaptation, in scene light and before the lens, as a camera's exposure would be; and
|
|
318
|
+
* each region's share of it, read from the light before either scales it, because that is the
|
|
319
|
+
* light the grid measured.
|
|
320
|
+
*/
|
|
321
|
+
float local = localExposureGain(vUv, light);
|
|
322
|
+
light *= autoExposureGain() * local;
|
|
323
|
+
vec3 lensed = withVignette(light, vUv, vec2(textureSize(uScene, 0)));
|
|
324
|
+
return withGrain(withVeil(applyColourGrade(grade(lensed))), gl_FragCoord.xy);
|
|
325
|
+
}
|
|
326
|
+
|
|
296
327
|
void main() {
|
|
297
328
|
vec3 scene = textureLod(uScene, vUv, 0.0).rgb;
|
|
298
329
|
if (uMotionStrength > 0.0) scene = cameraBlur(scene);
|
|
@@ -330,7 +361,7 @@ void main() {
|
|
|
330
361
|
float edge = smoothstep(inner, 1.0, radius);
|
|
331
362
|
float amount = edge * uStrength;
|
|
332
363
|
if (amount <= 0.0) {
|
|
333
|
-
fragColor = vec4(
|
|
364
|
+
fragColor = vec4(finish(withBloom(scene * ao)), 1.0);
|
|
334
365
|
return;
|
|
335
366
|
}
|
|
336
367
|
|
|
@@ -349,9 +380,6 @@ void main() {
|
|
|
349
380
|
}
|
|
350
381
|
vec3 blurred = sum / 7.0;
|
|
351
382
|
|
|
352
|
-
fragColor = vec4(
|
|
353
|
-
withVeil(applyColourGrade(grade(withBloom(mix(scene, blurred, amount) * ao)))),
|
|
354
|
-
1.0
|
|
355
|
-
);
|
|
383
|
+
fragColor = vec4(finish(withBloom(mix(scene, blurred, amount) * ao)), 1.0);
|
|
356
384
|
}
|
|
357
385
|
`;
|
|
@@ -56,7 +56,9 @@ export const TANGENT_FRAME_GLSL = `mat3 tangentFrame(vec3 n, vec3 worldPos, vec2
|
|
|
56
56
|
* generateTangents does the same per vertex and says why: dropping it is a normal map that
|
|
57
57
|
* shears wherever the geometry curves.
|
|
58
58
|
*/
|
|
59
|
-
vec3
|
|
59
|
+
vec3 along = tangent.xyz - n * dot(n, tangent.xyz);
|
|
60
|
+
float alongSq = dot(along, along);
|
|
61
|
+
vec3 attrT = along * inversesqrt(max(alongSq, 1e-12));
|
|
60
62
|
/*
|
|
61
63
|
* The bitangent's sign, which is the whole reason w is stored. An artist mapping the left and
|
|
62
64
|
* right of a model onto one patch of texture gives one side a mirrored frame, and a bitangent
|
|
@@ -64,7 +66,17 @@ export const TANGENT_FRAME_GLSL = `mat3 tangentFrame(vec3 n, vec3 worldPos, vec2
|
|
|
64
66
|
*/
|
|
65
67
|
vec3 attrB = cross(n, attrT) * tangent.w;
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
/*
|
|
70
|
+
* **And only where there is a frame to keep.** A tangent parallel to its normal, or two that
|
|
71
|
+
* nearly cancel across one triangle at a mirrored seam, leaves nothing after the subtraction,
|
|
72
|
+
* and a normalised nothing is NaN. One such pixel in one probe face was a whole courtyard drawn
|
|
73
|
+
* black: a probe convolves its faces into every direction, and the bounce carries that into
|
|
74
|
+
* every other probe. So where nothing is left, the derived frame stands in. What it gives up is a
|
|
75
|
+
* direction on a pixel that had none; what would make it wrong is a threshold large enough to
|
|
76
|
+
* catch a frame that is merely short, and a unit tangent interpolated is nowhere near 1e-4.
|
|
77
|
+
*/
|
|
78
|
+
bool framed = hasTangents != 0 && alongSq > 1e-8;
|
|
79
|
+
vec3 t = framed ? attrT : derivedT;
|
|
80
|
+
vec3 b = framed ? attrB : derivedB;
|
|
69
81
|
return mat3(t, b, n);
|
|
70
82
|
}`;
|
|
@@ -111,6 +111,44 @@ vec3 clipToNeighbourhood(vec3 history, vec3 lo, vec3 hi) {
|
|
|
111
111
|
return centre + offset / ratio;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/*
|
|
115
|
+
* **The history read through a Catmull-Rom filter, not a bilinear one.**
|
|
116
|
+
*
|
|
117
|
+
* The reprojection lands between texels nearly everywhere, and a bilinear fetch there is a small blur;
|
|
118
|
+
* applied to a picture that is itself last frame's blend, it compounds every frame, so a still camera
|
|
119
|
+
* settled on a picture measurably softer than one frame of it. A courtyard's stone read as out of
|
|
120
|
+
* focus beside the same frame with the resolve off. Catmull-Rom passes a straight ramp through exactly
|
|
121
|
+
* and keeps the edge between two texels an edge, which is what the history is for.
|
|
122
|
+
*
|
|
123
|
+
* Five bilinear fetches rather than sixteen point ones: the middle two weights of each axis share a
|
|
124
|
+
* fetch placed between them, and the four corners, which carry a few hundredths, are left out and
|
|
125
|
+
* the rest renormalised. Its negative lobes can ring past what the frame holds, which the
|
|
126
|
+
* neighbourhood clip after it takes back, and the floor at zero keeps a float target out of the
|
|
127
|
+
* negatives meanwhile.
|
|
128
|
+
*/
|
|
129
|
+
vec3 historyCatmullRom(vec2 uv) {
|
|
130
|
+
vec2 size = 1.0 / uTexel;
|
|
131
|
+
vec2 at = uv * size;
|
|
132
|
+
vec2 base = floor(at - 0.5) + 0.5;
|
|
133
|
+
vec2 f = at - base;
|
|
134
|
+
vec2 w0 = f * (-0.5 + f * (1.0 - 0.5 * f));
|
|
135
|
+
vec2 w1 = 1.0 + f * f * (-2.5 + 1.5 * f);
|
|
136
|
+
vec2 w2 = f * (0.5 + f * (2.0 - 1.5 * f));
|
|
137
|
+
vec2 w3 = f * f * (-0.5 + 0.5 * f);
|
|
138
|
+
vec2 w12 = w1 + w2;
|
|
139
|
+
vec2 at0 = (base - 1.0) * uTexel;
|
|
140
|
+
vec2 at3 = (base + 2.0) * uTexel;
|
|
141
|
+
vec2 at12 = (base + w2 / w12) * uTexel;
|
|
142
|
+
vec3 sum =
|
|
143
|
+
textureLod(uHistory, vec2(at12.x, at0.y), 0.0).rgb * (w12.x * w0.y) +
|
|
144
|
+
textureLod(uHistory, vec2(at0.x, at12.y), 0.0).rgb * (w0.x * w12.y) +
|
|
145
|
+
textureLod(uHistory, at12, 0.0).rgb * (w12.x * w12.y) +
|
|
146
|
+
textureLod(uHistory, vec2(at3.x, at12.y), 0.0).rgb * (w3.x * w12.y) +
|
|
147
|
+
textureLod(uHistory, vec2(at12.x, at3.y), 0.0).rgb * (w12.x * w3.y);
|
|
148
|
+
float weight = w12.x * w0.y + w0.x * w12.y + w12.x * w12.y + w3.x * w12.y + w12.x * w3.y;
|
|
149
|
+
return max(sum / weight, vec3(0.0));
|
|
150
|
+
}
|
|
151
|
+
|
|
114
152
|
void main() {
|
|
115
153
|
vec3 current = textureLod(uScene, vUv, 0.0).rgb;
|
|
116
154
|
|
|
@@ -143,7 +181,7 @@ void main() {
|
|
|
143
181
|
vec3 lo;
|
|
144
182
|
vec3 hi;
|
|
145
183
|
neighbourhood(lo, hi);
|
|
146
|
-
vec3 history =
|
|
184
|
+
vec3 history = historyCatmullRom(wasUv);
|
|
147
185
|
vec3 bounded = clipToNeighbourhood(history, lo, hi);
|
|
148
186
|
|
|
149
187
|
fragColor = vec4(mix(current, bounded, uHistoryBlend), 1.0);
|
|
@@ -27,9 +27,11 @@ import type { SurfaceTextureHandle } from './backend/api.ts';
|
|
|
27
27
|
* enumeration serves the directional cascade and every point-light cubemap.
|
|
28
28
|
*
|
|
29
29
|
* **Every draw may also carry its material, which is what lets one enumeration serve a colour
|
|
30
|
-
* pass as well as a depth one.** A depth pass
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* pass as well as a depth one.** A depth pass reads one thing from it, since 4.4.0: whether the
|
|
31
|
+
* caster is a cutout (`cutoutOf`), so a leaf card casts the leaf rather than the card. Everything
|
|
32
|
+
* else a material says cannot change a depth, which is why this interface carried only a handle, a
|
|
33
|
+
* matrix and a palette for as long as depth was the only thing replaying it — and why, until then,
|
|
34
|
+
* every alpha-cut surface cast its whole quad. What that cost was measured from outside. A
|
|
33
35
|
* consumer wanting the scene from a second viewpoint — a mirror, a probe face — had one option,
|
|
34
36
|
* which was to run its whole draw path again; the first attempt to avoid it replayed this sink
|
|
35
37
|
* into the mirror and got **every car unpainted**, because the list carries no material. So the
|
package/src/render/shadowMap.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* "zero textures" rule is about the art direction (no image assets in the
|
|
10
10
|
* payload), and nothing here adds a byte to it.
|
|
11
11
|
*/
|
|
12
|
+
import { SHADOW_DEPTH_CLEAR } from './depthConvention.ts';
|
|
12
13
|
import { DEFAULT_RENDER_QUALITY } from './renderQuality.ts';
|
|
13
14
|
|
|
14
15
|
export class ShadowMap {
|
|
@@ -54,6 +55,19 @@ export class ShadowMap {
|
|
|
54
55
|
throw new Error(`ShadowMap: framebuffer incomplete (0x${status.toString(16)})`);
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/*
|
|
59
|
+
* **Cleared once, here, to the far plane, because nothing else says what the texture holds.**
|
|
60
|
+
* WebGL zeroes it, and a zero compared with `LEQUAL` is an occluder at the light itself, so
|
|
61
|
+
* a layer no pass has drawn put the whole world in shade. ANGLE on Vulkan hands the same
|
|
62
|
+
* texture back reading 1, which is lit, and that is what every capture here ran on: a scene
|
|
63
|
+
* with a second depth layer it never peeled lost its sun only under ANGLE's GL backend, the
|
|
64
|
+
* one Chrome on Linux runs by default. The frame's clear value is borrowed and put back.
|
|
65
|
+
*/
|
|
66
|
+
const frameClear = gl.getParameter(gl.DEPTH_CLEAR_VALUE) as number;
|
|
67
|
+
gl.clearDepth(SHADOW_DEPTH_CLEAR);
|
|
68
|
+
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
69
|
+
gl.clearDepth(frameClear);
|
|
70
|
+
|
|
57
71
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
58
72
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
59
73
|
}
|
|
@@ -60,6 +60,11 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
|
|
|
60
60
|
vScale?: number;
|
|
61
61
|
/** Alpha below which a fragment is discarded. See `uAlbedoCutout`. */
|
|
62
62
|
cutout?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Whether both faces are seen: glTF's `doubleSided`, what a curtain or a leaf card is. Drawn
|
|
65
|
+
* without culling, and a back face is lit as its front, with the normal turned to the viewer.
|
|
66
|
+
*/
|
|
67
|
+
doubleSided?: boolean;
|
|
63
68
|
/**
|
|
64
69
|
* How hard the normal map turns the shading normal.
|
|
65
70
|
*
|