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