@ifc-lite/renderer 1.49.0 → 1.50.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/dist/camera.d.ts +24 -0
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +72 -1
- package/dist/camera.js.map +1 -1
- package/dist/device.d.ts +11 -0
- package/dist/device.d.ts.map +1 -1
- package/dist/device.js +71 -5
- package/dist/device.js.map +1 -1
- package/dist/frame-timing-cpu.d.ts +31 -0
- package/dist/frame-timing-cpu.d.ts.map +1 -0
- package/dist/frame-timing-cpu.js +25 -0
- package/dist/frame-timing-cpu.js.map +1 -0
- package/dist/frame-timing-gpu.d.ts +144 -0
- package/dist/frame-timing-gpu.d.ts.map +1 -0
- package/dist/frame-timing-gpu.js +176 -0
- package/dist/frame-timing-gpu.js.map +1 -0
- package/dist/frame-timing-stats.d.ts +100 -0
- package/dist/frame-timing-stats.d.ts.map +1 -0
- package/dist/frame-timing-stats.js +116 -0
- package/dist/frame-timing-stats.js.map +1 -0
- package/dist/frame-timing.d.ts +102 -0
- package/dist/frame-timing.d.ts.map +1 -0
- package/dist/frame-timing.js +112 -0
- package/dist/frame-timing.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +229 -0
- package/dist/index.js.map +1 -1
- package/dist/lod-simplify.d.ts +0 -9
- package/dist/lod-simplify.d.ts.map +1 -1
- package/dist/lod-simplify.js +45 -7
- package/dist/lod-simplify.js.map +1 -1
- package/dist/pipeline.d.ts +16 -0
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +89 -3
- package/dist/pipeline.js.map +1 -1
- package/dist/pointcloud/point-cloud-spatial-index.d.ts +81 -9
- package/dist/pointcloud/point-cloud-spatial-index.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-spatial-index.js +140 -29
- package/dist/pointcloud/point-cloud-spatial-index.js.map +1 -1
- package/dist/raycast-engine.d.ts.map +1 -1
- package/dist/raycast-engine.js +10 -3
- package/dist/raycast-engine.js.map +1 -1
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +42 -5
- package/dist/scene.js.map +1 -1
- package/dist/section-2d-overlay.d.ts.map +1 -1
- package/dist/section-2d-overlay.js +6 -3
- package/dist/section-2d-overlay.js.map +1 -1
- package/dist/section-plane-basis.d.ts +17 -11
- package/dist/section-plane-basis.d.ts.map +1 -1
- package/dist/section-plane-basis.js +55 -16
- package/dist/section-plane-basis.js.map +1 -1
- package/dist/section-plane.d.ts +11 -0
- package/dist/section-plane.d.ts.map +1 -1
- package/dist/section-plane.js.map +1 -1
- package/dist/shaders/main.wgsl.d.ts +1 -1
- package/dist/shaders/main.wgsl.d.ts.map +1 -1
- package/dist/shaders/main.wgsl.js +87 -4
- package/dist/shaders/main.wgsl.js.map +1 -1
- package/dist/shaders/shadow.wgsl.d.ts +31 -0
- package/dist/shaders/shadow.wgsl.d.ts.map +1 -0
- package/dist/shaders/shadow.wgsl.js +141 -0
- package/dist/shaders/shadow.wgsl.js.map +1 -0
- package/dist/shadow-light-matrix.d.ts +106 -0
- package/dist/shadow-light-matrix.d.ts.map +1 -0
- package/dist/shadow-light-matrix.js +185 -0
- package/dist/shadow-light-matrix.js.map +1 -0
- package/dist/shadow-occluders.d.ts +106 -0
- package/dist/shadow-occluders.d.ts.map +1 -0
- package/dist/shadow-occluders.js +156 -0
- package/dist/shadow-occluders.js.map +1 -0
- package/dist/shadow-pass.d.ts +132 -0
- package/dist/shadow-pass.d.ts.map +1 -0
- package/dist/shadow-pass.js +342 -0
- package/dist/shadow-pass.js.map +1 -0
- package/dist/snap-detector.d.ts +10 -0
- package/dist/snap-detector.d.ts.map +1 -1
- package/dist/snap-detector.js.map +1 -1
- package/dist/symbolic-overlay-pipelines.d.ts +35 -0
- package/dist/symbolic-overlay-pipelines.d.ts.map +1 -1
- package/dist/symbolic-overlay-pipelines.js +31 -15
- package/dist/symbolic-overlay-pipelines.js.map +1 -1
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -43,6 +43,89 @@ export const mainShaderSource = `
|
|
|
43
43
|
}
|
|
44
44
|
@binding(0) @group(1) var<uniform> env: Environment;
|
|
45
45
|
|
|
46
|
+
// Sun shadow map (#2670, Phase 2b) at group(1). The depth map, a
|
|
47
|
+
// comparison sampler, and the light matrix + params. Bound on every
|
|
48
|
+
// main-family pipeline; sampling is gated by shadowU.params.y (enabled),
|
|
49
|
+
// so when shadows are off this reads the 1×1 dummy and returns 1.0.
|
|
50
|
+
@binding(1) @group(1) var shadowMap: texture_depth_2d;
|
|
51
|
+
@binding(2) @group(1) var shadowCmp: sampler_comparison;
|
|
52
|
+
struct Shadow {
|
|
53
|
+
lightViewProj: mat4x4<f32>,
|
|
54
|
+
// x = texelSize (1/resolution), y = enabled (0/1),
|
|
55
|
+
// z = normalBias (world units), w = pcfRadius (texels).
|
|
56
|
+
params: vec4<f32>,
|
|
57
|
+
// x = depthBias (reverse-Z clip units, nudges toward lit).
|
|
58
|
+
params2: vec4<f32>,
|
|
59
|
+
}
|
|
60
|
+
@binding(3) @group(1) var<uniform> shadowU: Shadow;
|
|
61
|
+
|
|
62
|
+
// Fraction of the sun reaching this surface point (1 = lit, 0 = fully
|
|
63
|
+
// shadowed). Normal-offset + slope-scaled bias defeats acne without
|
|
64
|
+
// peter-panning; the penumbra is sampled with a 12-tap Poisson disk
|
|
65
|
+
// ROTATED per pixel (interleaved gradient noise). A fixed grid kernel
|
|
66
|
+
// undersamples a wide penumbra and breaks into discrete bands (the
|
|
67
|
+
// "tripled shadow" at high softness); a rotated disk turns that banding
|
|
68
|
+
// into fine dither that reads as smooth at any softness.
|
|
69
|
+
// textureSampleCompareLevel is used (not ...Compare) so it is legal in
|
|
70
|
+
// this non-uniform control flow.
|
|
71
|
+
const SHADOW_POISSON = array<vec2<f32>, 12>(
|
|
72
|
+
vec2<f32>(-0.326, -0.406), vec2<f32>(-0.840, -0.074), vec2<f32>(-0.696, 0.457),
|
|
73
|
+
vec2<f32>(-0.203, 0.621), vec2<f32>( 0.962, -0.195), vec2<f32>( 0.473, -0.480),
|
|
74
|
+
vec2<f32>( 0.519, 0.767), vec2<f32>( 0.185, -0.893), vec2<f32>( 0.507, 0.064),
|
|
75
|
+
vec2<f32>( 0.896, 0.412), vec2<f32>(-0.322, -0.933), vec2<f32>(-0.792, -0.598),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
fn sunShadowFactor(worldPos: vec3<f32>, N: vec3<f32>, fragCoord: vec2<f32>) -> f32 {
|
|
79
|
+
if (shadowU.params.y < 0.5) { return 1.0; }
|
|
80
|
+
// The diffuse sun term is TWO-SIDED (abs(dot(N, sun)) in the shading
|
|
81
|
+
// below), so a face whose stabilized normal points away from the sun is
|
|
82
|
+
// still lit. Orient the normal toward the sun before biasing: otherwise
|
|
83
|
+
// the normal-offset push (params.z) moves the sample AWAY from the light
|
|
84
|
+
// (deeper behind the surface), and the slope term below collapses to its
|
|
85
|
+
// max (NdotL→0), together biasing the compare toward "lit" — which leaks
|
|
86
|
+
// direct sun onto interior faces the roof occludes (#2670 review).
|
|
87
|
+
let L = normalize(env.sunDirection);
|
|
88
|
+
let Ns = N * select(-1.0, 1.0, dot(N, L) >= 0.0);
|
|
89
|
+
let biased = worldPos + Ns * shadowU.params.z;
|
|
90
|
+
let clip = shadowU.lightViewProj * vec4<f32>(biased, 1.0);
|
|
91
|
+
let ndc = clip.xyz / clip.w;
|
|
92
|
+
let uv = vec2<f32>(ndc.x * 0.5 + 0.5, ndc.y * -0.5 + 0.5);
|
|
93
|
+
let inBounds = uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0 && ndc.z > 0.0;
|
|
94
|
+
if (!inBounds) { return 1.0; }
|
|
95
|
+
// Slope-scaled depth bias: at a grazing sun the receiver's light-space
|
|
96
|
+
// depth changes fast across the kernel, so a constant bias can't clear
|
|
97
|
+
// the whole footprint and the surface rings with moiré. Grow the bias
|
|
98
|
+
// as the surface tilts away from the sun (NdotL → 0) and with the
|
|
99
|
+
// kernel width. The hardware slope bias in the depth pass covers the
|
|
100
|
+
// occluder side; this covers the receiver side.
|
|
101
|
+
let NdotL = max(dot(Ns, L), 0.0);
|
|
102
|
+
let slope = clamp(sqrt(max(1.0 - NdotL * NdotL, 0.0)) / max(NdotL, 0.1), 1.0, 12.0);
|
|
103
|
+
let refDepth = ndc.z + shadowU.params2.x * slope * (1.0 + shadowU.params.w);
|
|
104
|
+
let radius = shadowU.params.x * shadowU.params.w; // penumbra, uv units
|
|
105
|
+
// Per-pixel rotation (interleaved gradient noise) dithers the disk so
|
|
106
|
+
// the discrete taps never line up into bands.
|
|
107
|
+
let ign = fract(52.9829189 * fract(dot(fragCoord, vec2<f32>(0.06711056, 0.00583715))));
|
|
108
|
+
let ang = ign * 6.2831853;
|
|
109
|
+
let cr = cos(ang);
|
|
110
|
+
let sr = sin(ang);
|
|
111
|
+
var sum = 0.0;
|
|
112
|
+
for (var i = 0; i < 12; i = i + 1) {
|
|
113
|
+
let p = SHADOW_POISSON[i];
|
|
114
|
+
let off = vec2<f32>(p.x * cr - p.y * sr, p.x * sr + p.y * cr) * radius;
|
|
115
|
+
sum = sum + textureSampleCompareLevel(shadowMap, shadowCmp, uv + off, refDepth);
|
|
116
|
+
}
|
|
117
|
+
let pcf = sum / 12.0;
|
|
118
|
+
// Terminator fade. On a surface nearly PARALLEL to the sun rays
|
|
119
|
+
// (NdotL → 0, e.g. a vertical wall under a midday sun) the receiver
|
|
120
|
+
// straddles the shadow threshold, so the rotated-disk taps randomly
|
|
121
|
+
// pass/fail and the wall breaks into salt-and-pepper speckle. The
|
|
122
|
+
// direct sun term is near zero there anyway, so fade the cast shadow
|
|
123
|
+
// smoothly toward lit as the surface goes grazing: a clean gradient
|
|
124
|
+
// replaces the ripple (#2670).
|
|
125
|
+
let graze = smoothstep(0.0, 0.3, NdotL);
|
|
126
|
+
return mix(1.0, pcf, graze);
|
|
127
|
+
}
|
|
128
|
+
|
|
46
129
|
struct VertexInput {
|
|
47
130
|
@location(0) position: vec3<f32>,
|
|
48
131
|
@location(1) normal: vec3<f32>,
|
|
@@ -388,10 +471,10 @@ export const mainShaderSource = `
|
|
|
388
471
|
// Darken whites/grays more to reduce washed-out appearance
|
|
389
472
|
baseColor = mix(baseColor, baseColor * 0.7, isWhiteish * 0.4);
|
|
390
473
|
|
|
391
|
-
// Combine all lighting.
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
let lightTerm = ambient + env.sunColor * diffuseSun + vec3<f32>(diffuseFill + rim);
|
|
474
|
+
// Combine all lighting. Only the DIRECT sun term is occluded by cast
|
|
475
|
+
// shadows (#2670); ambient/fill/rim are indirect and stay unshadowed.
|
|
476
|
+
let sunShadow = sunShadowFactor(input.worldPos, N, input.position.xy);
|
|
477
|
+
let lightTerm = ambient + env.sunColor * (diffuseSun * sunShadow) + vec3<f32>(diffuseFill + rim);
|
|
395
478
|
var color = baseColor * lightTerm;
|
|
396
479
|
|
|
397
480
|
// flags.x is a bitfield:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.wgsl.js","sourceRoot":"","sources":["../../src/shaders/main.wgsl.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG
|
|
1
|
+
{"version":3,"file":"main.wgsl.js","sourceRoot":"","sources":["../../src/shaders/main.wgsl.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqnBzB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sun shadow-map depth pre-pass shaders (issue #2670, Phase 2).
|
|
3
|
+
*
|
|
4
|
+
* Depth-only: each entry point transforms a vertex to the sun's light clip
|
|
5
|
+
* space and lets the depth test record the closest occluder. There is ONE
|
|
6
|
+
* entry point per geometry path, and each MUST reproduce the world-space
|
|
7
|
+
* position its main-shader counterpart computes, or that path silently stops
|
|
8
|
+
* casting (the divergence class #2670's acceptance test guards):
|
|
9
|
+
*
|
|
10
|
+
* • vs_shadow_flat ← vs_main worldPos = model * position
|
|
11
|
+
* • vs_shadow_quantized ← vs_main_quantized worldPos = model * dequant(q)
|
|
12
|
+
* • vs_shadow_instanced ← vs_instanced worldPos = instMat * position
|
|
13
|
+
* • vs_shadow_textured ← textured vs_main worldPos = model * position
|
|
14
|
+
*
|
|
15
|
+
* `lightViewProj` replaces the camera `viewProj`; `model`/`quantParams` come
|
|
16
|
+
* per-draw via a dynamic-offset uniform (unused by the instanced path, whose
|
|
17
|
+
* per-occurrence matrix arrives on vertex slot 1). The anti-z-fighting depth
|
|
18
|
+
* nudge from the main shaders is deliberately omitted — it perturbs camera
|
|
19
|
+
* clip depth for coplanar-face separation and has no meaning in light space.
|
|
20
|
+
*
|
|
21
|
+
* CLIPPING. Every entry point also passes world position to `fs_shadow_clip`,
|
|
22
|
+
* which re-applies the section plane and clip box exactly as `fs_main` does,
|
|
23
|
+
* so geometry a section cut removed from view stops casting too (otherwise the
|
|
24
|
+
* cut-away roof keeps shadowing the floor it no longer covers). The fragment
|
|
25
|
+
* stage is only attached when a clip is actually active — with no clipping the
|
|
26
|
+
* pipelines stay fragment-less, keeping the double-speed depth-only path — and
|
|
27
|
+
* the unused `worldPos` output on those pipelines costs an interpolator, not a
|
|
28
|
+
* shader stage.
|
|
29
|
+
*/
|
|
30
|
+
export declare const shadowShaderSource = "\n struct Light {\n lightViewProj: mat4x4<f32>,\n }\n @binding(0) @group(0) var<uniform> light: Light;\n\n struct Draw {\n model: mat4x4<f32>,\n // xyz = lattice-aligned quantMin (batch-origin-relative), w = step.\n // Read only by vs_shadow_quantized; zero for the other paths.\n quantParams: vec4<f32>,\n }\n @binding(1) @group(0) var<uniform> draw: Draw;\n\n struct Clip {\n // xyz = plane normal, w = plane distance (world space).\n sectionPlane: vec4<f32>,\n clipBoxMin: vec4<f32>,\n clipBoxMax: vec4<f32>,\n // x packs: bit 0 = section enabled, bit 1 = section flipped,\n // bit 2 = clip box enabled. Mirrors main.wgsl's flags.y.\n flags: vec4<u32>,\n }\n @binding(2) @group(0) var<uniform> clip: Clip;\n\n struct FlatIn {\n @location(0) position: vec3<f32>,\n }\n\n struct QuantIn {\n @location(0) q: vec4<u32>, // uint16x4: lattice x, y, z, packedOct\n }\n\n struct InstanceIn {\n @location(3) m0: vec4<f32>,\n @location(4) m1: vec4<f32>,\n @location(5) m2: vec4<f32>,\n @location(6) m3: vec4<f32>,\n // Per-occurrence flags lane (bit 1 = hidden), same instance buffer the\n // colour pass reads. Locations 7 (entityId) and 8 (rgba) are unused by\n // the depth pass and left unbound.\n @location(9) flags: u32,\n }\n\n struct ShadowOut {\n @builtin(position) position: vec4<f32>,\n @location(0) worldPos: vec3<f32>,\n }\n\n fn emit(worldPos: vec4<f32>) -> ShadowOut {\n var out: ShadowOut;\n out.position = light.lightViewProj * worldPos;\n out.worldPos = worldPos.xyz;\n return out;\n }\n\n @vertex\n fn vs_shadow_flat(input: FlatIn) -> ShadowOut {\n return emit(draw.model * vec4<f32>(input.position, 1.0));\n }\n\n @vertex\n fn vs_shadow_quantized(input: QuantIn) -> ShadowOut {\n let p = draw.quantParams.xyz\n + vec3<f32>(f32(input.q.x), f32(input.q.y), f32(input.q.z)) * draw.quantParams.w;\n return emit(draw.model * vec4<f32>(p, 1.0));\n }\n\n @vertex\n fn vs_shadow_instanced(input: FlatIn, inst: InstanceIn) -> ShadowOut {\n // A hidden / isolation-excluded occurrence (flags bit 1) must not cast:\n // the colour pass discards it in fs_main, so collapse it to a zero-area\n // triangle here (every vertex to one clip point) \u2014 the rasterizer drops\n // it and it writes no depth. Mirrors main.wgsl's instSelected & 2u check.\n if ((inst.flags & 2u) != 0u) {\n var out: ShadowOut;\n out.position = vec4<f32>(0.0, 0.0, 0.0, 1.0);\n out.worldPos = vec3<f32>(0.0, 0.0, 0.0);\n return out;\n }\n let instMat = mat4x4<f32>(inst.m0, inst.m1, inst.m2, inst.m3);\n return emit(instMat * vec4<f32>(input.position, 1.0));\n }\n\n @vertex\n fn vs_shadow_textured(input: FlatIn) -> ShadowOut {\n return emit(draw.model * vec4<f32>(input.position, 1.0));\n }\n\n // Clipped variant: discard before the depth write, so a clipped-away\n // occluder leaves the shadow map untouched. Kept byte-for-byte in step\n // with the fs_main section/clip-box branches.\n @fragment\n fn fs_shadow_clip(@location(0) worldPos: vec3<f32>) {\n if ((clip.flags.x & 1u) == 1u) {\n let side = select(1.0, -1.0, (clip.flags.x & 2u) == 2u);\n let distToPlane = (dot(worldPos, clip.sectionPlane.xyz) - clip.sectionPlane.w) * side;\n if (distToPlane > 0.0) {\n discard;\n }\n }\n if ((clip.flags.x & 4u) != 0u) {\n if (any(worldPos < clip.clipBoxMin.xyz) || any(worldPos > clip.clipBoxMax.xyz)) {\n discard;\n }\n }\n }\n";
|
|
31
|
+
//# sourceMappingURL=shadow.wgsl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shadow.wgsl.d.ts","sourceRoot":"","sources":["../../src/shaders/shadow.wgsl.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,kBAAkB,4hIA2G9B,CAAC"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* Sun shadow-map depth pre-pass shaders (issue #2670, Phase 2).
|
|
6
|
+
*
|
|
7
|
+
* Depth-only: each entry point transforms a vertex to the sun's light clip
|
|
8
|
+
* space and lets the depth test record the closest occluder. There is ONE
|
|
9
|
+
* entry point per geometry path, and each MUST reproduce the world-space
|
|
10
|
+
* position its main-shader counterpart computes, or that path silently stops
|
|
11
|
+
* casting (the divergence class #2670's acceptance test guards):
|
|
12
|
+
*
|
|
13
|
+
* • vs_shadow_flat ← vs_main worldPos = model * position
|
|
14
|
+
* • vs_shadow_quantized ← vs_main_quantized worldPos = model * dequant(q)
|
|
15
|
+
* • vs_shadow_instanced ← vs_instanced worldPos = instMat * position
|
|
16
|
+
* • vs_shadow_textured ← textured vs_main worldPos = model * position
|
|
17
|
+
*
|
|
18
|
+
* `lightViewProj` replaces the camera `viewProj`; `model`/`quantParams` come
|
|
19
|
+
* per-draw via a dynamic-offset uniform (unused by the instanced path, whose
|
|
20
|
+
* per-occurrence matrix arrives on vertex slot 1). The anti-z-fighting depth
|
|
21
|
+
* nudge from the main shaders is deliberately omitted — it perturbs camera
|
|
22
|
+
* clip depth for coplanar-face separation and has no meaning in light space.
|
|
23
|
+
*
|
|
24
|
+
* CLIPPING. Every entry point also passes world position to `fs_shadow_clip`,
|
|
25
|
+
* which re-applies the section plane and clip box exactly as `fs_main` does,
|
|
26
|
+
* so geometry a section cut removed from view stops casting too (otherwise the
|
|
27
|
+
* cut-away roof keeps shadowing the floor it no longer covers). The fragment
|
|
28
|
+
* stage is only attached when a clip is actually active — with no clipping the
|
|
29
|
+
* pipelines stay fragment-less, keeping the double-speed depth-only path — and
|
|
30
|
+
* the unused `worldPos` output on those pipelines costs an interpolator, not a
|
|
31
|
+
* shader stage.
|
|
32
|
+
*/
|
|
33
|
+
export const shadowShaderSource = `
|
|
34
|
+
struct Light {
|
|
35
|
+
lightViewProj: mat4x4<f32>,
|
|
36
|
+
}
|
|
37
|
+
@binding(0) @group(0) var<uniform> light: Light;
|
|
38
|
+
|
|
39
|
+
struct Draw {
|
|
40
|
+
model: mat4x4<f32>,
|
|
41
|
+
// xyz = lattice-aligned quantMin (batch-origin-relative), w = step.
|
|
42
|
+
// Read only by vs_shadow_quantized; zero for the other paths.
|
|
43
|
+
quantParams: vec4<f32>,
|
|
44
|
+
}
|
|
45
|
+
@binding(1) @group(0) var<uniform> draw: Draw;
|
|
46
|
+
|
|
47
|
+
struct Clip {
|
|
48
|
+
// xyz = plane normal, w = plane distance (world space).
|
|
49
|
+
sectionPlane: vec4<f32>,
|
|
50
|
+
clipBoxMin: vec4<f32>,
|
|
51
|
+
clipBoxMax: vec4<f32>,
|
|
52
|
+
// x packs: bit 0 = section enabled, bit 1 = section flipped,
|
|
53
|
+
// bit 2 = clip box enabled. Mirrors main.wgsl's flags.y.
|
|
54
|
+
flags: vec4<u32>,
|
|
55
|
+
}
|
|
56
|
+
@binding(2) @group(0) var<uniform> clip: Clip;
|
|
57
|
+
|
|
58
|
+
struct FlatIn {
|
|
59
|
+
@location(0) position: vec3<f32>,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
struct QuantIn {
|
|
63
|
+
@location(0) q: vec4<u32>, // uint16x4: lattice x, y, z, packedOct
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
struct InstanceIn {
|
|
67
|
+
@location(3) m0: vec4<f32>,
|
|
68
|
+
@location(4) m1: vec4<f32>,
|
|
69
|
+
@location(5) m2: vec4<f32>,
|
|
70
|
+
@location(6) m3: vec4<f32>,
|
|
71
|
+
// Per-occurrence flags lane (bit 1 = hidden), same instance buffer the
|
|
72
|
+
// colour pass reads. Locations 7 (entityId) and 8 (rgba) are unused by
|
|
73
|
+
// the depth pass and left unbound.
|
|
74
|
+
@location(9) flags: u32,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
struct ShadowOut {
|
|
78
|
+
@builtin(position) position: vec4<f32>,
|
|
79
|
+
@location(0) worldPos: vec3<f32>,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn emit(worldPos: vec4<f32>) -> ShadowOut {
|
|
83
|
+
var out: ShadowOut;
|
|
84
|
+
out.position = light.lightViewProj * worldPos;
|
|
85
|
+
out.worldPos = worldPos.xyz;
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@vertex
|
|
90
|
+
fn vs_shadow_flat(input: FlatIn) -> ShadowOut {
|
|
91
|
+
return emit(draw.model * vec4<f32>(input.position, 1.0));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@vertex
|
|
95
|
+
fn vs_shadow_quantized(input: QuantIn) -> ShadowOut {
|
|
96
|
+
let p = draw.quantParams.xyz
|
|
97
|
+
+ vec3<f32>(f32(input.q.x), f32(input.q.y), f32(input.q.z)) * draw.quantParams.w;
|
|
98
|
+
return emit(draw.model * vec4<f32>(p, 1.0));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@vertex
|
|
102
|
+
fn vs_shadow_instanced(input: FlatIn, inst: InstanceIn) -> ShadowOut {
|
|
103
|
+
// A hidden / isolation-excluded occurrence (flags bit 1) must not cast:
|
|
104
|
+
// the colour pass discards it in fs_main, so collapse it to a zero-area
|
|
105
|
+
// triangle here (every vertex to one clip point) — the rasterizer drops
|
|
106
|
+
// it and it writes no depth. Mirrors main.wgsl's instSelected & 2u check.
|
|
107
|
+
if ((inst.flags & 2u) != 0u) {
|
|
108
|
+
var out: ShadowOut;
|
|
109
|
+
out.position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
110
|
+
out.worldPos = vec3<f32>(0.0, 0.0, 0.0);
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
let instMat = mat4x4<f32>(inst.m0, inst.m1, inst.m2, inst.m3);
|
|
114
|
+
return emit(instMat * vec4<f32>(input.position, 1.0));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@vertex
|
|
118
|
+
fn vs_shadow_textured(input: FlatIn) -> ShadowOut {
|
|
119
|
+
return emit(draw.model * vec4<f32>(input.position, 1.0));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Clipped variant: discard before the depth write, so a clipped-away
|
|
123
|
+
// occluder leaves the shadow map untouched. Kept byte-for-byte in step
|
|
124
|
+
// with the fs_main section/clip-box branches.
|
|
125
|
+
@fragment
|
|
126
|
+
fn fs_shadow_clip(@location(0) worldPos: vec3<f32>) {
|
|
127
|
+
if ((clip.flags.x & 1u) == 1u) {
|
|
128
|
+
let side = select(1.0, -1.0, (clip.flags.x & 2u) == 2u);
|
|
129
|
+
let distToPlane = (dot(worldPos, clip.sectionPlane.xyz) - clip.sectionPlane.w) * side;
|
|
130
|
+
if (distToPlane > 0.0) {
|
|
131
|
+
discard;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if ((clip.flags.x & 4u) != 0u) {
|
|
135
|
+
if (any(worldPos < clip.clipBoxMin.xyz) || any(worldPos > clip.clipBoxMax.xyz)) {
|
|
136
|
+
discard;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
`;
|
|
141
|
+
//# sourceMappingURL=shadow.wgsl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shadow.wgsl.js","sourceRoot":"","sources":["../../src/shaders/shadow.wgsl.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2GjC,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sun shadow-map light matrix (issue #2670, Phase 2).
|
|
3
|
+
*
|
|
4
|
+
* Builds the orthographic light-view-projection the sun shadow depth pass
|
|
5
|
+
* renders occluders through. A directional sun is an orthographic camera:
|
|
6
|
+
* the frustum is a box whose axis is the (negated) sun direction, and whose
|
|
7
|
+
* cross-section must cover everything that can cast into the view.
|
|
8
|
+
*
|
|
9
|
+
* Fitting policy: by default the lateral (x/y) extent is fitted to the whole
|
|
10
|
+
* model AABB — a single, well-conditioned cascade that is correct at
|
|
11
|
+
* building scale. A caller MAY instead pass `focusCorners` (e.g. the camera
|
|
12
|
+
* frustum clipped to the model, for a site-scale view-fitted map, maintainer
|
|
13
|
+
* constraint #1 on #2670) and the lateral fit tightens to those; that path is
|
|
14
|
+
* a documented follow-up and needs a proper cascade to be robust against a
|
|
15
|
+
* reverse-Z / infinite-far camera, so the live renderer uses the bounds fit.
|
|
16
|
+
*
|
|
17
|
+
* The DEPTH range is deliberately NOT fitted to the focus alone: an occluder
|
|
18
|
+
* BEHIND the visible region along the sun axis (a roof over the room you are
|
|
19
|
+
* looking at) still has to be in the map to cast into it, so the near plane is
|
|
20
|
+
* pushed back to enclose the whole model along the light direction while the
|
|
21
|
+
* lateral (x/y) extent stays fitted to the focus.
|
|
22
|
+
*
|
|
23
|
+
* Everything here is pure and framework-free so it can be unit-tested without
|
|
24
|
+
* a GPU; the depth pass (`shadow-pass.ts`) consumes {@link SunLightFit}.
|
|
25
|
+
*/
|
|
26
|
+
import type { Mat4, Vec3 } from './types.js';
|
|
27
|
+
export interface SunLightFitParams {
|
|
28
|
+
/**
|
|
29
|
+
* Unit vector TOWARD the sun in viewer/world space (Y-up), matching the
|
|
30
|
+
* shading path's `env.sunDirection` convention. Normalized defensively; a
|
|
31
|
+
* non-finite or zero vector falls back to straight-down sun (Y-up).
|
|
32
|
+
*/
|
|
33
|
+
sunDirection: readonly [number, number, number];
|
|
34
|
+
/** World-space model AABB min corner. */
|
|
35
|
+
boundsMin: readonly [number, number, number];
|
|
36
|
+
/** World-space model AABB max corner. */
|
|
37
|
+
boundsMax: readonly [number, number, number];
|
|
38
|
+
/**
|
|
39
|
+
* Eight corners of the camera frustum clipped to the model bounds, world
|
|
40
|
+
* space. When provided the lateral fit uses these (tight, view-fitted map);
|
|
41
|
+
* when omitted or shorter than 4 the model AABB corners are used instead.
|
|
42
|
+
*/
|
|
43
|
+
focusCorners?: readonly Vec3[];
|
|
44
|
+
/**
|
|
45
|
+
* Fractional slack added around the fitted lateral extent so a PCF/normal
|
|
46
|
+
* -offset kernel near the map edge still has neighbours to sample. Default
|
|
47
|
+
* 0.05 (5%). Clamped to [0, 1].
|
|
48
|
+
*/
|
|
49
|
+
lateralPadding?: number;
|
|
50
|
+
}
|
|
51
|
+
export interface SunLightFit {
|
|
52
|
+
/** Column-major light view-projection (`ortho * lightView`), reverse-Z. */
|
|
53
|
+
lightViewProj: Mat4;
|
|
54
|
+
/** Light view matrix on its own (world → light view space). */
|
|
55
|
+
lightView: Mat4;
|
|
56
|
+
/** Fitted ortho half-width in world units (light-space x). */
|
|
57
|
+
orthoHalfWidth: number;
|
|
58
|
+
/** Fitted ortho half-height in world units (light-space y). */
|
|
59
|
+
orthoHalfHeight: number;
|
|
60
|
+
/** Near→far depth span along the sun axis, world units. */
|
|
61
|
+
depthRange: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Fit the sun's orthographic shadow frustum. Pure; see the file header for the
|
|
65
|
+
* fitting policy. The returned matrix maps world space straight to the shadow
|
|
66
|
+
* map's clip space (reverse-Z, depth 1 at near → 0 at far), so the depth pass
|
|
67
|
+
* uses it exactly where the main pass uses the camera `viewProj`.
|
|
68
|
+
*/
|
|
69
|
+
export declare function fitSunLightMatrix(params: SunLightFitParams): SunLightFit;
|
|
70
|
+
export interface CameraFrustumParams {
|
|
71
|
+
/** Camera eye (world). */
|
|
72
|
+
eye: Vec3;
|
|
73
|
+
/** Unit view direction (eye → target). */
|
|
74
|
+
forward: Vec3;
|
|
75
|
+
/** Unit screen-right. */
|
|
76
|
+
right: Vec3;
|
|
77
|
+
/** Unit screen-up. */
|
|
78
|
+
up: Vec3;
|
|
79
|
+
/** Vertical FOV in radians (perspective). */
|
|
80
|
+
fovY: number;
|
|
81
|
+
/** Viewport width / height. */
|
|
82
|
+
aspect: number;
|
|
83
|
+
/** Orthographic projection? Then `orthoHalfHeight` sizes the frustum. */
|
|
84
|
+
ortho: boolean;
|
|
85
|
+
/** Half-height of the ortho view volume, world units (ortho only). */
|
|
86
|
+
orthoHalfHeight: number;
|
|
87
|
+
boundsMin: readonly [number, number, number];
|
|
88
|
+
boundsMax: readonly [number, number, number];
|
|
89
|
+
/** Lateral expansion so a caster just off-screen still reaches the map. */
|
|
90
|
+
margin?: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Eight corners of the camera frustum, clipped to the model AABB — the region
|
|
94
|
+
* the camera can actually see (maintainer constraint #1 on #2670). Passing
|
|
95
|
+
* these to {@link fitSunLightMatrix} as `focusCorners` concentrates the shadow
|
|
96
|
+
* map on visible geometry instead of a site-scale model's whole footprint,
|
|
97
|
+
* which is what makes distant terrain steal all the resolution.
|
|
98
|
+
*
|
|
99
|
+
* Unlike unprojecting the clip cube, this builds the frustum from the camera
|
|
100
|
+
* BASIS at finite near/far distances derived from where the model sits along
|
|
101
|
+
* the view axis, so it never depends on a reverse-Z / infinite-far plane.
|
|
102
|
+
* Returns `null` when the model is entirely behind the camera (no usable
|
|
103
|
+
* focus — the caller falls back to a whole-bounds fit).
|
|
104
|
+
*/
|
|
105
|
+
export declare function cameraFrustumFocusCorners(p: CameraFrustumParams): Vec3[] | null;
|
|
106
|
+
//# sourceMappingURL=shadow-light-matrix.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shadow-light-matrix.d.ts","sourceRoot":"","sources":["../src/shadow-light-matrix.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,YAAY,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,yCAAyC;IACzC,SAAS,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,yCAAyC;IACzC,SAAS,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;IAC/B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,2EAA2E;IAC3E,aAAa,EAAE,IAAI,CAAC;IACpB,+DAA+D;IAC/D,SAAS,EAAE,IAAI,CAAC;IAChB,8DAA8D;IAC9D,cAAc,EAAE,MAAM,CAAC;IACvB,+DAA+D;IAC/D,eAAe,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;CACpB;AAwCD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,WAAW,CA0FxE;AAED,MAAM,WAAW,mBAAmB;IAClC,0BAA0B;IAC1B,GAAG,EAAE,IAAI,CAAC;IACV,0CAA0C;IAC1C,OAAO,EAAE,IAAI,CAAC;IACd,yBAAyB;IACzB,KAAK,EAAE,IAAI,CAAC;IACZ,sBAAsB;IACtB,EAAE,EAAE,IAAI,CAAC;IACT,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,KAAK,EAAE,OAAO,CAAC;IACf,sEAAsE;IACtE,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,SAAS,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,mBAAmB,GAAG,IAAI,EAAE,GAAG,IAAI,CAoC/E"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { MathUtils } from './math.js';
|
|
5
|
+
const DEFAULT_SUN = { x: 0, y: 1, z: 0 };
|
|
6
|
+
/** Unit sun direction, falling back to straight down-up for unusable input. */
|
|
7
|
+
function resolveSun(dir) {
|
|
8
|
+
const x = dir[0], y = dir[1], z = dir[2];
|
|
9
|
+
const lenSq = x * x + y * y + z * z;
|
|
10
|
+
// Finiteness matters as much as length (#2489): an Infinity component makes
|
|
11
|
+
// the length Infinity and the divide below NaN, so guard both ends.
|
|
12
|
+
if (!(lenSq > 1e-12 && lenSq < Infinity))
|
|
13
|
+
return DEFAULT_SUN;
|
|
14
|
+
const inv = 1 / Math.sqrt(lenSq);
|
|
15
|
+
return { x: x * inv, y: y * inv, z: z * inv };
|
|
16
|
+
}
|
|
17
|
+
/** The eight corners of an axis-aligned box. */
|
|
18
|
+
function aabbCorners(min, max) {
|
|
19
|
+
const corners = [];
|
|
20
|
+
for (let i = 0; i < 8; i++) {
|
|
21
|
+
corners.push({
|
|
22
|
+
x: (i & 1) ? max[0] : min[0],
|
|
23
|
+
y: (i & 2) ? max[1] : min[1],
|
|
24
|
+
z: (i & 4) ? max[2] : min[2],
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return corners;
|
|
28
|
+
}
|
|
29
|
+
/** Transform a world point by a column-major Mat4 (w = 1), returning xyz. */
|
|
30
|
+
function transformPoint(m, p) {
|
|
31
|
+
return {
|
|
32
|
+
x: m[0] * p.x + m[4] * p.y + m[8] * p.z + m[12],
|
|
33
|
+
y: m[1] * p.x + m[5] * p.y + m[9] * p.z + m[13],
|
|
34
|
+
z: m[2] * p.x + m[6] * p.y + m[10] * p.z + m[14],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Fit the sun's orthographic shadow frustum. Pure; see the file header for the
|
|
39
|
+
* fitting policy. The returned matrix maps world space straight to the shadow
|
|
40
|
+
* map's clip space (reverse-Z, depth 1 at near → 0 at far), so the depth pass
|
|
41
|
+
* uses it exactly where the main pass uses the camera `viewProj`.
|
|
42
|
+
*/
|
|
43
|
+
export function fitSunLightMatrix(params) {
|
|
44
|
+
const sun = resolveSun(params.sunDirection);
|
|
45
|
+
const modelCorners = aabbCorners(params.boundsMin, params.boundsMax);
|
|
46
|
+
// Lateral fit target: the view-clipped frustum corners when usable, else the
|
|
47
|
+
// whole model. Depth always encloses the whole model (see header).
|
|
48
|
+
const lateralPts = params.focusCorners && params.focusCorners.length >= 4
|
|
49
|
+
? params.focusCorners
|
|
50
|
+
: modelCorners;
|
|
51
|
+
// Centre the light view on the lateral focus so the fitted x/y box is
|
|
52
|
+
// symmetric around the region of interest.
|
|
53
|
+
let cx = 0, cy = 0, cz = 0;
|
|
54
|
+
for (const p of lateralPts) {
|
|
55
|
+
cx += p.x;
|
|
56
|
+
cy += p.y;
|
|
57
|
+
cz += p.z;
|
|
58
|
+
}
|
|
59
|
+
const n = lateralPts.length;
|
|
60
|
+
const center = { x: cx / n, y: cy / n, z: cz / n };
|
|
61
|
+
// Light view: eye one unit up-sun of the centre, looking down the sun axis
|
|
62
|
+
// (forward = -sun). The eye offset only shifts light-space z uniformly; near
|
|
63
|
+
// /far below are derived from the transformed points, so its magnitude is
|
|
64
|
+
// immaterial. `up` comes from viewBasis's degeneracy handling for the
|
|
65
|
+
// straight-overhead sun where any fixed up would be parallel.
|
|
66
|
+
const eye = {
|
|
67
|
+
x: center.x + sun.x,
|
|
68
|
+
y: center.y + sun.y,
|
|
69
|
+
z: center.z + sun.z,
|
|
70
|
+
};
|
|
71
|
+
// A world-Y up hint, swapped to world-Z when the sun is near-vertical, keeps
|
|
72
|
+
// the basis well-conditioned; viewBasis substitutes its own hint if this is
|
|
73
|
+
// still parallel, so the result is always finite.
|
|
74
|
+
const upHint = Math.abs(sun.y) > 0.99 ? { x: 0, y: 0, z: 1 } : { x: 0, y: 1, z: 0 };
|
|
75
|
+
const lightView = MathUtils.lookAt(eye, center, upHint);
|
|
76
|
+
const lv = lightView.m;
|
|
77
|
+
// Lateral (x/y) extent from the focus points.
|
|
78
|
+
let minX = Infinity, maxX = -Infinity;
|
|
79
|
+
let minY = Infinity, maxY = -Infinity;
|
|
80
|
+
for (const p of lateralPts) {
|
|
81
|
+
const v = transformPoint(lv, p);
|
|
82
|
+
if (v.x < minX)
|
|
83
|
+
minX = v.x;
|
|
84
|
+
if (v.x > maxX)
|
|
85
|
+
maxX = v.x;
|
|
86
|
+
if (v.y < minY)
|
|
87
|
+
minY = v.y;
|
|
88
|
+
if (v.y > maxY)
|
|
89
|
+
maxY = v.y;
|
|
90
|
+
}
|
|
91
|
+
// Depth (z) extent from the WHOLE model, so occluders behind the focus still
|
|
92
|
+
// cast into it. In lookAt view space the camera looks down -Z, so scene
|
|
93
|
+
// points have negative z and their distance from the eye is -z.
|
|
94
|
+
let minZ = Infinity, maxZ = -Infinity;
|
|
95
|
+
for (const p of modelCorners) {
|
|
96
|
+
const vz = lv[2] * p.x + lv[6] * p.y + lv[10] * p.z + lv[14];
|
|
97
|
+
if (vz < minZ)
|
|
98
|
+
minZ = vz;
|
|
99
|
+
if (vz > maxZ)
|
|
100
|
+
maxZ = vz;
|
|
101
|
+
}
|
|
102
|
+
// Pad the lateral box so an edge texel's kernel still has neighbours.
|
|
103
|
+
const pad = Math.min(Math.max(params.lateralPadding ?? 0.05, 0), 1);
|
|
104
|
+
const halfW = Math.max((maxX - minX) * 0.5, 1e-4);
|
|
105
|
+
const halfH = Math.max((maxY - minY) * 0.5, 1e-4);
|
|
106
|
+
const midX = (minX + maxX) * 0.5;
|
|
107
|
+
const midY = (minY + maxY) * 0.5;
|
|
108
|
+
const padW = halfW * (1 + pad);
|
|
109
|
+
const padH = halfH * (1 + pad);
|
|
110
|
+
const left = midX - padW;
|
|
111
|
+
const right = midX + padW;
|
|
112
|
+
const bottom = midY - padH;
|
|
113
|
+
const top = midY + padH;
|
|
114
|
+
// Distances along the view direction. In lookAt view space scene points have
|
|
115
|
+
// negative z, so distance = -z: the nearest point (largest z) sets `near`,
|
|
116
|
+
// the farthest (smallest z) sets `far`. `near` is NOT clamped to 0 — the eye
|
|
117
|
+
// sits just up-sun of the FOCUS centre, so model geometry further up-sun is
|
|
118
|
+
// behind it (positive z ⇒ negative near), and clamping would push those
|
|
119
|
+
// occluders out of the depth range so they stopped casting. A small epsilon
|
|
120
|
+
// pads both ends against precision and a zero-thickness (flat) frustum.
|
|
121
|
+
const zEps = Math.max((maxZ - minZ) * 1e-3, 1e-3);
|
|
122
|
+
const near = -maxZ - zEps;
|
|
123
|
+
const far = -minZ + zEps;
|
|
124
|
+
const ortho = MathUtils.orthographicReverseZ(left, right, bottom, top, near, far);
|
|
125
|
+
const lightViewProj = MathUtils.multiply(ortho, lightView);
|
|
126
|
+
return {
|
|
127
|
+
lightViewProj,
|
|
128
|
+
lightView,
|
|
129
|
+
orthoHalfWidth: padW,
|
|
130
|
+
orthoHalfHeight: padH,
|
|
131
|
+
depthRange: far - near,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Eight corners of the camera frustum, clipped to the model AABB — the region
|
|
136
|
+
* the camera can actually see (maintainer constraint #1 on #2670). Passing
|
|
137
|
+
* these to {@link fitSunLightMatrix} as `focusCorners` concentrates the shadow
|
|
138
|
+
* map on visible geometry instead of a site-scale model's whole footprint,
|
|
139
|
+
* which is what makes distant terrain steal all the resolution.
|
|
140
|
+
*
|
|
141
|
+
* Unlike unprojecting the clip cube, this builds the frustum from the camera
|
|
142
|
+
* BASIS at finite near/far distances derived from where the model sits along
|
|
143
|
+
* the view axis, so it never depends on a reverse-Z / infinite-far plane.
|
|
144
|
+
* Returns `null` when the model is entirely behind the camera (no usable
|
|
145
|
+
* focus — the caller falls back to a whole-bounds fit).
|
|
146
|
+
*/
|
|
147
|
+
export function cameraFrustumFocusCorners(p) {
|
|
148
|
+
const corners = aabbCorners(p.boundsMin, p.boundsMax);
|
|
149
|
+
// Depth span of the model along the view axis, relative to the eye.
|
|
150
|
+
let nearD = Infinity;
|
|
151
|
+
let farD = -Infinity;
|
|
152
|
+
for (const c of corners) {
|
|
153
|
+
const d = (c.x - p.eye.x) * p.forward.x + (c.y - p.eye.y) * p.forward.y + (c.z - p.eye.z) * p.forward.z;
|
|
154
|
+
if (d < nearD)
|
|
155
|
+
nearD = d;
|
|
156
|
+
if (d > farD)
|
|
157
|
+
farD = d;
|
|
158
|
+
}
|
|
159
|
+
if (!(farD > 1e-3))
|
|
160
|
+
return null; // model behind the camera
|
|
161
|
+
nearD = Math.max(nearD, 0.01);
|
|
162
|
+
const margin = Math.min(Math.max(p.margin ?? 0.15, 0), 2);
|
|
163
|
+
const half = (d) => {
|
|
164
|
+
const h = (p.ortho ? p.orthoHalfHeight : d * Math.tan(p.fovY / 2)) * (1 + margin);
|
|
165
|
+
return { w: h * p.aspect, h };
|
|
166
|
+
};
|
|
167
|
+
const out = [];
|
|
168
|
+
for (const d of [nearD, farD]) {
|
|
169
|
+
const { w, h } = half(d);
|
|
170
|
+
for (const sx of [-1, 1]) {
|
|
171
|
+
for (const sy of [-1, 1]) {
|
|
172
|
+
const x = p.eye.x + p.forward.x * d + p.right.x * (sx * w) + p.up.x * (sy * h);
|
|
173
|
+
const y = p.eye.y + p.forward.y * d + p.right.y * (sx * w) + p.up.y * (sy * h);
|
|
174
|
+
const z = p.eye.z + p.forward.z * d + p.right.z * (sx * w) + p.up.z * (sy * h);
|
|
175
|
+
out.push({
|
|
176
|
+
x: Math.min(Math.max(x, p.boundsMin[0]), p.boundsMax[0]),
|
|
177
|
+
y: Math.min(Math.max(y, p.boundsMin[1]), p.boundsMax[1]),
|
|
178
|
+
z: Math.min(Math.max(z, p.boundsMin[2]), p.boundsMax[2]),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=shadow-light-matrix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shadow-light-matrix.js","sourceRoot":"","sources":["../src/shadow-light-matrix.ts"],"names":[],"mappings":"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"}
|