@driftengine/core 4.5.0 → 4.6.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 +51 -0
- package/README.md +1 -1
- package/dist/geometry/boxCsg.d.ts +28 -0
- package/dist/geometry/boxCsg.js +139 -0
- package/dist/geometry/csg.d.ts +36 -0
- package/dist/geometry/csg.js +329 -0
- package/dist/geometry/meshBuilder.js +13 -0
- package/dist/geometry/meshConcat.js +1 -0
- package/dist/geometry/pixelFont.js +2 -0
- package/dist/geometry/solid.d.ts +61 -0
- package/dist/geometry/solid.js +152 -0
- package/dist/geometry/solidFlat.d.ts +35 -0
- package/dist/geometry/solidFlat.js +428 -0
- package/dist/geometry/solidHarness.d.ts +19 -0
- package/dist/geometry/solidHarness.js +66 -0
- package/dist/geometry/solidNormals.d.ts +17 -0
- package/dist/geometry/solidNormals.js +86 -0
- package/dist/geometry/solidRound.d.ts +41 -0
- package/dist/geometry/solidRound.js +323 -0
- package/dist/geometry/solidWriter.d.ts +16 -0
- package/dist/geometry/solidWriter.js +42 -0
- package/dist/index.d.ts +23 -2
- package/dist/index.js +13 -1
- package/dist/render/backend/api.d.ts +17 -3
- package/dist/render/backend/drawFog.d.ts +26 -0
- package/dist/render/backend/drawFog.js +30 -0
- package/dist/render/backend/materialChanges.d.ts +2 -1
- package/dist/render/backend/materialChanges.js +6 -1
- package/dist/render/backend/webgl2/instanced.d.ts +24 -5
- package/dist/render/backend/webgl2/instanced.js +72 -15
- package/dist/render/backend/webgl2/renderer.d.ts +122 -8
- package/dist/render/backend/webgl2/renderer.js +238 -121
- package/dist/render/backend/webgpu/buffers.d.ts +10 -0
- package/dist/render/backend/webgpu/buffers.js +34 -3
- package/dist/render/backend/webgpu/depthPass.js +5 -1
- package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +4 -1
- package/dist/render/backend/webgpu/flatPass.js +38 -9
- package/dist/render/backend/webgpu/glassTintPass.js +2 -1
- package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
- package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
- package/dist/render/backend/webgpu/instanced.d.ts +11 -1
- package/dist/render/backend/webgpu/instanced.js +21 -3
- package/dist/render/backend/webgpu/latePass.d.ts +6 -2
- package/dist/render/backend/webgpu/latePass.js +11 -4
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +100 -10
- package/dist/render/backend/webgpu/renderer.js +462 -201
- package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +17 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +77 -38
- package/dist/render/driftLight/bake.d.ts +7 -0
- package/dist/render/driftLight/bake.js +61 -56
- package/dist/render/driftLight/denseField.d.ts +46 -0
- package/dist/render/driftLight/denseField.js +109 -0
- package/dist/render/driftLight/lightField.d.ts +10 -5
- package/dist/render/driftLight/lightField.js +25 -19
- package/dist/render/driftLight/presence.d.ts +50 -0
- package/dist/render/driftLight/presence.js +43 -0
- package/dist/render/driftLight/uniforms.d.ts +3 -3
- package/dist/render/driftLight/uniforms.js +5 -6
- package/dist/render/driftLight/worldLightField.d.ts +41 -0
- package/dist/render/driftLight/worldLightField.js +66 -0
- package/dist/render/fixtureAtlas.d.ts +20 -0
- package/dist/render/fixtureAtlas.js +49 -0
- package/dist/render/instanceCull.d.ts +63 -0
- package/dist/render/instanceCull.js +160 -0
- package/dist/render/instances.d.ts +15 -0
- package/dist/render/lightBudget.d.ts +22 -33
- package/dist/render/lightBudget.js +24 -35
- package/dist/render/lightGrid.d.ts +42 -0
- package/dist/render/lightGrid.js +87 -0
- package/dist/render/mesh.d.ts +18 -24
- package/dist/render/mesh.js +51 -68
- package/dist/render/occlusion.d.ts +10 -0
- package/dist/render/occlusion.js +36 -7
- package/dist/render/pointLightSelection.d.ts +7 -0
- package/dist/render/pointLightSelection.js +29 -4
- package/dist/render/rainField.d.ts +13 -0
- package/dist/render/rainField.js +27 -5
- package/dist/render/recon/lateDepth.d.ts +11 -0
- package/dist/render/recon/lateDepth.js +70 -0
- package/dist/render/recon/resolve.d.ts +34 -4
- package/dist/render/recon/resolve.js +161 -20
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +2 -0
- package/dist/render/shaders/depth.js +12 -9
- package/dist/render/shaders/flat/driftLight.d.ts +1 -1
- package/dist/render/shaders/flat/driftLight.js +32 -7
- package/dist/render/shaders/flat/index.js +5 -3
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +75 -23
- package/dist/render/shaders/flat/preamble.js +75 -20
- package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
- package/dist/render/shaders/flat/surfaceEffects.js +152 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +8 -8
- package/dist/render/shaders/generated/depth.wgsl.js +8 -8
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1320 -1320
- package/dist/render/shaders/generated/flat.wgsl.js +1359 -1345
- package/dist/render/shaders/generated/lightVolume.wgsl.js +18 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
- package/dist/render/shaders/generated/rush.wgsl.js +21 -16
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
- package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
- package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/text.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
- package/dist/render/shaders/recon/lateDepth.wgsl.js +38 -13
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
- package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
- package/dist/render/shaders/rush.js +11 -1
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/sdfText.js +3 -2
- package/dist/render/shaders/text.d.ts +1 -1
- package/dist/render/shaders/text.js +18 -9
- package/dist/render/surfaceEffects.d.ts +83 -0
- package/dist/render/surfaceEffects.js +75 -0
- package/dist/render/surfaceScene.d.ts +8 -0
- package/dist/render/surfaceScene.js +8 -0
- package/dist/render/surfaceTexture.d.ts +18 -2
- package/dist/render/surfaceTexture.js +68 -15
- package/dist/render/temporalAa.d.ts +25 -6
- package/dist/render/temporalAa.js +45 -15
- package/dist/render/textureSource.d.ts +30 -0
- package/dist/render/textureSource.js +53 -0
- package/dist/render/vertexDefaults.js +2 -0
- package/dist/world/hlod.d.ts +104 -0
- package/dist/world/hlod.js +214 -0
- package/package.json +6 -6
- package/src/geometry/boxCsg.ts +186 -0
- package/src/geometry/csg.ts +384 -0
- package/src/geometry/meshBuilder.ts +15 -0
- package/src/geometry/meshConcat.ts +1 -0
- package/src/geometry/pixelFont.ts +2 -0
- package/src/geometry/solid.ts +189 -0
- package/src/geometry/solidFlat.ts +522 -0
- package/src/geometry/solidHarness.ts +83 -0
- package/src/geometry/solidNormals.ts +103 -0
- package/src/geometry/solidRound.ts +443 -0
- package/src/geometry/solidWriter.ts +58 -0
- package/src/index.ts +51 -1
- package/src/render/backend/api.ts +21 -2
- package/src/render/backend/drawFog.ts +31 -0
- package/src/render/backend/materialChanges.ts +9 -2
- package/src/render/backend/webgl2/instanced.ts +80 -14
- package/src/render/backend/webgl2/renderer.ts +300 -143
- package/src/render/backend/webgpu/buffers.ts +42 -3
- package/src/render/backend/webgpu/depthPass.ts +5 -1
- package/src/render/backend/webgpu/drawCommand.ts +6 -0
- package/src/render/backend/webgpu/flatPass.ts +41 -11
- package/src/render/backend/webgpu/glassTintPass.ts +2 -1
- package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
- package/src/render/backend/webgpu/instanced.ts +21 -3
- package/src/render/backend/webgpu/latePass.ts +12 -3
- package/src/render/backend/webgpu/renderer.ts +540 -226
- package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
- package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +90 -50
- package/src/render/driftLight/bake.ts +85 -62
- package/src/render/driftLight/denseField.ts +149 -0
- package/src/render/driftLight/lightField.ts +33 -18
- package/src/render/driftLight/presence.ts +69 -0
- package/src/render/driftLight/uniforms.ts +8 -9
- package/src/render/driftLight/worldLightField.ts +83 -0
- package/src/render/fixtureAtlas.ts +63 -0
- package/src/render/instanceCull.ts +205 -0
- package/src/render/instances.ts +16 -0
- package/src/render/lightBudget.ts +25 -36
- package/src/render/lightGrid.ts +130 -0
- package/src/render/mesh.ts +59 -72
- package/src/render/occlusion.ts +51 -7
- package/src/render/pointLightSelection.ts +72 -4
- package/src/render/rainField.ts +39 -5
- package/src/render/recon/lateDepth.ts +83 -0
- package/src/render/recon/resolve.ts +206 -20
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneTarget.ts +4 -0
- package/src/render/shaders/depth.ts +12 -9
- package/src/render/shaders/flat/driftLight.ts +32 -7
- package/src/render/shaders/flat/index.ts +5 -3
- package/src/render/shaders/flat/main.ts +75 -23
- package/src/render/shaders/flat/preamble.ts +75 -20
- package/src/render/shaders/flat/surfaceEffects.ts +152 -0
- package/src/render/shaders/generated/depth.wgsl.ts +8 -8
- package/src/render/shaders/generated/flat.wgsl.ts +1361 -1345
- package/src/render/shaders/generated/lightVolume.wgsl.ts +20 -4
- package/src/render/shaders/generated/rush.wgsl.ts +21 -16
- package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
- package/src/render/shaders/generated/text.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
- package/src/render/shaders/recon/lateDepth.wgsl.ts +38 -13
- package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
- package/src/render/shaders/rush.ts +11 -1
- package/src/render/shaders/sdfText.ts +3 -2
- package/src/render/shaders/text.ts +18 -9
- package/src/render/surfaceEffects.ts +132 -0
- package/src/render/surfaceScene.ts +16 -0
- package/src/render/surfaceTexture.ts +110 -19
- package/src/render/temporalAa.ts +78 -14
- package/src/render/textureSource.ts +72 -0
- package/src/render/vertexDefaults.ts +2 -0
- package/src/world/hlod.ts +290 -0
package/src/render/rainField.ts
CHANGED
|
@@ -53,6 +53,17 @@ export interface RainFieldOptions {
|
|
|
53
53
|
streakSec?: number;
|
|
54
54
|
/** The ground drops land on, and the roofs they do not fall through. */
|
|
55
55
|
ground?: GroundSurface;
|
|
56
|
+
/**
|
|
57
|
+
* How near the viewer a streak may pass and still be drawn, metres. Default 1.
|
|
58
|
+
*
|
|
59
|
+
* **A drop that close is out of any focus a camera holds**, and drawn sharp it was a bar across
|
|
60
|
+
* the frame: a streak is a world-space width, so a 6 mm drop 30 cm from the eye drew fourteen
|
|
61
|
+
* pixels thick and as long as the frame, from a city seen from the air in the rain. A streak
|
|
62
|
+
* whose nearest point is inside this distance is not drawn; the drop keeps falling, as a
|
|
63
|
+
* sheltered one does. What it gives up is rain on a lens or a visor, which this field never
|
|
64
|
+
* drew as such anyway. What would make it wrong is a view that wants drops in macro: pass 0.
|
|
65
|
+
*/
|
|
66
|
+
clearM?: number;
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
export class RainField {
|
|
@@ -97,6 +108,8 @@ export class RainField {
|
|
|
97
108
|
*/
|
|
98
109
|
streakSec: number;
|
|
99
110
|
private readonly ground: GroundSurface | null;
|
|
111
|
+
/** How near the viewer a streak may pass and still be drawn, squared. */
|
|
112
|
+
private readonly clear2: number;
|
|
100
113
|
/** Drops not drawn this update because something was over them. */
|
|
101
114
|
private sheltered = 0;
|
|
102
115
|
/**
|
|
@@ -125,6 +138,8 @@ export class RainField {
|
|
|
125
138
|
this.speedMps = options.speedMps ?? 9;
|
|
126
139
|
this.streakSec = options.streakSec ?? 0.04;
|
|
127
140
|
this.ground = options.ground ?? null;
|
|
141
|
+
const clear = options.clearM ?? 1;
|
|
142
|
+
this.clear2 = clear * clear;
|
|
128
143
|
this.segments = createLineSegments(options.count);
|
|
129
144
|
this.x = new Float32Array(options.count);
|
|
130
145
|
this.y = new Float32Array(options.count);
|
|
@@ -212,15 +227,34 @@ export class RainField {
|
|
|
212
227
|
continue;
|
|
213
228
|
}
|
|
214
229
|
|
|
230
|
+
/* Where it was a shutter-time ago, which is the streak: down by its fall and back along the
|
|
231
|
+
wind, so a gust leans every drop at the same angle without anything trigonometric. */
|
|
232
|
+
const backX = -windX * this.streakSec;
|
|
233
|
+
const backY = this.speedMps * this.streakSec;
|
|
234
|
+
const backZ = -windZ * this.streakSec;
|
|
235
|
+
/* Too near the eye to be drawn (`clearM`): the streak's nearest point to the viewer. */
|
|
236
|
+
if (this.clear2 > 0) {
|
|
237
|
+
const ox = dropX - viewerX;
|
|
238
|
+
const oy = dropY - viewerY;
|
|
239
|
+
const oz = dropZ - viewerZ;
|
|
240
|
+
const length2 = backX * backX + backY * backY + backZ * backZ;
|
|
241
|
+
const along =
|
|
242
|
+
length2 > 0
|
|
243
|
+
? Math.min(Math.max(-(ox * backX + oy * backY + oz * backZ) / length2, 0), 1)
|
|
244
|
+
: 0;
|
|
245
|
+
const nx = ox + backX * along;
|
|
246
|
+
const ny = oy + backY * along;
|
|
247
|
+
const nz = oz + backZ * along;
|
|
248
|
+
if (nx * nx + ny * ny + nz * nz < this.clear2) continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
215
251
|
const at = this.segments.count;
|
|
216
252
|
this.segments.from[at * 3] = dropX;
|
|
217
253
|
this.segments.from[at * 3 + 1] = dropY;
|
|
218
254
|
this.segments.from[at * 3 + 2] = dropZ;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
this.segments.to[at * 3] =
|
|
222
|
-
this.segments.to[at * 3 + 1] = dropY + this.speedMps * this.streakSec;
|
|
223
|
-
this.segments.to[at * 3 + 2] = dropZ - windZ * this.streakSec;
|
|
255
|
+
this.segments.to[at * 3] = dropX + backX;
|
|
256
|
+
this.segments.to[at * 3 + 1] = dropY + backY;
|
|
257
|
+
this.segments.to[at * 3 + 2] = dropZ + backZ;
|
|
224
258
|
this.segments.count = at + 1;
|
|
225
259
|
}
|
|
226
260
|
this.primed = true;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The depth a reconstructing frame's late draws are tested against at an output pixel, taken from
|
|
3
|
+
* the render's jittered depth. `shaders/recon/lateDepth.wgsl.ts` is the same arithmetic on the device.
|
|
4
|
+
*
|
|
5
|
+
* **Each surface taken to the pixel, and the nearest of them.** A late draw — a glow, a pane, a
|
|
6
|
+
* caption — is drawn unjittered at the output size, and it has to meet the depth of what it lies on
|
|
7
|
+
* where *it* is. Each of the four render texels about the pixel, placed where the jitter put it,
|
|
8
|
+
* carries its surface on to the pixel's unjittered centre along that surface's own slope; the
|
|
9
|
+
* nearest of the four so carried stands, and a quarter of its slope is given back toward the far
|
|
10
|
+
* side so that a draw lying exactly on the surface is not decided by rounding. On a plane all four
|
|
11
|
+
* arrive at the plane's own depth at the pixel: exact, and the same whatever the jitter was.
|
|
12
|
+
*
|
|
13
|
+
* **A texel's slope is the smaller of its two differences along each axis, and none where they
|
|
14
|
+
* disagree in sign** — the minmod limiter. Beside an edge one difference is the jump, so it is the
|
|
15
|
+
* other, the surface's own, that is taken; a sliver one texel wide disagrees both ways and keeps its
|
|
16
|
+
* own depth, the nearest of the four as before. So a translucent draw still never comes through an
|
|
17
|
+
* opaque edge the render covered by more than the render's own texel.
|
|
18
|
+
*
|
|
19
|
+
* **Why this replaced the nearest of the four.** On a wall seen aslant the nearest of four neighbours
|
|
20
|
+
* stands up to a texel's step in front of the wall at the pixel, so a glow lying a few centimetres
|
|
21
|
+
* proud of it was judged behind the wall; and which neighbour was nearest moved with the jitter, so
|
|
22
|
+
* the glow was drawn one frame and not the next. Measured on a street of lit facades: every strip of
|
|
23
|
+
* glow on every tower flickered frame by frame, 12,415 pixels of one tower between two frames.
|
|
24
|
+
*
|
|
25
|
+
* What it gives up: a surface curving within a texel is carried on as a plane, which a quarter-step
|
|
26
|
+
* tolerance covers. What would make it wrong is depth that is not affine on a plane — a depth written
|
|
27
|
+
* from the fragment stage — which nothing drawn before the late pass writes.
|
|
28
|
+
*/
|
|
29
|
+
import { REVERSED_DEPTH } from '../depthConvention.ts';
|
|
30
|
+
|
|
31
|
+
/** The share of a surface's slope given back toward the far side, for a draw lying on it. */
|
|
32
|
+
const TOLERANCE_SHARE = 0.25;
|
|
33
|
+
|
|
34
|
+
/** The smaller of two differences, or none where they disagree in sign. */
|
|
35
|
+
function minmod(a: number, b: number): number {
|
|
36
|
+
if (a * b <= 0) return 0;
|
|
37
|
+
return Math.abs(a) < Math.abs(b) ? a : b;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Where output pixel (`outX`, `outY`) — its centre, in pixels — falls among the render texels
|
|
42
|
+
* jittered by (`jitterX`, `jitterY`) render texels: into `out`, the cell's first texel and the
|
|
43
|
+
* fraction across it, `[x, y, fx, fy]`.
|
|
44
|
+
*/
|
|
45
|
+
export function lateDepthCell(
|
|
46
|
+
outX: number,
|
|
47
|
+
outY: number,
|
|
48
|
+
renderWidth: number,
|
|
49
|
+
renderHeight: number,
|
|
50
|
+
outputWidth: number,
|
|
51
|
+
outputHeight: number,
|
|
52
|
+
jitterX: number,
|
|
53
|
+
jitterY: number,
|
|
54
|
+
out: Float64Array | Float32Array,
|
|
55
|
+
): void {
|
|
56
|
+
/* A texel stands at its index plus a half less the jitter, as the resolve places it. */
|
|
57
|
+
const x = (outX * renderWidth) / outputWidth - 0.5 + jitterX;
|
|
58
|
+
const y = (outY * renderHeight) / outputHeight - 0.5 + jitterY;
|
|
59
|
+
out[0] = Math.floor(x);
|
|
60
|
+
out[1] = Math.floor(y);
|
|
61
|
+
out[2] = x - Math.floor(x);
|
|
62
|
+
out[3] = y - Math.floor(y);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The depth to test against, from `taps` — a 4x4 block of render texels, row-major, starting one
|
|
67
|
+
* texel up and left of the cell's first — at (`fx`, `fy`) across the cell.
|
|
68
|
+
*/
|
|
69
|
+
export function lateDepthAt(taps: ArrayLike<number>, fx: number, fy: number): number {
|
|
70
|
+
const t = (x: number, y: number): number => taps[(y + 1) * 4 + x + 1] as number;
|
|
71
|
+
let depth = REVERSED_DEPTH ? -Infinity : Infinity;
|
|
72
|
+
for (let oy = 0; oy < 2; oy += 1) {
|
|
73
|
+
for (let ox = 0; ox < 2; ox += 1) {
|
|
74
|
+
const d = t(ox, oy);
|
|
75
|
+
const gx = minmod(t(ox + 1, oy) - d, d - t(ox - 1, oy));
|
|
76
|
+
const gy = minmod(t(ox, oy + 1) - d, d - t(ox, oy - 1));
|
|
77
|
+
const carried = d + gx * (fx - ox) + gy * (fy - oy);
|
|
78
|
+
const give = TOLERANCE_SHARE * Math.max(Math.abs(gx), Math.abs(gy));
|
|
79
|
+
depth = REVERSED_DEPTH ? Math.max(depth, carried - give) : Math.min(depth, carried + give);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return depth;
|
|
83
|
+
}
|
|
@@ -30,6 +30,29 @@
|
|
|
30
30
|
* the render read bicubically fills the gap. The history keeps colour and weight; the picture shown
|
|
31
31
|
* is it sharpened.
|
|
32
32
|
*
|
|
33
|
+
* **And the temporal resolve's anti-flicker, where the caller carries its records** (`temporalAa.ts`
|
|
34
|
+
* says why it works and what it gives up). A thread finer than a render texel is caught in some
|
|
35
|
+
* phases of the jitter and missed in others, and in a phase that misses it the nine texels can all
|
|
36
|
+
* miss it: the box collapses onto what is behind, the history is clipped out, and a still picture
|
|
37
|
+
* flashes at every fine edge each period. A still camera over a still scene gathers every pixel the
|
|
38
|
+
* same way each period, so each pixel keeps a record of this frame's gathered luma over the period,
|
|
39
|
+
* and once two periods running have repeated its box widens by its own spread and it keeps more of
|
|
40
|
+
* its history. **The record stays at the pixel rather than following the reprojection**, because the
|
|
41
|
+
* proof is only ever for a pixel that has not moved; it starts over wherever the history did not
|
|
42
|
+
* survive, and is forgotten in proportion to motion.
|
|
43
|
+
*
|
|
44
|
+
* **And a still pixel whose depth has been seen to swing is not a disocclusion.** A rail finer than
|
|
45
|
+
* a texel in front of a wall is caught in some phases and missed in others, so last frame can hold
|
|
46
|
+
* the wall where this frame holds the rail with nothing moved — and the depth test threw the history
|
|
47
|
+
* away, leaving the record nothing to keep. So each pixel also keeps the largest relative miss of
|
|
48
|
+
* last frame's depth it has seen lately, kept whatever the test says, and while still its tolerance
|
|
49
|
+
* widens to `DEPTH_SWING_WIDTH` of it — the temporal resolve's answer to a crack of light round a
|
|
50
|
+
* door, as a held peak rather than its running mean: a rail caught in one phase of eighteen misses
|
|
51
|
+
* once a period, and a mean has worn that miss down to a thirtieth by the next. **What it gives up**
|
|
52
|
+
* is the depth test's word at such a pixel while still: a surface arriving there inside the swing
|
|
53
|
+
* keeps the history, and the neighbourhood clip, which the unproven sample of an arrival leaves
|
|
54
|
+
* unwidened, is what takes it out.
|
|
55
|
+
*
|
|
33
56
|
* **Conventions.** Images are rows from `v = 0` upward. Depths are clip depths in the matrices'
|
|
34
57
|
* convention, OpenGL's here — the nearest is the smallest — and the renderer converts its reversed
|
|
35
58
|
* buffer before a sample reaches this arithmetic, as the temporal resolve already does.
|
|
@@ -44,6 +67,16 @@ import {
|
|
|
44
67
|
} from './disocclusion.ts';
|
|
45
68
|
import type { DisocclusionParams } from './disocclusion.ts';
|
|
46
69
|
import { sampleBilinear, sampleHistoryBicubic5 } from './reproject.ts';
|
|
70
|
+
import {
|
|
71
|
+
TEMPORAL_FLICKER,
|
|
72
|
+
flickerBlend,
|
|
73
|
+
flickerRule,
|
|
74
|
+
flickerWidening,
|
|
75
|
+
freshFlicker,
|
|
76
|
+
nextFlicker,
|
|
77
|
+
stillness,
|
|
78
|
+
} from '../temporalAa.ts';
|
|
79
|
+
import type { Flicker, FlickerRule } from '../temporalAa.ts';
|
|
47
80
|
|
|
48
81
|
/** What a reconstruction is asked for. */
|
|
49
82
|
export interface ReconQuality {
|
|
@@ -140,6 +173,12 @@ export interface ResolveFrame {
|
|
|
140
173
|
readonly inverseViewProj: ArrayLike<number>;
|
|
141
174
|
/** Last frame's view-projection and its inverse, unjittered. */
|
|
142
175
|
readonly previousViewProj: ArrayLike<number>;
|
|
176
|
+
/**
|
|
177
|
+
* Last frame's view-projection times this frame's inverse: where a texel's clip position was
|
|
178
|
+
* last frame. Derived from the two when absent; a caller holding its matrices at a lower precision
|
|
179
|
+
* than it multiplies at passes its own product, as the renderer does.
|
|
180
|
+
*/
|
|
181
|
+
readonly reprojection?: ArrayLike<number>;
|
|
143
182
|
readonly previousInverseViewProj: ArrayLike<number>;
|
|
144
183
|
readonly eye: ArrayLike<number>;
|
|
145
184
|
readonly previousEye: ArrayLike<number>;
|
|
@@ -148,6 +187,22 @@ export interface ResolveFrame {
|
|
|
148
187
|
readonly previousJitter: ArrayLike<number>;
|
|
149
188
|
readonly quality: ReconQuality;
|
|
150
189
|
readonly disocclusion: DisocclusionParams;
|
|
190
|
+
/**
|
|
191
|
+
* Last frame's flicker records, output size, four floats a pixel — the spread, this period's
|
|
192
|
+
* running sum, the last period's mean, and how many periods running repeated, as `Flicker` holds
|
|
193
|
+
* them — or absent for the resolve without its anti-flicker.
|
|
194
|
+
*/
|
|
195
|
+
readonly flicker?: ArrayLike<number> | null;
|
|
196
|
+
/**
|
|
197
|
+
* Last frame's depth swings, output size, one float a pixel: the largest miss, relative to the
|
|
198
|
+
* depth, of last frame's depth against the one this pixel's surface should have had, seen lately
|
|
199
|
+
* — held, and halving over a period. Carried with `flicker` and read only with it.
|
|
200
|
+
*/
|
|
201
|
+
readonly depthSwing?: ArrayLike<number> | null;
|
|
202
|
+
/** Whether this frame samples the first phase of the jitter's period. */
|
|
203
|
+
readonly periodStart?: boolean;
|
|
204
|
+
/** The jitter's period: the reconstruction's own phase count, not the temporal resolve's. */
|
|
205
|
+
readonly flickerPhases?: number;
|
|
151
206
|
}
|
|
152
207
|
|
|
153
208
|
/**
|
|
@@ -170,6 +225,13 @@ export function reconstructionWeight(dx: number, dy: number): number {
|
|
|
170
225
|
*/
|
|
171
226
|
export const CONFIDENT_WEIGHT = 1;
|
|
172
227
|
|
|
228
|
+
/**
|
|
229
|
+
* How many of its held depth swings a still pixel's depth test tolerates. The peak halves over a
|
|
230
|
+
* period, so the frame before the next once-a-period miss holds just over half of it: tolerating
|
|
231
|
+
* twice the peak passes that miss outright, and three leaves the same room again.
|
|
232
|
+
*/
|
|
233
|
+
export const DEPTH_SWING_WIDTH = 3;
|
|
234
|
+
|
|
173
235
|
const SAMPLE = new Float32Array(3);
|
|
174
236
|
const GATHERED = new Float64Array(3);
|
|
175
237
|
const HISTORY = new Float32Array(4);
|
|
@@ -185,6 +247,36 @@ const UP = new Float64Array(3);
|
|
|
185
247
|
const NORMAL = new Float64Array(3);
|
|
186
248
|
const PREVIOUS_NORMAL = new Float64Array(3);
|
|
187
249
|
const CLIP = new Float64Array(4);
|
|
250
|
+
/** The last frame's reprojection, derived once a frame rather than once a pixel. */
|
|
251
|
+
const REPROJECTION = new Float64Array(16);
|
|
252
|
+
let reprojected: ResolveFrame | null = null;
|
|
253
|
+
|
|
254
|
+
/** `frame.reprojection`, or the product of its two matrices, multiplied once per frame. */
|
|
255
|
+
function reprojectionOf(frame: ResolveFrame): ArrayLike<number> {
|
|
256
|
+
if (frame.reprojection !== undefined) return frame.reprojection;
|
|
257
|
+
if (reprojected !== frame) {
|
|
258
|
+
reprojected = frame;
|
|
259
|
+
const a = frame.previousViewProj;
|
|
260
|
+
const b = frame.inverseViewProj;
|
|
261
|
+
for (let c = 0; c < 4; c += 1) {
|
|
262
|
+
for (let r = 0; r < 4; r += 1) {
|
|
263
|
+
let sum = 0;
|
|
264
|
+
for (let k = 0; k < 4; k += 1) sum += (a[k * 4 + r] as number) * (b[c * 4 + k] as number);
|
|
265
|
+
REPROJECTION[c * 4 + r] = sum;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return REPROJECTION;
|
|
270
|
+
}
|
|
271
|
+
const HELD: Flicker = { spread: 0, sum: 0, mean: 0, repeated: 0 };
|
|
272
|
+
const CARRIED: Flicker = { spread: 0, sum: 0, mean: 0, repeated: 0 };
|
|
273
|
+
/** How far, in output pixels, the last `historyWeight` found its surface had moved. */
|
|
274
|
+
let moved = 0;
|
|
275
|
+
/** How far, relative, last frame's depth missed the one expected there; -1 where nothing was held. */
|
|
276
|
+
let missed = -1;
|
|
277
|
+
/** The rule for the last period asked for, kept: one is made per period, not per pixel. */
|
|
278
|
+
let rulePhases = 0;
|
|
279
|
+
let rule: FlickerRule = TEMPORAL_FLICKER;
|
|
188
280
|
|
|
189
281
|
function clampTexel(value: number, size: number): number {
|
|
190
282
|
return value < 0 ? 0 : value >= size ? size - 1 : value;
|
|
@@ -296,7 +388,15 @@ function normalAt(
|
|
|
296
388
|
* was behind last frame's eye or off its picture, and otherwise what it gathered, scaled by whether it
|
|
297
389
|
* is the same surface. Reads `HISTORY`; writes the history's colour there.
|
|
298
390
|
*/
|
|
299
|
-
function historyWeight(
|
|
391
|
+
function historyWeight(
|
|
392
|
+
frame: ResolveFrame,
|
|
393
|
+
u: number,
|
|
394
|
+
v: number,
|
|
395
|
+
dx: number,
|
|
396
|
+
dy: number,
|
|
397
|
+
swing: number,
|
|
398
|
+
): number {
|
|
399
|
+
missed = -1;
|
|
300
400
|
if (!frame.hasHistory) return 0;
|
|
301
401
|
const rw = frame.renderWidth;
|
|
302
402
|
const rh = frame.renderHeight;
|
|
@@ -309,28 +409,46 @@ function historyWeight(frame: ResolveFrame, u: number, v: number, dx: number, dy
|
|
|
309
409
|
mv = frame.motion[flagAt + 1] as number;
|
|
310
410
|
expected = frame.motion[flagAt + 2] as number;
|
|
311
411
|
} else {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
412
|
+
/*
|
|
413
|
+
* **Through one matrix, last frame's view-projection times this frame's inverse**, rather than
|
|
414
|
+
* out to a world position and back. The two are the same arithmetic in exact numbers; in single
|
|
415
|
+
* precision the round trip is not the identity — a near plane a quarter of a metre from a far one
|
|
416
|
+
* five kilometres off — and a still camera measured a few hundredths of a pixel of motion growing
|
|
417
|
+
* out from the middle of the frame, which crept every still history and kept every pixel from
|
|
418
|
+
* the anti-flicker's stillness. The product is taken where the precision is (`reprojection`),
|
|
419
|
+
* and for a still camera it is the identity.
|
|
420
|
+
*/
|
|
421
|
+
const du = (dx + 0.5 - (frame.jitter[0] as number)) / rw;
|
|
422
|
+
const dv = (dy + 0.5 - (frame.jitter[1] as number)) / rh;
|
|
423
|
+
const x = du * 2 - 1;
|
|
424
|
+
const y = dv * 2 - 1;
|
|
425
|
+
const z = frame.depth[dy * rw + dx] as number;
|
|
426
|
+
const inverse = frame.inverseViewProj;
|
|
427
|
+
/* The homogeneous w of the world position: none, or behind the eye, is no surface. */
|
|
428
|
+
const hw =
|
|
429
|
+
(inverse[3] as number) * x +
|
|
430
|
+
(inverse[7] as number) * y +
|
|
431
|
+
(inverse[11] as number) * z +
|
|
432
|
+
(inverse[15] as number);
|
|
433
|
+
if (!(hw > 1e-12)) return 0;
|
|
434
|
+
const m = reprojectionOf(frame);
|
|
316
435
|
for (let r = 0; r < 4; r += 1) {
|
|
317
436
|
CLIP[r] =
|
|
318
|
-
(m[r] as number) *
|
|
319
|
-
(m[4 + r] as number) *
|
|
320
|
-
(m[8 + r] as number) *
|
|
437
|
+
(m[r] as number) * x +
|
|
438
|
+
(m[4 + r] as number) * y +
|
|
439
|
+
(m[8 + r] as number) * z +
|
|
321
440
|
(m[12 + r] as number);
|
|
322
441
|
}
|
|
323
442
|
/*
|
|
324
443
|
* A surface behind last frame's eye has a w that is not positive: the disocclusion trusts no
|
|
325
444
|
* such depth, so it needs no refusal of its own here.
|
|
326
445
|
*/
|
|
327
|
-
expected = CLIP[3] as number;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
mu = ((CLIP[0] as number) / expected) * 0.5 + 0.5 - du;
|
|
331
|
-
mv = ((CLIP[1] as number) / expected) * 0.5 + 0.5 - dv;
|
|
446
|
+
expected = (CLIP[3] as number) / hw;
|
|
447
|
+
mu = ((CLIP[0] as number) / (CLIP[3] as number)) * 0.5 + 0.5 - du;
|
|
448
|
+
mv = ((CLIP[1] as number) / (CLIP[3] as number)) * 0.5 + 0.5 - dv;
|
|
332
449
|
}
|
|
333
450
|
|
|
451
|
+
moved = Math.hypot(mu * frame.outputWidth, mv * frame.outputHeight);
|
|
334
452
|
const pu = u + mu;
|
|
335
453
|
const pv = v + mv;
|
|
336
454
|
if (!(pu >= 0 && pu <= 1 && pv >= 0 && pv <= 1)) return 0;
|
|
@@ -440,22 +558,32 @@ function historyWeight(frame: ResolveFrame, u: number, v: number, dx: number, dy
|
|
|
440
558
|
(NORMAL[2] as number) * (PREVIOUS_NORMAL[2] as number);
|
|
441
559
|
normalDot = 1 - planar * (1 - cosine);
|
|
442
560
|
}
|
|
561
|
+
if (held > 0 && expected > 0) missed = Math.abs(held - expected) / expected;
|
|
562
|
+
/* A still pixel's own depth swing, as wide as the anti-flicker's box: see the header. */
|
|
563
|
+
const swung = swing > 0 ? DEPTH_SWING_WIDTH * swing * stillness(moved) : 0;
|
|
443
564
|
const trust = disocclusionWeight(
|
|
444
565
|
expected,
|
|
445
566
|
held,
|
|
446
567
|
Math.hypot(mu, mv),
|
|
447
568
|
normalDot,
|
|
448
569
|
frame.disocclusion,
|
|
449
|
-
slope,
|
|
570
|
+
slope + swung,
|
|
450
571
|
);
|
|
451
572
|
return (HISTORY[3] as number) * trust;
|
|
452
573
|
}
|
|
453
574
|
|
|
454
575
|
/**
|
|
455
576
|
* One output pixel: the history's next colour, before sharpening, and the weight it has gathered —
|
|
456
|
-
* four values into `out`.
|
|
577
|
+
* four values into `out` — and, where the frame carries records, the pixel's next one into `record`.
|
|
457
578
|
*/
|
|
458
|
-
export function resolvePixel(
|
|
579
|
+
export function resolvePixel(
|
|
580
|
+
frame: ResolveFrame,
|
|
581
|
+
ox: number,
|
|
582
|
+
oy: number,
|
|
583
|
+
out: Float32Array,
|
|
584
|
+
record?: Float32Array,
|
|
585
|
+
swingOut?: Float32Array,
|
|
586
|
+
): void {
|
|
459
587
|
const rw = frame.renderWidth;
|
|
460
588
|
const rh = frame.renderHeight;
|
|
461
589
|
const ow = frame.outputWidth;
|
|
@@ -508,8 +636,58 @@ export function resolvePixel(frame: ResolveFrame, ox: number, oy: number, out: F
|
|
|
508
636
|
for (let c = 0; c < 3; c += 1) GATHERED[c] = (GATHERED[c] as number) / gathered;
|
|
509
637
|
|
|
510
638
|
/* 3 and 4. The history, and the weight it still deserves. */
|
|
511
|
-
const
|
|
512
|
-
const
|
|
639
|
+
const alpha = frame.quality.alpha;
|
|
640
|
+
const records = frame.flicker;
|
|
641
|
+
const anti = records !== undefined && records !== null;
|
|
642
|
+
if (anti) {
|
|
643
|
+
const phases = frame.flickerPhases ?? 0;
|
|
644
|
+
if (phases !== rulePhases) {
|
|
645
|
+
rulePhases = phases;
|
|
646
|
+
rule = phases > 0 ? flickerRule(phases) : TEMPORAL_FLICKER;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
const swings = frame.depthSwing;
|
|
650
|
+
const swing =
|
|
651
|
+
anti && swings !== undefined && swings !== null ? (swings[oy * ow + ox] as number) : 0;
|
|
652
|
+
const weight = historyWeight(frame, u, v, dx, dy, swing);
|
|
653
|
+
let cap = (1 - alpha) / alpha;
|
|
654
|
+
/* The anti-flicker: a proven pixel's box widened by its spread, and more of its history kept. */
|
|
655
|
+
if (anti) {
|
|
656
|
+
const sample =
|
|
657
|
+
(GATHERED[0] as number) * 0.2126 +
|
|
658
|
+
(GATHERED[1] as number) * 0.7152 +
|
|
659
|
+
(GATHERED[2] as number) * 0.0722;
|
|
660
|
+
const at = (oy * ow + ox) * 4;
|
|
661
|
+
if (weight > 0) {
|
|
662
|
+
HELD.spread = records[at] as number;
|
|
663
|
+
HELD.sum = records[at + 1] as number;
|
|
664
|
+
HELD.mean = records[at + 2] as number;
|
|
665
|
+
HELD.repeated = records[at + 3] as number;
|
|
666
|
+
} else {
|
|
667
|
+
freshFlicker(HELD, sample, rule);
|
|
668
|
+
}
|
|
669
|
+
const still = weight > 0 ? stillness(moved) : 0;
|
|
670
|
+
const widen = flickerWidening(HELD, sample, still, rule);
|
|
671
|
+
for (let c = 0; c < 3; c += 1) {
|
|
672
|
+
LOW[c] = (LOW[c] as number) - widen;
|
|
673
|
+
HIGH[c] = (HIGH[c] as number) + widen;
|
|
674
|
+
}
|
|
675
|
+
const keep = flickerBlend(1 - alpha, HELD, sample, still, rule);
|
|
676
|
+
cap = keep / (1 - keep);
|
|
677
|
+
nextFlicker(CARRIED, HELD, sample, still, frame.periodStart === true, rule);
|
|
678
|
+
if (record !== undefined) {
|
|
679
|
+
record[0] = CARRIED.spread;
|
|
680
|
+
record[1] = CARRIED.sum;
|
|
681
|
+
record[2] = CARRIED.mean;
|
|
682
|
+
record[3] = CARRIED.repeated;
|
|
683
|
+
}
|
|
684
|
+
/* The peak carried on, halving over a period, whatever the test said; none where nothing was
|
|
685
|
+
held to miss. */
|
|
686
|
+
if (swingOut !== undefined) {
|
|
687
|
+
swingOut[0] = missed >= 0 ? Math.max(missed, swing * 0.5 ** rule.memory) : 0;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
const kept = Math.min(cap, weight);
|
|
513
691
|
|
|
514
692
|
/* 5. Clipped, mixed by weight, and the gaps filled from the render. */
|
|
515
693
|
const total = kept + gathered;
|
|
@@ -529,6 +707,8 @@ export function resolvePixel(frame: ResolveFrame, ox: number, oy: number, out: F
|
|
|
529
707
|
}
|
|
530
708
|
|
|
531
709
|
const TAP = new Float32Array(4);
|
|
710
|
+
const RECORD = new Float32Array(4);
|
|
711
|
+
const SWING = new Float32Array(1);
|
|
532
712
|
const CENTRE = new Float32Array(4);
|
|
533
713
|
const AROUND = new Float32Array(12);
|
|
534
714
|
const SHOWN = new Float32Array(3);
|
|
@@ -541,19 +721,25 @@ const OFFSETS: readonly (readonly [number, number])[] = [
|
|
|
541
721
|
|
|
542
722
|
/**
|
|
543
723
|
* The whole frame: the next history into `history`, four floats a pixel, and the picture shown into
|
|
544
|
-
* `shown`, three — the history sharpened against the four direct neighbours it has just been given
|
|
724
|
+
* `shown`, three — the history sharpened against the four direct neighbours it has just been given —
|
|
725
|
+
* and, where the frame carries records, the next ones into `records`, four floats a pixel, and the
|
|
726
|
+
* next depth swings into `swings`, one.
|
|
545
727
|
*/
|
|
546
728
|
export function resolveFrame(
|
|
547
729
|
frame: ResolveFrame,
|
|
548
730
|
history: Float32Array,
|
|
549
731
|
shown: Float32Array,
|
|
732
|
+
records?: Float32Array,
|
|
733
|
+
swings?: Float32Array,
|
|
550
734
|
): void {
|
|
551
735
|
const ow = frame.outputWidth;
|
|
552
736
|
const oh = frame.outputHeight;
|
|
553
737
|
for (let y = 0; y < oh; y += 1) {
|
|
554
738
|
for (let x = 0; x < ow; x += 1) {
|
|
555
|
-
resolvePixel(frame, x, y, TAP);
|
|
739
|
+
resolvePixel(frame, x, y, TAP, RECORD, SWING);
|
|
556
740
|
history.set(TAP, (y * ow + x) * 4);
|
|
741
|
+
if (records !== undefined) records.set(RECORD, (y * ow + x) * 4);
|
|
742
|
+
if (swings !== undefined) swings[y * ow + x] = SWING[0] as number;
|
|
557
743
|
}
|
|
558
744
|
}
|
|
559
745
|
for (let y = 0; y < oh; y += 1) {
|
|
@@ -72,6 +72,10 @@ export function recordingGl(
|
|
|
72
72
|
const programFragments = new WeakMap<object, string>();
|
|
73
73
|
const calls: { name: string; args: unknown[] }[] = [];
|
|
74
74
|
const constants: Record<string, number> = {
|
|
75
|
+
/* The canvas's size, as a real context reports its drawing buffer; a function here made every
|
|
76
|
+
jitter and every per-pixel offset divided by it a NaN. */
|
|
77
|
+
drawingBufferWidth: 640,
|
|
78
|
+
drawingBufferHeight: 480,
|
|
75
79
|
ACTIVE_UNIFORMS: 0x8b86,
|
|
76
80
|
ACTIVE_ATTRIBUTES: 0x8b89,
|
|
77
81
|
COMPILE_STATUS: 0x8b81,
|
|
@@ -755,6 +755,8 @@ export class SceneTarget {
|
|
|
755
755
|
/** The projection's x and y scales, which turn a world radius into a screen one. */
|
|
756
756
|
readonly projScale: Float32Array;
|
|
757
757
|
readonly invProjection: Float32Array;
|
|
758
|
+
/** Where the frame's jitter put the occlusion, in uv: see the shader's `uAoOffset`. */
|
|
759
|
+
readonly offset?: Float32Array;
|
|
758
760
|
},
|
|
759
761
|
bloom?: {
|
|
760
762
|
readonly strength: number;
|
|
@@ -1023,6 +1025,8 @@ export class SceneTarget {
|
|
|
1023
1025
|
this.uniforms['uAoStrength'] ?? null,
|
|
1024
1026
|
aoTexture === null ? 0 : (ao?.strength ?? 0),
|
|
1025
1027
|
);
|
|
1028
|
+
const offset = ao?.offset;
|
|
1029
|
+
gl.uniform2f(this.uniforms['uAoOffset'] ?? null, offset?.[0] ?? 0, offset?.[1] ?? 0);
|
|
1026
1030
|
|
|
1027
1031
|
/* Bloom on its own unit, and the placeholder for the same reason as the one above: a
|
|
1028
1032
|
driver may fetch a texture's descriptor before it evaluates the branch that skips the
|
|
@@ -64,14 +64,15 @@ ${CHANNEL_BEND}
|
|
|
64
64
|
* uniforms declared after every other, so no field of the plain variant moves and a binder written
|
|
65
65
|
* against it stays right for this one; and two outputs after vLightPosition, which keeps location 0.
|
|
66
66
|
*/
|
|
67
|
-
layout(location = 5) in
|
|
67
|
+
layout(location = 5) in vec3 aUv;
|
|
68
68
|
uniform vec2 uUvScale;
|
|
69
69
|
uniform float uAlphaCutout;
|
|
70
70
|
#endif
|
|
71
71
|
|
|
72
72
|
out vec4 vLightPosition;
|
|
73
73
|
#if CUTOUT
|
|
74
|
-
|
|
74
|
+
/* The texture coordinate and, in z, the texture-array layer the face wears. */
|
|
75
|
+
out vec3 vUv;
|
|
75
76
|
flat out float vAlphaCutout;
|
|
76
77
|
#endif
|
|
77
78
|
#if GLASS
|
|
@@ -103,7 +104,7 @@ void main() {
|
|
|
103
104
|
vLightPosition = uLightViewProj * vec4(bent, world.w);
|
|
104
105
|
gl_Position = vLightPosition;
|
|
105
106
|
#if CUTOUT
|
|
106
|
-
vUv = aUv * uUvScale;
|
|
107
|
+
vUv = vec3(aUv.xy * uUvScale, aUv.z);
|
|
107
108
|
vAlphaCutout = uAlphaCutout;
|
|
108
109
|
#endif
|
|
109
110
|
#if GLASS
|
|
@@ -196,16 +197,17 @@ void main() {
|
|
|
196
197
|
export const DEPTH_CUTOUT_FRAG = `#version 300 es
|
|
197
198
|
precision highp float;
|
|
198
199
|
in vec4 vLightPosition;
|
|
199
|
-
in
|
|
200
|
+
in vec3 vUv;
|
|
200
201
|
flat in float vAlphaCutout;
|
|
201
202
|
|
|
202
203
|
uniform highp sampler2DArray uPreviousShadowMap;
|
|
203
204
|
uniform int uPeelShadowLayer;
|
|
204
|
-
uniform highp
|
|
205
|
+
uniform highp sampler2DArray uCutoutMap;
|
|
205
206
|
${CUTOUT_COVERAGE_GLSL}
|
|
206
207
|
void main() {
|
|
207
208
|
/* Credited for its mip level as the surface's own test is, so a leaf casts the shape it draws. */
|
|
208
|
-
|
|
209
|
+
vec3 at = vec3(vUv.xy, floor(vUv.z + 0.5));
|
|
210
|
+
float alpha = cutoutAlpha(texture(uCutoutMap, at).a, vUv.xy * vec2(textureSize(uCutoutMap, 0).xy));
|
|
209
211
|
if (uPeelShadowLayer != 0) {
|
|
210
212
|
vec3 p = vLightPosition.xyz / vLightPosition.w;
|
|
211
213
|
vec2 uv = p.xy * 0.5 + 0.5;
|
|
@@ -289,19 +291,20 @@ ${GLASS_TINT_BODY}
|
|
|
289
291
|
export const GLASS_TINT_CUTOUT_FRAG = `#version 300 es
|
|
290
292
|
precision highp float;
|
|
291
293
|
in vec4 vLightPosition;
|
|
292
|
-
in
|
|
294
|
+
in vec3 vUv;
|
|
293
295
|
flat in float vAlphaCutout;
|
|
294
296
|
in vec3 vGlassWorld;
|
|
295
297
|
|
|
296
298
|
uniform vec4 uGlassPane;
|
|
297
299
|
uniform vec4 uGlassLight;
|
|
298
|
-
uniform highp
|
|
300
|
+
uniform highp sampler2DArray uCutoutMap;
|
|
299
301
|
${CUTOUT_COVERAGE_GLSL}
|
|
300
302
|
out vec4 outTint;
|
|
301
303
|
|
|
302
304
|
void main() {
|
|
303
305
|
/* First, in uniform control flow, as the depth cutout samples it. */
|
|
304
|
-
|
|
306
|
+
vec3 at = vec3(vUv.xy, floor(vUv.z + 0.5));
|
|
307
|
+
float alpha = cutoutAlpha(texture(uCutoutMap, at).a, vUv.xy * vec2(textureSize(uCutoutMap, 0).xy));
|
|
305
308
|
${GLASS_TINT_BODY}
|
|
306
309
|
if (alpha < vAlphaCutout) discard;
|
|
307
310
|
}
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
export const DRIFT_LIGHT_GLSL = /* glsl */ `
|
|
30
30
|
/** x how much of the summed light is in the frame (0 off), y the radius, z the band, w the scale. */
|
|
31
31
|
uniform vec4 uDriftLight;
|
|
32
|
-
/** xyz the first
|
|
32
|
+
/** xyz the first sample; w metres between samples — a third of a brick, or negated for a dense volume. */
|
|
33
33
|
uniform vec4 uDriftLightOrigin;
|
|
34
34
|
uniform highp usampler3D uDriftLightIndex; // wgsl:share shadow
|
|
35
35
|
uniform highp sampler3D uDriftLightAtlas;
|
|
@@ -41,9 +41,38 @@ float driftLightShare(vec3 world) {
|
|
|
41
41
|
return smoothstep(uDriftLight.y - uDriftLight.z, uDriftLight.y, away) * uDriftLight.x;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* A sample's light and direction turned into the light on a surface facing \`n\`: Lambert for a
|
|
46
|
+
* single light, a quarter for light arriving from everywhere (see \`bake.ts\`). One statement for
|
|
47
|
+
* both kinds of volume.
|
|
48
|
+
*/
|
|
49
|
+
vec3 driftLightFacing(vec4 light, vec4 toward, vec3 n) {
|
|
50
|
+
vec3 arriving = light.rgb / light.a;
|
|
51
|
+
vec3 from = toward.xyz / light.a;
|
|
52
|
+
float facing = max(dot(n, from), 0.0) + (1.0 - min(length(from), 1.0)) * 0.25;
|
|
53
|
+
return arriving * facing;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The same from a dense volume: one sample a texel, the light in the lower half of y and its
|
|
58
|
+
* direction in the upper. A lookup kept inside the samples never filters across the halves.
|
|
59
|
+
*/
|
|
60
|
+
vec3 driftLightDense(vec3 world, vec3 n, float spacing) {
|
|
61
|
+
ivec3 size = textureSize(uDriftLightAtlas, 0);
|
|
62
|
+
vec3 dims = vec3(float(size.x), float(size.y / 2), float(size.z));
|
|
63
|
+
vec3 at = (world + n * (0.5 * spacing) - uDriftLightOrigin.xyz) / spacing;
|
|
64
|
+
if (any(lessThan(at, vec3(0.0))) || any(greaterThan(at, dims - 1.0))) return vec3(0.0);
|
|
65
|
+
vec3 uvw = (at + 0.5) / vec3(dims.x, dims.y * 2.0, dims.z);
|
|
66
|
+
vec4 light = textureLod(uDriftLightAtlas, uvw, 0.0);
|
|
67
|
+
if (light.a < 1e-3) return vec3(0.0);
|
|
68
|
+
vec4 toward = textureLod(uDriftLightAtlas, uvw + vec3(0.0, 0.5, 0.0), 0.0);
|
|
69
|
+
return driftLightFacing(light, toward, n);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The summed light arriving on a surface at \`world\` facing \`n\`. A negative spacing is dense. */
|
|
45
73
|
vec3 driftLightIrradiance(vec3 world, vec3 n) {
|
|
46
74
|
float spacing = uDriftLightOrigin.w;
|
|
75
|
+
if (spacing < 0.0) return driftLightDense(world, n, -spacing);
|
|
47
76
|
vec3 at = (world + n * (0.5 * spacing) - uDriftLightOrigin.xyz) / (3.0 * spacing);
|
|
48
77
|
ivec3 cell = ivec3(floor(at));
|
|
49
78
|
if (any(lessThan(cell, ivec3(0))) || any(greaterThanEqual(cell, textureSize(uDriftLightIndex, 0)))) {
|
|
@@ -66,10 +95,6 @@ vec3 driftLightIrradiance(vec3 world, vec3 n) {
|
|
|
66
95
|
vec4 light = textureLod(uDriftLightAtlas, (corner + inside) * texel, 0.0);
|
|
67
96
|
if (light.a < 1e-3) return vec3(0.0);
|
|
68
97
|
vec4 toward = textureLod(uDriftLightAtlas, (corner + vec3(4.0, 0.0, 0.0) + inside) * texel, 0.0);
|
|
69
|
-
|
|
70
|
-
vec3 from = toward.xyz / light.a;
|
|
71
|
-
/* Lambert for a single light, a quarter for light arriving from everywhere: see \`bake.ts\`. */
|
|
72
|
-
float facing = max(dot(n, from), 0.0) + (1.0 - min(length(from), 1.0)) * 0.25;
|
|
73
|
-
return arriving * facing;
|
|
98
|
+
return driftLightFacing(light, toward, n);
|
|
74
99
|
}
|
|
75
100
|
`;
|