@aethervtt/shard-render 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/assets.d.ts +104 -0
  4. package/dist/assets.js +213 -0
  5. package/dist/atmosphere-model.d.ts +96 -0
  6. package/dist/atmosphere-model.js +370 -0
  7. package/dist/atmosphere-nodes.d.ts +55 -0
  8. package/dist/atmosphere-nodes.js +780 -0
  9. package/dist/atmosphere-plugin.d.ts +5 -0
  10. package/dist/atmosphere-plugin.js +42 -0
  11. package/dist/atmosphere-shaders.d.ts +7 -0
  12. package/dist/atmosphere-shaders.js +595 -0
  13. package/dist/atmosphere-state.d.ts +72 -0
  14. package/dist/atmosphere-state.js +18 -0
  15. package/dist/atmosphere.d.ts +191 -0
  16. package/dist/atmosphere.js +950 -0
  17. package/dist/camera.d.ts +83 -0
  18. package/dist/camera.js +163 -0
  19. package/dist/clusters.d.ts +68 -0
  20. package/dist/clusters.js +288 -0
  21. package/dist/culling-shaders.d.ts +2 -0
  22. package/dist/culling-shaders.js +203 -0
  23. package/dist/culling.d.ts +84 -0
  24. package/dist/culling.js +498 -0
  25. package/dist/debug-shaders.d.ts +3 -0
  26. package/dist/debug-shaders.js +207 -0
  27. package/dist/debug-views.d.ts +60 -0
  28. package/dist/debug-views.js +197 -0
  29. package/dist/deferred-plugin.d.ts +5 -0
  30. package/dist/deferred-plugin.js +20 -0
  31. package/dist/deferred-shaders.d.ts +2 -0
  32. package/dist/deferred-shaders.js +151 -0
  33. package/dist/deferred.d.ts +22 -0
  34. package/dist/deferred.js +296 -0
  35. package/dist/deform.d.ts +80 -0
  36. package/dist/deform.js +213 -0
  37. package/dist/describe-plugin.d.ts +5 -0
  38. package/dist/describe-plugin.js +19 -0
  39. package/dist/display-nodes.d.ts +5 -0
  40. package/dist/display-nodes.js +189 -0
  41. package/dist/dynamic-resolution.d.ts +47 -0
  42. package/dist/dynamic-resolution.js +191 -0
  43. package/dist/environment-plugin.d.ts +6 -0
  44. package/dist/environment-plugin.js +44 -0
  45. package/dist/environment-shaders.d.ts +2 -0
  46. package/dist/environment-shaders.js +260 -0
  47. package/dist/environment-state.d.ts +71 -0
  48. package/dist/environment-state.js +144 -0
  49. package/dist/environment.d.ts +70 -0
  50. package/dist/environment.js +529 -0
  51. package/dist/forward.d.ts +123 -0
  52. package/dist/forward.js +1216 -0
  53. package/dist/fxaa.d.ts +2 -0
  54. package/dist/fxaa.js +65 -0
  55. package/dist/gizmo-font.d.ts +21 -0
  56. package/dist/gizmo-font.js +132 -0
  57. package/dist/gizmos-plugin.d.ts +5 -0
  58. package/dist/gizmos-plugin.js +34 -0
  59. package/dist/gizmos.d.ts +156 -0
  60. package/dist/gizmos.js +772 -0
  61. package/dist/gpu-assets.d.ts +114 -0
  62. package/dist/gpu-assets.js +494 -0
  63. package/dist/graph.d.ts +204 -0
  64. package/dist/graph.js +417 -0
  65. package/dist/index.d.ts +53 -0
  66. package/dist/index.js +540 -0
  67. package/dist/instances.d.ts +349 -0
  68. package/dist/instances.js +1335 -0
  69. package/dist/large-world.d.ts +13 -0
  70. package/dist/large-world.js +20 -0
  71. package/dist/lights.d.ts +160 -0
  72. package/dist/lights.js +468 -0
  73. package/dist/material-noise.d.ts +19 -0
  74. package/dist/material-noise.js +7 -0
  75. package/dist/material-pipelines.d.ts +46 -0
  76. package/dist/material-pipelines.js +166 -0
  77. package/dist/materials.d.ts +108 -0
  78. package/dist/materials.js +219 -0
  79. package/dist/noise.d.ts +76 -0
  80. package/dist/noise.js +286 -0
  81. package/dist/overlay-registry.d.ts +18 -0
  82. package/dist/overlay-registry.js +30 -0
  83. package/dist/overlays.d.ts +35 -0
  84. package/dist/overlays.js +344 -0
  85. package/dist/paths.d.ts +20 -0
  86. package/dist/paths.js +46 -0
  87. package/dist/picking-plugin.d.ts +2 -0
  88. package/dist/picking-plugin.js +20 -0
  89. package/dist/picking.d.ts +94 -0
  90. package/dist/picking.js +456 -0
  91. package/dist/pixel-perfect-plugin.d.ts +9 -0
  92. package/dist/pixel-perfect-plugin.js +135 -0
  93. package/dist/pixel-perfect.d.ts +27 -0
  94. package/dist/pixel-perfect.js +70 -0
  95. package/dist/plugin.d.ts +111 -0
  96. package/dist/plugin.js +182 -0
  97. package/dist/pool.d.ts +19 -0
  98. package/dist/pool.js +43 -0
  99. package/dist/post-common.d.ts +51 -0
  100. package/dist/post-common.js +148 -0
  101. package/dist/post-nodes.d.ts +39 -0
  102. package/dist/post-nodes.js +849 -0
  103. package/dist/post-plugin.d.ts +5 -0
  104. package/dist/post-plugin.js +26 -0
  105. package/dist/post-shaders.d.ts +8 -0
  106. package/dist/post-shaders.js +698 -0
  107. package/dist/post.d.ts +175 -0
  108. package/dist/post.js +453 -0
  109. package/dist/readback.d.ts +4 -0
  110. package/dist/readback.js +95 -0
  111. package/dist/render-scale.d.ts +53 -0
  112. package/dist/render-scale.js +63 -0
  113. package/dist/shaders.d.ts +122 -0
  114. package/dist/shaders.js +1168 -0
  115. package/dist/shadows.d.ts +119 -0
  116. package/dist/shadows.js +562 -0
  117. package/dist/skin-asset.d.ts +26 -0
  118. package/dist/skin-asset.js +25 -0
  119. package/dist/skinning-plugin.d.ts +5 -0
  120. package/dist/skinning-plugin.js +16 -0
  121. package/dist/skinning.d.ts +19 -0
  122. package/dist/skinning.js +343 -0
  123. package/dist/standard-fields.d.ts +67 -0
  124. package/dist/standard-fields.js +73 -0
  125. package/dist/standard.d.ts +7 -0
  126. package/dist/standard.js +37 -0
  127. package/dist/stats.d.ts +25 -0
  128. package/dist/stats.js +18 -0
  129. package/dist/target.d.ts +55 -0
  130. package/dist/target.js +70 -0
  131. package/dist/testing.d.ts +34 -0
  132. package/dist/testing.js +126 -0
  133. package/dist/timer.d.ts +28 -0
  134. package/dist/timer.js +136 -0
  135. package/dist/view.d.ts +117 -0
  136. package/dist/view.js +389 -0
  137. package/dist/visibility.d.ts +10 -0
  138. package/dist/visibility.js +84 -0
  139. package/package.json +49 -0
@@ -0,0 +1,119 @@
1
+ import { type World } from '@aethervtt/shard-core';
2
+ import { GpuBuffer, type GpuContext } from '@aethervtt/shard-gpu';
3
+ import type { NodeContext } from './graph.js';
4
+ import { type DrawList } from './instances.js';
5
+ import { type LightRecord, type LightStore } from './lights.js';
6
+ import { type MaterialPipelines } from './material-pipelines.js';
7
+ import type { CameraData } from './view.js';
8
+ export declare const MAX_CASCADES = 4;
9
+ export declare const MAX_SHADOWED_SPOTS = 8;
10
+ export declare const MAX_SHADOWED_POINTS = 4;
11
+ /** Floats in a view's ShadowData buffer (see `shard::pbr::shadows`). */
12
+ export declare const SHADOW_DATA_FLOATS = 644;
13
+ /** Point shadow faces cover a little more than 90° so PCF taps near an edge stay on the face. */
14
+ export declare function pointTanHalf(size: number): number;
15
+ export interface ShadowViewDraw {
16
+ viewProj: Float32Array;
17
+ frustum: Float32Array;
18
+ draws: DrawList;
19
+ /** Dynamic offset of this view's matrix in the frame's shadow uniforms. */
20
+ offset: number;
21
+ }
22
+ /** A camera's cascades: fitted each frame, one depth layer each. */
23
+ export declare class Cascades {
24
+ count: number;
25
+ readonly views: ShadowViewDraw[];
26
+ readonly splits: Float32Array<ArrayBuffer>;
27
+ readonly texel: Float32Array<ArrayBuffer>;
28
+ /** Light-space centers after snapping (for tests: sub-texel moves don't change them). */
29
+ readonly centers: Float32Array<ArrayBuffer>;
30
+ texture: GPUTexture | undefined;
31
+ size: number;
32
+ generation: number;
33
+ ensureTexture(gpu: GpuContext, size: number, label: string): GPUTexture;
34
+ }
35
+ /**
36
+ * Practical split scheme: a blend of logarithmic and uniform splits. Returns the far view depth of
37
+ * each cascade.
38
+ */
39
+ export declare function cascadeSplits(out: Float32Array, count: number, near: number, far: number, lambda: number): Float32Array;
40
+ /**
41
+ * The bounding sphere of a view-frustum slice [a, b] (view depths): its center's view depth and its
42
+ * radius. Depends only on the projection, so the sphere doesn't change size as the camera turns.
43
+ */
44
+ export declare function sliceSphere(cam: CameraData, a: number, b: number): [number, number];
45
+ /**
46
+ * Fits and culls a camera's cascades for a sun direction (toward the light). Each cascade is the
47
+ * slice's bounding sphere, snapped to texels in light space, with its depth range reaching back to
48
+ * the casters that can shade it.
49
+ */
50
+ export declare function fitCascades(cascades: Cascades, cam: CameraData, sun: {
51
+ direction: Float32Array;
52
+ count: number;
53
+ maxDistance: number;
54
+ splitLambda: number;
55
+ }, size: number, cull: (view: ShadowViewDraw, planes: Float32Array, box: Float32Array) => boolean): void;
56
+ /** Spot and point shadows: shared by every camera, rendered once per frame. */
57
+ export declare class LocalShadows {
58
+ readonly spots: (LightRecord | undefined)[];
59
+ readonly points: (LightRecord | undefined)[];
60
+ readonly spotViews: ShadowViewDraw[];
61
+ readonly pointViews: ShadowViewDraw[];
62
+ /** Near plane and tan(half angle) per spot. */
63
+ readonly spotParams: Float32Array<ArrayBuffer>;
64
+ /** Position and near plane per point light. */
65
+ readonly pointPositions: Float32Array<ArrayBuffer>;
66
+ /** Shadow-casting lights that didn't fit the budget this frame. */
67
+ overBudget: LightRecord[];
68
+ spotTexture: GPUTexture | undefined;
69
+ pointTexture: GPUTexture | undefined;
70
+ spotLayers: number;
71
+ pointLayers: number;
72
+ size: number;
73
+ generation: number;
74
+ /** The frame these were last rendered (they render once per frame, not per view). */
75
+ renderedFrame: number;
76
+ constructor();
77
+ ensureTextures(gpu: GpuContext, size: number): void;
78
+ }
79
+ /**
80
+ * Ranks shadow-casting spot and point lights by screen-space influence from `cam` and assigns
81
+ * shadow maps within the budgets. Lights over budget render without shadows this frame.
82
+ */
83
+ export declare function assignLocalShadows(shadows: LocalShadows, store: LightStore, cam: CameraData | undefined, maxSpots: number, maxPoints: number): void;
84
+ /** Builds the spot and point shadow view matrices for the assigned lights. */
85
+ export declare function fitLocalShadows(shadows: LocalShadows, size: number): void;
86
+ /** Packs a view's ShadowData: its cascades plus the shared spot and point shadows. */
87
+ export declare function packShadowData(out: Float32Array, cascades: Cascades | undefined, sun: {
88
+ direction: Float32Array;
89
+ bias: number;
90
+ normalBias: number;
91
+ softness: number;
92
+ } | null, local: LocalShadows, cascadeSize: number, size: number): void;
93
+ /** Shadow view matrices for every shadow pass in the frame, at 256-byte offsets. */
94
+ export declare class ShadowPassUniforms {
95
+ data: Float32Array<ArrayBuffer>;
96
+ count: number;
97
+ readonly buffer: GpuBuffer;
98
+ bindGroup: GPUBindGroup | undefined;
99
+ private bound;
100
+ private readonly gpu;
101
+ constructor(gpu: GpuContext);
102
+ reset(): void;
103
+ /** Adds a view matrix; returns its dynamic offset. */
104
+ push(viewProj: Float32Array): number;
105
+ upload(layout: GPUBindGroupLayout, globals: GpuBuffer): void;
106
+ }
107
+ export declare function shadowViewLayout(gpu: GpuContext): GPUBindGroupLayout;
108
+ /**
109
+ * Draws a shadow view's casters into a depth layer, with each material type's vertex stage (so
110
+ * `vertex_position` displacement moves shadows too). Opaque materials are vertex-only; masked
111
+ * standard materials run the surface stage to discard.
112
+ */
113
+ export declare function drawShadowCasters(ctx: NodeContext, target: GPUTextureView, view: ShadowViewDraw, offset: number, uniforms: ShadowPassUniforms, pipelines: MaterialPipelines, shadowView: GPUBindGroupLayout, label: string): void;
114
+ /** Culls instances that cast shadows into a shadow view, and unions their world bounds. */
115
+ export declare function cullCasters(world: World, view: ShadowViewDraw, planes: Float32Array, box?: Float32Array): boolean;
116
+ export declare const ShadowsResource: import("@aethervtt/shard-core").ResourceDef<{
117
+ local: LocalShadows;
118
+ uniforms: ShadowPassUniforms;
119
+ }>;
@@ -0,0 +1,562 @@
1
+ import { defineResource, frustum, mat4 } from "@aethervtt/shard-core";
2
+ import { GpuBuffer } from "@aethervtt/shard-gpu";
3
+ import { Culler } from "./culling.js";
4
+ import { GpuAssetsResource } from "./gpu-assets.js";
5
+ import { createDrawList, InstanceFlags, Instances } from "./instances.js";
6
+ import { setShadowIndex } from "./lights.js";
7
+ import {
8
+ materialVariant,
9
+ typeOrdinal,
10
+ variantBlend,
11
+ variantCull
12
+ } from "./material-pipelines.js";
13
+ const MAX_CASCADES = 4;
14
+ const MAX_SHADOWED_SPOTS = 8;
15
+ const MAX_SHADOWED_POINTS = 4;
16
+ const SHADOW_DATA_FLOATS = 644;
17
+ const OFF = {
18
+ cascadeViewProj: 0,
19
+ cascadeSplits: 64,
20
+ cascadeTexel: 68,
21
+ cascadeParams: 72,
22
+ cascadeLight: 76,
23
+ spotViewProj: 80,
24
+ pointPosition: 208,
25
+ pointViewProj: 224,
26
+ sizes: 608,
27
+ spotParams: 612
28
+ };
29
+ function pointTanHalf(size) {
30
+ return 1 + 3 / size;
31
+ }
32
+ const VIEW_STRIDE = 256;
33
+ function shadowView() {
34
+ return { viewProj: mat4.create(), frustum: frustum.create(), draws: createDrawList(), offset: 0 };
35
+ }
36
+ class Cascades {
37
+ count = 0;
38
+ views = [shadowView(), shadowView(), shadowView(), shadowView()];
39
+ splits = new Float32Array(4);
40
+ texel = new Float32Array(4);
41
+ /** Light-space centers after snapping (for tests: sub-texel moves don't change them). */
42
+ centers = new Float32Array(12);
43
+ texture;
44
+ size = 0;
45
+ generation = -1;
46
+ ensureTexture(gpu, size, label) {
47
+ if (!this.texture || this.size !== size || this.generation !== gpu.generation) {
48
+ this.texture?.destroy();
49
+ this.texture = gpu.device.createTexture({
50
+ label: `${label}/cascades`,
51
+ size: [size, size, MAX_CASCADES],
52
+ format: "depth32float",
53
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC
54
+ });
55
+ this.size = size;
56
+ this.generation = gpu.generation;
57
+ }
58
+ return this.texture;
59
+ }
60
+ }
61
+ const scratchPlanes = new Float32Array(24);
62
+ const scratchBox = new Float32Array(6);
63
+ function lightBasis(out, fx, fy, fz) {
64
+ let ux = 0;
65
+ let uy = 1;
66
+ let uz = 0;
67
+ if (Math.abs(fy) > 0.99) {
68
+ ux = 1;
69
+ uy = 0;
70
+ }
71
+ let rx = fy * uz - fz * uy;
72
+ let ry = fz * ux - fx * uz;
73
+ let rz = fx * uy - fy * ux;
74
+ const rl = Math.sqrt(rx * rx + ry * ry + rz * rz);
75
+ rx /= rl;
76
+ ry /= rl;
77
+ rz /= rl;
78
+ ux = ry * fz - rz * fy;
79
+ uy = rz * fx - rx * fz;
80
+ uz = rx * fy - ry * fx;
81
+ out[0] = rx;
82
+ out[1] = ry;
83
+ out[2] = rz;
84
+ out[3] = ux;
85
+ out[4] = uy;
86
+ out[5] = uz;
87
+ out[6] = fx;
88
+ out[7] = fy;
89
+ out[8] = fz;
90
+ return out;
91
+ }
92
+ const basis = new Float32Array(9);
93
+ const scratchLightView = mat4.create();
94
+ const scratchLightProj = mat4.create();
95
+ function cascadeSplits(out, count, near, far, lambda) {
96
+ for (let i = 1; i <= count; i++) {
97
+ const log = near * (far / near) ** (i / count);
98
+ const uniform = near + (far - near) * i / count;
99
+ out[i - 1] = lambda * log + (1 - lambda) * uniform;
100
+ }
101
+ return out;
102
+ }
103
+ function sliceSphere(cam, a, b) {
104
+ let ha;
105
+ let hb;
106
+ if (cam.orthographic) {
107
+ const hy = cam.orthoHeight / 2;
108
+ const hx = hy * cam.aspect;
109
+ ha = hb = hx * hx + hy * hy;
110
+ } else {
111
+ const t = Math.tan(cam.fovY / 2);
112
+ const k = 1 + cam.aspect * cam.aspect;
113
+ ha = a * a * t * t * k;
114
+ hb = b * b * t * t * k;
115
+ }
116
+ let c = (a + b) / 2 + (hb - ha) / (2 * (b - a));
117
+ if (c > b) c = b;
118
+ if (c < a) c = a;
119
+ const r = Math.sqrt(Math.max(hb + (b - c) * (b - c), ha + (c - a) * (c - a)));
120
+ return [c, r];
121
+ }
122
+ function fitCascades(cascades, cam, sun, size, cull) {
123
+ const count = Math.min(MAX_CASCADES, Math.max(1, sun.count));
124
+ cascades.count = count;
125
+ const near = Math.max(cam.near, 1e-3);
126
+ const far = Math.max(near * 1.01, sun.maxDistance);
127
+ cascadeSplits(cascades.splits, count, near, far, sun.splitLambda);
128
+ for (let i = count; i < 4; i++) cascades.splits[i] = 0;
129
+ const fx = -sun.direction[0];
130
+ const fy = -sun.direction[1];
131
+ const fz = -sun.direction[2];
132
+ lightBasis(basis, fx, fy, fz);
133
+ let start = near;
134
+ for (let i = 0; i < count; i++) {
135
+ const end = cascades.splits[i];
136
+ const [depth, radius] = sliceSphere(cam, start, end);
137
+ start = end;
138
+ const cxw = cam.position[0] + cam.forward[0] * depth;
139
+ const cyw = cam.position[1] + cam.forward[1] * depth;
140
+ const czw = cam.position[2] + cam.forward[2] * depth;
141
+ const texel = 2 * radius / size;
142
+ const lx = Math.floor((basis[0] * cxw + basis[1] * cyw + basis[2] * czw) / texel) * texel;
143
+ const ly = Math.floor((basis[3] * cxw + basis[4] * cyw + basis[5] * czw) / texel) * texel;
144
+ const lz = Math.floor((basis[6] * cxw + basis[7] * cyw + basis[8] * czw) / texel) * texel;
145
+ cascades.centers[i * 3] = lx;
146
+ cascades.centers[i * 3 + 1] = ly;
147
+ cascades.centers[i * 3 + 2] = lz;
148
+ cascades.texel[i] = texel;
149
+ const p = scratchPlanes;
150
+ const set = (k, nx, ny, nz, d) => {
151
+ p[k * 4] = nx;
152
+ p[k * 4 + 1] = ny;
153
+ p[k * 4 + 2] = nz;
154
+ p[k * 4 + 3] = d;
155
+ };
156
+ set(0, basis[0], basis[1], basis[2], -(lx - radius));
157
+ set(1, -basis[0], -basis[1], -basis[2], lx + radius);
158
+ set(2, basis[3], basis[4], basis[5], -(ly - radius));
159
+ set(3, -basis[3], -basis[4], -basis[5], ly + radius);
160
+ set(4, -basis[6], -basis[7], -basis[8], lz + radius);
161
+ set(5, 0, 0, 0, 1);
162
+ const view = cascades.views[i];
163
+ const any = cull(view, p, scratchBox);
164
+ let zNear = lz - radius;
165
+ if (any) {
166
+ for (let c = 0; c < 8; c++) {
167
+ const x = c & 1 ? scratchBox[3] : scratchBox[0];
168
+ const y = c & 2 ? scratchBox[4] : scratchBox[1];
169
+ const z = c & 4 ? scratchBox[5] : scratchBox[2];
170
+ const d = basis[6] * x + basis[7] * y + basis[8] * z;
171
+ if (d < zNear) zNear = d;
172
+ }
173
+ }
174
+ const q = radius * 0.25;
175
+ zNear = Math.floor((zNear - q * 0.5) / q) * q;
176
+ const zFar = lz + radius;
177
+ const v = mat4.identity(scratchLightView);
178
+ v[0] = basis[0];
179
+ v[4] = basis[1];
180
+ v[8] = basis[2];
181
+ v[1] = basis[3];
182
+ v[5] = basis[4];
183
+ v[9] = basis[5];
184
+ v[2] = -basis[6];
185
+ v[6] = -basis[7];
186
+ v[10] = -basis[8];
187
+ const proj = mat4.orthographicReversedZ(
188
+ scratchLightProj,
189
+ lx - radius,
190
+ lx + radius,
191
+ ly - radius,
192
+ ly + radius,
193
+ zNear,
194
+ zFar
195
+ );
196
+ mat4.multiply(view.viewProj, proj, v);
197
+ frustum.fromViewProjection(view.frustum, view.viewProj);
198
+ }
199
+ }
200
+ class LocalShadows {
201
+ spots = [];
202
+ points = [];
203
+ spotViews = [];
204
+ pointViews = [];
205
+ /** Near plane and tan(half angle) per spot. */
206
+ spotParams = new Float32Array(MAX_SHADOWED_SPOTS * 4);
207
+ /** Position and near plane per point light. */
208
+ pointPositions = new Float32Array(MAX_SHADOWED_POINTS * 4);
209
+ /** Shadow-casting lights that didn't fit the budget this frame. */
210
+ overBudget = [];
211
+ spotTexture;
212
+ pointTexture;
213
+ spotLayers = 0;
214
+ pointLayers = 0;
215
+ size = 0;
216
+ generation = -1;
217
+ /** The frame these were last rendered (they render once per frame, not per view). */
218
+ renderedFrame = -1;
219
+ constructor() {
220
+ for (let i = 0; i < MAX_SHADOWED_SPOTS; i++) this.spotViews.push(shadowView());
221
+ for (let i = 0; i < MAX_SHADOWED_POINTS * 6; i++) this.pointViews.push(shadowView());
222
+ }
223
+ ensureTextures(gpu, size) {
224
+ const spotLayers = Math.max(1, this.spots.length);
225
+ const pointLayers = Math.max(1, this.points.length) * 6;
226
+ const fresh = this.generation !== gpu.generation || this.size !== size;
227
+ const make = (label, layers) => gpu.device.createTexture({
228
+ label,
229
+ size: [size, size, layers],
230
+ format: "depth32float",
231
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC
232
+ });
233
+ if (fresh || !this.spotTexture || spotLayers > this.spotLayers) {
234
+ this.spotTexture?.destroy();
235
+ this.spotLayers = Math.max(spotLayers, fresh ? 1 : this.spotLayers);
236
+ this.spotTexture = make("shadows/spots", this.spotLayers);
237
+ }
238
+ if (fresh || !this.pointTexture || pointLayers > this.pointLayers) {
239
+ this.pointTexture?.destroy();
240
+ this.pointLayers = Math.max(pointLayers, fresh ? 6 : this.pointLayers);
241
+ this.pointTexture = make("shadows/points", this.pointLayers);
242
+ }
243
+ this.size = size;
244
+ this.generation = gpu.generation;
245
+ }
246
+ }
247
+ const FACES = [
248
+ [1, 0, 0, 0, -1, 0],
249
+ [-1, 0, 0, 0, -1, 0],
250
+ [0, 1, 0, 0, 0, 1],
251
+ [0, -1, 0, 0, 0, -1],
252
+ [0, 0, 1, 0, -1, 0],
253
+ [0, 0, -1, 0, -1, 0]
254
+ ];
255
+ function lookDir(out, px, py, pz, dx, dy, dz, upx, upy, upz) {
256
+ return mat4.lookAt(out, [px, py, pz], [px + dx, py + dy, pz + dz], [upx, upy, upz]);
257
+ }
258
+ const scratchView = mat4.create();
259
+ const scratchProj = mat4.create();
260
+ function assignLocalShadows(shadows, store, cam, maxSpots, maxPoints) {
261
+ const spots = [];
262
+ const points = [];
263
+ for (let s = 0; s < store.high; s++) {
264
+ const r = store.records[s];
265
+ if (!r?.alive) continue;
266
+ if (!r.shadows || !cam) {
267
+ setShadowIndex(store, r, -1);
268
+ continue;
269
+ }
270
+ const dx = r.x - cam.position[0];
271
+ const dy = r.y - cam.position[1];
272
+ const dz = r.z - cam.position[2];
273
+ const d = Math.sqrt(dx * dx + dy * dy + dz * dz);
274
+ let score = r.range / Math.max(d - r.range, 1e-3);
275
+ let visible = true;
276
+ for (let p = 0; p < 24; p += 4) {
277
+ const f = cam.frustum;
278
+ if (f[p] * r.x + f[p + 1] * r.y + f[p + 2] * r.z + f[p + 3] < -r.range) visible = false;
279
+ }
280
+ if (!visible) score *= 1e-6;
281
+ (r.kind === 1 ? spots : points).push({ r, score });
282
+ }
283
+ const byScore = (a, b) => b.score - a.score || b.r.intensity - a.r.intensity || a.r.slot - b.r.slot;
284
+ spots.sort(byScore);
285
+ points.sort(byScore);
286
+ const over = [];
287
+ shadows.spots.length = 0;
288
+ shadows.points.length = 0;
289
+ spots.forEach(({ r }, i) => {
290
+ if (i < Math.min(maxSpots, MAX_SHADOWED_SPOTS)) {
291
+ setShadowIndex(store, r, i);
292
+ shadows.spots.push(r);
293
+ } else {
294
+ setShadowIndex(store, r, -1);
295
+ over.push(r);
296
+ }
297
+ });
298
+ points.forEach(({ r }, i) => {
299
+ if (i < Math.min(maxPoints, MAX_SHADOWED_POINTS)) {
300
+ setShadowIndex(store, r, i);
301
+ shadows.points.push(r);
302
+ } else {
303
+ setShadowIndex(store, r, -1);
304
+ over.push(r);
305
+ }
306
+ });
307
+ shadows.overBudget = over;
308
+ }
309
+ function fitLocalShadows(shadows, size) {
310
+ for (let i = 0; i < shadows.spots.length; i++) {
311
+ const r = shadows.spots[i];
312
+ const near = Math.max(0.05, r.radius);
313
+ const tanHalf2 = Math.tan(Math.min(r.outerAngle, 89) * Math.PI / 180) * (1 + 3 / size);
314
+ const up = Math.abs(r.dy) > 0.99 ? [1, 0, 0] : [0, 1, 0];
315
+ lookDir(scratchView, r.x, r.y, r.z, r.dx, r.dy, r.dz, up[0], up[1], up[2]);
316
+ mat4.perspectiveReversedZ(scratchProj, 2 * Math.atan(tanHalf2), 1, near);
317
+ const view = shadows.spotViews[i];
318
+ mat4.multiply(view.viewProj, scratchProj, scratchView);
319
+ frustum.fromViewProjection(view.frustum, view.viewProj);
320
+ shadows.spotParams[i * 4] = tanHalf2;
321
+ shadows.spotParams[i * 4 + 1] = near;
322
+ }
323
+ const tanHalf = pointTanHalf(size);
324
+ for (let i = 0; i < shadows.points.length; i++) {
325
+ const r = shadows.points[i];
326
+ const near = Math.max(0.05, r.radius);
327
+ shadows.pointPositions[i * 4] = r.x;
328
+ shadows.pointPositions[i * 4 + 1] = r.y;
329
+ shadows.pointPositions[i * 4 + 2] = r.z;
330
+ shadows.pointPositions[i * 4 + 3] = near;
331
+ mat4.perspectiveReversedZ(scratchProj, 2 * Math.atan(tanHalf), 1, near);
332
+ for (let f = 0; f < 6; f++) {
333
+ const face = FACES[f];
334
+ lookDir(scratchView, r.x, r.y, r.z, face[0], face[1], face[2], face[3], face[4], face[5]);
335
+ const view = shadows.pointViews[i * 6 + f];
336
+ mat4.multiply(view.viewProj, scratchProj, scratchView);
337
+ frustum.fromViewProjection(view.frustum, view.viewProj);
338
+ }
339
+ }
340
+ }
341
+ function packShadowData(out, cascades, sun, local, cascadeSize, size) {
342
+ out.fill(0);
343
+ if (cascades && sun && cascades.count > 0) {
344
+ for (let i = 0; i < cascades.count; i++) {
345
+ out.set(cascades.views[i].viewProj, OFF.cascadeViewProj + i * 16);
346
+ out[OFF.cascadeSplits + i] = cascades.splits[i];
347
+ out[OFF.cascadeTexel + i] = cascades.texel[i];
348
+ }
349
+ out[OFF.cascadeParams] = cascades.count;
350
+ out[OFF.cascadeParams + 1] = sun.bias;
351
+ out[OFF.cascadeParams + 2] = sun.normalBias;
352
+ out[OFF.cascadeParams + 3] = sun.softness;
353
+ out[OFF.cascadeLight] = sun.direction[0];
354
+ out[OFF.cascadeLight + 1] = sun.direction[1];
355
+ out[OFF.cascadeLight + 2] = sun.direction[2];
356
+ out[OFF.cascadeLight + 3] = 1;
357
+ }
358
+ for (let i = 0; i < local.spots.length; i++) {
359
+ out.set(local.spotViews[i].viewProj, OFF.spotViewProj + i * 16);
360
+ out[OFF.spotParams + i * 4] = local.spotParams[i * 4];
361
+ out[OFF.spotParams + i * 4 + 1] = local.spotParams[i * 4 + 1];
362
+ }
363
+ for (let i = 0; i < local.points.length; i++) {
364
+ for (let k = 0; k < 4; k++)
365
+ out[OFF.pointPosition + i * 4 + k] = local.pointPositions[i * 4 + k];
366
+ for (let f = 0; f < 6; f++)
367
+ out.set(local.pointViews[i * 6 + f].viewProj, OFF.pointViewProj + (i * 6 + f) * 16);
368
+ }
369
+ out[OFF.sizes] = cascadeSize;
370
+ out[OFF.sizes + 1] = size;
371
+ out[OFF.sizes + 2] = pointTanHalf(size);
372
+ }
373
+ class ShadowPassUniforms {
374
+ data = new Float32Array(VIEW_STRIDE / 4 * 64);
375
+ count = 0;
376
+ buffer;
377
+ bindGroup;
378
+ bound = "";
379
+ gpu;
380
+ constructor(gpu) {
381
+ this.gpu = gpu;
382
+ this.buffer = new GpuBuffer(gpu, {
383
+ label: "shadows/views",
384
+ usage: GPUBufferUsage.UNIFORM,
385
+ size: VIEW_STRIDE * 64
386
+ });
387
+ }
388
+ reset() {
389
+ this.count = 0;
390
+ }
391
+ /** Adds a view matrix; returns its dynamic offset. */
392
+ push(viewProj) {
393
+ const floats = VIEW_STRIDE / 4;
394
+ if ((this.count + 1) * floats > this.data.length) {
395
+ const grown = new Float32Array(this.data.length * 2);
396
+ grown.set(this.data);
397
+ this.data = grown;
398
+ }
399
+ this.data.set(viewProj, this.count * floats);
400
+ return this.count++ * VIEW_STRIDE;
401
+ }
402
+ upload(layout, globals) {
403
+ if (this.count > 0) this.buffer.write(this.data, 0, 0, this.count * VIEW_STRIDE / 4);
404
+ const key = `${this.buffer.version}/${globals.version}/${this.gpu.generation}`;
405
+ if (!this.bindGroup || this.bound !== key) {
406
+ this.bindGroup = this.gpu.device.createBindGroup({
407
+ label: "shadows/views",
408
+ layout,
409
+ entries: [
410
+ { binding: 0, resource: { buffer: this.buffer.buffer, size: 64 } },
411
+ { binding: 14, resource: { buffer: globals.buffer } }
412
+ ]
413
+ });
414
+ this.bound = key;
415
+ }
416
+ }
417
+ }
418
+ function shadowViewLayout(gpu) {
419
+ return gpu.layouts.bindGroupLayout({
420
+ label: "shadows/view",
421
+ entries: [
422
+ {
423
+ binding: 0,
424
+ visibility: GPUShaderStage.VERTEX,
425
+ buffer: { type: "uniform", hasDynamicOffset: true }
426
+ },
427
+ {
428
+ binding: 14,
429
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
430
+ buffer: { type: "uniform" }
431
+ }
432
+ ]
433
+ });
434
+ }
435
+ const SHADOW_BUFFERS = [
436
+ { arrayStride: 12, attributes: [{ shaderLocation: 0, offset: 0, format: "float32x3" }] },
437
+ { arrayStride: 12, attributes: [{ shaderLocation: 1, offset: 0, format: "float32x3" }] },
438
+ { arrayStride: 8, attributes: [{ shaderLocation: 2, offset: 0, format: "float32x2" }] },
439
+ { arrayStride: 8, attributes: [{ shaderLocation: 3, offset: 0, format: "float32x2" }] },
440
+ { arrayStride: 16, attributes: [{ shaderLocation: 4, offset: 0, format: "float32x4" }] }
441
+ ];
442
+ function drawShadowCasters(ctx, target, view, offset, uniforms, pipelines, shadowView2, label) {
443
+ const world = ctx.world;
444
+ const gpu = ctx.gpu;
445
+ const store = world.resource(Instances);
446
+ const assets = world.resource(GpuAssetsResource);
447
+ const pass = ctx.encoder.beginRenderPass({
448
+ label,
449
+ colorAttachments: [],
450
+ depthStencilAttachment: {
451
+ view: target,
452
+ depthLoadOp: "clear",
453
+ depthClearValue: 0,
454
+ depthStoreOp: "store"
455
+ },
456
+ timestampWrites: ctx.timestamps(label)
457
+ });
458
+ const draws0 = view.draws;
459
+ const instances = draws0.cullView >= 0 ? store.gpuBindGroup : store.bindGroup;
460
+ if (!instances || !uniforms.bindGroup) {
461
+ pass.end();
462
+ return;
463
+ }
464
+ const args = world.resource(Culler).args.buffer;
465
+ pass.setBindGroup(0, uniforms.bindGroup, [offset]);
466
+ pass.setBindGroup(2, instances);
467
+ let current;
468
+ let boundMaterial;
469
+ let boundPositions;
470
+ let boundTangents;
471
+ let boundIndices;
472
+ const draws = view.draws;
473
+ for (let d = 0; d < draws.length; d++) {
474
+ const item = draws.items[d];
475
+ const material = item.batch.material;
476
+ const type = material.type;
477
+ const variant = materialVariant(material);
478
+ const masked = type.standard && variantBlend(variant) === "mask";
479
+ const key = ((PASS_SHADOW * 1024 + typeOrdinal(type)) * 16 + variant) * 8;
480
+ let pipeline = pipelines.cached(key);
481
+ if (!pipeline) {
482
+ const module = pipelines.module(world, gpu, type, 8, "shard::pbr::shadow", void 0);
483
+ if (!module) {
484
+ gpu.pipelines.skipped++;
485
+ continue;
486
+ }
487
+ pipeline = pipelines.create(gpu, key, {
488
+ label: `shadow/${type.name}/${masked ? "mask" : "opaque"}/${variantCull(variant)}`,
489
+ layout: gpu.layouts.pipelineLayout({
490
+ label: `shadow/${type.name}`,
491
+ bindGroupLayouts: [shadowView2, assets.layoutOf(type), store.layout]
492
+ }),
493
+ vertex: { module, entryPoint: "vs", buffers: SHADOW_BUFFERS },
494
+ fragment: masked ? { module, entryPoint: "fs_mask", targets: [] } : void 0,
495
+ primitive: { topology: "triangle-list", cullMode: variantCull(variant), frontFace: "ccw" },
496
+ depthStencil: { format: "depth32float", depthWriteEnabled: true, depthCompare: "greater" }
497
+ });
498
+ if (!pipeline) continue;
499
+ }
500
+ const mat = assets.material(world, material);
501
+ if (!mat?.bindGroup) continue;
502
+ const gm = assets.mesh(item.batch.mesh);
503
+ if (pipeline !== current) {
504
+ pass.setPipeline(pipeline);
505
+ current = pipeline;
506
+ boundMaterial = void 0;
507
+ }
508
+ if (mat.bindGroup !== boundMaterial) {
509
+ pass.setBindGroup(1, mat.bindGroup);
510
+ boundMaterial = mat.bindGroup;
511
+ }
512
+ if (gm.positions !== boundPositions || gm.tangents !== boundTangents) {
513
+ pass.setVertexBuffer(0, gm.positions);
514
+ pass.setVertexBuffer(1, gm.normals);
515
+ pass.setVertexBuffer(2, gm.uvs);
516
+ pass.setVertexBuffer(3, gm.uvs1);
517
+ pass.setVertexBuffer(4, gm.tangents);
518
+ boundPositions = gm.positions;
519
+ boundTangents = gm.tangents;
520
+ }
521
+ if (gm.indices) {
522
+ if (gm.indices !== boundIndices) {
523
+ pass.setIndexBuffer(gm.indices, gm.indexFormat);
524
+ boundIndices = gm.indices;
525
+ }
526
+ if (item.indirect >= 0) pass.drawIndexedIndirect(args, item.indirect);
527
+ else pass.drawIndexed(gm.count, item.count, 0, gm.baseVertex, item.first);
528
+ } else if (item.indirect >= 0) {
529
+ pass.drawIndirect(args, item.indirect);
530
+ } else {
531
+ pass.draw(gm.count, item.count, 0, item.first);
532
+ }
533
+ }
534
+ pass.end();
535
+ }
536
+ const PASS_SHADOW = 2;
537
+ function cullCasters(world, view, planes, box) {
538
+ const store = world.resource(Instances);
539
+ store.cull(view.draws, planes, InstanceFlags.Caster, box);
540
+ return view.draws.visible > 0;
541
+ }
542
+ const ShadowsResource = defineResource("render/Shadows", { description: "Spot and point shadow maps, shared by all cameras." });
543
+ export {
544
+ Cascades,
545
+ LocalShadows,
546
+ MAX_CASCADES,
547
+ MAX_SHADOWED_POINTS,
548
+ MAX_SHADOWED_SPOTS,
549
+ SHADOW_DATA_FLOATS,
550
+ ShadowPassUniforms,
551
+ ShadowsResource,
552
+ assignLocalShadows,
553
+ cascadeSplits,
554
+ cullCasters,
555
+ drawShadowCasters,
556
+ fitCascades,
557
+ fitLocalShadows,
558
+ packShadowData,
559
+ pointTanHalf,
560
+ shadowViewLayout,
561
+ sliceSphere
562
+ };