@driftengine/core 4.4.2 → 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 +46 -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 +79 -8
- package/dist/render/backend/webgl2/renderer.js +360 -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 +2 -12
- package/dist/render/backend/webgpu/renderer.d.ts +171 -54
- package/dist/render/backend/webgpu/renderer.js +944 -390
- 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/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/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/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/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 +7 -1
- package/dist/render/sceneTarget.js +58 -12
- 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/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/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/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 +401 -108
- 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/renderer.ts +1108 -421
- 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/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/pointShadowMap.ts +41 -0
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +12 -0
- package/src/render/sceneTarget.ts +58 -11
- 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/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/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/translucentQueue.ts +20 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The second pipeline's sunlight through glass, as a reference: `glassTintAt` over the glass depth
|
|
3
|
+
* and tint maps the GPU-driven pass draws from the sun, for `shade.wgsl.ts`'s lookup to be read
|
|
4
|
+
* against.
|
|
5
|
+
*
|
|
6
|
+
* **The forward path's rule, not a second one** (`glassShadow.ts`, and `sunGlassLookup` in
|
|
7
|
+
* `flat/directionalShadow.ts`): the outline from taps at the base filter radius that count a pane
|
|
8
|
+
* only where the receiver is behind it, and the colour from spread taps read coarse, unmixed from
|
|
9
|
+
* the ground around the pane by its clarity and weighed by its share. Frost widens only the
|
|
10
|
+
* colour's taps, so the light a frosted pane passes is the light a clear one does.
|
|
11
|
+
*
|
|
12
|
+
* **What differs is where two numbers come from.** A compute invocation has no derivatives, so the
|
|
13
|
+
* map's scale in metres is the caller's (`uvPerMetre`, which an orthographic light matrix states
|
|
14
|
+
* outright); and a coarse level here is a box average over the spread's footprint, which is what a
|
|
15
|
+
* mip level approximates on the device. `scripts/gpu-parity.mjs` holds the device's per-tap
|
|
16
|
+
* arithmetic to `glassShadow.ts`; the sampling is what the glass page measures.
|
|
17
|
+
*/
|
|
18
|
+
import { frostRadius, spreadTint, unmixTint, type GlassTexel } from '../glassShadow.ts';
|
|
19
|
+
import { SHADOW_DEPTH_BIAS, SHADOW_PCF_OFFSETS, SHADOW_TEXEL_SCALE } from './shadow.ts';
|
|
20
|
+
|
|
21
|
+
/** The two maps the sun's glass pass draws, square, row-major from v = 0. */
|
|
22
|
+
export interface SunGlassMaps {
|
|
23
|
+
readonly size: number;
|
|
24
|
+
/** The nearest pane's depth a texel, 1 where no pane was drawn. */
|
|
25
|
+
readonly glassDepth: Float32Array;
|
|
26
|
+
/** What the panes on a texel's ray let through, and their clarity: four floats a texel. */
|
|
27
|
+
readonly tint: Float32Array;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SunGlassSettings {
|
|
31
|
+
/** Metres the light's depth range spans, which turns a depth difference into a distance. */
|
|
32
|
+
readonly depthSpan: number;
|
|
33
|
+
/** Map units a metre across the light, which turns a spread in metres into one on the map. */
|
|
34
|
+
readonly uvPerMetre: number;
|
|
35
|
+
readonly taps: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const centre: GlassTexel = { r: 1, g: 1, b: 1, clarity: 1 };
|
|
39
|
+
const coarse: GlassTexel = { r: 1, g: 1, b: 1, clarity: 1 };
|
|
40
|
+
const pane: GlassTexel = { r: 1, g: 1, b: 1, clarity: 1 };
|
|
41
|
+
const found: GlassTexel = { r: 0, g: 0, b: 0, clarity: 0 };
|
|
42
|
+
|
|
43
|
+
function index(maps: SunGlassMaps, u: number, v: number): number {
|
|
44
|
+
const x = Math.min(maps.size - 1, Math.max(0, Math.floor(u * maps.size)));
|
|
45
|
+
const y = Math.min(maps.size - 1, Math.max(0, Math.floor(v * maps.size)));
|
|
46
|
+
return y * maps.size + x;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The average of every texel within `radius` of (u, v): a mip level's footprint, in the large. */
|
|
50
|
+
function coarseAt(maps: SunGlassMaps, u: number, v: number, radius: number, out: GlassTexel): void {
|
|
51
|
+
const reach = radius * maps.size;
|
|
52
|
+
const x0 = Math.max(0, Math.floor(u * maps.size - reach));
|
|
53
|
+
const x1 = Math.min(maps.size - 1, Math.floor(u * maps.size + reach));
|
|
54
|
+
const y0 = Math.max(0, Math.floor(v * maps.size - reach));
|
|
55
|
+
const y1 = Math.min(maps.size - 1, Math.floor(v * maps.size + reach));
|
|
56
|
+
out.r = 0;
|
|
57
|
+
out.g = 0;
|
|
58
|
+
out.b = 0;
|
|
59
|
+
out.clarity = 0;
|
|
60
|
+
let n = 0;
|
|
61
|
+
for (let y = y0; y <= y1; y++) {
|
|
62
|
+
for (let x = x0; x <= x1; x++) {
|
|
63
|
+
const at = (y * maps.size + x) * 4;
|
|
64
|
+
out.r += maps.tint[at] as number;
|
|
65
|
+
out.g += maps.tint[at + 1] as number;
|
|
66
|
+
out.b += maps.tint[at + 2] as number;
|
|
67
|
+
out.clarity += maps.tint[at + 3] as number;
|
|
68
|
+
n++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
out.r /= n;
|
|
72
|
+
out.g /= n;
|
|
73
|
+
out.b /= n;
|
|
74
|
+
out.clarity /= n;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** What the sun keeps through glass at a receiver at map position (u, v) and light depth `depth`. */
|
|
78
|
+
export function glassTintAt(
|
|
79
|
+
u: number,
|
|
80
|
+
v: number,
|
|
81
|
+
depth: number,
|
|
82
|
+
maps: SunGlassMaps,
|
|
83
|
+
settings: SunGlassSettings,
|
|
84
|
+
out: GlassTexel,
|
|
85
|
+
): GlassTexel {
|
|
86
|
+
out.r = 1;
|
|
87
|
+
out.g = 1;
|
|
88
|
+
out.b = 1;
|
|
89
|
+
out.clarity = 1;
|
|
90
|
+
if (u < 0 || u > 1 || v < 0 || v > 1) return out;
|
|
91
|
+
const texel = SHADOW_TEXEL_SCALE / maps.size;
|
|
92
|
+
const bias = SHADOW_DEPTH_BIAS / settings.depthSpan;
|
|
93
|
+
const taps = Math.min(settings.taps, SHADOW_PCF_OFFSETS.length);
|
|
94
|
+
|
|
95
|
+
const at = index(maps, u, v);
|
|
96
|
+
centre.clarity = maps.tint[at * 4 + 3] as number;
|
|
97
|
+
const behind = Math.max(depth - (maps.glassDepth[at] as number), 0) * settings.depthSpan;
|
|
98
|
+
const radius = frostRadius(centre.clarity, behind * settings.uvPerMetre, texel);
|
|
99
|
+
|
|
100
|
+
let cover = 0;
|
|
101
|
+
found.r = 0;
|
|
102
|
+
found.g = 0;
|
|
103
|
+
found.b = 0;
|
|
104
|
+
let shares = 0;
|
|
105
|
+
for (let i = 0; i < taps; i++) {
|
|
106
|
+
const offset = SHADOW_PCF_OFFSETS[i] as readonly [number, number];
|
|
107
|
+
const nearest = maps.glassDepth[index(maps, u + offset[0] * texel, v + offset[1] * texel)];
|
|
108
|
+
if (depth > (nearest as number) + bias) cover++;
|
|
109
|
+
coarseAt(maps, u + offset[0] * radius, v + offset[1] * radius, radius, coarse);
|
|
110
|
+
const share = unmixTint(coarse, centre.clarity, pane);
|
|
111
|
+
found.r += pane.r * share;
|
|
112
|
+
found.g += pane.g * share;
|
|
113
|
+
found.b += pane.b * share;
|
|
114
|
+
shares += share;
|
|
115
|
+
}
|
|
116
|
+
return spreadTint(cover / Math.max(taps, 1), found, shares, out);
|
|
117
|
+
}
|
|
@@ -38,13 +38,14 @@ export const MAX_POINT_LIGHTS = 16;
|
|
|
38
38
|
export const LIVE_POINT_SHADOW_MAPS = 2;
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Texture units the
|
|
41
|
+
* Texture units the sun's shadow binds: one, for its static, moving and peeled layers.
|
|
42
42
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
* **It was three**, a `sampler2D` per map, until 4.5.0 folded them into one `sampler2DArray`
|
|
44
|
+
* (`shadowMap.ts`) the way the lamps' twelve cubemaps became one — which is where the two tints
|
|
45
|
+
* glass casts with found their units. Named because the shading pass's total has to be compared
|
|
46
|
+
* against what a device offers; see the guard in `Renderer`'s constructor.
|
|
46
47
|
*/
|
|
47
|
-
export const DIRECTIONAL_SHADOW_UNITS =
|
|
48
|
+
export const DIRECTIONAL_SHADOW_UNITS = 1;
|
|
48
49
|
|
|
49
50
|
/**
|
|
50
51
|
* Texture units every point light's shadow binds: one, for all of them.
|
|
@@ -230,6 +231,19 @@ export const REFRACT_SCENE_TEXTURE_UNIT = COOKIE_ATLAS_TEXTURE_UNIT + 1;
|
|
|
230
231
|
export const DRIFT_LIGHT_INDEX_TEXTURE_UNIT = REFRACT_SCENE_TEXTURE_UNIT + 1;
|
|
231
232
|
export const DRIFT_LIGHT_ATLAS_TEXTURE_UNIT = DRIFT_LIGHT_INDEX_TEXTURE_UNIT + 1;
|
|
232
233
|
|
|
234
|
+
/**
|
|
235
|
+
* What the sun keeps through glass (`sunGlassTint.ts`), the lit program's fourteenth fragment
|
|
236
|
+
* sampler: the unit the sun's three maps gave back when they became one array.
|
|
237
|
+
*/
|
|
238
|
+
export const SUN_GLASS_TINT_TEXTURE_UNIT = DRIFT_LIGHT_ATLAS_TEXTURE_UNIT + 1;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* What each lamp keeps through glass (`pointGlassTint.ts`), the lit program's fifteenth fragment
|
|
242
|
+
* sampler and the last one that leaves a sixteenth spare. Unit sixteen of the combined pool, which
|
|
243
|
+
* WebGL2 guarantees thirty-two of; what a stage may declare is the sixteen, and this is fifteen.
|
|
244
|
+
*/
|
|
245
|
+
export const POINT_GLASS_TINT_TEXTURE_UNIT = SUN_GLASS_TINT_TEXTURE_UNIT + 1;
|
|
246
|
+
|
|
233
247
|
/**
|
|
234
248
|
* A cookie's tile, in texels a side.
|
|
235
249
|
*
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { mat3 } from 'gl-matrix';
|
|
2
|
+
|
|
3
|
+
import { createEmptyTexture2D } from './emptyTexture.ts';
|
|
4
|
+
import { pointShadowFaceRotation } from './pointShadowImage.ts';
|
|
5
|
+
import { compileProgram, uniformLocations } from './shader.ts';
|
|
6
|
+
import {
|
|
7
|
+
OCTAHEDRAL_RESOLVE_TINT_FRAG,
|
|
8
|
+
OCTAHEDRAL_RESOLVE_VERT,
|
|
9
|
+
} from './shaders/octahedralResolve.ts';
|
|
10
|
+
|
|
11
|
+
/** Filled per resolved face, at module scope, because a bake runs inside the frame's budget. */
|
|
12
|
+
const faceRotation = mat3.create();
|
|
13
|
+
const CLEAR = new Float32Array([1, 1, 1, 1]);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* What a lamp's light keeps through glass: an RGBA8 array of one layer a light, parallel to the
|
|
17
|
+
* point shadow array and indexed by the light, not by the interleaved depth layer. RGB is the
|
|
18
|
+
* product of every pane's `transmission × (1 − F) × tint`, A the product of their clarities
|
|
19
|
+
* (`glassShadow.ts`).
|
|
20
|
+
*
|
|
21
|
+
* **Drawn a face at a time, as the depth is**: into a face-sized colour scratch cleared to white,
|
|
22
|
+
* with a multiplying blend and no depth test, then resolved into the face's region of the layer by
|
|
23
|
+
* `OCTAHEDRAL_RESOLVE_TINT_FRAG`. One scratch for the whole engine, for the reason
|
|
24
|
+
* `PointShadowArray` gives for its own.
|
|
25
|
+
*
|
|
26
|
+
* **Which faces hold glass is remembered, six bits a layer**, so a face with no glass and none to
|
|
27
|
+
* forget costs nothing, and a face that inherited a previous owner's glass is cleared. **The chain
|
|
28
|
+
* frost reads is rebuilt once a round**, for the layers the round touched, through a 2D staging
|
|
29
|
+
* chain: `generateMipmap` on an array rebuilds every layer, which is the whole pool's texels for one
|
|
30
|
+
* lamp's glass. What that costs is one layer of memory with its chain — 5.6 MB at 1024, 1.4 MB at
|
|
31
|
+
* `'half'` — and a copy out and back per touched layer.
|
|
32
|
+
*/
|
|
33
|
+
export class PointGlassTint {
|
|
34
|
+
readonly texture: WebGLTexture;
|
|
35
|
+
readonly edge: number;
|
|
36
|
+
|
|
37
|
+
private readonly layers: number;
|
|
38
|
+
private readonly levels: number;
|
|
39
|
+
private readonly faceSize: number;
|
|
40
|
+
private readonly scratch: WebGLTexture;
|
|
41
|
+
private readonly scratchFramebuffer: WebGLFramebuffer;
|
|
42
|
+
private readonly staging: WebGLTexture;
|
|
43
|
+
private readonly framebuffer: WebGLFramebuffer;
|
|
44
|
+
private readonly readFramebuffer: WebGLFramebuffer;
|
|
45
|
+
/** Parked on the resolve's unit while the scratch is a render target; see `beginFace`. */
|
|
46
|
+
private readonly parked: WebGLTexture;
|
|
47
|
+
private readonly program: WebGLProgram;
|
|
48
|
+
private readonly uniforms: Record<string, WebGLUniformLocation>;
|
|
49
|
+
private readonly vao: WebGLVertexArrayObject;
|
|
50
|
+
/** Six bits a layer: which faces' regions hold glass. */
|
|
51
|
+
private readonly heldFaces: Uint8Array;
|
|
52
|
+
/** Layers whose level 0 changed since the chain was last rebuilt. */
|
|
53
|
+
private readonly dirty: Uint8Array;
|
|
54
|
+
|
|
55
|
+
constructor(gl: WebGL2RenderingContext, faceSize: number, edge: number, layers: number) {
|
|
56
|
+
this.edge = edge;
|
|
57
|
+
this.layers = layers;
|
|
58
|
+
this.faceSize = faceSize;
|
|
59
|
+
this.levels = Math.floor(Math.log2(Math.max(1, edge))) + 1;
|
|
60
|
+
this.heldFaces = new Uint8Array(layers);
|
|
61
|
+
this.dirty = new Uint8Array(layers);
|
|
62
|
+
|
|
63
|
+
this.texture = colourTexture(gl, gl.TEXTURE_2D_ARRAY, this.levels, edge, layers);
|
|
64
|
+
this.scratch = colourTexture(gl, gl.TEXTURE_2D, 1, faceSize, 1);
|
|
65
|
+
this.staging = colourTexture(gl, gl.TEXTURE_2D, this.levels, edge, 1);
|
|
66
|
+
this.parked = createEmptyTexture2D(gl);
|
|
67
|
+
this.scratchFramebuffer = framebufferOf(gl);
|
|
68
|
+
this.framebuffer = framebufferOf(gl);
|
|
69
|
+
this.readFramebuffer = framebufferOf(gl);
|
|
70
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.scratchFramebuffer);
|
|
71
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.scratch, 0);
|
|
72
|
+
|
|
73
|
+
this.program = compileProgram(
|
|
74
|
+
gl,
|
|
75
|
+
OCTAHEDRAL_RESOLVE_VERT,
|
|
76
|
+
OCTAHEDRAL_RESOLVE_TINT_FRAG,
|
|
77
|
+
'octahedralResolveTint',
|
|
78
|
+
);
|
|
79
|
+
this.uniforms = uniformLocations(gl, this.program, 'octahedralResolveTint');
|
|
80
|
+
const vao = gl.createVertexArray();
|
|
81
|
+
if (vao === null) throw new Error('PointGlassTint: createVertexArray failed');
|
|
82
|
+
this.vao = vao;
|
|
83
|
+
|
|
84
|
+
/* Every layer born clear: a light no pane has reached lets everything through. */
|
|
85
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
86
|
+
for (let layer = 0; layer < layers; layer++) {
|
|
87
|
+
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, layer);
|
|
88
|
+
gl.clearBufferfv(gl.COLOR, 0, CLEAR);
|
|
89
|
+
}
|
|
90
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
91
|
+
/* Once, for every layer: white all the way down. */
|
|
92
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
|
|
93
|
+
gl.generateMipmap(gl.TEXTURE_2D_ARRAY);
|
|
94
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Whether `face` of `layer` holds glass: a face that does must be cleared before it is reused. */
|
|
98
|
+
held(layer: number, face: number): boolean {
|
|
99
|
+
return (((this.heldFaces[layer] ?? 0) >> face) & 1) === 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Bind the colour scratch, clear it to white, and set the multiplying state.
|
|
104
|
+
*
|
|
105
|
+
* The previous resolve left the scratch bound for sampling on unit 0, and it is about to be the
|
|
106
|
+
* target: parked first, for the reason `PointShadowArray.beginFace` gives.
|
|
107
|
+
*/
|
|
108
|
+
beginFace(gl: WebGL2RenderingContext): void {
|
|
109
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
110
|
+
gl.bindTexture(gl.TEXTURE_2D, this.parked);
|
|
111
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.scratchFramebuffer);
|
|
112
|
+
gl.viewport(0, 0, this.faceSize, this.faceSize);
|
|
113
|
+
gl.clearBufferfv(gl.COLOR, 0, CLEAR);
|
|
114
|
+
gl.disable(gl.DEPTH_TEST);
|
|
115
|
+
gl.depthMask(false);
|
|
116
|
+
gl.enable(gl.BLEND);
|
|
117
|
+
gl.blendEquation(gl.FUNC_ADD);
|
|
118
|
+
gl.blendFuncSeparate(gl.DST_COLOR, gl.ZERO, gl.DST_ALPHA, gl.ZERO);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Copy the scratch into `face`'s region of `layer`, and remember whether it held glass.
|
|
123
|
+
*
|
|
124
|
+
* Leaves depth testing and writing on, which is what the next face's casters expect: the depth
|
|
125
|
+
* resolve that runs before them turns both on, and this one ran after it and turned them off.
|
|
126
|
+
*/
|
|
127
|
+
resolveFace(gl: WebGL2RenderingContext, layer: number, face: number, drawn: boolean): void {
|
|
128
|
+
gl.disable(gl.BLEND);
|
|
129
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
130
|
+
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, layer);
|
|
131
|
+
gl.viewport(0, 0, this.edge, this.edge);
|
|
132
|
+
gl.useProgram(this.program);
|
|
133
|
+
gl.bindVertexArray(this.vao);
|
|
134
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
135
|
+
gl.bindTexture(gl.TEXTURE_2D, this.scratch);
|
|
136
|
+
gl.uniform1i(this.uniforms['uFace'] ?? null, 0);
|
|
137
|
+
gl.uniform1i(this.uniforms['uFaceIndex'] ?? null, face);
|
|
138
|
+
gl.uniformMatrix3fv(
|
|
139
|
+
this.uniforms['uFaceRotation'] ?? null,
|
|
140
|
+
false,
|
|
141
|
+
pointShadowFaceRotation(face, faceRotation),
|
|
142
|
+
);
|
|
143
|
+
gl.uniform1f(this.uniforms['uEdge'] ?? null, this.edge);
|
|
144
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
145
|
+
gl.bindVertexArray(null);
|
|
146
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
147
|
+
gl.enable(gl.DEPTH_TEST);
|
|
148
|
+
gl.depthMask(true);
|
|
149
|
+
|
|
150
|
+
const bit = 1 << face;
|
|
151
|
+
const was = this.heldFaces[layer] ?? 0;
|
|
152
|
+
this.heldFaces[layer] = drawn ? was | bit : was & ~bit;
|
|
153
|
+
this.dirty[layer] = 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Rebuild the chain of every layer touched since the last call, one layer at a time. */
|
|
157
|
+
fillMips(gl: WebGL2RenderingContext): void {
|
|
158
|
+
for (let layer = 0; layer < this.layers; layer++) {
|
|
159
|
+
if (this.dirty[layer] !== 1) continue;
|
|
160
|
+
this.dirty[layer] = 0;
|
|
161
|
+
/* Level 0 out into the staging chain, which mipmaps as one 2D texture. */
|
|
162
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.readFramebuffer);
|
|
163
|
+
gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, layer);
|
|
164
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.framebuffer);
|
|
165
|
+
gl.framebufferTexture2D(
|
|
166
|
+
gl.DRAW_FRAMEBUFFER,
|
|
167
|
+
gl.COLOR_ATTACHMENT0,
|
|
168
|
+
gl.TEXTURE_2D,
|
|
169
|
+
this.staging,
|
|
170
|
+
0,
|
|
171
|
+
);
|
|
172
|
+
blit(gl, this.edge, this.edge);
|
|
173
|
+
gl.bindTexture(gl.TEXTURE_2D, this.staging);
|
|
174
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
175
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
176
|
+
/* And every level after it back into the layer. */
|
|
177
|
+
for (let level = 1; level < this.levels; level++) {
|
|
178
|
+
const size = Math.max(1, this.edge >> level);
|
|
179
|
+
gl.framebufferTexture2D(
|
|
180
|
+
gl.READ_FRAMEBUFFER,
|
|
181
|
+
gl.COLOR_ATTACHMENT0,
|
|
182
|
+
gl.TEXTURE_2D,
|
|
183
|
+
this.staging,
|
|
184
|
+
level,
|
|
185
|
+
);
|
|
186
|
+
gl.framebufferTextureLayer(
|
|
187
|
+
gl.DRAW_FRAMEBUFFER,
|
|
188
|
+
gl.COLOR_ATTACHMENT0,
|
|
189
|
+
this.texture,
|
|
190
|
+
level,
|
|
191
|
+
layer,
|
|
192
|
+
);
|
|
193
|
+
blit(gl, size, size);
|
|
194
|
+
}
|
|
195
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
|
|
196
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
dispose(gl: WebGL2RenderingContext): void {
|
|
201
|
+
gl.deleteFramebuffer(this.scratchFramebuffer);
|
|
202
|
+
gl.deleteFramebuffer(this.framebuffer);
|
|
203
|
+
gl.deleteFramebuffer(this.readFramebuffer);
|
|
204
|
+
gl.deleteTexture(this.texture);
|
|
205
|
+
gl.deleteTexture(this.scratch);
|
|
206
|
+
gl.deleteTexture(this.staging);
|
|
207
|
+
gl.deleteTexture(this.parked);
|
|
208
|
+
gl.deleteProgram(this.program);
|
|
209
|
+
gl.deleteVertexArray(this.vao);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** A colour texture with `levels` levels: nearest for a single level, trilinear for a chain. */
|
|
214
|
+
function colourTexture(
|
|
215
|
+
gl: WebGL2RenderingContext,
|
|
216
|
+
target: number,
|
|
217
|
+
levels: number,
|
|
218
|
+
edge: number,
|
|
219
|
+
layers: number,
|
|
220
|
+
): WebGLTexture {
|
|
221
|
+
const texture = gl.createTexture();
|
|
222
|
+
if (texture === null) throw new Error('PointGlassTint: createTexture failed');
|
|
223
|
+
gl.bindTexture(target, texture);
|
|
224
|
+
if (target === gl.TEXTURE_2D_ARRAY) {
|
|
225
|
+
gl.texStorage3D(target, levels, gl.RGBA8, edge, edge, layers);
|
|
226
|
+
} else {
|
|
227
|
+
gl.texStorage2D(target, levels, gl.RGBA8, edge, edge);
|
|
228
|
+
}
|
|
229
|
+
gl.texParameteri(
|
|
230
|
+
target,
|
|
231
|
+
gl.TEXTURE_MIN_FILTER,
|
|
232
|
+
levels > 1 ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST,
|
|
233
|
+
);
|
|
234
|
+
gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, levels > 1 ? gl.LINEAR : gl.NEAREST);
|
|
235
|
+
gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
236
|
+
gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
237
|
+
gl.bindTexture(target, null);
|
|
238
|
+
return texture;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function framebufferOf(gl: WebGL2RenderingContext): WebGLFramebuffer {
|
|
242
|
+
const framebuffer = gl.createFramebuffer();
|
|
243
|
+
if (framebuffer === null) throw new Error('PointGlassTint: createFramebuffer failed');
|
|
244
|
+
return framebuffer;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** The bound read framebuffer's colour into the bound draw framebuffer's, texel for texel. */
|
|
248
|
+
function blit(gl: WebGL2RenderingContext, width: number, height: number): void {
|
|
249
|
+
gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
|
|
250
|
+
}
|
|
@@ -2,6 +2,7 @@ import { mat3 } from 'gl-matrix';
|
|
|
2
2
|
|
|
3
3
|
import { createEmptyTexture2D } from './emptyTexture.ts';
|
|
4
4
|
import { LIVE_POINT_SHADOW_MAPS, POINT_SHADOW_POOL } from './lightBudget.ts';
|
|
5
|
+
import { PointGlassTint } from './pointGlassTint.ts';
|
|
5
6
|
import { pointShadowFaceRotation } from './pointShadowImage.ts';
|
|
6
7
|
import { compileProgram, uniformLocations } from './shader.ts';
|
|
7
8
|
import { OCTAHEDRAL_RESOLVE_FRAG, OCTAHEDRAL_RESOLVE_VERT } from './shaders/octahedralResolve.ts';
|
|
@@ -34,6 +35,37 @@ export interface PointShadowTarget {
|
|
|
34
35
|
far: number,
|
|
35
36
|
near: number,
|
|
36
37
|
): void;
|
|
38
|
+
/** Whether the array has grown its glass layers: `growForGlass`. */
|
|
39
|
+
readonly hasGlass: boolean;
|
|
40
|
+
/** Whether `face` of `layer` holds glass, which a face that offers none must clear. */
|
|
41
|
+
heldGlass(layer: number, face: number): boolean;
|
|
42
|
+
/** The depth scratch again, cleared, for the face's glass. */
|
|
43
|
+
beginGlassFace(gl: WebGL2RenderingContext): void;
|
|
44
|
+
resolveGlassFace(
|
|
45
|
+
gl: WebGL2RenderingContext,
|
|
46
|
+
layer: number,
|
|
47
|
+
face: number,
|
|
48
|
+
far: number,
|
|
49
|
+
near: number,
|
|
50
|
+
): void;
|
|
51
|
+
/** The colour scratch, cleared to white, for what the face's glass lets through. */
|
|
52
|
+
beginTintFace(gl: WebGL2RenderingContext): void;
|
|
53
|
+
resolveTintFace(gl: WebGL2RenderingContext, layer: number, face: number, drawn: boolean): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Every published layer as the array stores it: `layer × stride`, and −1 left as −1.
|
|
58
|
+
*
|
|
59
|
+
* **Applied to the resolved arrays right after each resolve**, so every consumer of an uploaded
|
|
60
|
+
* index reads the physical layer: once glass arrives, light L's depth is at 2L and its glass at
|
|
61
|
+
* 2L + 1 (`PointShadowArray.growForGlass`). In place, because the resolve rewrites these every time.
|
|
62
|
+
*/
|
|
63
|
+
export function strideLayers(layers: Int32Array, stride: number): void {
|
|
64
|
+
if (stride === 1) return;
|
|
65
|
+
for (let i = 0; i < layers.length; i++) {
|
|
66
|
+
const layer = layers[i] as number;
|
|
67
|
+
if (layer >= 0) layers[i] = layer * stride;
|
|
68
|
+
}
|
|
37
69
|
}
|
|
38
70
|
|
|
39
71
|
/**
|
|
@@ -53,12 +85,16 @@ export interface PointShadowTarget {
|
|
|
53
85
|
* never the same object.
|
|
54
86
|
*/
|
|
55
87
|
export class PointShadowArray implements PointShadowTarget {
|
|
56
|
-
readonly texture: WebGLTexture;
|
|
57
88
|
/**
|
|
58
|
-
* How many
|
|
59
|
-
* for each rectangle a world said would cast. See the constructor's `extraLayers`.
|
|
89
|
+
* How many lights this array holds: the point pool, the two live transition maps, and two more
|
|
90
|
+
* for each rectangle a world said would cast. See the constructor's `extraLayers`. Counted in
|
|
91
|
+
* lights, so it does not move when glass doubles the storage under it.
|
|
60
92
|
*/
|
|
61
93
|
readonly layers: number;
|
|
94
|
+
/** Replaced once, when glass first casts: see `growForGlass`. */
|
|
95
|
+
private current: WebGLTexture;
|
|
96
|
+
/** What the lights' glass lets through, made with the glass layers. */
|
|
97
|
+
private tint: PointGlassTint | null = null;
|
|
62
98
|
|
|
63
99
|
private readonly framebuffer: WebGLFramebuffer;
|
|
64
100
|
private readonly scratchTexture: WebGLTexture;
|
|
@@ -96,23 +132,8 @@ export class PointShadowArray implements PointShadowTarget {
|
|
|
96
132
|
this.layers =
|
|
97
133
|
Math.min(lightCount, POINT_SHADOW_POOL) + LIVE_POINT_SHADOW_MAPS + Math.max(0, extraLayers);
|
|
98
134
|
|
|
99
|
-
const texture = gl.
|
|
100
|
-
|
|
101
|
-
this.texture = texture;
|
|
102
|
-
gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
|
|
103
|
-
gl.texStorage3D(
|
|
104
|
-
gl.TEXTURE_2D_ARRAY,
|
|
105
|
-
1,
|
|
106
|
-
gl.DEPTH_COMPONENT24,
|
|
107
|
-
OCTAHEDRAL_EDGE,
|
|
108
|
-
OCTAHEDRAL_EDGE,
|
|
109
|
-
this.layers,
|
|
110
|
-
);
|
|
111
|
-
/* One storage level and no filtering, so textureLod(..., 0.0) is the only fetch there is. */
|
|
112
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
113
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
114
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
115
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
135
|
+
const texture = depthArray(gl, this.layers);
|
|
136
|
+
this.current = texture;
|
|
116
137
|
|
|
117
138
|
const scratchTexture = gl.createTexture();
|
|
118
139
|
if (scratchTexture === null) throw new Error('PointShadowArray: createTexture failed');
|
|
@@ -183,6 +204,117 @@ export class PointShadowArray implements PointShadowTarget {
|
|
|
183
204
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
|
|
184
205
|
}
|
|
185
206
|
|
|
207
|
+
/** The array texture the lit pass samples. Replaced once, when glass first casts. */
|
|
208
|
+
get texture(): WebGLTexture {
|
|
209
|
+
return this.current;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
get hasGlass(): boolean {
|
|
213
|
+
return this.tint !== null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Physical layers a light: 1, or 2 once glass has interleaved the array. */
|
|
217
|
+
get stride(): number {
|
|
218
|
+
return this.tint === null ? 1 : 2;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** The glass tint, one layer a light, once glass has cast; the lit pass samples it. */
|
|
222
|
+
get tintTexture(): WebGLTexture | null {
|
|
223
|
+
return this.tint?.texture ?? null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Interleave the array for glass: light L's depth moves to 2L and its glass takes 2L + 1, and
|
|
228
|
+
* the tint is made. Returns whether it grew — once, on the first face whose casters held glass.
|
|
229
|
+
*
|
|
230
|
+
* **Interleaved rather than stacked above the opaque half** because a shader cannot ask an array
|
|
231
|
+
* its layer count under naga, so it cannot find `L + N`; `layer + 1` it can. **Every map already
|
|
232
|
+
* baked is carried across** by a depth blit, so nothing goes dark, and **no map needs baking
|
|
233
|
+
* again**: this runs on the first face that saw glass, so no map baked before it saw any, and a
|
|
234
|
+
* glass layer born at the far plane with a white tint is exactly what its bake would draw. What
|
|
235
|
+
* it costs is the doubled depth (4.19 MB a light at 1024) and the tint (5.6 MB a light with its
|
|
236
|
+
* chain, 1.4 MB at `'half'`), from then on.
|
|
237
|
+
*/
|
|
238
|
+
growForGlass(gl: WebGL2RenderingContext, tintEdge: number): boolean {
|
|
239
|
+
if (this.tint !== null) return false;
|
|
240
|
+
const old = this.current;
|
|
241
|
+
const texture = depthArray(gl, this.layers * 2);
|
|
242
|
+
/* Off the unit it was made on, as the constructor leaves its own: see `beginFace`. */
|
|
243
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
|
|
244
|
+
const read = gl.createFramebuffer();
|
|
245
|
+
if (read === null) throw new Error('PointShadowArray: createFramebuffer failed');
|
|
246
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, read);
|
|
247
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.framebuffer);
|
|
248
|
+
for (let layer = 0; layer < this.layers; layer++) {
|
|
249
|
+
gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, old, 0, layer);
|
|
250
|
+
gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, texture, 0, 2 * layer);
|
|
251
|
+
gl.blitFramebuffer(
|
|
252
|
+
0,
|
|
253
|
+
0,
|
|
254
|
+
OCTAHEDRAL_EDGE,
|
|
255
|
+
OCTAHEDRAL_EDGE,
|
|
256
|
+
0,
|
|
257
|
+
0,
|
|
258
|
+
OCTAHEDRAL_EDGE,
|
|
259
|
+
OCTAHEDRAL_EDGE,
|
|
260
|
+
gl.DEPTH_BUFFER_BIT,
|
|
261
|
+
gl.NEAREST,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
/* Nothing is nearer a lamp than a pane that has not been drawn. */
|
|
265
|
+
for (let layer = 0; layer < this.layers; layer++) {
|
|
266
|
+
gl.framebufferTextureLayer(
|
|
267
|
+
gl.DRAW_FRAMEBUFFER,
|
|
268
|
+
gl.DEPTH_ATTACHMENT,
|
|
269
|
+
texture,
|
|
270
|
+
0,
|
|
271
|
+
2 * layer + 1,
|
|
272
|
+
);
|
|
273
|
+
gl.clearBufferfv(gl.DEPTH, 0, FAR_PLANE);
|
|
274
|
+
}
|
|
275
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
|
|
276
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
277
|
+
gl.deleteFramebuffer(read);
|
|
278
|
+
gl.deleteTexture(old);
|
|
279
|
+
this.current = texture;
|
|
280
|
+
this.tint = new PointGlassTint(gl, this.faceSize, tintEdge, this.layers);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
heldGlass(layer: number, face: number): boolean {
|
|
285
|
+
return this.tint?.held(layer, face) ?? false;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** The depth scratch, cleared, for the face's glass: `beginFace` exactly. */
|
|
289
|
+
beginGlassFace(gl: WebGL2RenderingContext): void {
|
|
290
|
+
this.beginFace(gl);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** The face's glass depth into light `layer`'s glass layer, 2L + 1. */
|
|
294
|
+
resolveGlassFace(
|
|
295
|
+
gl: WebGL2RenderingContext,
|
|
296
|
+
layer: number,
|
|
297
|
+
face: number,
|
|
298
|
+
far: number,
|
|
299
|
+
near: number,
|
|
300
|
+
): void {
|
|
301
|
+
if (this.tint === null) return;
|
|
302
|
+
this.resolveInto(gl, 2 * layer + 1, face, far, near);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
beginTintFace(gl: WebGL2RenderingContext): void {
|
|
306
|
+
this.tint?.beginFace(gl);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
resolveTintFace(gl: WebGL2RenderingContext, layer: number, face: number, drawn: boolean): void {
|
|
310
|
+
this.tint?.resolveFace(gl, layer, face, drawn);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** The chain frost reads, for every light whose tint a round touched: once a round. */
|
|
314
|
+
finishTints(gl: WebGL2RenderingContext): void {
|
|
315
|
+
this.tint?.fillMips(gl);
|
|
316
|
+
}
|
|
317
|
+
|
|
186
318
|
/**
|
|
187
319
|
* Bind the scratch as the render target for one face of one bake, and clear it.
|
|
188
320
|
*
|
|
@@ -224,9 +356,20 @@ export class PointShadowArray implements PointShadowTarget {
|
|
|
224
356
|
face: number,
|
|
225
357
|
far: number,
|
|
226
358
|
near: number,
|
|
359
|
+
): void {
|
|
360
|
+
this.resolveInto(gl, layer * this.stride, face, far, near);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** The scratch into physical layer `physical`'s region for `face`. */
|
|
364
|
+
private resolveInto(
|
|
365
|
+
gl: WebGL2RenderingContext,
|
|
366
|
+
physical: number,
|
|
367
|
+
face: number,
|
|
368
|
+
far: number,
|
|
369
|
+
near: number,
|
|
227
370
|
): void {
|
|
228
371
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
229
|
-
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, this.
|
|
372
|
+
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, this.current, 0, physical);
|
|
230
373
|
gl.viewport(0, 0, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE);
|
|
231
374
|
|
|
232
375
|
gl.useProgram(this.program);
|
|
@@ -267,10 +410,33 @@ export class PointShadowArray implements PointShadowTarget {
|
|
|
267
410
|
dispose(gl: WebGL2RenderingContext): void {
|
|
268
411
|
gl.deleteFramebuffer(this.framebuffer);
|
|
269
412
|
gl.deleteFramebuffer(this.scratchFramebuffer);
|
|
270
|
-
gl.deleteTexture(this.
|
|
413
|
+
gl.deleteTexture(this.current);
|
|
271
414
|
gl.deleteTexture(this.scratchTexture);
|
|
415
|
+
this.tint?.dispose(gl);
|
|
272
416
|
gl.deleteTexture(this.parked);
|
|
273
417
|
gl.deleteProgram(this.program);
|
|
274
418
|
gl.deleteVertexArray(this.vao);
|
|
275
419
|
}
|
|
276
420
|
}
|
|
421
|
+
|
|
422
|
+
const FAR_PLANE = new Float32Array([1]);
|
|
423
|
+
|
|
424
|
+
/** The depth array: one storage level and no filtering, so textureLod(..., 0.0) is the only fetch. */
|
|
425
|
+
function depthArray(gl: WebGL2RenderingContext, layers: number): WebGLTexture {
|
|
426
|
+
const texture = gl.createTexture();
|
|
427
|
+
if (texture === null) throw new Error('PointShadowArray: createTexture failed');
|
|
428
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
|
|
429
|
+
gl.texStorage3D(
|
|
430
|
+
gl.TEXTURE_2D_ARRAY,
|
|
431
|
+
1,
|
|
432
|
+
gl.DEPTH_COMPONENT24,
|
|
433
|
+
OCTAHEDRAL_EDGE,
|
|
434
|
+
OCTAHEDRAL_EDGE,
|
|
435
|
+
layers,
|
|
436
|
+
);
|
|
437
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
438
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
439
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
440
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
441
|
+
return texture;
|
|
442
|
+
}
|