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
@@ -1,4 +1,4 @@
1
- import { SHADOW_CASCADES } from "../shadow-cascades";
1
+ import { SHADOW_ATLAS_SIZE, SHADOW_CASCADES } from "../shadow-cascades";
2
2
  // The scene's own light, as an effect author sees it: the sun's shadow and the
3
3
  // world's ambient.
4
4
  //
@@ -49,30 +49,59 @@ fn rzWorldAmbientAvg() -> vec3f {
49
49
  }
50
50
  `;
51
51
  /**
52
- * 3×3 PCF over one cascade's map, for a point already in that cascade's NDC.
52
+ * The sun's shadow atlas, as every reader looks it up: `<p>Locate(q)` finds
53
+ * world point q in the first cascade whose box holds it and answers
54
+ * (atlas uv, depth, cascade) — cascade -1 outside them all — and `<p>Taps(a)`
55
+ * is the 3×3 PCF at that place.
53
56
  *
54
- * Shared with the materials, so an effect's shadow and her own shadow soften
55
- * alike. The sampler is linear-filtered, which makes each tap a 2×2 bilinear
56
- * compare. Unrolled — Safari's Metal backend doesn't unroll nested shadow
57
- * loops reliably. The body expects `ndc` in scope and returns.
57
+ * ONE implementation for the materials, the ground and effects, so a blade of
58
+ * grass and her own skin go dark at the same edge. `vp` names the cascades'
59
+ * view-projection array in the module that includes it.
60
+ *
61
+ * The box test keeps a margin (SHADOW_MARGIN of the tile's half-width) so the
62
+ * whole filter stays inside the tile: a tap that crossed into the neighbouring
63
+ * tile would compare against another cascade's depths. The sampler is linear,
64
+ * so each tap is itself a 2×2 compare. Unrolled — Safari's Metal backend
65
+ * doesn't unroll nested shadow loops reliably.
58
66
  */
59
- export const pcf9 = (map, ts, sampler = "shadowSampler") => /* wgsl */ `
60
- let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
61
- let cmpZ = ndc.z - 0.001;
62
- let ts = ${ts};
63
- let s00 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(-ts, -ts), cmpZ);
64
- let s10 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(0.0, -ts), cmpZ);
65
- let s20 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f( ts, -ts), cmpZ);
66
- let s01 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(-ts, 0.0), cmpZ);
67
- let s11 = textureSampleCompareLevel(${map}, ${sampler}, suv, cmpZ);
68
- let s21 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f( ts, 0.0), cmpZ);
69
- let s02 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(-ts, ts), cmpZ);
70
- let s12 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(0.0, ts), cmpZ);
71
- let s22 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f( ts, ts), cmpZ);
67
+ export const SHADOW_MARGIN = 0.98;
68
+ export const sunShadowWgsl = (p, map, sampler, vp) => {
69
+ const n = SHADOW_CASCADES.length;
70
+ const tiles = SHADOW_CASCADES.map((c) => `vec2f(${c.origin[0] / SHADOW_ATLAS_SIZE}, ${c.origin[1] / SHADOW_ATLAS_SIZE})`).join(", ");
71
+ const scale = SHADOW_CASCADES[0].mapSize / SHADOW_ATLAS_SIZE;
72
+ return /* wgsl */ `
73
+ const ${p}Tiles = array<vec2f, ${n}>(${tiles});
74
+
75
+ fn ${p}Locate(q: vec3f) -> vec4f {
76
+ for (var i = 0u; i < ${n}u; i++) {
77
+ let c = ${vp}[i] * vec4f(q, 1.0);
78
+ let ndc = c.xyz / max(c.w, 1e-6);
79
+ if (all(abs(ndc.xy) < vec2f(${SHADOW_MARGIN})) && ndc.z > 0.0 && ndc.z < 1.0) {
80
+ let uv = ${p}Tiles[i] + vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5) * ${scale};
81
+ return vec4f(uv, ndc.z, f32(i));
82
+ }
83
+ }
84
+ return vec4f(0.0, 0.0, 0.0, -1.0);
85
+ }
86
+
87
+ fn ${p}Taps(a: vec4f) -> f32 {
88
+ let cmpZ = a.z - 0.001;
89
+ let ts = ${1 / SHADOW_ATLAS_SIZE};
90
+ let s00 = textureSampleCompareLevel(${map}, ${sampler}, a.xy + vec2f(-ts, -ts), cmpZ);
91
+ let s10 = textureSampleCompareLevel(${map}, ${sampler}, a.xy + vec2f(0.0, -ts), cmpZ);
92
+ let s20 = textureSampleCompareLevel(${map}, ${sampler}, a.xy + vec2f( ts, -ts), cmpZ);
93
+ let s01 = textureSampleCompareLevel(${map}, ${sampler}, a.xy + vec2f(-ts, 0.0), cmpZ);
94
+ let s11 = textureSampleCompareLevel(${map}, ${sampler}, a.xy, cmpZ);
95
+ let s21 = textureSampleCompareLevel(${map}, ${sampler}, a.xy + vec2f( ts, 0.0), cmpZ);
96
+ let s02 = textureSampleCompareLevel(${map}, ${sampler}, a.xy + vec2f(-ts, ts), cmpZ);
97
+ let s12 = textureSampleCompareLevel(${map}, ${sampler}, a.xy + vec2f(0.0, ts), cmpZ);
98
+ let s22 = textureSampleCompareLevel(${map}, ${sampler}, a.xy + vec2f( ts, ts), cmpZ);
72
99
  return (s00 + s10 + s20 + s01 + s11 + s21 + s02 + s12 + s22) * (1.0 / 9.0);
100
+ }
73
101
  `;
102
+ };
74
103
  /** Bindings the real accessors take, from `binding` upward. */
75
- export const SCENE_LIGHT_API_BINDINGS = 5;
104
+ export const SCENE_LIGHT_API_BINDINGS = 4;
76
105
  /**
77
106
  * `rzShadow(p)`: how much of the sun reaches world point `p` — 1 lit, 0 in a
78
107
  * caster's shadow, soft in between.
@@ -80,12 +109,11 @@ export const SCENE_LIGHT_API_BINDINGS = 5;
80
109
  * `rzWorldAmbient(n)`: the world's light arriving at a surface facing `n` —
81
110
  * the materials' own ambient, so an effect's shade can match hers.
82
111
  *
83
- * With `on`, declares five bindings from `binding`:
112
+ * With `on`, declares four bindings from `binding`:
84
113
  * +0 the light uniform (ambient, the sun — direction, and in .w the shadow
85
114
  * switch — and the HDRI's irradiance)
86
115
  * +1 the cascade view-projections, then the caster sphere
87
- * +2 the near cascade's map +3 the far cascade's map
88
- * +4 the comparison sampler
116
+ * +2 the sun's shadow atlas +3 the comparison sampler
89
117
  *
90
118
  * It takes a POINT and no normal. The materials' sampleShadow also rejects a
91
119
  * surface facing away from the sun, which is right for skin and wrong for a
@@ -110,17 +138,9 @@ struct _RzSceneLightU { ambientColor: vec4f, lights: array<_RzSceneLight, 4>, sh
110
138
  struct _RzShadowBlock { viewProj: array<mat4x4f, ${n}>, casters: vec4f, }
111
139
  @group(${group}) @binding(${binding}) var<uniform> _rzLight: _RzSceneLightU;
112
140
  @group(${group}) @binding(${binding + 1}) var<uniform> _rzShadowVP: _RzShadowBlock;
113
- @group(${group}) @binding(${binding + 2}) var _rzShadowNear: texture_depth_2d;
114
- @group(${group}) @binding(${binding + 3}) var _rzShadowFar: texture_depth_2d;
115
- @group(${group}) @binding(${binding + 4}) var _rzShadowCmp: sampler_comparison;
116
-
117
- fn _rzShadowNearTaps(ndc: vec3f) -> f32 {
118
- ${pcf9("_rzShadowNear", `1.0 / ${SHADOW_CASCADES[0].mapSize}.0`, "_rzShadowCmp")}
119
- }
120
-
121
- fn _rzShadowFarTaps(ndc: vec3f) -> f32 {
122
- ${pcf9("_rzShadowFar", `1.0 / ${SHADOW_CASCADES[n - 1].mapSize}.0`, "_rzShadowCmp")}
123
- }
141
+ @group(${group}) @binding(${binding + 2}) var _rzShadowAtlas: texture_depth_2d;
142
+ @group(${group}) @binding(${binding + 3}) var _rzShadowCmp: sampler_comparison;
143
+ ${sunShadowWgsl("_rzSun", "_rzShadowAtlas", "_rzShadowCmp", "_rzShadowVP.viewProj")}
124
144
 
125
145
  fn rzShadow(p: vec3f) -> f32 {
126
146
  // The scene's one shadow switch, on the sun — see sampleShadow.
@@ -138,18 +158,10 @@ fn rzShadow(p: vec3f) -> f32 {
138
158
  let perp = length(toCaster - toSun * along);
139
159
  if (along <= -cs.w || perp > cs.w) { return 1.0; }
140
160
  }
141
- let q = p + toSun * 0.08;
142
- let c0 = _rzShadowVP.viewProj[0] * vec4f(q, 1.0);
143
- let n0 = c0.xyz / max(c0.w, 1e-6);
144
- if (all(abs(n0.xy) < vec2f(0.98)) && n0.z > 0.0 && n0.z < 1.0) {
145
- return mix(1.0, _rzShadowNearTaps(n0), castAmt);
146
- }
147
- let c1 = _rzShadowVP.viewProj[${n - 1}] * vec4f(q, 1.0);
148
- let n1 = c1.xyz / max(c1.w, 1e-6);
149
- if (all(abs(n1.xy) < vec2f(0.98)) && n1.z > 0.0 && n1.z < 1.0) {
150
- return mix(1.0, _rzShadowFarTaps(n1), castAmt);
151
- }
152
- return 1.0;
161
+ let a = _rzSunLocate(p + toSun * 0.08);
162
+ // Outside every cascade there is no occlusion information: lit.
163
+ if (a.w < 0.0) { return 1.0; }
164
+ return mix(1.0, _rzSunTaps(a), castAmt);
153
165
  }
154
166
  ${worldAmbientWgsl("_rzLight")}`;
155
167
  }
@@ -1,34 +1,47 @@
1
+ import { Vec3 } from "./math";
1
2
  /**
2
3
  * THE SUN'S SHADOW FOLLOWS THE CAMERA, the way Blender's does.
3
4
  *
4
- * Each cascade is fitted every frame to a slice of the view frustum: the near
5
- * one to the stretch around what the camera is looking at, the far one to the
6
- * whole of what it sees. Whatever is in view is in a cascade, and whatever
7
- * could throw a shadow onto it is in the cascade's depth range, which is fitted
8
- * to the scene's bounds along the light. A room forty metres across with its
9
- * window frames behind the camera shadows its floor as the game does; a lone
10
- * dancer on an empty floor keeps a crisp near map.
5
+ * Each cascade is fitted every frame to a slice of the view frustum: the
6
+ * innermost to the stretch around what the camera is looking at, the
7
+ * outermost to the whole of what it sees, two more between. Whatever is in
8
+ * view is in a cascade, and whatever could throw a shadow onto it is in the
9
+ * cascade's depth range, which is fitted to the scene's bounds along the
10
+ * light. A room forty metres across with its window frames behind the camera
11
+ * shadows its floor as the game does; a lone dancer on an empty floor keeps a
12
+ * crisp inner tile.
11
13
  *
12
- * The earlier shape was two fixed boxes around the camera target, 64 and 256
13
- * units across, and a stage reached past them: the floor near the windows lay
14
- * outside every box and was drawn lit, the shadows stopping at the box's edge
15
- * in a straight line.
14
+ * The earlier shapes were two fixed boxes around the camera target, then two
15
+ * fitted maps; the game's pipeline draws four cascades into one atlas, and so
16
+ * does this.
16
17
  *
17
- * INVARIANT the sampler and the cull both lean on: the outer cascade CONTAINS
18
- * the inner one. The sampler falls from cascade 0 to 1 at the box edge, which
19
- * is only seamless if 1 covers where 0 ends, and the cull tests the OUTERMOST
20
- * frustum alone. Both hold because the outer slice is the whole frustum, of
21
- * which the inner slice is a part, and both take the same depth range.
18
+ * INVARIANT the sampler and the cull both lean on: each cascade CONTAINS the
19
+ * ones inside it. The sampler takes the first box that holds a point, which
20
+ * is only seamless if the next covers where one ends, and the cull tests the
21
+ * OUTERMOST frustum alone. Both hold because every slice starts at the near
22
+ * plane and the outermost is the whole frustum, and all take the same depth
23
+ * range.
22
24
  * tests/shadow-cascades.test.mjs pins it.
23
25
  */
24
26
  export type ShadowCascade = {
25
- /** Texels per side of this cascade's map — sets the snap quantum. */
27
+ /** Texels per side of this cascade's tile — sets the snap quantum. */
26
28
  mapSize: number;
29
+ /** Where the tile sits in the atlas, in texels from its top-left corner. */
30
+ origin: readonly [number, number];
27
31
  };
32
+ /**
33
+ * ONE ATLAS, FOUR TILES, as the game's pipeline (and URP) lays its sun shadow
34
+ * out: one depth texture, cascade i in tile (i mod 2, i div 2). One texture
35
+ * means one binding for every reader — materials, ground, effects, particles —
36
+ * and room in the same scheme for the local lights' shadows later.
37
+ */
38
+ export declare const SHADOW_ATLAS_SIZE = 4096;
28
39
  export declare const SHADOW_CASCADES: readonly ShadowCascade[];
29
- /** How far past the camera's point of interest the near cascade reaches, in
30
- * world units: the dancer and the floor around her, at the near map's texel. */
31
- export declare const NEAR_REACH = 40;
40
+ /** How far past the camera's point of interest the inner cascade reaches, in
41
+ * world units: the dancer and the floor around her. Half the old two-map
42
+ * reach, which keeps her texel where it was at half the tile; the three
43
+ * outer cascades carry the rest of the room. */
44
+ export declare const NEAR_REACH = 20;
32
45
  /** The view the cascades are fitted to: the camera's eye and basis (world
33
46
  * space, left-handed, +Z forward as the projection is), its vertical field of
34
47
  * view, aspect and clip planes, and how far away the thing it looks at is. */
@@ -70,6 +83,14 @@ type XYZ = {
70
83
  y: number;
71
84
  z: number;
72
85
  };
86
+ /**
87
+ * Where each cascade's slice of the view starts and ends, as distances along
88
+ * the view, inner to outer: every slice starts at the near plane and they
89
+ * nest, the innermost ending just past the point of interest and the
90
+ * outermost where the scene ends, the two between spaced evenly in ratio.
91
+ * Nested rather than abutting so the invariant above holds: the outermost
92
+ * box contains all of them, and the sampler takes the first that holds a point.
93
+ */
73
94
  export declare function cascadeSlices(view: ShadowView, bounds: ShadowBounds): [number, number][];
74
95
  /** The eight corners of a slice, for tests and for the fit's own checks. */
75
96
  export declare function sliceCorners(view: ShadowView, n: number, f: number): XYZ[];
@@ -83,6 +104,12 @@ export declare function sliceCorners(view: ShadowView, n: number, f: number): XY
83
104
  * Writes the 16 floats into `out` at `offset` and returns `out`.
84
105
  */
85
106
  export declare function fitShadowVP(view: ShadowView, slice: [number, number], sunDirection: XYZ, bounds: ShadowBounds, cascade: ShadowCascade, out: Float32Array, offset: number): Float32Array;
107
+ /** Every cascade's bounding sphere (centre, radius), inner to outer — the
108
+ * split spheres a game shader picks its cascade by (_CascadeShadowSplitSpheres). */
109
+ export declare function cascadeSpheres(view: ShadowView, bounds: ShadowBounds): {
110
+ center: Vec3;
111
+ radius: number;
112
+ }[];
86
113
  /** Every cascade's view-projection in a row, inner to outer. */
87
114
  export declare function buildShadowCascades(view: ShadowView, sunDirection: XYZ, bounds: ShadowBounds, out: Float32Array): Float32Array;
88
115
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"shadow-cascades.d.ts","sourceRoot":"","sources":["../src/shadow-cascades.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EAA2C,CAAA;AAE/F;iFACiF;AACjF,eAAO,MAAM,UAAU,KAAK,CAAA;AAE5B;;+EAE+E;AAC/E,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACxC,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,EAAE,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACvC,OAAO,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,2EAA2E;AAC3E,MAAM,MAAM,YAAY,GAAG;IAAE,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,IAAI,CAAA;AAElG,KAAK,GAAG,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAY9C,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAqBxF;AAwBD,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE,CAc1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EACvB,YAAY,EAAE,GAAG,EACjB,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,MAAM,GACb,YAAY,CA0Cd;AAED,gEAAgE;AAChE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,GAAG,YAAY,CAI9H"}
1
+ {"version":3,"file":"shadow-cascades.d.ts","sourceRoot":"","sources":["../src/shadow-cascades.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,IAAI,EAAE,MAAM,QAAQ,CAAA;AAEnC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAA;IACf,4EAA4E;IAC5E,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAClC,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,OAAO,CAAA;AAIrC,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EAGjD,CAAA;AAEH;;;iDAGiD;AACjD,eAAO,MAAM,UAAU,KAAK,CAAA;AAE5B;;+EAE+E;AAC/E,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACxC,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,EAAE,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACvC,OAAO,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,2EAA2E;AAC3E,MAAM,MAAM,YAAY,GAAG;IAAE,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,IAAI,CAAA;AAElG,KAAK,GAAG,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAM9C;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAwBxF;AAwBD,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE,CAc1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EACvB,YAAY,EAAE,GAAG,EACjB,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,MAAM,GACb,YAAY,CA0Cd;AAED;qFACqF;AACrF,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,GAAG;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,CAEzG;AAED,gEAAgE;AAChE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,GAAG,YAAY,CAI9H"}
@@ -1,17 +1,32 @@
1
1
  import { Mat4, Vec3 } from "./math";
2
- export const SHADOW_CASCADES = [{ mapSize: 4096 }, { mapSize: 2048 }];
3
- /** How far past the camera's point of interest the near cascade reaches, in
4
- * world units: the dancer and the floor around her, at the near map's texel. */
5
- export const NEAR_REACH = 40;
6
2
  /**
7
- * Where each cascade's slice of the view starts and ends, as distances along
8
- * the view: [near, split] and [near, farFit]. The far end is where the scene
9
- * ends, so an empty floor with a dancer on it keeps a short, sharp frustum
10
- * rather than the camera's far plane.
3
+ * ONE ATLAS, FOUR TILES, as the game's pipeline (and URP) lays its sun shadow
4
+ * out: one depth texture, cascade i in tile (i mod 2, i div 2). One texture
5
+ * means one binding for every reader — materials, ground, effects, particles —
6
+ * and room in the same scheme for the local lights' shadows later.
11
7
  */
8
+ export const SHADOW_ATLAS_SIZE = 4096;
9
+ const TILE = SHADOW_ATLAS_SIZE / 2;
10
+ export const SHADOW_CASCADES = [0, 1, 2, 3].map((i) => ({
11
+ mapSize: TILE,
12
+ origin: [(i % 2) * TILE, Math.floor(i / 2) * TILE],
13
+ }));
14
+ /** How far past the camera's point of interest the inner cascade reaches, in
15
+ * world units: the dancer and the floor around her. Half the old two-map
16
+ * reach, which keeps her texel where it was at half the tile; the three
17
+ * outer cascades carry the rest of the room. */
18
+ export const NEAR_REACH = 20;
12
19
  /** The deepest a cascade reaches along the view, in world units — the far
13
20
  * plane's old cap, which is where every stage's shadows were fitted before. */
14
21
  const SHADOW_FAR = 8000;
22
+ /**
23
+ * Where each cascade's slice of the view starts and ends, as distances along
24
+ * the view, inner to outer: every slice starts at the near plane and they
25
+ * nest, the innermost ending just past the point of interest and the
26
+ * outermost where the scene ends, the two between spaced evenly in ratio.
27
+ * Nested rather than abutting so the invariant above holds: the outermost
28
+ * box contains all of them, and the sampler takes the first that holds a point.
29
+ */
15
30
  export function cascadeSlices(view, bounds) {
16
31
  let farFit = view.near + 200;
17
32
  if (bounds) {
@@ -28,11 +43,14 @@ export function cascadeSlices(view, bounds) {
28
43
  // a kilometre out is in the scene's bounds, and fitting the far cascade to it
29
44
  // would spread the stage's shadow map across the sky.
30
45
  farFit = Math.min(view.far, SHADOW_FAR, Math.max(view.near + 1, farFit));
31
- const split = Math.min(farFit, Math.max(view.near + 8, view.focus + NEAR_REACH));
32
- return [
33
- [view.near, split],
34
- [view.near, farFit],
35
- ];
46
+ const first = Math.min(farFit, Math.max(view.near + 8, view.focus + NEAR_REACH));
47
+ const n = SHADOW_CASCADES.length;
48
+ const out = [];
49
+ for (let i = 0; i < n; i++) {
50
+ const end = i === n - 1 ? farFit : first * Math.pow(farFit / first, i / (n - 1));
51
+ out.push([view.near, end]);
52
+ }
53
+ return out;
36
54
  }
37
55
  /** The bounding sphere of a frustum slice: on the view axis, at the depth that
38
56
  * balances the near and far rectangles' corners. */
@@ -113,6 +131,11 @@ export function fitShadowVP(view, slice, sunDirection, bounds, cascade, out, off
113
131
  out.set(proj.multiply(viewM).values, offset);
114
132
  return out;
115
133
  }
134
+ /** Every cascade's bounding sphere (centre, radius), inner to outer — the
135
+ * split spheres a game shader picks its cascade by (_CascadeShadowSplitSpheres). */
136
+ export function cascadeSpheres(view, bounds) {
137
+ return cascadeSlices(view, bounds).map(([n, f]) => sliceSphere(view, n, f));
138
+ }
116
139
  /** Every cascade's view-projection in a row, inner to outer. */
117
140
  export function buildShadowCascades(view, sunDirection, bounds, out) {
118
141
  const slices = cascadeSlices(view, bounds);
@@ -0,0 +1,59 @@
1
+ import type { NativeValue } from "./host";
2
+ /** A game character's CharacterEffect values, as its prefab serialises them. */
3
+ export type CharacterRig = {
4
+ localLightInclination: number;
5
+ localLightAzimuth: number;
6
+ localLightIntensity: number;
7
+ localLightColor: [number, number, number, number];
8
+ faceReceiveShadow: boolean;
9
+ disableFaceSDFShadow: boolean;
10
+ emissionColor: [number, number, number, number];
11
+ fillOuter: [number, number, number, number];
12
+ fillInner: [number, number, number, number];
13
+ fillColor: [number, number, number, number];
14
+ fillRatio: number;
15
+ rimlightColor: [number, number, number, number];
16
+ rimlightThreshold: number;
17
+ rimlightFade: number;
18
+ rimlightRange: number;
19
+ rimlightInclination: number;
20
+ rimlightAzimuth1: number;
21
+ rimlightAzimuth2: number;
22
+ characterLayer: number;
23
+ /** EnvironmentEffect's trilight, sRGB as serialised. */
24
+ skyColor: [number, number, number];
25
+ equatorColor: [number, number, number];
26
+ groundColor: [number, number, number];
27
+ };
28
+ /**
29
+ * Unity's ambient probe of a trilight (RenderSettings.ambientProbe in Trilight
30
+ * mode), measured in Unity 6 rather than derived: the colours are taken to
31
+ * linear, then
32
+ * c0 = (S + 4E + G) / 6 c1 = (√6 / 9)(S − G)
33
+ * c6 = (5/192)(2E − S − G) c8 = (5/64)(2E − S − G)
34
+ * and every other coefficient is zero — Unity's raw coefficients, which its
35
+ * shader constants pack as SHA = (c3, c1, c2, c0 − c6), SHB = (c4, c5, 3c6, c7),
36
+ * SHC = c8. Checked against the game's recorded block for (0.8, 0.5, 0.3).
37
+ */
38
+ export declare function trilightProbe(sky: [number, number, number], equator: [number, number, number], ground: [number, number, number]): number[][];
39
+ /** Unity's shader constants from raw coefficients (channel, index). */
40
+ export declare function probeConstants(c: number[][]): Record<string, number[]>;
41
+ /**
42
+ * sim_TowardMatrix (LightingFeatures.SetupTowardMatrix): the frame the
43
+ * character light is expressed in — the camera's backward direction flattened
44
+ * to the ground, as columns (up × d, up, d). Taken immediately, as the DLC
45
+ * poster sets CameraExtension.shadowRotationImmediately.
46
+ */
47
+ export declare function towardMatrix(cameraForward: [number, number, number]): Float32Array;
48
+ /** CharacterGlobalParams.Execute: the outline's resolution allowance. */
49
+ export declare function outlineMaxOffsetMultiplier(pixelWidth: number): number;
50
+ /**
51
+ * The character's property block (CharacterEffect.LateUpdate with the default
52
+ * look: no dissolve, dither, interference), by name.
53
+ *
54
+ * `head` is the head bone's skinning matrix (its pose relative to the bind
55
+ * pose, engine units) and `headRest` its rest position; `scale` engine units
56
+ * per game unit.
57
+ */
58
+ export declare function characterBlock(rig: CharacterRig, head: ArrayLike<number> | null, headRest: [number, number, number], scale: number): Record<string, NativeValue>;
59
+ //# sourceMappingURL=character.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"character.d.ts","sourceRoot":"","sources":["../../src/unity/character.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AAGzC,gFAAgF;AAChF,MAAM,MAAM,YAAY,GAAG;IACzB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,iBAAiB,EAAE,MAAM,CAAA;IACzB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IACjD,iBAAiB,EAAE,OAAO,CAAA;IAC1B,oBAAoB,EAAE,OAAO,CAAA;IAC7B,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/C,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC3C,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC3C,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC3C,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/C,iBAAiB,EAAE,MAAM,CAAA;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,gBAAgB,EAAE,MAAM,CAAA;IACxB,gBAAgB,EAAE,MAAM,CAAA;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,wDAAwD;IACxD,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAClC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IACtC,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CACtC,CAAA;AAID;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAC7B,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EACjC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAC/B,MAAM,EAAE,EAAE,CAUZ;AAED,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAUtE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAMlF;AAED,yEAAyE;AACzE,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,EAC9B,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAClC,KAAK,EAAE,MAAM,GACZ,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CA+C7B"}
@@ -0,0 +1,120 @@
1
+ // The game's per-character shading inputs, for a model wearing its character
2
+ // shaders.
3
+ //
4
+ // In the game a CharacterEffect component (P08.RenderPipeline, ported in
5
+ // ag-rip's charfx/CharacterEffect.cs from the decompiled GameAssembly.dll)
6
+ // writes one property block shared by all of a character's renderers: its own
7
+ // key light, expressed in a camera-facing frame (_LocalLightDir in
8
+ // sim_TowardMatrix, LightingFeatures.SetupTowardMatrix); its face frame for the
9
+ // SDF face shadow (_World2Face); its rim lights; its fills; and an ambient of
10
+ // its own from three colours (EnvironmentEffect). Nothing here is per scene:
11
+ // it is the character's look, carried by the character.
12
+ //
13
+ // An MMD model has no CharacterEffect. It takes a CharacterRig — the values a
14
+ // game character's prefab carries, exported with the look — and the frames
15
+ // come from its own bones: the face is the head bone turned to face the way
16
+ // the game's faces do (the game's face node rests aligned with the world,
17
+ // looking +Z; a PMX model looks −Z, so a half turn about Y).
18
+ import { gameToEngine, invert4, mul4 } from "./globals";
19
+ const srgbToLinear = (c) => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
20
+ /**
21
+ * Unity's ambient probe of a trilight (RenderSettings.ambientProbe in Trilight
22
+ * mode), measured in Unity 6 rather than derived: the colours are taken to
23
+ * linear, then
24
+ * c0 = (S + 4E + G) / 6 c1 = (√6 / 9)(S − G)
25
+ * c6 = (5/192)(2E − S − G) c8 = (5/64)(2E − S − G)
26
+ * and every other coefficient is zero — Unity's raw coefficients, which its
27
+ * shader constants pack as SHA = (c3, c1, c2, c0 − c6), SHB = (c4, c5, 3c6, c7),
28
+ * SHC = c8. Checked against the game's recorded block for (0.8, 0.5, 0.3).
29
+ */
30
+ export function trilightProbe(sky, equator, ground) {
31
+ const out = [];
32
+ for (let k = 0; k < 3; k++) {
33
+ const S = srgbToLinear(sky[k]), E = srgbToLinear(equator[k]), G = srgbToLinear(ground[k]);
34
+ const ring = 2 * E - S - G;
35
+ out.push([(S + 4 * E + G) / 6, (Math.sqrt(6) / 9) * (S - G), 0, 0, 0, 0, (5 / 192) * ring, 0, (5 / 64) * ring]);
36
+ }
37
+ return out;
38
+ }
39
+ /** Unity's shader constants from raw coefficients (channel, index). */
40
+ export function probeConstants(c) {
41
+ const ch = ["r", "g", "b"];
42
+ const o = {};
43
+ for (let k = 0; k < 3; k++) {
44
+ const s = c[k];
45
+ o[`SHA${ch[k]}`] = [s[3], s[1], s[2], s[0] - s[6]];
46
+ o[`SHB${ch[k]}`] = [s[4], s[5], s[6] * 3, s[7]];
47
+ }
48
+ o.SHC = [c[0][8], c[1][8], c[2][8], 1];
49
+ return o;
50
+ }
51
+ /**
52
+ * sim_TowardMatrix (LightingFeatures.SetupTowardMatrix): the frame the
53
+ * character light is expressed in — the camera's backward direction flattened
54
+ * to the ground, as columns (up × d, up, d). Taken immediately, as the DLC
55
+ * poster sets CameraExtension.shadowRotationImmediately.
56
+ */
57
+ export function towardMatrix(cameraForward) {
58
+ const x = -cameraForward[0], z = -cameraForward[2];
59
+ const len = Math.hypot(x, z);
60
+ const d = len > 1e-5 ? [x / len, 0, z / len] : [0, 0, 0];
61
+ return Float32Array.from([d[2], 0, -d[0], 0, 0, 1, 0, 0, d[0], d[1], d[2], 0, 0, 0, 0, 1]);
62
+ }
63
+ /** CharacterGlobalParams.Execute: the outline's resolution allowance. */
64
+ export function outlineMaxOffsetMultiplier(pixelWidth) {
65
+ return Math.max(pixelWidth / 768 - 1, 0);
66
+ }
67
+ /**
68
+ * The character's property block (CharacterEffect.LateUpdate with the default
69
+ * look: no dissolve, dither, interference), by name.
70
+ *
71
+ * `head` is the head bone's skinning matrix (its pose relative to the bind
72
+ * pose, engine units) and `headRest` its rest position; `scale` engine units
73
+ * per game unit.
74
+ */
75
+ export function characterBlock(rig, head, headRest, scale) {
76
+ const o = {};
77
+ // _World2Face: the face frame's world-to-local, in game units. The face is
78
+ // the head's pose applied to a frame at the head's rest position looking −Z.
79
+ const faceRest = Float32Array.from([-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, headRest[0], headRest[1], headRest[2], 1]);
80
+ const face = head ? mul4(head, faceRest) : faceRest;
81
+ o._World2Face = mul4(invert4(face), gameToEngine(scale));
82
+ o._ExtralEmissionColor = rig.emissionColor;
83
+ let inner = rig.fillInner, outer = rig.fillOuter;
84
+ inner = [inner[0], inner[1], inner[2], inner[3] * rig.fillRatio];
85
+ outer = [outer[0], outer[1], outer[2], outer[3] * rig.fillRatio];
86
+ o._FillOuter = outer;
87
+ o._FillInner = inner;
88
+ o._FillClamp = 0;
89
+ o._FillSoft = 0;
90
+ o._DissolveFactor = 0;
91
+ // inclination about the horizon, azimuth negated — float math as the game's
92
+ const incl = (rig.localLightInclination * Math.PI) / 180;
93
+ const az = (-rig.localLightAzimuth * Math.PI) / 180;
94
+ const c = Math.cos(incl);
95
+ o._LocalLightDir = [Math.sin(az) * -c, Math.sin(incl), Math.cos(az) * c, 0];
96
+ const k = rig.localLightIntensity;
97
+ o._LocalLightColor = [rig.localLightColor[0] * k, rig.localLightColor[1] * k, rig.localLightColor[2] * k, rig.localLightColor[3] * k];
98
+ o._UseFaceReceiveShadow = rig.faceReceiveShadow ? 1 : 0;
99
+ o._DisableFaceSDFShadow = rig.disableFaceSDFShadow ? 1 : 0;
100
+ o._RimlightColor = rig.rimlightColor;
101
+ o._RimlightThreshold = rig.rimlightThreshold;
102
+ o._RimlightFade = rig.rimlightFade;
103
+ o._RimlightRange = rig.rimlightRange;
104
+ const zr = rig.rimlightInclination;
105
+ const sr = Math.sqrt(1 - zr * zr);
106
+ const a1 = rig.rimlightAzimuth1 * Math.PI, a2 = rig.rimlightAzimuth2 * Math.PI;
107
+ o._RimlightDir1 = [Math.cos(a1) * sr, Math.sin(a1) * sr, zr, 0];
108
+ o._RimlightDir2 = [Math.cos(a2) * sr, Math.sin(a2) * sr, zr, 0];
109
+ o._ZOffset_Unit = rig.characterLayer;
110
+ o._DitherAlpha = 1;
111
+ // EnvironmentEffect: the character's own ambient, in both names it is read by
112
+ const sh = probeConstants(trilightProbe(rig.skyColor, rig.equatorColor, rig.groundColor));
113
+ for (const [n, v] of Object.entries(sh)) {
114
+ o[`unity_${n}`] = v;
115
+ o[`_Replica_${n}`] = v;
116
+ }
117
+ // CharacterEffect.OnEnable: every character renderer on rendering layer 0x40000001
118
+ o.unity_RenderingLayer = new Uint32Array([0x40000001, 0, 0, 0]);
119
+ return o;
120
+ }
@@ -0,0 +1,93 @@
1
+ import type { NativeValue } from "./host";
2
+ export type Mat4Like = ArrayLike<number>;
3
+ export type UnityLight = {
4
+ /** World position, engine units. */
5
+ position: [number, number, number];
6
+ /** Reach, engine units. */
7
+ radius: number;
8
+ /** Linear colour times intensity. */
9
+ color: [number, number, number];
10
+ /** Unit aim, away from the light; zero for a point light. */
11
+ aim: [number, number, number];
12
+ cosOuter: number;
13
+ cosInner: number;
14
+ /** Rendering-layer bits it reaches. */
15
+ layers: number;
16
+ /** What the game's pipeline packs beside a light it lit with, when the light
17
+ * came from a game stage (stage_native.py): spot direction with w = 1 /
18
+ * its sphere's radius (the area term reads it), colour alpha, Extra,
19
+ * WorldToLight and type — all in the game's world, used as they are. */
20
+ unity?: {
21
+ spotDir: number[];
22
+ colorW: number;
23
+ extra: number[];
24
+ worldToLight: number[] | null;
25
+ lightType: number;
26
+ simSpotDir?: number[] | null;
27
+ simColorW?: number;
28
+ };
29
+ };
30
+ export type UnityFrameInput = {
31
+ /** Engine units per game unit. */
32
+ scale: number;
33
+ view: Mat4Like;
34
+ proj: Mat4Like;
35
+ eye: [number, number, number];
36
+ near: number;
37
+ far: number;
38
+ width: number;
39
+ height: number;
40
+ /** Scene clock and its step, seconds. */
41
+ time: number;
42
+ dt: number;
43
+ /** The direction the main light travels, unit; null for none. */
44
+ sunDirection: [number, number, number] | null;
45
+ sunColor: [number, number, number];
46
+ sunShadow: number;
47
+ lights: UnityLight[];
48
+ /** Ambient as the engine's folded SH9 (27 floats, ibl.ts), or null for flat. */
49
+ ambientSH: ArrayLike<number> | null;
50
+ ambientFlat: [number, number, number];
51
+ /** The cascades as the engine drew them: render view-projections (engine
52
+ * units, the atlas's own depth convention), their tiles, and the spheres
53
+ * they were fitted to. */
54
+ shadow: {
55
+ viewProj: Mat4Like[];
56
+ tiles: [number, number][];
57
+ tileSize: number;
58
+ atlasSize: number;
59
+ spheres: {
60
+ center: [number, number, number];
61
+ radius: number;
62
+ }[];
63
+ } | null;
64
+ /** What a loaded game stage sets for its scene (stage.ts): fog, tint, ambient
65
+ * SH, the environment cube's numbers — the pipeline's per-scene globals, in
66
+ * place of the defaults below. Never camera, time, lights or shadows. */
67
+ settings?: Record<string, NativeValue> | null;
68
+ };
69
+ /** Column-major 4x4 multiply. */
70
+ export declare function mul4(a: Mat4Like, b: Mat4Like): Float32Array;
71
+ export declare function invert4(m: Mat4Like): Float32Array;
72
+ /** Game world -> engine world: `s` PMX per unit, half a turn about Y. */
73
+ export declare function gameToEngine(s: number): Float32Array;
74
+ /** Engine world -> game world. */
75
+ export declare function engineToGame(s: number): Float32Array;
76
+ /** An engine point in the game's world. */
77
+ export declare function gamePoint(p: ArrayLike<number>, s: number): [number, number, number];
78
+ /** An engine direction in the game's world. */
79
+ export declare function gameDir(d: ArrayLike<number>): [number, number, number];
80
+ /** Uniform scale, as a column-major matrix. */
81
+ export declare function scale4(s: number): Float32Array;
82
+ /** The projection with clip y negated — undone by naga's own negation. */
83
+ export declare function flipY(p: Mat4Like): Float32Array;
84
+ /**
85
+ * The engine's folded SH9 (ibl.ts: c0 + c1·y + c2·z + c3·x + c4·xy + c5·yz +
86
+ * c6·(3z²−1) + c7·xz + c8·(x²−y²)) as Unity's seven shader constants —
87
+ * SHA = (x, y, z, constant), SHB = (xy, yz, z², zx), SHC = (x²−y²) — the same
88
+ * polynomial, regrouped.
89
+ */
90
+ export declare function unitySH(sh: ArrayLike<number> | null, flat: [number, number, number]): Record<string, number[]>;
91
+ /** Everything the frame sets, by name. */
92
+ export declare function unityFrameGlobals(f: UnityFrameInput): Record<string, NativeValue>;
93
+ //# sourceMappingURL=globals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"globals.d.ts","sourceRoot":"","sources":["../../src/unity/globals.ts"],"names":[],"mappings":"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"}