reze-engine 0.63.0 → 0.65.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.
Files changed (106) hide show
  1. package/dist/engine.d.ts +226 -96
  2. package/dist/engine.d.ts.map +1 -1
  3. package/dist/engine.js +1187 -732
  4. package/dist/graph/compile.d.ts.map +1 -1
  5. package/dist/graph/compile.js +4 -1
  6. package/dist/graph/registry.d.ts +3 -0
  7. package/dist/graph/registry.d.ts.map +1 -1
  8. package/dist/graph/registry.js +133 -0
  9. package/dist/graph/schema.d.ts +12 -0
  10. package/dist/graph/schema.d.ts.map +1 -1
  11. package/dist/graph/slots.d.ts.map +1 -1
  12. package/dist/graph/slots.js +15 -18
  13. package/dist/index.d.ts +9 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/model.d.ts +10 -1
  17. package/dist/model.d.ts.map +1 -1
  18. package/dist/model.js +15 -1
  19. package/dist/outline-normals.d.ts +12 -0
  20. package/dist/outline-normals.d.ts.map +1 -0
  21. package/dist/outline-normals.js +100 -0
  22. package/dist/pmx-loader.d.ts +3 -0
  23. package/dist/pmx-loader.d.ts.map +1 -1
  24. package/dist/pmx-loader.js +7 -2
  25. package/dist/shaders/lights.d.ts +3 -2
  26. package/dist/shaders/lights.d.ts.map +1 -1
  27. package/dist/shaders/lights.js +16 -3
  28. package/dist/shaders/materials/common.d.ts.map +1 -1
  29. package/dist/shaders/materials/common.js +65 -56
  30. package/dist/shaders/materials/nodes.d.ts +1 -1
  31. package/dist/shaders/materials/nodes.d.ts.map +1 -1
  32. package/dist/shaders/materials/nodes.js +210 -0
  33. package/dist/shaders/passes/bloom.d.ts +4 -3
  34. package/dist/shaders/passes/bloom.d.ts.map +1 -1
  35. package/dist/shaders/passes/bloom.js +58 -87
  36. package/dist/shaders/passes/composite.d.ts.map +1 -1
  37. package/dist/shaders/passes/composite.js +122 -64
  38. package/dist/shaders/passes/cull.d.ts +1 -1
  39. package/dist/shaders/passes/cull.d.ts.map +1 -1
  40. package/dist/shaders/passes/cull.js +7 -3
  41. package/dist/shaders/passes/ground.d.ts.map +1 -1
  42. package/dist/shaders/passes/ground.js +32 -55
  43. package/dist/shaders/passes/mirror.d.ts +18 -12
  44. package/dist/shaders/passes/mirror.d.ts.map +1 -1
  45. package/dist/shaders/passes/mirror.js +59 -13
  46. package/dist/shaders/passes/outline.d.ts.map +1 -1
  47. package/dist/shaders/passes/outline.js +71 -14
  48. package/dist/shaders/passes/particles.d.ts +1 -1
  49. package/dist/shaders/passes/particles.js +1 -1
  50. package/dist/shaders/scene-light-api.d.ts +18 -10
  51. package/dist/shaders/scene-light-api.d.ts.map +1 -1
  52. package/dist/shaders/scene-light-api.js +58 -46
  53. package/dist/shadow-cascades.d.ts +47 -20
  54. package/dist/shadow-cascades.d.ts.map +1 -1
  55. package/dist/shadow-cascades.js +36 -13
  56. package/dist/unity/character.d.ts +59 -0
  57. package/dist/unity/character.d.ts.map +1 -0
  58. package/dist/unity/character.js +120 -0
  59. package/dist/unity/globals.d.ts +93 -0
  60. package/dist/unity/globals.d.ts.map +1 -0
  61. package/dist/unity/globals.js +340 -0
  62. package/dist/unity/host.d.ts +152 -0
  63. package/dist/unity/host.d.ts.map +1 -0
  64. package/dist/unity/host.js +529 -0
  65. package/dist/unity/looks.d.ts +93 -0
  66. package/dist/unity/looks.d.ts.map +1 -0
  67. package/dist/unity/looks.js +312 -0
  68. package/dist/unity/skin.d.ts +9 -0
  69. package/dist/unity/skin.d.ts.map +1 -0
  70. package/dist/unity/skin.js +120 -0
  71. package/dist/unity/stage.d.ts +155 -0
  72. package/dist/unity/stage.d.ts.map +1 -0
  73. package/dist/unity/stage.js +410 -0
  74. package/dist/unity/state.d.ts +22 -0
  75. package/dist/unity/state.d.ts.map +1 -0
  76. package/dist/unity/state.js +170 -0
  77. package/package.json +1 -1
  78. package/src/engine.ts +1535 -964
  79. package/src/graph/compile.ts +3 -1
  80. package/src/graph/registry.ts +142 -0
  81. package/src/graph/schema.ts +9 -0
  82. package/src/graph/slots.ts +15 -18
  83. package/src/index.ts +12 -0
  84. package/src/model.ts +18 -1
  85. package/src/outline-normals.ts +110 -0
  86. package/src/pmx-loader.ts +8 -2
  87. package/src/shaders/lights.ts +16 -3
  88. package/src/shaders/materials/common.ts +65 -57
  89. package/src/shaders/materials/nodes.ts +210 -0
  90. package/src/shaders/passes/bloom.ts +59 -87
  91. package/src/shaders/passes/composite.ts +122 -64
  92. package/src/shaders/passes/cull.ts +7 -3
  93. package/src/shaders/passes/ground.ts +32 -55
  94. package/src/shaders/passes/mirror.ts +60 -13
  95. package/src/shaders/passes/outline.ts +71 -14
  96. package/src/shaders/passes/particles.ts +1 -1
  97. package/src/shaders/scene-light-api.ts +59 -46
  98. package/src/shadow-cascades.ts +61 -32
  99. package/src/unity/character.ts +169 -0
  100. package/src/unity/globals.ts +443 -0
  101. package/src/unity/host.ts +630 -0
  102. package/src/unity/looks.ts +391 -0
  103. package/src/unity/skin.ts +131 -0
  104. package/src/unity/stage.ts +604 -0
  105. package/src/unity/state.ts +172 -0
  106. package/src/shaders/agx-lut.ts +0 -30
@@ -19,6 +19,9 @@ export class PmxLoader {
19
19
  this.inverseBindMatrices = null;
20
20
  this.joints0 = null;
21
21
  this.weights0 = null;
22
+ /** PMX per-vertex edge scale — the outline's width multiplier, as the
23
+ * game's outline takes vertex colour alpha. */
24
+ this.edgeScales = null;
22
25
  this.morphs = [];
23
26
  this.vertexCount = 0;
24
27
  this.rigidbodies = [];
@@ -117,6 +120,7 @@ export class PmxLoader {
117
120
  // Prepare skinning arrays (4 influences per vertex)
118
121
  const joints = new Uint16Array(count * 4);
119
122
  const weights = new Uint8Array(count * 4); // UNORM8, will be normalized to 255
123
+ const edgeScales = new Float32Array(count);
120
124
  for (let i = 0; i < count; i++) {
121
125
  const px = this.getFloat32();
122
126
  const py = this.getFloat32();
@@ -207,10 +211,11 @@ export class PmxLoader {
207
211
  else {
208
212
  throw new Error(`Invalid bone weight type: ${type}`);
209
213
  }
210
- this.offset += 4; // edge scale
214
+ edgeScales[i] = this.getFloat32();
211
215
  }
212
216
  this.joints0 = joints;
213
217
  this.weights0 = weights;
218
+ this.edgeScales = edgeScales;
214
219
  return { positions, normals, uvs };
215
220
  }
216
221
  // (removed) skipBoneWeight – replaced by inline parsing
@@ -1024,7 +1029,7 @@ export class PmxLoader {
1024
1029
  const morphing = {
1025
1030
  morphs: this.morphs,
1026
1031
  };
1027
- return new Model(vertexData, indexData, this.textures, this.materials, skeleton, skinning, morphing, this.rigidbodies, this.joints, this.warnings);
1032
+ return new Model(vertexData, indexData, this.textures, this.materials, skeleton, skinning, morphing, this.rigidbodies, this.joints, this.warnings, this.edgeScales);
1028
1033
  }
1029
1034
  getUint8() {
1030
1035
  if (this.offset >= this.view.buffer.byteLength) {
@@ -72,6 +72,7 @@ export declare function hasLightEmit(wgsl: string): boolean;
72
72
  export declare function buildLightEmitShader(wgsl: string, sceneApi: string, cast: {
73
73
  trailCount: number;
74
74
  }, paramsDecl?: string): string;
75
- /** The rz*Light accessors, with the buffer declared at the given binding. */
76
- export declare function lightsApi(group: number, binding: number): string;
75
+ /** The rz*Light accessors, with the buffer declared at the given binding.
76
+ * `layers` is the WGSL expression for the drawing's rendering-layer bits. */
77
+ export declare function lightsApi(group: number, binding: number, layers: string): string;
77
78
  //# sourceMappingURL=lights.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../src/shaders/lights.ts"],"names":[],"mappings":"AAqDA;uDACuD;AACvD,eAAO,MAAM,YAAY,KAAK,CAAA;AAC9B,+CAA+C;AAC/C,eAAO,MAAM,YAAY,KAAK,CAAA;AAC9B;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,MAAM,CAAA;AAC7B,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,QAAkB,CAAA;AAC/C;0EAC0E;AAC1E,eAAO,MAAM,gBAAgB,QAAQ,CAAA;AACrC,uCAAuC;AACvC,eAAO,MAAM,eAAe,QAA2C,CAAA;AACvE,wDAAwD;AACxD,eAAO,MAAM,aAAa,QAAwD,CAAA;AAElF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,qJAQhC,CAAA;AAED,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,QAA4B,CAAA;AAE3D,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,EAC5B,UAAU,SAAK,GACd,MAAM,CAoGR;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CA2JhE"}
1
+ {"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../src/shaders/lights.ts"],"names":[],"mappings":"AA4DA;uDACuD;AACvD,eAAO,MAAM,YAAY,KAAK,CAAA;AAC9B,+CAA+C;AAC/C,eAAO,MAAM,YAAY,KAAK,CAAA;AAC9B;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,MAAM,CAAA;AAC7B,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,QAAkB,CAAA;AAC/C;0EAC0E;AAC1E,eAAO,MAAM,gBAAgB,QAAQ,CAAA;AACrC,uCAAuC;AACvC,eAAO,MAAM,eAAe,QAA2C,CAAA;AACvE,wDAAwD;AACxD,eAAO,MAAM,aAAa,QAAwD,CAAA;AAElF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,qJAQhC,CAAA;AAED,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,QAA4B,CAAA;AAE3D,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,EAC5B,UAAU,SAAK,GACd,MAAM,CAoGR;AAED;8EAC8E;AAC9E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAgKhF"}
@@ -28,7 +28,8 @@ import { subjectMaskApi } from "./cast-api";
28
28
  // [0..2] position, world space [3] radius
29
29
  // [4..6] colour PREMULTIPLIED by intensity [7] type
30
30
  // [8..10] aim, unit, pointing away from the light [11] cos of the outer angle
31
- // [12] cos of the inner angle [13..15] spare
31
+ // [12] cos of the inner angle
32
+ // [13] the rendering layers it does NOT reach (u32 bits) [14..15] spare
32
33
  //
33
34
  // then the grid: LIGHT_MASK_WORDS words of lamp bits per cell.
34
35
  //
@@ -40,6 +41,12 @@ import { subjectMaskApi } from "./cast-api";
40
41
  // branchless: it stores aim (0,0,0) and both cosines at -1, which makes the
41
42
  // cone term below saturate to exactly 1. `type` says which a light is for
42
43
  // anyone reading the buffer; the shading never asks.
44
+ //
45
+ // LAYERS, as the game's pipeline (URP's rendering layers) gates its lights: a
46
+ // draw carries layer bits, and a light reaches it only where its own mask
47
+ // shares one. Stored INVERTED — the layers a light does not reach — so the
48
+ // zero every writer that predates layers leaves in [13] reaches everything,
49
+ // which is what an effect's lamp and a plain document light mean.
43
50
  import { castDistanceStub } from "./passes/cast-distance";
44
51
  import { audioApi } from "./audio-api";
45
52
  import { midiApi } from "./midi-api";
@@ -231,8 +238,9 @@ fn lightEmitMain(@builtin(global_invocation_id) gid: vec3u) {
231
238
  }
232
239
  `;
233
240
  }
234
- /** The rz*Light accessors, with the buffer declared at the given binding. */
235
- export function lightsApi(group, binding) {
241
+ /** The rz*Light accessors, with the buffer declared at the given binding.
242
+ * `layers` is the WGSL expression for the drawing's rendering-layer bits. */
243
+ export function lightsApi(group, binding, layers) {
236
244
  const R = LIGHT_HEADER / 4;
237
245
  const S = LIGHT_STRIDE / 4;
238
246
  return /* wgsl */ `
@@ -274,6 +282,10 @@ fn rzLightAim(i: u32) -> vec3f { return _rzLightVec(i, 2u).xyz; }
274
282
  * light stores (-1, -1), which saturates the cone term to 1. */
275
283
  fn rzLightCone(i: u32) -> vec2f { return vec2f(_rzLightVec(i, 2u).w, _rzLightVec(i, 3u).x); }
276
284
 
285
+ /** Does light i reach this drawing's rendering layers? Read as bits, never as
286
+ * a float — see the layout. */
287
+ fn _rzLightReaches(i: u32) -> bool { return ((${layers}) & ~_rzLights[${R}u + i * ${S}u + 3u].y) != 0u; }
288
+
277
289
  /** The document lamps that can reach p: its grid cell's bits, or the outside
278
290
  * mask beyond the grid. Written so a NaN position fails the inside test. */
279
291
  fn _rzLightCellMask(p: vec3f) -> vec4u {
@@ -300,6 +312,7 @@ fn _rzLightCellMask(p: vec3f) -> vec4u {
300
312
  * writes in candela over 683.
301
313
  */
302
314
  fn _rzLightOne(i: u32, p: vec3f, n: vec3f) -> vec3f {
315
+ if (!_rzLightReaches(i)) { return vec3f(0.0); }
303
316
  let pr = _rzLightVec(i, 0u);
304
317
  let d = pr.xyz - p;
305
318
  let dist = length(d);
@@ -1 +1 @@
1
- {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../src/shaders/materials/common.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,oBAAoB,QAqMhC,CAAC;AAmIF,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAGD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,aAAa,orMAgHzB,CAAA;AAQD,eAAO,MAAM,4BAA4B,QAC0D,CAAA"}
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../src/shaders/materials/common.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,oBAAoB,QA8NhC,CAAC;AAkHF,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAGD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,aAAa,orMAgHzB,CAAA;AAQD,eAAO,MAAM,4BAA4B,QACkF,CAAA"}
@@ -1,7 +1,7 @@
1
1
  import { sceneFsOutWgsl } from "../passes/scene-contract";
2
2
  import { lightsApi } from "../lights";
3
3
  import { SHADOW_CASCADES } from "../../shadow-cascades";
4
- import { pcf9 } from "../scene-light-api";
4
+ import { sunShadowWgsl } from "../scene-light-api";
5
5
  import { WORLD_AMBIENT_WGSL } from "../lights";
6
6
  // Shared WGSL blocks concatenated by every material shader.
7
7
  // Splits the boilerplate (uniform structs, bind group layout, skinning VS, PCF shadow)
@@ -58,6 +58,9 @@ struct LightUniforms {
58
58
  * amount 0 is off. Then the view-projection its map was drawn with. */
59
59
  castShadow: vec4f,
60
60
  castVP: mat4x4f,
61
+ /** The rendering layers each directional light reaches, slot by slot — a
62
+ * draw is lit by the first whose bits it shares (rzMainLight). */
63
+ dirLayers: vec4u,
61
64
  };
62
65
 
63
66
  // Per-material uniforms. Every material binds this layout even if it ignores fields;
@@ -145,13 +148,10 @@ struct LightVP { viewProj: array<mat4x4f, ${SHADOW_CASCADES.length}>, };
145
148
  @group(0) @binding(0) var<uniform> camera: CameraUniforms;
146
149
  @group(0) @binding(1) var<uniform> light: LightUniforms;
147
150
  @group(0) @binding(2) var diffuseSampler: sampler;
148
- @group(0) @binding(3) var shadowMap: texture_depth_2d;
151
+ // The sun's shadow atlas: every cascade in its own tile — see shadow-cascades.ts.
152
+ @group(0) @binding(3) var shadowAtlas: texture_depth_2d;
149
153
  @group(0) @binding(4) var shadowSampler: sampler_comparison;
150
154
  @group(0) @binding(5) var<uniform> lightVP: LightVP;
151
- // The far cascade's map — coarser texels over a much wider box, so the stage
152
- // keeps its shadows when the crisp near volume ends. Binding 7: 6 is the
153
- // positional lights, 9 the BRDF LUT.
154
- @group(0) @binding(7) var shadowMapFar: texture_depth_2d;
155
155
  // The world's own sky, for a surface that REFLECTS it rather than merely being
156
156
  // lit by it. Equirect, in the same layout and the same linear radiance the
157
157
  // composite draws the dome from, and mipped — a rough surface reads a coarser
@@ -185,14 +185,39 @@ fn _rzCastShadow(wp: vec3f) -> f32 {
185
185
  }
186
186
  // binding(9) brdfLut is declared inside NODES_WGSL (nodes.ts).
187
187
  @group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
188
- // The light this model takes apart from the scene's — see Engine.setModelFill
189
- // and Engine.setModelSun. fill brightens it after its graph (fill ×
190
- // surface colour, graph/slots.ts), zero for a model nobody gave one; sun replaces the scene's sun colour while its w is set,
191
- // which is how a stage keeps the daylight its game lit it by while the cast
192
- // keeps the key the scene set for them. opts.x (Engine.setModelFlatSky) gives
193
- // it the sky's average in place of its shape - see graph/slots.ts.
194
- struct ModelLight { fill: vec4f, sun: vec4f, opts: vec4f }
195
- @group(1) @binding(1) var<uniform> modelLight: ModelLight;
188
+ // What this object's lighting is, apart from the scene's, as the game's
189
+ // pipeline gives each renderer its own:
190
+ // layers.x its rendering-layer bits, which decide the lights that reach it —
191
+ // a stage's daylight and the cast's key are two lights on two
192
+ // layers, as the game lights them;
193
+ // ambient.x 1 while it has an ambient of its own (Engine.setModelAmbient),
194
+ // 0 for the world's;
195
+ // sh that ambient's irradiance, folded as the world's (ibl.ts);
196
+ // fill.rgb its fill (Engine.setModelFill), added after its graph times its
197
+ // surface colour — zero for a model that has none.
198
+ struct ObjectLight { layers: vec4u, ambient: vec4f, sh: array<vec4f, 9>, fill: vec4f }
199
+ @group(1) @binding(1) var<uniform> objectLight: ObjectLight;
200
+
201
+ /** The light arriving at a surface of this object facing n: its own ambient
202
+ * where loaded data gave it one (a stage's probes, a character's), else the
203
+ * world's. The same polynomial rzWorldAmbient evaluates. */
204
+ fn rzObjectAmbient(n: vec3f) -> vec3f {
205
+ if (objectLight.ambient.x < 0.5) { return rzWorldAmbient(n); }
206
+ let s = objectLight.sh;
207
+ let c = s[0].xyz + s[1].xyz * n.y + s[2].xyz * n.z + s[3].xyz * n.x
208
+ + s[4].xyz * (n.x * n.y) + s[5].xyz * (n.y * n.z) + s[6].xyz * (3.0 * n.z * n.z - 1.0)
209
+ + s[7].xyz * (n.x * n.z) + s[8].xyz * (n.x * n.x - n.y * n.y);
210
+ return max(c, vec3f(0.0));
211
+ }
212
+
213
+ /** The directional light slot that lights this draw: the first whose layers
214
+ * it shares, else the sun's. */
215
+ fn rzMainLight() -> u32 {
216
+ for (var k = 0u; k < 4u; k++) {
217
+ if ((light.dirLayers[k] & objectLight.layers.x) != 0u) { return k; }
218
+ }
219
+ return 0u;
220
+ }
196
221
  @group(2) @binding(0) var diffuseTexture: texture_2d<f32>;
197
222
  @group(2) @binding(1) var<uniform> material: MaterialUniforms;
198
223
  // Reserved for future sphere/toon graph nodes; graphs that don't read them get the
@@ -222,56 +247,40 @@ fn safe_normal(nIn: vec3f) -> vec3f {
222
247
 
223
248
  `;
224
249
  // ─── Shadow sampler (3×3 PCF, cascade-selected) ─────────────────────
225
- // Normal-bias 0.08, depth-bias 0.001 NDC; the kernel is pcf9 in scene-light-api.ts,
226
- // shared with effects. Texel sizes are interpolated
227
- // from SHADOW_CASCADES so they cannot go stale the way the hardcoded 1/2048
228
- // once did (PCF taps landed TWO texels apart after the map grew to 4096,
229
- // quantizing self-shadow edges into crawling gray squares).
250
+ // Normal-bias 0.08, depth-bias 0.001 NDC; the lookup is sunShadowWgsl in
251
+ // scene-light-api.ts, shared with the ground and effects.
230
252
  //
231
- // Selection: the crisp near cascade wherever its box covers the point — with a
232
- // margin that keeps the whole PCF kernel inside the map, so selection never
233
- // mixes maps mid-kernel — else the far cascade, else LIT. Lit is a fix, not
234
- // merely a default: the old single-volume path clamped, and past the light's
235
- // far plane the comparison failed against every stored depth, silently
236
- // shadowing any stage deeper than the box.
253
+ // Selection: the first cascade whose box covers the point — with a margin that
254
+ // keeps the whole PCF kernel inside its tile — else LIT. Lit is a fix, not
255
+ // merely a default: past every cascade there is no occlusion information, and
256
+ // comparing against unrelated depths would silently shadow any stage deeper
257
+ // than the boxes.
237
258
  const SAMPLE_SHADOW_WGSL = /* wgsl */ `
259
+ ${sunShadowWgsl("_rzSun", "shadowAtlas", "shadowSampler", "lightVP.viewProj")}
238
260
 
239
- fn sampleShadowNear(ndc: vec3f) -> f32 {
240
- ${pcf9("shadowMap", `1.0 / ${SHADOW_CASCADES[0].mapSize}.0`)}
241
- }
242
-
243
- fn sampleShadowFar(ndc: vec3f) -> f32 {
244
- ${pcf9("shadowMapFar", `1.0 / ${SHADOW_CASCADES[SHADOW_CASCADES.length - 1].mapSize}.0`)}
261
+ /** The sun's cast shadow alone, without sampleShadow's facing test: what a
262
+ * character's ramp is min'd with (its own N·L carries the terminator). */
263
+ fn rzSunOcclusion(worldPos: vec3f, n: vec3f) -> f32 {
264
+ let castAmt = light.lights[0].direction.w;
265
+ if (castAmt <= 0.0) { return 1.0; }
266
+ let a = _rzSunLocate(worldPos + n * 0.08);
267
+ if (a.w < 0.0) { return 1.0; }
268
+ return mix(1.0, _rzSunTaps(a), castAmt);
245
269
  }
246
270
 
247
271
  fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
248
272
  // THE SCENE'S ONE SHADOW SWITCH, and it lives on the sun because the sun is
249
- // the only thing that casts. Positional lamps are diffuse-only — rzLightsDiffuse
250
- // has no shadow term at all — so turning this off turns off every shadow there
251
- // is, on the ground and on the cast alike. Gated here rather than by not
252
- // rendering the map: the map is also what the ground catcher reads, and a host
253
- // that wants a figure lit with no shadow still wants everything else about the
254
- // frame unchanged.
255
- //
256
- // Lit is the answer when it is off. Returning 0 would be "fully shadowed",
257
- // which is the opposite of what a disabled shadow means.
273
+ // the only thing that casts. Lit is the answer when it is off: returning 0
274
+ // would be "fully shadowed", the opposite of what a disabled shadow means.
258
275
  let castAmt = light.lights[0].direction.w;
259
276
  if (castAmt <= 0.0) { return 1.0; }
260
- if (dot(n, -light.lights[0].direction.xyz) <= 0.0) { return 0.0; }
261
- let biasedPos = worldPos + n * 0.08;
262
- let c0 = lightVP.viewProj[0] * vec4f(biasedPos, 1.0);
263
- let n0 = c0.xyz / max(c0.w, 1e-6);
264
- if (all(abs(n0.xy) < vec2f(0.98)) && n0.z > 0.0 && n0.z < 1.0) {
265
- return mix(1.0, sampleShadowNear(n0), castAmt);
266
- }
267
- let c1 = lightVP.viewProj[1] * vec4f(biasedPos, 1.0);
268
- let n1 = c1.xyz / max(c1.w, 1e-6);
269
- if (all(abs(n1.xy) < vec2f(0.98)) && n1.z > 0.0 && n1.z < 1.0) {
270
- return mix(1.0, sampleShadowFar(n1), castAmt);
271
- }
272
- // Outside every cascade there is no occlusion information; lit is the only
273
- // honest answer.
274
- return 1.0;
277
+ // A surface turned from the sun is in its own shade — when the sun is what
278
+ // keys it. Under a key of its own layer the atlas is only the occluders'
279
+ // shadow, and the key's N·L decides its terminator.
280
+ if (rzMainLight() == 0u && dot(n, -light.lights[0].direction.xyz) <= 0.0) { return 0.0; }
281
+ let a = _rzSunLocate(worldPos + n * 0.08);
282
+ if (a.w < 0.0) { return 1.0; }
283
+ return mix(1.0, _rzSunTaps(a), castAmt);
275
284
  }
276
285
 
277
286
  `;
@@ -485,4 +494,4 @@ fn rz_dissolve_threshold(restPos: vec3f) -> f32 {
485
494
  // pull in everything structural. Each material then adds its own constants + fs().
486
495
  // The FSOut struct is NOT in here any more — see commonFsOutWgsl above. Every
487
496
  // consumer of this constant appends it immediately, which is where it was.
488
- export const COMMON_MATERIAL_PRELUDE_WGSL = COMMON_BINDINGS_WGSL + WORLD_AMBIENT_WGSL + lightsApi(0, 6) + SAMPLE_SHADOW_WGSL + COMMON_VS_WGSL;
497
+ export const COMMON_MATERIAL_PRELUDE_WGSL = COMMON_BINDINGS_WGSL + WORLD_AMBIENT_WGSL + lightsApi(0, 6, "objectLight.layers.x") + SAMPLE_SHADOW_WGSL + COMMON_VS_WGSL;