reze-engine 0.65.0 → 0.66.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 (151) hide show
  1. package/dist/engine.d.ts +156 -42
  2. package/dist/engine.d.ts.map +1 -1
  3. package/dist/engine.js +876 -186
  4. package/dist/graph/compile.d.ts.map +1 -1
  5. package/dist/graph/compile.js +5 -0
  6. package/dist/graph/presets/body.d.ts.map +1 -1
  7. package/dist/graph/presets/body.js +26 -24
  8. package/dist/graph/presets/cloth_rough.js +8 -8
  9. package/dist/graph/presets/cloth_smooth.js +8 -8
  10. package/dist/graph/presets/default.js +4 -4
  11. package/dist/graph/presets/eye.js +10 -10
  12. package/dist/graph/presets/face.d.ts.map +1 -1
  13. package/dist/graph/presets/face.js +28 -26
  14. package/dist/graph/presets/hair.js +6 -6
  15. package/dist/graph/presets/metal.js +8 -8
  16. package/dist/graph/presets/stockings.d.ts.map +1 -1
  17. package/dist/graph/presets/stockings.js +4 -11
  18. package/dist/graph/presets/unlit.d.ts.map +1 -1
  19. package/dist/graph/presets/unlit.js +9 -13
  20. package/dist/graph/registry.d.ts.map +1 -1
  21. package/dist/graph/registry.js +128 -157
  22. package/dist/graph/render-class.d.ts +5 -2
  23. package/dist/graph/render-class.d.ts.map +1 -1
  24. package/dist/graph/slots.d.ts.map +1 -1
  25. package/dist/graph/slots.js +5 -1
  26. package/dist/model.d.ts +14 -1
  27. package/dist/model.d.ts.map +1 -1
  28. package/dist/model.js +27 -1
  29. package/dist/pmx-loader.d.ts +4 -0
  30. package/dist/pmx-loader.d.ts.map +1 -1
  31. package/dist/pmx-loader.js +11 -2
  32. package/dist/shaders/anchor-table.d.ts +11 -0
  33. package/dist/shaders/anchor-table.d.ts.map +1 -1
  34. package/dist/shaders/anchor-table.js +9 -3
  35. package/dist/shaders/directives.d.ts +42 -5
  36. package/dist/shaders/directives.d.ts.map +1 -1
  37. package/dist/shaders/directives.js +51 -4
  38. package/dist/shaders/lights.d.ts +11 -7
  39. package/dist/shaders/lights.d.ts.map +1 -1
  40. package/dist/shaders/lights.js +315 -271
  41. package/dist/shaders/materials/common.d.ts.map +1 -1
  42. package/dist/shaders/materials/common.js +26 -3
  43. package/dist/shaders/materials/nodes.d.ts +1 -1
  44. package/dist/shaders/materials/nodes.d.ts.map +1 -1
  45. package/dist/shaders/materials/nodes.js +191 -271
  46. package/dist/shaders/passes/composite.d.ts +1 -1
  47. package/dist/shaders/passes/composite.d.ts.map +1 -1
  48. package/dist/shaders/passes/composite.js +3 -5
  49. package/dist/shaders/passes/ground.js +1 -1
  50. package/dist/shaders/passes/particles.d.ts.map +1 -1
  51. package/dist/shaders/passes/particles.js +9 -3
  52. package/dist/shaders/passes/probe.d.ts +45 -0
  53. package/dist/shaders/passes/probe.d.ts.map +1 -0
  54. package/dist/shaders/passes/probe.js +230 -0
  55. package/dist/shaders/passes/scene-contract.d.ts +13 -0
  56. package/dist/shaders/passes/scene-contract.d.ts.map +1 -1
  57. package/dist/shaders/passes/scene-contract.js +4 -0
  58. package/dist/shaders/scene-light-api.d.ts.map +1 -1
  59. package/dist/shaders/scene-light-api.js +1 -0
  60. package/dist/unity/globals.d.ts +7 -0
  61. package/dist/unity/globals.d.ts.map +1 -1
  62. package/dist/unity/globals.js +4 -3
  63. package/dist/unity/host.d.ts +3 -0
  64. package/dist/unity/host.d.ts.map +1 -1
  65. package/dist/unity/host.js +41 -7
  66. package/dist/unity/looks.d.ts +42 -0
  67. package/dist/unity/looks.d.ts.map +1 -1
  68. package/dist/unity/looks.js +131 -4
  69. package/dist/unity/stage.d.ts +17 -0
  70. package/dist/unity/stage.d.ts.map +1 -1
  71. package/dist/unity/stage.js +22 -5
  72. package/package.json +1 -1
  73. package/src/engine.ts +949 -215
  74. package/src/graph/compile.ts +4 -0
  75. package/src/graph/presets/body.ts +26 -24
  76. package/src/graph/presets/cloth_rough.ts +8 -8
  77. package/src/graph/presets/cloth_smooth.ts +8 -8
  78. package/src/graph/presets/default.ts +4 -4
  79. package/src/graph/presets/eye.ts +10 -10
  80. package/src/graph/presets/face.ts +28 -26
  81. package/src/graph/presets/hair.ts +6 -6
  82. package/src/graph/presets/metal.ts +8 -8
  83. package/src/graph/presets/stockings.ts +4 -11
  84. package/src/graph/presets/unlit.ts +9 -13
  85. package/src/graph/registry.ts +133 -158
  86. package/src/graph/render-class.ts +5 -2
  87. package/src/graph/slots.ts +5 -1
  88. package/src/model.ts +33 -1
  89. package/src/pmx-loader.ts +11 -2
  90. package/src/shaders/anchor-table.ts +15 -3
  91. package/src/shaders/directives.ts +88 -6
  92. package/src/shaders/lights.ts +464 -415
  93. package/src/shaders/materials/common.ts +26 -3
  94. package/src/shaders/materials/nodes.ts +191 -271
  95. package/src/shaders/passes/composite.ts +3 -5
  96. package/src/shaders/passes/ground.ts +1 -1
  97. package/src/shaders/passes/particles.ts +9 -3
  98. package/src/shaders/passes/probe.ts +240 -0
  99. package/src/shaders/passes/scene-contract.ts +17 -0
  100. package/src/shaders/scene-light-api.ts +1 -0
  101. package/src/unity/globals.ts +7 -3
  102. package/src/unity/host.ts +36 -6
  103. package/src/unity/looks.ts +161 -7
  104. package/src/unity/stage.ts +28 -5
  105. package/dist/physics-debug.d.ts +0 -30
  106. package/dist/physics-debug.d.ts.map +0 -1
  107. package/dist/physics-debug.js +0 -526
  108. package/dist/shaders/agx-lut.d.ts +0 -11
  109. package/dist/shaders/agx-lut.d.ts.map +0 -1
  110. package/dist/shaders/agx-lut.js +0 -29
  111. package/dist/shaders/dfg_lut.d.ts +0 -3
  112. package/dist/shaders/dfg_lut.d.ts.map +0 -1
  113. package/dist/shaders/dfg_lut.js +0 -129
  114. package/dist/shaders/ltc_mag_lut.d.ts +0 -3
  115. package/dist/shaders/ltc_mag_lut.d.ts.map +0 -1
  116. package/dist/shaders/ltc_mag_lut.js +0 -1033
  117. package/dist/shaders/materials/body.d.ts +0 -2
  118. package/dist/shaders/materials/body.d.ts.map +0 -1
  119. package/dist/shaders/materials/body.js +0 -95
  120. package/dist/shaders/materials/cloth_rough.d.ts +0 -2
  121. package/dist/shaders/materials/cloth_rough.d.ts.map +0 -1
  122. package/dist/shaders/materials/cloth_rough.js +0 -69
  123. package/dist/shaders/materials/cloth_smooth.d.ts +0 -2
  124. package/dist/shaders/materials/cloth_smooth.d.ts.map +0 -1
  125. package/dist/shaders/materials/cloth_smooth.js +0 -61
  126. package/dist/shaders/materials/default.d.ts +0 -2
  127. package/dist/shaders/materials/default.d.ts.map +0 -1
  128. package/dist/shaders/materials/default.js +0 -43
  129. package/dist/shaders/materials/eye.d.ts +0 -2
  130. package/dist/shaders/materials/eye.d.ts.map +0 -1
  131. package/dist/shaders/materials/eye.js +0 -60
  132. package/dist/shaders/materials/face.d.ts +0 -2
  133. package/dist/shaders/materials/face.d.ts.map +0 -1
  134. package/dist/shaders/materials/face.js +0 -95
  135. package/dist/shaders/materials/hair.d.ts +0 -2
  136. package/dist/shaders/materials/hair.d.ts.map +0 -1
  137. package/dist/shaders/materials/hair.js +0 -90
  138. package/dist/shaders/materials/metal.d.ts +0 -2
  139. package/dist/shaders/materials/metal.d.ts.map +0 -1
  140. package/dist/shaders/materials/metal.js +0 -77
  141. package/dist/shaders/materials/mmd_classic.d.ts +0 -2
  142. package/dist/shaders/materials/mmd_classic.d.ts.map +0 -1
  143. package/dist/shaders/materials/mmd_classic.js +0 -66
  144. package/dist/shaders/materials/stockings.d.ts +0 -2
  145. package/dist/shaders/materials/stockings.d.ts.map +0 -1
  146. package/dist/shaders/materials/stockings.js +0 -122
  147. package/dist/shaders/passes/physics-debug.d.ts +0 -2
  148. package/dist/shaders/passes/physics-debug.d.ts.map +0 -1
  149. package/dist/shaders/passes/physics-debug.js +0 -69
  150. package/src/shaders/dfg_lut.ts +0 -131
  151. package/src/shaders/ltc_mag_lut.ts +0 -1035
@@ -17,11 +17,11 @@ import { subjectMaskApi } from "./cast-api";
17
17
  // the handful standing near it. The lamps an EFFECT emits are placed by a
18
18
  // compute pass every frame, which the grid never sees, and are walked in full.
19
19
  //
20
- // LAYOUT, in 32-bit words, read by the shader as vec4u. A 16-word header:
20
+ // LAYOUT, in 32-bit words, read by the shader as vec4u. A 20-word header:
21
21
  //
22
22
  // [0] light count (f32) [1] how many are the document's (f32)
23
23
  // [4..6] grid origin (f32) [7] 1 / cell edge (f32)
24
- // [8..10] grid dims (u32) [12..15] the OUTSIDE mask (u32 bits)
24
+ // [8..10] grid dims (u32) [12..19] the OUTSIDE mask (u32 bits)
25
25
  //
26
26
  // then MAX_LIGHTS records of 16 floats — four vec4s:
27
27
  //
@@ -29,7 +29,9 @@ import { subjectMaskApi } from "./cast-api";
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
31
  // [12] cos of the inner angle
32
- // [13] the rendering layers it does NOT reach (u32 bits) [14..15] spare
32
+ // [13] the rendering layers it does NOT reach (u32 bits)
33
+ // [14] its bulb: the distance inside which the inverse square is held flat
34
+ // (0 = RZ_LAMP_NEAR) [15] its cookie: layer + 1, 0 none
33
35
  //
34
36
  // then the grid: LIGHT_MASK_WORDS words of lamp bits per cell.
35
37
  //
@@ -57,8 +59,8 @@ import { sceneLightApi, worldAmbientWgsl } from "./scene-light-api";
57
59
  import { pointsApi } from "./points-api";
58
60
  import { textureApi } from "./texture-api";
59
61
  /** Words before the first record: the counts, the grid's placement and the
60
- * outside mask, four vec4s — see the layout above. */
61
- export const LIGHT_HEADER = 16;
62
+ * outside mask, five vec4s — see the layout above. */
63
+ export const LIGHT_HEADER = 20;
62
64
  /** Floats per light — see the layout above. */
63
65
  export const LIGHT_STRIDE = 16;
64
66
  /**
@@ -73,10 +75,11 @@ export const LIGHT_STRIDE = 16;
73
75
  * forty-eight each cut a real scene in half. Past this the extras are dropped.
74
76
  *
75
77
  * A fragment pays for the document lamps its grid cell names, so the cost
76
- * follows how many reach a place rather than how many the scene holds. It is
77
- * also why the cap is 128: one vec4u of bits per cell.
78
+ * follows how many reach a place rather than how many the scene holds. 256,
79
+ * two vec4u of bits per cell, is URP's own Forward+ budget for visible lights
80
+ * — and holds a lit interior's whole rig (X317's 153).
78
81
  */
79
- export const MAX_LIGHTS = 128;
82
+ export const MAX_LIGHTS = 256;
80
83
  /** Words of lamp bits per grid cell — one bit per lamp the cap allows. */
81
84
  export const LIGHT_MASK_WORDS = MAX_LIGHTS / 32;
82
85
  /** The grid's cell budget. 32k cells over a stage a couple of hundred units
@@ -95,14 +98,17 @@ export const LIGHTS_FLOATS = LIGHT_GRID_BASE + LIGHT_GRID_CELLS * LIGHT_MASK_WOR
95
98
  * out of those modules is a compile error on a function the author was right to
96
99
  * write, which is the same trap the grid's step-only half documents.
97
100
  */
98
- export const RZ_LIGHT_STRUCT_WGSL = /* wgsl */ `
99
- /** What an effect returns for one of its lights. */
100
- struct RzLight {
101
- pos: vec3f,
102
- color: vec3f,
103
- intensity: f32,
104
- radius: f32,
105
- }
101
+ export const RZ_LIGHT_STRUCT_WGSL = /* wgsl */ `
102
+ /** What an effect returns for one of its lights. */
103
+ struct RzLight {
104
+ pos: vec3f,
105
+ color: vec3f,
106
+ intensity: f32,
107
+ radius: f32,
108
+ /** Its bulb: the distance inside which it stops getting brighter. 0 is the
109
+ * engine's default; a glow worn on a hand wants one the size of the glow. */
110
+ near: f32,
111
+ }
106
112
  `;
107
113
  /** rzWorldAmbient against the materials' and the ground's `light` uniform. */
108
114
  export const WORLD_AMBIENT_WGSL = worldAmbientWgsl("light");
@@ -138,266 +144,304 @@ export function hasLightEmit(wgsl) {
138
144
  * brings RzLight, which is why this builder does not declare it again.
139
145
  */
140
146
  export function buildLightEmitShader(wgsl, sceneApi, cast, paramsDecl = "") {
141
- return /* wgsl */ `
142
- // read_write HERE and read-only in the material shaders. Different passes, so
143
- // the two never coexist: this compute runs before the scene pass that reads it.
144
- @group(0) @binding(0) var<storage, read_write> _rzLightsOut: array<f32>;
145
- // [0] (time, base slot, count, weight) — see buildLightEmitShader on why the
146
- // base is here and not in the text — and [1].x the subject mask, which is the
147
- // only thing the second vec4 is for: an emitter reads the cast through the same
148
- // accessors its drawing half does, so a rig aimed at one dancer must see one.
149
- @group(0) @binding(1) var<uniform> _rzLightU: array<vec4f, 2>;
150
- // The camera block and the cast — the two buffers the scene API reads. Same
151
- // contents the field and grid modules bind, so an effect's lightEmit sees the
152
- // scene exactly as its drawing half does.
153
- @group(0) @binding(2) var<uniform> viewU: array<vec4<f32>, 15>;
154
- @group(0) @binding(3) var<storage, read> _rzCast: array<vec4f>;
155
- ${sceneApi}
156
- ${subjectMaskApi("u32(_rzLightU[1].x)")}
157
- ${SCENE_TAP_STUB}
158
- // Audio and score at 4 and 5, the same bindings the particle and trail modules
159
- // put them on. A lamp that pulses on the beat or lights on a note is the whole
160
- // point of a light an effect owns rather than one the document places, so this
161
- // is not compile-safety padding — it is the mount's reason to exist.
162
- ${audioApi(0, 4)}
163
- ${midiApi(0, 5)}
164
- ${lyricsApi(0, 6)}
165
- // The rest of the hosted API. Every one of these is here because the AUTHOR'S
166
- // WHOLE FILE lands below, not because lightEmit needs it: a trail effect that
167
- // grows a lamp at its tip compiles its ribbon code in this module too. The math
168
- // helpers and the Particle struct are NOT repeated — they arrive with the scene
169
- // API above, and a second copy is a redefinition error in engine code.
170
- ${clockApi("_rzLightU[0].x", "0.0")}
171
- // Canvas height — the same number the drawing modules read out of their camera
172
- // struct, both written from canvas.height, so this name means ONE value in
173
- // every module. Verified against the writers, not assumed: cameraMatrixData[35]
174
- // and viewU[6].w have the same source.
175
- ${viewportApi("viewU[6].w")}
176
- ${trailSlotsApi(cast.trailCount)}
177
- // The id accessors, stubbed: this module cannot read an attachment the
178
- // scene pass writes. See id-api.ts — the author's whole file compiles here.
179
- ${idApi(false, 0, 0) + castDistanceStub() + sceneLightApi(false, 0, 0) + pointsApi(false) + textureApi(0)}
180
- // The dials the author declared, if any. A lamp is exactly the thing someone
181
- // retunes — its colour and its reach — so an emitter reads params like every
182
- // other mount rather than being the one place a #param resolves to nothing.
183
- ${paramsDecl}
184
- ${wgsl}
185
-
186
- @compute @workgroup_size(64)
187
- fn lightEmitMain(@builtin(global_invocation_id) gid: vec3u) {
188
- let i = gid.x;
189
- // The dispatch is sized to the count, but a workgroup is 64 wide and the
190
- // count rarely is — the tail must not write into the next effect's slots.
191
- if (i >= u32(_rzLightU[0].z)) { return; }
192
- // Time is a PARAMETER, not an rzTime() call: this same source compiles
193
- // inside the field, particle, trail and grid modules, and those already
194
- // define rzTime differently or not at all. A parameter needs nothing from
195
- // the module it lands in, which is why the field mounts take theirs too.
196
- let l = lightEmit(i, _rzLightU[0].x);
197
- // SANITIZED at the one write site, because lightEmit is HOSTED USER CODE and
198
- // this buffer feeds every fragment of every material: one NaN position would
199
- // poison the whole frame, and WGSL leaves max(NaN, 0) indeterminate, so it
200
- // would not even fail the same way on every GPU. A light that fails the
201
- // check writes zeros — radius 0 is off. Colour is clamped at zero on top:
202
- // this layer is ADDITIVE, and a negative channel would darken what it lands
203
- // on and can push HDR negative into bloom.
204
- let finite = l.pos.x == l.pos.x && l.pos.y == l.pos.y && l.pos.z == l.pos.z &&
205
- l.radius == l.radius && l.intensity == l.intensity &&
206
- l.color.x == l.color.x && l.color.y == l.color.y && l.color.z == l.color.z;
207
- // Weight rides along with the sanitisation, which is already the one place
208
- // this buffer is written: a lamp at half weight is half as bright, and one
209
- // at zero never reaches here because the dispatch is skipped.
210
- let c = select(vec3f(0.0), max(l.color * l.intensity, vec3f(0.0)), finite) * _rzLightU[0].w;
211
- let b = ${LIGHT_HEADER}u + (u32(_rzLightU[0].y) + i) * ${LIGHT_STRIDE}u;
212
- _rzLightsOut[b] = select(0.0, l.pos.x, finite);
213
- _rzLightsOut[b + 1u] = select(0.0, l.pos.y, finite);
214
- _rzLightsOut[b + 2u] = select(0.0, l.pos.z, finite);
215
- // Radius goes to zero when the effect is OFF, and is left alone at every
216
- // weight above it. Scaling it with the fade would shrink a dimming lamp's
217
- // reach, which is a different thing than dimming it; zeroing it at nothing
218
- // is what lets a material's distance cull drop the slot entirely.
219
- _rzLightsOut[b + 3u] = select(0.0, max(l.radius, 0.0), finite && _rzLightU[0].w > 0.0);
220
- // Colour carries intensity, exactly as the CPU writer stores it — one product,
221
- // one place, so the two producers cannot disagree about what a slot means.
222
- _rzLightsOut[b + 4u] = c.x;
223
- _rzLightsOut[b + 5u] = c.y;
224
- _rzLightsOut[b + 6u] = c.z;
225
- // An effect emits POINT lights: RzLight carries no aim, and widening it would
226
- // fail to compile every emitter already written against it. The rest of the
227
- // record is written anyway — these slots are reused frame to frame, and a spot
228
- // the document placed here last frame would otherwise keep its cone.
229
- _rzLightsOut[b + 7u] = 0.0;
230
- _rzLightsOut[b + 8u] = 0.0;
231
- _rzLightsOut[b + 9u] = 0.0;
232
- _rzLightsOut[b + 10u] = 0.0;
233
- _rzLightsOut[b + 11u] = -1.0;
234
- _rzLightsOut[b + 12u] = -1.0;
235
- _rzLightsOut[b + 13u] = 0.0;
236
- _rzLightsOut[b + 14u] = 0.0;
237
- _rzLightsOut[b + 15u] = 0.0;
238
- }
147
+ return /* wgsl */ `
148
+ // read_write HERE and read-only in the material shaders. Different passes, so
149
+ // the two never coexist: this compute runs before the scene pass that reads it.
150
+ @group(0) @binding(0) var<storage, read_write> _rzLightsOut: array<f32>;
151
+ // [0] (time, base slot, count, weight) — see buildLightEmitShader on why the
152
+ // base is here and not in the text — and [1].x the subject mask, which is the
153
+ // only thing the second vec4 is for: an emitter reads the cast through the same
154
+ // accessors its drawing half does, so a rig aimed at one dancer must see one.
155
+ @group(0) @binding(1) var<uniform> _rzLightU: array<vec4f, 2>;
156
+ // The camera block and the cast — the two buffers the scene API reads. Same
157
+ // contents the field and grid modules bind, so an effect's lightEmit sees the
158
+ // scene exactly as its drawing half does.
159
+ @group(0) @binding(2) var<uniform> viewU: array<vec4<f32>, 15>;
160
+ @group(0) @binding(3) var<storage, read> _rzCast: array<vec4f>;
161
+ ${sceneApi}
162
+ ${subjectMaskApi("u32(_rzLightU[1].x)")}
163
+ ${SCENE_TAP_STUB}
164
+ // Audio and score at 4 and 5, the same bindings the particle and trail modules
165
+ // put them on. A lamp that pulses on the beat or lights on a note is the whole
166
+ // point of a light an effect owns rather than one the document places, so this
167
+ // is not compile-safety padding — it is the mount's reason to exist.
168
+ ${audioApi(0, 4)}
169
+ ${midiApi(0, 5)}
170
+ ${lyricsApi(0, 6)}
171
+ // The rest of the hosted API. Every one of these is here because the AUTHOR'S
172
+ // WHOLE FILE lands below, not because lightEmit needs it: a trail effect that
173
+ // grows a lamp at its tip compiles its ribbon code in this module too. The math
174
+ // helpers and the Particle struct are NOT repeated — they arrive with the scene
175
+ // API above, and a second copy is a redefinition error in engine code.
176
+ ${clockApi("_rzLightU[0].x", "0.0")}
177
+ // Canvas height — the same number the drawing modules read out of their camera
178
+ // struct, both written from canvas.height, so this name means ONE value in
179
+ // every module. Verified against the writers, not assumed: cameraMatrixData[35]
180
+ // and viewU[6].w have the same source.
181
+ ${viewportApi("viewU[6].w")}
182
+ ${trailSlotsApi(cast.trailCount)}
183
+ // The id accessors, stubbed: this module cannot read an attachment the
184
+ // scene pass writes. See id-api.ts — the author's whole file compiles here.
185
+ ${idApi(false, 0, 0) + castDistanceStub() + sceneLightApi(false, 0, 0) + pointsApi(false) + textureApi(0)}
186
+ // The dials the author declared, if any. A lamp is exactly the thing someone
187
+ // retunes — its colour and its reach — so an emitter reads params like every
188
+ // other mount rather than being the one place a #param resolves to nothing.
189
+ ${paramsDecl}
190
+ ${wgsl}
191
+
192
+ @compute @workgroup_size(64)
193
+ fn lightEmitMain(@builtin(global_invocation_id) gid: vec3u) {
194
+ let i = gid.x;
195
+ // The dispatch is sized to the count, but a workgroup is 64 wide and the
196
+ // count rarely is — the tail must not write into the next effect's slots.
197
+ if (i >= u32(_rzLightU[0].z)) { return; }
198
+ // Time is a PARAMETER, not an rzTime() call: this same source compiles
199
+ // inside the field, particle, trail and grid modules, and those already
200
+ // define rzTime differently or not at all. A parameter needs nothing from
201
+ // the module it lands in, which is why the field mounts take theirs too.
202
+ let l = lightEmit(i, _rzLightU[0].x);
203
+ // SANITIZED at the one write site, because lightEmit is HOSTED USER CODE and
204
+ // this buffer feeds every fragment of every material: one NaN position would
205
+ // poison the whole frame, and WGSL leaves max(NaN, 0) indeterminate, so it
206
+ // would not even fail the same way on every GPU. A light that fails the
207
+ // check writes zeros — radius 0 is off. Colour is clamped at zero on top:
208
+ // this layer is ADDITIVE, and a negative channel would darken what it lands
209
+ // on and can push HDR negative into bloom.
210
+ let finite = l.pos.x == l.pos.x && l.pos.y == l.pos.y && l.pos.z == l.pos.z &&
211
+ l.radius == l.radius && l.intensity == l.intensity &&
212
+ l.color.x == l.color.x && l.color.y == l.color.y && l.color.z == l.color.z;
213
+ // Weight rides along with the sanitisation, which is already the one place
214
+ // this buffer is written: a lamp at half weight is half as bright, and one
215
+ // at zero never reaches here because the dispatch is skipped.
216
+ let c = select(vec3f(0.0), max(l.color * l.intensity, vec3f(0.0)), finite) * _rzLightU[0].w;
217
+ let b = ${LIGHT_HEADER}u + (u32(_rzLightU[0].y) + i) * ${LIGHT_STRIDE}u;
218
+ _rzLightsOut[b] = select(0.0, l.pos.x, finite);
219
+ _rzLightsOut[b + 1u] = select(0.0, l.pos.y, finite);
220
+ _rzLightsOut[b + 2u] = select(0.0, l.pos.z, finite);
221
+ // Radius goes to zero when the effect is OFF, and is left alone at every
222
+ // weight above it. Scaling it with the fade would shrink a dimming lamp's
223
+ // reach, which is a different thing than dimming it; zeroing it at nothing
224
+ // is what lets a material's distance cull drop the slot entirely.
225
+ _rzLightsOut[b + 3u] = select(0.0, max(l.radius, 0.0), finite && _rzLightU[0].w > 0.0);
226
+ // Colour carries intensity, exactly as the CPU writer stores it — one product,
227
+ // one place, so the two producers cannot disagree about what a slot means.
228
+ _rzLightsOut[b + 4u] = c.x;
229
+ _rzLightsOut[b + 5u] = c.y;
230
+ _rzLightsOut[b + 6u] = c.z;
231
+ // An effect emits POINT lights: RzLight carries no aim, and widening it would
232
+ // fail to compile every emitter already written against it. The rest of the
233
+ // record is written anyway — these slots are reused frame to frame, and a spot
234
+ // the document placed here last frame would otherwise keep its cone.
235
+ _rzLightsOut[b + 7u] = 0.0;
236
+ _rzLightsOut[b + 8u] = 0.0;
237
+ _rzLightsOut[b + 9u] = 0.0;
238
+ _rzLightsOut[b + 10u] = 0.0;
239
+ _rzLightsOut[b + 11u] = -1.0;
240
+ _rzLightsOut[b + 12u] = -1.0;
241
+ _rzLightsOut[b + 13u] = 0.0;
242
+ _rzLightsOut[b + 14u] = select(0.0, max(l.near, 0.0), finite && l.near == l.near);
243
+ _rzLightsOut[b + 15u] = 0.0;
244
+ }
239
245
  `;
240
246
  }
241
247
  /** The rz*Light accessors, with the buffer declared at the given binding.
242
248
  * `layers` is the WGSL expression for the drawing's rendering-layer bits. */
243
- export function lightsApi(group, binding, layers) {
249
+ export function lightsApi(group, binding, layers, cookies) {
244
250
  const R = LIGHT_HEADER / 4;
245
251
  const S = LIGHT_STRIDE / 4;
246
- return /* wgsl */ `
247
- // vec4u, so a record is four loads and a cell's lamp bits are one. Floats come
248
- // out through bitcast; the bits must NOT pass through f32 on the way, where a
249
- // mask that happens to spell a NaN is not guaranteed to survive a load.
250
- @group(${group}) @binding(${binding}) var<storage, read> _rzLights: array<vec4u>;
251
-
252
- const RZ_MAX_LIGHTS: u32 = ${MAX_LIGHTS}u;
253
- // A lamp's bulb, in world units: the inverse square is held flat inside it,
254
- // so the spike beside the lamp is finite. Aether Gazer's lamps are
255
- // 0.1 m across (their shapeRadius, capping 1/d² at 1/0.1), which at MMD scale
256
- // is 2.5 units.
257
- const RZ_LAMP_NEAR: f32 = 2.5;
258
-
259
- /** How many positional lights the scene has. Zero is the ordinary case. */
260
- fn rzLightCount() -> u32 { return min(u32(bitcast<f32>(_rzLights[0].x)), RZ_MAX_LIGHTS); }
261
-
262
- /** How many of them the document placed — the ones the grid indexes. */
263
- fn _rzLightDocCount() -> u32 { return min(u32(bitcast<f32>(_rzLights[0].y)), rzLightCount()); }
264
-
265
- /** One vec4 of light i's record. */
266
- fn _rzLightVec(i: u32, k: u32) -> vec4f { return bitcast<vec4f>(_rzLights[${R}u + i * ${S}u + k]); }
267
-
268
- /** Light i's world position. */
269
- fn rzLightPos(i: u32) -> vec3f { return _rzLightVec(i, 0u).xyz; }
270
-
271
- /** How far light i reaches. Its falloff is zero AT this distance, not merely
272
- * small, which is the bound the grid is built from. */
273
- fn rzLightRadius(i: u32) -> f32 { return _rzLightVec(i, 0u).w; }
274
-
275
- /** Light i's colour, already multiplied by its intensity. */
276
- fn rzLightColor(i: u32) -> vec3f { return _rzLightVec(i, 1u).xyz; }
277
-
278
- /** Where light i points, away from itself. The zero vector for a point light. */
279
- fn rzLightAim(i: u32) -> vec3f { return _rzLightVec(i, 2u).xyz; }
280
-
281
- /** The cosines a spot fades between: x its outer edge, y its inner one. A point
282
- * light stores (-1, -1), which saturates the cone term to 1. */
283
- fn rzLightCone(i: u32) -> vec2f { return vec2f(_rzLightVec(i, 2u).w, _rzLightVec(i, 3u).x); }
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
-
289
- /** The document lamps that can reach p: its grid cell's bits, or the outside
290
- * mask beyond the grid. Written so a NaN position fails the inside test. */
291
- fn _rzLightCellMask(p: vec3f) -> vec4u {
292
- let g = bitcast<vec4f>(_rzLights[1]);
293
- let dims = _rzLights[2].xyz;
294
- let c = floor((p - g.xyz) * g.w);
295
- if (!(all(c >= vec3f(0.0)) && all(c < vec3f(dims)))) { return _rzLights[3]; }
296
- let ci = vec3u(c);
297
- return _rzLights[${LIGHT_GRID_BASE / 4}u + (ci.z * dims.y + ci.y) * dims.x + ci.x];
298
- }
299
-
300
- /**
301
- * One light's contribution at a surface point.
302
- *
303
- * A LIGHT FALLS OFF AS THE INVERSE SQUARE, the curve Unity, Unreal, Blender and
304
- * glTF all light with: intensity / max(d², RZ_LAMP_NEAR²), so its intensity is
305
- * the brightness one unit away, windowed by (1 − (d/R)⁴)² so it is exactly zero
306
- * at its radius and the bound the grid is built from is real.
307
- *
308
- * THE UNITS ARE BLENDER'S. Intensity is radiant intensity, a point light's
309
- * power over 4π, and a Lambertian surface returns albedo × irradiance / π —
310
- * the π the sun term already carries. So a stage exported from Blender lights
311
- * here as it lit there, and a lamp's intensity is what Blender's exporter
312
- * writes in candela over 683.
313
- */
314
- fn _rzLightOne(i: u32, p: vec3f, n: vec3f) -> vec3f {
315
- if (!_rzLightReaches(i)) { return vec3f(0.0); }
316
- let pr = _rzLightVec(i, 0u);
317
- let d = pr.xyz - p;
318
- let dist = length(d);
319
- // Out of reach before anything else is computed. The grid is conservative —
320
- // a cell's bit means the lamp CAN reach part of it — so this still runs.
321
- if (dist >= pr.w) { return vec3f(0.0); }
322
- let toLight = d / max(dist, 1e-4);
323
- // Facing the light, and nothing behind it. No wrap or half-lambert: this
324
- // layer adds light, and a wrapped term would lift the shadow side, which is
325
- // the ramp's business and not this one's.
326
- let ndl = max(dot(n, toLight), 0.0);
327
- if (ndl <= 0.0) { return vec3f(0.0); }
328
- let t = clamp(dist / max(pr.w, 1e-4), 0.0, 1.0);
329
- let t2 = t * t;
330
- let window = 1.0 - t2 * t2;
331
- let falloff = window * window / max(dist * dist, RZ_LAMP_NEAR * RZ_LAMP_NEAR);
332
- // How far inside the cone this point sits: 1 within the inner angle, 0 past
333
- // the outer one, squared for the same soft edge the falloff has. A point
334
- // light's (-1, -1) divides by the floor and clamps to 1, so it pays one
335
- // dot product and no branch.
336
- let cone = rzLightCone(i);
337
- let aim = clamp((dot(-toLight, rzLightAim(i)) - cone.x) / max(cone.y - cone.x, 1e-4), 0.0, 1.0);
338
- return rzLightColor(i) * (ndl * falloff * aim * aim);
339
- }
340
-
341
- /** The lamps named by one word of a cell's bits, lowest first. */
342
- fn _rzLightWord(bits0: u32, base: u32, p: vec3f, n: vec3f) -> vec3f {
343
- var acc = vec3f(0.0);
344
- var bits = bits0;
345
- loop {
346
- if (bits == 0u) { break; }
347
- let i = base + firstTrailingBit(bits);
348
- bits = bits & (bits - 1u);
349
- acc = acc + _rzLightOne(i, p, n);
350
- }
351
- return acc;
352
- }
353
-
354
- /**
355
- * Every positional light's contribution at a surface point, as light — not as a
356
- * finished colour. Multiply by whatever the surface's albedo is.
357
- *
358
- * WITH NO LIGHTS THIS RETURNS EXACTLY ZERO and neither walk runs, so a scene
359
- * that declares none is arithmetically identical to one compiled before lights
360
- * existed. That is the property the whole feature is gated on: adding this to
361
- * every material must cost nothing until someone asks for a light.
362
- */
363
- fn rzLightsDiffuse(p: vec3f, n: vec3f) -> vec3f {
364
- return _rzLightsIrradiance(p, n) * (1.0 / 3.141592653589793);
365
- }
366
-
367
- // ONE WALK PER FRAGMENT. A principled closure walks the lamps for its specular
368
- // anyway, and every lamp's reach, falloff and cone are the same numbers the
369
- // diffuse layer needs — so that walk computes both, against the normal the
370
- // material prelude recorded here, and leaves the diffuse for the epilogue.
371
- // Walking the grid twice was ~60% of a lit stage's frame.
372
- var<private> _rzLampN: vec3f;
373
- var<private> _rzLampDiffuse: vec3f;
374
- var<private> _rzLampDiffuseSet: bool = false;
375
- // Set by a closure that shaded the lamps' diffuse itself (a Unity-mode
376
- // principled, which tints it by its own base) — the epilogue then adds none.
377
- var<private> _rzLampDiffuseTaken: bool = false;
378
-
379
- /** rzLightsDiffuse(p, _rzLampN), taken from a principled walk when one ran. */
380
- fn rzLightsDiffuseOnce(p: vec3f, n: vec3f) -> vec3f {
381
- if (_rzLampDiffuseTaken) { return vec3f(0.0); }
382
- if (_rzLampDiffuseSet) { return _rzLampDiffuse; }
383
- return rzLightsDiffuse(p, n);
384
- }
385
-
386
- fn _rzLightsIrradiance(p: vec3f, n: vec3f) -> vec3f {
387
- var acc = vec3f(0.0);
388
- let count = rzLightCount();
389
- let docs = _rzLightDocCount();
390
- // The document's lamps: only those the grid says can reach this cell.
391
- if (docs > 0u) {
392
- let m = _rzLightCellMask(p);
393
- acc = acc + _rzLightWord(m.x, 0u, p, n) + _rzLightWord(m.y, 32u, p, n) +
394
- _rzLightWord(m.z, 64u, p, n) + _rzLightWord(m.w, 96u, p, n);
395
- }
396
- // The effects' lamps, which move every frame on the GPU: all of them.
397
- for (var i = docs; i < count; i = i + 1u) {
398
- acc = acc + _rzLightOne(i, p, n);
399
- }
400
- return acc;
401
- }
252
+ return /* wgsl */ `
253
+ // vec4u, so a record is four loads and a cell's lamp bits are one. Floats come
254
+ // out through bitcast; the bits must NOT pass through f32 on the way, where a
255
+ // mask that happens to spell a NaN is not guaranteed to survive a load.
256
+ @group(${group}) @binding(${binding}) var<storage, read> _rzLights: array<vec4u>;
257
+ // The spot lamps' cookies, one picture a layer (Engine.loadLightCookie).
258
+ @group(${group}) @binding(${cookies.binding}) var _rzCookies: texture_2d_array<f32>;
259
+
260
+ const RZ_MAX_LIGHTS: u32 = ${MAX_LIGHTS}u;
261
+ // The DEFAULT bulb, in world units, for a record that names none (word 14):
262
+ // the inverse square is held flat inside it, so the spike beside the lamp is
263
+ // finite. URP's own floor — distance² at least HALF_MIN, 6.1e-5 m² — which at
264
+ // MMD scale (8 units a metre) is a bulb of 0.0625. A lamp with a real size (a
265
+ // game rig's shapeRadius) says so in word 14.
266
+ const RZ_LAMP_NEAR: f32 = 0.0625;
267
+
268
+ /** How many positional lights the scene has. Zero is the ordinary case. */
269
+ fn rzLightCount() -> u32 { return min(u32(bitcast<f32>(_rzLights[0].x)), RZ_MAX_LIGHTS); }
270
+
271
+ /** How many of them the document placed — the ones the grid indexes. */
272
+ fn _rzLightDocCount() -> u32 { return min(u32(bitcast<f32>(_rzLights[0].y)), rzLightCount()); }
273
+
274
+ /** One vec4 of light i's record. */
275
+ fn _rzLightVec(i: u32, k: u32) -> vec4f { return bitcast<vec4f>(_rzLights[${R}u + i * ${S}u + k]); }
276
+
277
+ /** Light i's world position. */
278
+ fn rzLightPos(i: u32) -> vec3f { return _rzLightVec(i, 0u).xyz; }
279
+
280
+ /** How far light i reaches. Its falloff is zero AT this distance, not merely
281
+ * small, which is the bound the grid is built from. */
282
+ fn rzLightRadius(i: u32) -> f32 { return _rzLightVec(i, 0u).w; }
283
+
284
+ /** Light i's colour, already multiplied by its intensity. */
285
+ fn rzLightColor(i: u32) -> vec3f { return _rzLightVec(i, 1u).xyz; }
286
+
287
+ /** Where light i points, away from itself. The zero vector for a point light. */
288
+ fn rzLightAim(i: u32) -> vec3f { return _rzLightVec(i, 2u).xyz; }
289
+
290
+ /** A spot's cookie, as URP's: its picture projected along the cone, so the
291
+ * light a point takes is the picture's colour there. The picture's square
292
+ * spans the cone's outer angle, up on the picture toward world up (or +z for
293
+ * a lamp aimed straight up or down). 1 for a lamp without one, and for a
294
+ * point lamp, whose cookie would need a cube. */
295
+ fn rzLightCookie(i: u32, toLight: vec3f) -> vec3f {
296
+ let k = _rzLightVec(i, 3u).w;
297
+ let a = rzLightAim(i);
298
+ if (k < 0.5 || dot(a, a) < 0.5) { return vec3f(1.0); }
299
+ let up0 = select(vec3f(0.0, 1.0, 0.0), vec3f(0.0, 0.0, 1.0), abs(a.y) > 0.99);
300
+ let right = normalize(cross(up0, a));
301
+ let up = cross(a, right);
302
+ let d = -toLight;
303
+ let c = rzLightCone(i).x;
304
+ let spread = sqrt(max(1.0 - c * c, 0.0)) / max(c, 1e-4);
305
+ let z = max(dot(d, a), 1e-4);
306
+ let uv = vec2f(dot(d, right), -dot(d, up)) / (z * spread) * 0.5 + 0.5;
307
+ return textureSampleLevel(_rzCookies, ${cookies.sampler}, uv, i32(k) - 1, 0.0).rgb;
308
+ }
309
+
310
+ /** Light i's bulb: inside this distance the inverse square is held flat. A
311
+ * record that names none (0, every writer that predates the word) takes
312
+ * RZ_LAMP_NEAR. A game lamp's is its shapeRadius: Unity caps 1/d² at
313
+ * 1/shapeRadius, so the flat core is sqrt(shapeRadius) - and a character rig
314
+ * tuned with a bulb wider than its reach is a lamp of constant strength inside
315
+ * a soft sphere, which a single global bulb cannot spell. */
316
+ fn rzLightNear(i: u32) -> f32 { let r = _rzLightVec(i, 3u).z; return select(RZ_LAMP_NEAR, r, r > 0.0); }
317
+
318
+ /** The cosines a spot fades between: x its outer edge, y its inner one. A point
319
+ * light stores (-1, -1), which saturates the cone term to 1. */
320
+ fn rzLightCone(i: u32) -> vec2f { return vec2f(_rzLightVec(i, 2u).w, _rzLightVec(i, 3u).x); }
321
+
322
+ /** Does light i reach this drawing's rendering layers? Read as bits, never as
323
+ * a float — see the layout. */
324
+ fn _rzLightReaches(i: u32) -> bool { return ((${layers}) & ~_rzLights[${R}u + i * ${S}u + 3u].y) != 0u; }
325
+
326
+ /** The document lamps that can reach p — 256 bits, lamps 0..127 in .lo and
327
+ * 128..255 in .hi: its grid cell's, or the outside mask beyond the grid.
328
+ * Written so a NaN position fails the inside test. */
329
+ struct _RzLampMask { lo: vec4u, hi: vec4u };
330
+ fn _rzLightCellMask(p: vec3f) -> _RzLampMask {
331
+ let g = bitcast<vec4f>(_rzLights[1]);
332
+ let dims = _rzLights[2].xyz;
333
+ let c = floor((p - g.xyz) * g.w);
334
+ if (!(all(c >= vec3f(0.0)) && all(c < vec3f(dims)))) { return _RzLampMask(_rzLights[3], _rzLights[4]); }
335
+ let ci = vec3u(c);
336
+ let at = ${LIGHT_GRID_BASE / 4}u + ((ci.z * dims.y + ci.y) * dims.x + ci.x) * 2u;
337
+ return _RzLampMask(_rzLights[at], _rzLights[at + 1u]);
338
+ }
339
+
340
+ /**
341
+ * One light's contribution at a surface point.
342
+ *
343
+ * A LIGHT FALLS OFF AS THE INVERSE SQUARE, the curve Unity, Unreal, Blender and
344
+ * glTF all light with: intensity / max(d², bulb²) (rzLightNear), so its intensity is
345
+ * the brightness one unit away, windowed by (1 − (d/R)⁴)² so it is exactly zero
346
+ * at its radius and the bound the grid is built from is real.
347
+ *
348
+ * LIGHT UNITS ARE UNITY'S, for every light in this engine — the sun, the
349
+ * directional slots and these lamps: a surface returns albedo × light × N·L
350
+ * with no 1/π (URP folds it into its intensities), and a highlight is the
351
+ * lobe as URP writes it. So a white lamp of intensity 1 one unit away lights
352
+ * albedo 1:1 head on, as a Unity point light does. A Blender light carries
353
+ * divided by π (the app's GLB loader does it).
354
+ */
355
+ fn _rzLightOne(i: u32, p: vec3f, n: vec3f) -> vec3f {
356
+ if (!_rzLightReaches(i)) { return vec3f(0.0); }
357
+ let pr = _rzLightVec(i, 0u);
358
+ let d = pr.xyz - p;
359
+ let dist = length(d);
360
+ // Out of reach before anything else is computed. The grid is conservative —
361
+ // a cell's bit means the lamp CAN reach part of it — so this still runs.
362
+ if (dist >= pr.w) { return vec3f(0.0); }
363
+ let toLight = d / max(dist, 1e-4);
364
+ // Facing the light, and nothing behind it. No wrap or half-lambert: this
365
+ // layer adds light, and a wrapped term would lift the shadow side, which is
366
+ // the ramp's business and not this one's.
367
+ let ndl = max(dot(n, toLight), 0.0);
368
+ if (ndl <= 0.0) { return vec3f(0.0); }
369
+ let t = clamp(dist / max(pr.w, 1e-4), 0.0, 1.0);
370
+ let t2 = t * t;
371
+ let window = 1.0 - t2 * t2;
372
+ let bulb = rzLightNear(i);
373
+ let falloff = window * window / max(dist * dist, bulb * bulb);
374
+ // How far inside the cone this point sits: 1 within the inner angle, 0 past
375
+ // the outer one, squared for the same soft edge the falloff has. A point
376
+ // light's (-1, -1) divides by the floor and clamps to 1, so it pays one
377
+ // dot product and no branch.
378
+ let cone = rzLightCone(i);
379
+ let aim = clamp((dot(-toLight, rzLightAim(i)) - cone.x) / max(cone.y - cone.x, 1e-4), 0.0, 1.0);
380
+ return rzLightColor(i) * rzLightCookie(i, toLight) * (ndl * falloff * aim * aim);
381
+ }
382
+
383
+ /** The lamps named by one word of a cell's bits, lowest first. */
384
+ fn _rzLightWord(bits0: u32, base: u32, p: vec3f, n: vec3f) -> vec3f {
385
+ var acc = vec3f(0.0);
386
+ var bits = bits0;
387
+ loop {
388
+ if (bits == 0u) { break; }
389
+ let i = base + firstTrailingBit(bits);
390
+ bits = bits & (bits - 1u);
391
+ acc = acc + _rzLightOne(i, p, n);
392
+ }
393
+ return acc;
394
+ }
395
+
396
+ /**
397
+ * Every positional light's contribution at a surface point, as light — not as a
398
+ * finished colour. Multiply by whatever the surface's albedo is.
399
+ *
400
+ * WITH NO LIGHTS THIS RETURNS EXACTLY ZERO and neither walk runs, so a scene
401
+ * that declares none is arithmetically identical to one compiled before lights
402
+ * existed. That is the property the whole feature is gated on: adding this to
403
+ * every material must cost nothing until someone asks for a light.
404
+ */
405
+ fn rzLightsDiffuse(p: vec3f, n: vec3f) -> vec3f {
406
+ return _rzLightsIrradiance(p, n);
407
+ }
408
+
409
+ // ONE WALK PER FRAGMENT. A principled closure walks the lamps for its specular
410
+ // anyway, and every lamp's reach, falloff and cone are the same numbers the
411
+ // diffuse layer needs — so that walk computes both, against the normal the
412
+ // material prelude recorded here, and leaves the diffuse for the epilogue.
413
+ // Walking the grid twice was ~60% of a lit stage's frame.
414
+ var<private> _rzLampN: vec3f;
415
+ var<private> _rzLampDiffuse: vec3f;
416
+ var<private> _rzLampDiffuseSet: bool = false;
417
+ // Set by a closure that shaded the lamps' diffuse itself (a Unity-mode
418
+ // principled, which tints it by its own base) — the epilogue then adds none.
419
+ var<private> _rzLampDiffuseTaken: bool = false;
420
+
421
+ /** rzLightsDiffuse(p, _rzLampN), taken from a principled walk when one ran. */
422
+ fn rzLightsDiffuseOnce(p: vec3f, n: vec3f) -> vec3f {
423
+ if (_rzLampDiffuseTaken) { return vec3f(0.0); }
424
+ if (_rzLampDiffuseSet) { return _rzLampDiffuse; }
425
+ return rzLightsDiffuse(p, n);
426
+ }
427
+
428
+ fn _rzLightsIrradiance(p: vec3f, n: vec3f) -> vec3f {
429
+ var acc = vec3f(0.0);
430
+ let count = rzLightCount();
431
+ let docs = _rzLightDocCount();
432
+ // The document's lamps: only those the grid says can reach this cell.
433
+ if (docs > 0u) {
434
+ let m = _rzLightCellMask(p);
435
+ acc = acc + _rzLightWord(m.lo.x, 0u, p, n) + _rzLightWord(m.lo.y, 32u, p, n) +
436
+ _rzLightWord(m.lo.z, 64u, p, n) + _rzLightWord(m.lo.w, 96u, p, n) +
437
+ _rzLightWord(m.hi.x, 128u, p, n) + _rzLightWord(m.hi.y, 160u, p, n) +
438
+ _rzLightWord(m.hi.z, 192u, p, n) + _rzLightWord(m.hi.w, 224u, p, n);
439
+ }
440
+ // The effects' lamps, which move every frame on the GPU: all of them.
441
+ for (var i = docs; i < count; i = i + 1u) {
442
+ acc = acc + _rzLightOne(i, p, n);
443
+ }
444
+ return acc;
445
+ }
402
446
  `;
403
447
  }