reze-engine 0.55.0 → 0.55.2
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/dist/camera.d.ts +13 -0
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +53 -2
- package/dist/engine.d.ts +121 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +231 -24
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +18 -1
- package/dist/shaders/passes/ground.d.ts +5 -1
- package/dist/shaders/passes/ground.d.ts.map +1 -1
- package/dist/shaders/passes/ground.js +83 -18
- package/package.json +1 -1
- package/src/camera.ts +60 -2
- package/src/engine.ts +248 -20
- package/src/shaders/passes/composite.ts +18 -1
- package/src/shaders/passes/ground.ts +87 -18
|
@@ -10,7 +10,79 @@ import { WORLD_AMBIENT_WGSL } from "../lights"
|
|
|
10
10
|
// the struct this returns depends on what the probe at init found, and a string
|
|
11
11
|
// baked at import time cannot know that. Called once, when the module is built.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// How far the softest setting spreads the taps, in units of the sharp kernel's
|
|
14
|
+
// own step, and how many taps carry that spread. Sixteen is what keeps a wide
|
|
15
|
+
// disk reading as a penumbra instead of sixteen shadows.
|
|
16
|
+
const SOFT_TAPS = 16
|
|
17
|
+
const SOFT_MAX_SPREAD = 14
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* PCF taps for one cascade, emitted per shadow map.
|
|
21
|
+
*
|
|
22
|
+
* At softness 0 this is the 3x3 box the ground has always used — same offsets,
|
|
23
|
+
* same weights, same result to the bit. Above it the taps spread over a Vogel
|
|
24
|
+
* disk whose radius IS the penumbra: an overcast sky throws no edge, and a
|
|
25
|
+
* razor-edged shadow under one is the loudest thing wrong in a composite.
|
|
26
|
+
*
|
|
27
|
+
* The branch is on a uniform, so a scene at softness 0 genuinely takes the
|
|
28
|
+
* nine-tap side rather than masking the wide one — the same reasoning the
|
|
29
|
+
* shadowStrength branch above it already documents.
|
|
30
|
+
*
|
|
31
|
+
* A string rather than a WGSL function because a texture is the one handle type
|
|
32
|
+
* this shader has never passed as a parameter, and the file is already a
|
|
33
|
+
* template that interpolates its constants.
|
|
34
|
+
*
|
|
35
|
+
* `acc` must already be declared and zeroed; the caller reads it back
|
|
36
|
+
* normalised, so there is no `/ 9.0` left at the call site.
|
|
37
|
+
*/
|
|
38
|
+
function pcfWgsl(map: string, uv: string, texel: string, z: string, acc: string, pad: string, soft: boolean): string {
|
|
39
|
+
const golden = 2.39996323
|
|
40
|
+
// ONE BRANCH IS COMPILED, NOT BOTH.
|
|
41
|
+
//
|
|
42
|
+
// A runtime `if` on the softness uniform was the first cut, and it costs a
|
|
43
|
+
// scene that never softens a shadow. Both bodies land in the module, the wide
|
|
44
|
+
// one needs more live registers than the narrow one, and occupancy on this
|
|
45
|
+
// draw is set by the worst of them — so the frame gets slower whether or not
|
|
46
|
+
// the branch is ever taken. That is expensive HERE in particular: this file
|
|
47
|
+
// already records that the ground is a full-coverage draw costing tens of
|
|
48
|
+
// millions of shadow fetches, and that bisection on a slow device pinned the
|
|
49
|
+
// whole cost to it.
|
|
50
|
+
//
|
|
51
|
+
// So the flag is the COMPILED VARIANT, which is the idiom the composite pass
|
|
52
|
+
// already uses for its effects. A scene at softness 0 gets byte-for-byte the
|
|
53
|
+
// shader it had before any of this existed, and cannot pay for a feature it
|
|
54
|
+
// is not using.
|
|
55
|
+
const sharp = [
|
|
56
|
+
`let st = ${texel} * 2.0;`,
|
|
57
|
+
`for (var y = -1; y <= 1; y++) {`,
|
|
58
|
+
` for (var x = -1; x <= 1; x++) {`,
|
|
59
|
+
// ...Level, not the implicit-derivative form: identical on a single-mip
|
|
60
|
+
// shadow map, and legal inside a branch.
|
|
61
|
+
` ${acc} += textureSampleCompareLevel(${map}, shadowSampler, ${uv} + vec2f(f32(x), f32(y)) * st, ${z});`,
|
|
62
|
+
` }`,
|
|
63
|
+
`}`,
|
|
64
|
+
`${acc} *= ${1 / 9};`,
|
|
65
|
+
]
|
|
66
|
+
const wide = [
|
|
67
|
+
`let radius = ${texel} * 2.0 * (1.0 + material.shadowSoftness * ${SOFT_MAX_SPREAD}.0);`,
|
|
68
|
+
`for (var s = 0; s < ${SOFT_TAPS}; s++) {`,
|
|
69
|
+
` let fs = f32(s);`,
|
|
70
|
+
// sqrt of the index spaces the ring radii evenly by AREA; the golden angle
|
|
71
|
+
// keeps successive taps from lining up into spokes.
|
|
72
|
+
` let r = sqrt((fs + 0.5) * ${1 / SOFT_TAPS});`,
|
|
73
|
+
` let a = fs * ${golden} + rot;`,
|
|
74
|
+
` ${acc} += textureSampleCompareLevel(${map}, shadowSampler, ${uv} + vec2f(cos(a), sin(a)) * (r * radius), ${z});`,
|
|
75
|
+
`}`,
|
|
76
|
+
`${acc} *= ${1 / SOFT_TAPS};`,
|
|
77
|
+
]
|
|
78
|
+
return (soft ? wide : sharp).map((l) => pad + l).join("\n")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The ground's shader. `soft` selects the shadow-edge variant — see pcfWgsl for
|
|
83
|
+
* why this is a compiled flag and not a uniform the shader branches on.
|
|
84
|
+
*/
|
|
85
|
+
export function groundShaderWgsl(soft = false): string {
|
|
14
86
|
return /* wgsl */ `
|
|
15
87
|
struct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };
|
|
16
88
|
struct Light { direction: vec4f, color: vec4f, };
|
|
@@ -22,7 +94,7 @@ struct GroundShadowMat {
|
|
|
22
94
|
gridLineColor: vec3f, mirror: f32,
|
|
23
95
|
// farCascade: 1 while a stage is loaded, 0 otherwise. See the branch below —
|
|
24
96
|
// with no stage the far map is never drawn into, so its taps are known.
|
|
25
|
-
mirrorBlur: f32, farCascade: f32,
|
|
97
|
+
mirrorBlur: f32, farCascade: f32, shadowSoftness: f32, _mb2: f32,
|
|
26
98
|
// Every shadow caster in one sphere, refreshed per frame. w = radius; 0 means
|
|
27
99
|
// nothing casts, negative means "do not use this" (a rigid caster has no
|
|
28
100
|
// sphere, so a scene with a stage keeps the taps). See rzShadowPossible.
|
|
@@ -152,6 +224,15 @@ ${sceneFsOutWgsl()}@fragment fn fs(i: VO) -> FSOut {
|
|
|
152
224
|
// The same reasoning the noise tint below already got, applied to the term
|
|
153
225
|
// that costs a hundred times more.
|
|
154
226
|
if (material.shadowStrength > 0.0 && shadowPossible) {
|
|
227
|
+
${
|
|
228
|
+
soft
|
|
229
|
+
? ` // Per-pixel rotation for the soft disk, so its rings break up into fine
|
|
230
|
+
// noise rather than banding. Interleaved gradient noise: a function of the
|
|
231
|
+
// pixel alone, so a still camera gives a still shadow.
|
|
232
|
+
let ign = fract(52.9829189 * fract(dot(i.position.xy, vec2f(0.06711056, 0.00583715))));
|
|
233
|
+
let rot = ign * 6.28318530718;`
|
|
234
|
+
: ""
|
|
235
|
+
}
|
|
155
236
|
// The far cascade's taps, skipped entirely when nothing ever drew into it.
|
|
156
237
|
//
|
|
157
238
|
// This branch is the expensive one on a wide floor: it runs wherever the NEAR
|
|
@@ -165,32 +246,20 @@ ${sceneFsOutWgsl()}@fragment fn fs(i: VO) -> FSOut {
|
|
|
165
246
|
if (material.farCascade > 0.0 && frustum < 1.0 && frustum1 > 0.0) {
|
|
166
247
|
let suv1 = vec2f(ndc1.x * 0.5 + 0.5, 0.5 - ndc1.y * 0.5);
|
|
167
248
|
let suv1_c = clamp(suv1, vec2f(0.02), vec2f(0.98));
|
|
168
|
-
let st1 = ${1 / SHADOW_CASCADES[SHADOW_CASCADES.length - 1].mapSize} * 2.0;
|
|
169
249
|
let compareZ1 = ndc1.z - 0.0035;
|
|
170
250
|
var acc1 = 0.0;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
acc1 += textureSampleCompareLevel(shadowMapFar, shadowSampler, suv1_c + vec2f(f32(x), f32(y)) * st1, compareZ1);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
vis = mix(1.0, acc1 * (1.0 / 9.0), frustum1);
|
|
251
|
+
${pcfWgsl("shadowMapFar", "suv1_c", `${1 / SHADOW_CASCADES[SHADOW_CASCADES.length - 1].mapSize}`, "compareZ1", "acc1", " ", soft)}
|
|
252
|
+
vis = mix(1.0, acc1, frustum1);
|
|
177
253
|
}
|
|
178
254
|
if (frustum > 0.0) {
|
|
179
255
|
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
180
256
|
let suv_c = clamp(suv, vec2f(0.02), vec2f(0.98));
|
|
181
|
-
let st = material.pcfTexel * 2.0;
|
|
182
257
|
let compareZ = ndc.z - 0.0035;
|
|
183
258
|
var acc = 0.0;
|
|
184
|
-
|
|
185
|
-
for (var x = -1; x <= 1; x++) {
|
|
186
|
-
// ...Level, not the implicit-derivative form: identical on a single-mip
|
|
187
|
-
// shadow map, and legal inside this branch.
|
|
188
|
-
acc += textureSampleCompareLevel(shadowMap, shadowSampler, suv_c + vec2f(f32(x), f32(y)) * st, compareZ);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
259
|
+
${pcfWgsl("shadowMap", "suv_c", "material.pcfTexel", "compareZ", "acc", " ", soft)}
|
|
191
260
|
// The base is whatever the far cascade decided, so the near border blends
|
|
192
261
|
// cascade to cascade rather than snapping to lit mid-floor.
|
|
193
|
-
vis = mix(vis, acc
|
|
262
|
+
vis = mix(vis, acc, frustum);
|
|
194
263
|
}
|
|
195
264
|
}
|
|
196
265
|
|