@driftengine/core 4.8.6 → 4.8.7
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/CHANGELOG.json +71 -0
- package/README.md +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/render/anisotropicLobe.d.ts +10 -0
- package/dist/render/anisotropicLobe.js +19 -0
- package/dist/render/areaLights.d.ts +35 -0
- package/dist/render/areaLights.js +35 -0
- package/dist/render/backend/api.d.ts +33 -3
- package/dist/render/backend/webgl2/renderer.d.ts +50 -14
- package/dist/render/backend/webgl2/renderer.js +141 -30
- package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
- package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
- package/dist/render/backend/webgpu/particlePass.js +2 -1
- package/dist/render/backend/webgpu/renderer.d.ts +35 -1
- package/dist/render/backend/webgpu/renderer.js +155 -16
- package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
- package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +34 -8
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/clusteredLights.d.ts +23 -5
- package/dist/render/clusteredLights.js +35 -8
- package/dist/render/driftLight/bake.d.ts +2 -0
- package/dist/render/driftLight/bake.js +1 -1
- package/dist/render/driftLight/falloff.d.ts +4 -2
- package/dist/render/driftLight/falloff.js +8 -2
- package/dist/render/gi/bounceLights.d.ts +3 -2
- package/dist/render/gi/bounceLights.js +8 -3
- package/dist/render/instanceCull.js +5 -0
- package/dist/render/instances.d.ts +19 -5
- package/dist/render/instances.js +9 -3
- package/dist/render/lightBudget.d.ts +12 -1
- package/dist/render/lightBudget.js +23 -3
- package/dist/render/lightmap.d.ts +23 -9
- package/dist/render/lightmap.js +40 -18
- package/dist/render/particlePool.d.ts +21 -0
- package/dist/render/particlePool.js +7 -0
- package/dist/render/pointLightSelection.d.ts +13 -0
- package/dist/render/pointLightSelection.js +3 -1
- package/dist/render/renderQuality.d.ts +8 -7
- package/dist/render/rendererHarness.js +3 -0
- package/dist/render/rgb9e5.d.ts +22 -0
- package/dist/render/rgb9e5.js +47 -0
- package/dist/render/sceneCapture.d.ts +29 -0
- package/dist/render/sceneCapture.js +36 -0
- package/dist/render/screenSpaceReflection.d.ts +27 -1
- package/dist/render/screenSpaceReflection.js +9 -0
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/bolt.js +7 -1
- package/dist/render/shaders/depth.js +11 -0
- package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
- package/dist/render/shaders/flat/anisotropic.js +37 -3
- package/dist/render/shaders/flat/areaLight.d.ts +1 -1
- package/dist/render/shaders/flat/areaLight.js +46 -0
- package/dist/render/shaders/flat/glassTint.d.ts +3 -2
- package/dist/render/shaders/flat/glassTint.js +3 -1
- package/dist/render/shaders/flat/index.d.ts +5 -0
- package/dist/render/shaders/flat/index.js +16 -2
- package/dist/render/shaders/flat/lobes.d.ts +6 -0
- package/dist/render/shaders/flat/lobes.js +49 -43
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +57 -22
- package/dist/render/shaders/flat/models.d.ts +5 -2
- package/dist/render/shaders/flat/models.js +100 -16
- package/dist/render/shaders/flat/preamble.js +10 -3
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/depth.wgsl.js +4 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +16 -0
- package/dist/render/shaders/generated/flat.wgsl.js +54 -38
- package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +11 -6
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +11 -6
- package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
- package/dist/render/shaders/generated/water.wgsl.js +23 -16
- package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +13 -2
- package/dist/render/shaders/ssrTrace.js +20 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +12 -3
- package/dist/render/skinScatter.d.ts +3 -1
- package/dist/render/skinScatter.js +3 -1
- package/dist/render/ssrPass.js +1 -0
- package/dist/render/surfaceModel.d.ts +29 -8
- package/dist/render/surfaceModel.js +33 -9
- package/dist/render/surfaceTexture.d.ts +25 -1
- package/dist/render/surfaceTexture.js +27 -5
- package/package.json +6 -6
- package/src/index.ts +6 -2
- package/src/render/anisotropicLobe.ts +29 -0
- package/src/render/areaLights.ts +58 -0
- package/src/render/backend/api.ts +49 -2
- package/src/render/backend/webgl2/renderer.ts +192 -38
- package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
- package/src/render/backend/webgpu/particlePass.ts +2 -1
- package/src/render/backend/webgpu/renderer.ts +194 -19
- package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +44 -14
- package/src/render/clusteredLights.ts +41 -8
- package/src/render/driftLight/bake.ts +3 -1
- package/src/render/driftLight/falloff.ts +13 -2
- package/src/render/gi/bounceLights.ts +12 -3
- package/src/render/instanceCull.ts +4 -0
- package/src/render/instances.ts +26 -6
- package/src/render/lightBudget.ts +40 -2
- package/src/render/lightmap.ts +64 -23
- package/src/render/particlePool.ts +23 -0
- package/src/render/pointLightSelection.ts +20 -1
- package/src/render/renderQuality.ts +8 -7
- package/src/render/rendererHarness.ts +3 -0
- package/src/render/rgb9e5.ts +52 -0
- package/src/render/sceneCapture.ts +56 -0
- package/src/render/screenSpaceReflection.ts +33 -1
- package/src/render/shaders/bolt.ts +7 -1
- package/src/render/shaders/depth.ts +11 -0
- package/src/render/shaders/flat/anisotropic.ts +37 -3
- package/src/render/shaders/flat/areaLight.ts +46 -0
- package/src/render/shaders/flat/glassTint.ts +9 -2
- package/src/render/shaders/flat/index.ts +21 -2
- package/src/render/shaders/flat/lobes.ts +50 -43
- package/src/render/shaders/flat/main.ts +57 -22
- package/src/render/shaders/flat/models.ts +100 -16
- package/src/render/shaders/flat/preamble.ts +10 -3
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +4 -4
- package/src/render/shaders/generated/flat.wgsl.ts +54 -38
- package/src/render/shaders/generated/ssrTrace.wgsl.ts +11 -6
- package/src/render/shaders/generated/water.wgsl.ts +23 -16
- package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
- package/src/render/shaders/recon/motion.wgsl.ts +13 -2
- package/src/render/shaders/ssrTrace.ts +20 -1
- package/src/render/shaders/water.ts +12 -3
- package/src/render/skinScatter.ts +3 -1
- package/src/render/ssrPass.ts +1 -0
- package/src/render/surfaceModel.ts +47 -10
- package/src/render/surfaceTexture.ts +69 -9
|
@@ -85,7 +85,7 @@ export declare const MAX_CLUSTERED_LIGHTS = 320;
|
|
|
85
85
|
* it cannot. That is what lets a light index the shadow uniforms by a number read from a texture,
|
|
86
86
|
* and it is the same fact the octahedral array texture was built on.
|
|
87
87
|
*/
|
|
88
|
-
export declare const LIGHT_TEXELS =
|
|
88
|
+
export declare const LIGHT_TEXELS = 6;
|
|
89
89
|
/**
|
|
90
90
|
* Where each field of a light's record sits, as an offset in `uint`s from `lightBase`.
|
|
91
91
|
*
|
|
@@ -115,7 +115,11 @@ export declare const LIGHT_TEXELS = 5;
|
|
|
115
115
|
* **Reordered 2026-10-02 so a plain light's fields are the first eight**, which is what lets the
|
|
116
116
|
* shader stop after two texels. Nine values are needed by every light and eight fit in two
|
|
117
117
|
* texels, so the emitter size and the weight share slot 7 as two halves; see `packSizeAndWeight`.
|
|
118
|
-
*
|
|
118
|
+
*
|
|
119
|
+
* **A sixth texel arrived in 4.8.7 for a light's own falloff exponent**, the fifth's last slot
|
|
120
|
+
* having gone to the channels: 6 KB more of a table near 300, read only for a light whose record
|
|
121
|
+
* carries a fixture, which a light with an exponent does. Slots 21 and 22 are a rectangle's barn
|
|
122
|
+
* doors and spare on a lamp; slot 23 is spare.
|
|
119
123
|
*/
|
|
120
124
|
export declare const LIGHT_RECORD: {
|
|
121
125
|
readonly positionX: 0;
|
|
@@ -146,6 +150,11 @@ export declare const LIGHT_RECORD: {
|
|
|
146
150
|
* last texels are fetched; a plain light's are not, and the shader takes the default for it.
|
|
147
151
|
*/
|
|
148
152
|
readonly channels: 19;
|
|
153
|
+
/** The light's own falloff exponent, or 0 for the frame's falloff. `PointLightSource.falloffExponent`. */
|
|
154
|
+
readonly falloffExponent: 20;
|
|
155
|
+
/** A rectangle's barn doors: the cosine of their angle from its normal, then their length in metres. */
|
|
156
|
+
readonly barnDoorCos: 21;
|
|
157
|
+
readonly barnDoorLength: 22;
|
|
149
158
|
};
|
|
150
159
|
/** The lighting channel every light and every surface is on unless it says otherwise. */
|
|
151
160
|
export declare const DEFAULT_LIGHT_CHANNELS = 1;
|
|
@@ -155,10 +164,16 @@ export declare const DEFAULT_LIGHT_CHANNELS = 1;
|
|
|
155
164
|
* this, so a light named badly is on channel 1 in both rather than on two different guesses.
|
|
156
165
|
*/
|
|
157
166
|
export declare function lightChannelsOf(value: number | undefined): number;
|
|
167
|
+
/**
|
|
168
|
+
* A light's own falloff exponent as the shader reads it: a positive finite number, or 0 for the
|
|
169
|
+
* frame's falloff where none was named or what was named is not one. The record and the uniform
|
|
170
|
+
* path both write this, so a light named badly falls off by the frame's rule in both.
|
|
171
|
+
*/
|
|
172
|
+
export declare function lightFalloffExponentOf(value: number | undefined): number;
|
|
158
173
|
/** Texels the lit pass reads for a light whose record carries no fixture. */
|
|
159
174
|
export declare const PLAIN_LIGHT_TEXELS = 2;
|
|
160
175
|
/**
|
|
161
|
-
* The bit of `sizeAndWeight` that says the record's last
|
|
176
|
+
* The bit of `sizeAndWeight` that says the record's last four texels hold something: the sign of
|
|
162
177
|
* the emitter size's half, which a size never needs.
|
|
163
178
|
*/
|
|
164
179
|
export declare const LIGHT_FIXTURE_FLAG = 32768;
|
|
@@ -287,6 +302,8 @@ export interface ClusterLightSet {
|
|
|
287
302
|
readonly iesProfiles?: Float32Array;
|
|
288
303
|
/** One per light: its lighting channels. See `PointLightSet.lightChannels`. */
|
|
289
304
|
readonly channels?: Float32Array;
|
|
305
|
+
/** One per light: its own falloff exponent, or 0. See `PointLightSource.falloffExponent`. */
|
|
306
|
+
readonly falloffExponents?: Float32Array;
|
|
290
307
|
/**
|
|
291
308
|
* The rectangles shaded through the table, after the `count` point lights: those of `areas` from
|
|
292
309
|
* `areaFrom` on, `areaCount` of them. The ones before `areaFrom` are the fixed arm's, which is
|
|
@@ -310,6 +327,7 @@ export interface ClusterLightArrays {
|
|
|
310
327
|
readonly lightIesAxes?: Float32Array;
|
|
311
328
|
readonly lightCookies?: Float32Array;
|
|
312
329
|
readonly lightChannels?: Float32Array;
|
|
330
|
+
readonly lightFalloffExponents?: Float32Array;
|
|
313
331
|
readonly areaLights?: AreaLightBuffer | null;
|
|
314
332
|
}
|
|
315
333
|
/** A `ClusterLightSet` a renderer refills every frame. */
|
|
@@ -336,8 +354,8 @@ export declare function fillClusterLightSet(env: ClusterLightArrays, out: Cluste
|
|
|
336
354
|
/** How many rectangles the fixed arm shades; the rest of the buffer is clustered. */
|
|
337
355
|
areaFrom?: number): ClusterLightSet;
|
|
338
356
|
/**
|
|
339
|
-
* Whether a light's record needs its last
|
|
340
|
-
* or a
|
|
357
|
+
* Whether a light's record needs its last four texels: it holds a shadow slot, a cone, a profile,
|
|
358
|
+
* a cookie, channels of its own or a falloff exponent of its own. Everything else is a plain light, read in `PLAIN_LIGHT_TEXELS`.
|
|
341
359
|
*
|
|
342
360
|
* **`shadowSlots` is zero where the lit shader has no point shadows**, and both binners are handed
|
|
343
361
|
* it that way: a slot is only read under `POINT_SHADOWS`, so a light holding one in a build without
|
|
@@ -84,7 +84,7 @@ export const MAX_CLUSTERED_LIGHTS = 320;
|
|
|
84
84
|
* it cannot. That is what lets a light index the shadow uniforms by a number read from a texture,
|
|
85
85
|
* and it is the same fact the octahedral array texture was built on.
|
|
86
86
|
*/
|
|
87
|
-
export const LIGHT_TEXELS =
|
|
87
|
+
export const LIGHT_TEXELS = 6;
|
|
88
88
|
/**
|
|
89
89
|
* Where each field of a light's record sits, as an offset in `uint`s from `lightBase`.
|
|
90
90
|
*
|
|
@@ -114,7 +114,11 @@ export const LIGHT_TEXELS = 5;
|
|
|
114
114
|
* **Reordered 2026-10-02 so a plain light's fields are the first eight**, which is what lets the
|
|
115
115
|
* shader stop after two texels. Nine values are needed by every light and eight fit in two
|
|
116
116
|
* texels, so the emitter size and the weight share slot 7 as two halves; see `packSizeAndWeight`.
|
|
117
|
-
*
|
|
117
|
+
*
|
|
118
|
+
* **A sixth texel arrived in 4.8.7 for a light's own falloff exponent**, the fifth's last slot
|
|
119
|
+
* having gone to the channels: 6 KB more of a table near 300, read only for a light whose record
|
|
120
|
+
* carries a fixture, which a light with an exponent does. Slots 21 and 22 are a rectangle's barn
|
|
121
|
+
* doors and spare on a lamp; slot 23 is spare.
|
|
118
122
|
*/
|
|
119
123
|
export const LIGHT_RECORD = {
|
|
120
124
|
positionX: 0,
|
|
@@ -145,6 +149,11 @@ export const LIGHT_RECORD = {
|
|
|
145
149
|
* last texels are fetched; a plain light's are not, and the shader takes the default for it.
|
|
146
150
|
*/
|
|
147
151
|
channels: 19,
|
|
152
|
+
/** The light's own falloff exponent, or 0 for the frame's falloff. `PointLightSource.falloffExponent`. */
|
|
153
|
+
falloffExponent: 20,
|
|
154
|
+
/** A rectangle's barn doors: the cosine of their angle from its normal, then their length in metres. */
|
|
155
|
+
barnDoorCos: 21,
|
|
156
|
+
barnDoorLength: 22,
|
|
148
157
|
};
|
|
149
158
|
/** The lighting channel every light and every surface is on unless it says otherwise. */
|
|
150
159
|
export const DEFAULT_LIGHT_CHANNELS = 1;
|
|
@@ -159,10 +168,18 @@ export function lightChannelsOf(value) {
|
|
|
159
168
|
}
|
|
160
169
|
return value;
|
|
161
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* A light's own falloff exponent as the shader reads it: a positive finite number, or 0 for the
|
|
173
|
+
* frame's falloff where none was named or what was named is not one. The record and the uniform
|
|
174
|
+
* path both write this, so a light named badly falls off by the frame's rule in both.
|
|
175
|
+
*/
|
|
176
|
+
export function lightFalloffExponentOf(value) {
|
|
177
|
+
return value !== undefined && Number.isFinite(value) && value > 0 ? value : 0;
|
|
178
|
+
}
|
|
162
179
|
/** Texels the lit pass reads for a light whose record carries no fixture. */
|
|
163
180
|
export const PLAIN_LIGHT_TEXELS = 2;
|
|
164
181
|
/**
|
|
165
|
-
* The bit of `sizeAndWeight` that says the record's last
|
|
182
|
+
* The bit of `sizeAndWeight` that says the record's last four texels hold something: the sign of
|
|
166
183
|
* the emitter size's half, which a size never needs.
|
|
167
184
|
*/
|
|
168
185
|
export const LIGHT_FIXTURE_FLAG = 0x8000;
|
|
@@ -392,6 +409,7 @@ areaFrom = 0) {
|
|
|
392
409
|
out.iesAxes = env.lightIesAxes;
|
|
393
410
|
out.cookies = env.lightCookies;
|
|
394
411
|
out.channels = env.lightChannels;
|
|
412
|
+
out.falloffExponents = env.lightFalloffExponents;
|
|
395
413
|
return out;
|
|
396
414
|
}
|
|
397
415
|
/*
|
|
@@ -436,8 +454,8 @@ function distanceSqToBounds(x, y, z, b) {
|
|
|
436
454
|
return dx * dx + dy * dy + dz * dz;
|
|
437
455
|
}
|
|
438
456
|
/**
|
|
439
|
-
* Whether a light's record needs its last
|
|
440
|
-
* or a
|
|
457
|
+
* Whether a light's record needs its last four texels: it holds a shadow slot, a cone, a profile,
|
|
458
|
+
* a cookie, channels of its own or a falloff exponent of its own. Everything else is a plain light, read in `PLAIN_LIGHT_TEXELS`.
|
|
441
459
|
*
|
|
442
460
|
* **`shadowSlots` is zero where the lit shader has no point shadows**, and both binners are handed
|
|
443
461
|
* it that way: a slot is only read under `POINT_SHADOWS`, so a light holding one in a build without
|
|
@@ -453,7 +471,8 @@ export function lightHasFixture(lights, light, shadowSlots) {
|
|
|
453
471
|
(cone?.[light * 2 + 1] ?? POINT_LIGHT_COS_OUTER) !== POINT_LIGHT_COS_OUTER ||
|
|
454
472
|
(lights.iesProfiles?.[light] ?? NO_IES_PROFILE) >= 0 ||
|
|
455
473
|
(lights.cookies?.[light] ?? NO_IES_PROFILE) >= 0 ||
|
|
456
|
-
lightChannelsOf(lights.channels?.[light]) !== DEFAULT_LIGHT_CHANNELS
|
|
474
|
+
lightChannelsOf(lights.channels?.[light]) !== DEFAULT_LIGHT_CHANNELS ||
|
|
475
|
+
lightFalloffExponentOf(lights.falloffExponents?.[light]) > 0);
|
|
457
476
|
}
|
|
458
477
|
/**
|
|
459
478
|
* The value of `uClustered` for a frame: 0 with clustering off, 1 when every light is plain, 2 when
|
|
@@ -525,6 +544,10 @@ export function writeLightRecord(lights, light, shadowSlots, out, at) {
|
|
|
525
544
|
out[at + LIGHT_RECORD.iesAxisY] = floatBits(axes?.[light * 3 + 1] ?? 0);
|
|
526
545
|
out[at + LIGHT_RECORD.iesAxisZ] = floatBits(axes?.[light * 3 + 2] ?? 0);
|
|
527
546
|
out[at + LIGHT_RECORD.channels] = floatBits(lightChannelsOf(lights.channels?.[light]));
|
|
547
|
+
out[at + LIGHT_RECORD.falloffExponent] = floatBits(lightFalloffExponentOf(lights.falloffExponents?.[light]));
|
|
548
|
+
out[at + LIGHT_RECORD.barnDoorCos] = 0;
|
|
549
|
+
out[at + LIGHT_RECORD.barnDoorLength] = 0;
|
|
550
|
+
out[at + LIGHT_RECORD.barnDoorLength + 1] = 0;
|
|
528
551
|
}
|
|
529
552
|
/**
|
|
530
553
|
* Which rectangle of the area buffer clustered light `light` is, for a light past the point
|
|
@@ -535,9 +558,9 @@ function areaSlot(lights, light) {
|
|
|
535
558
|
}
|
|
536
559
|
/**
|
|
537
560
|
* A rectangle's record, which the shader tells from a lamp's by its **negative radius** — its
|
|
538
|
-
* reach, negated — and reads to its
|
|
561
|
+
* reach, negated — and reads to its sixth texel: sidedness where a lamp keeps its shadow slot,
|
|
539
562
|
* the right axis where a spot keeps its direction, the half extents where its cone is, the up
|
|
540
|
-
* axis where the azimuth is, and the
|
|
563
|
+
* axis where the azimuth is, the channels, and the barn doors in the sixth. Never shadowed: a rectangle's occlusion is
|
|
541
564
|
* the fixed arm's, which is why the first `maxAreaLights` stay there.
|
|
542
565
|
*/
|
|
543
566
|
function writeAreaRecord(lights, light, out, at) {
|
|
@@ -568,6 +591,10 @@ function writeAreaRecord(lights, light, out, at) {
|
|
|
568
591
|
out[at + LIGHT_RECORD.iesAxisY] = floatBits(up?.[a * 3 + 1] ?? 1);
|
|
569
592
|
out[at + LIGHT_RECORD.iesAxisZ] = floatBits(up?.[a * 3 + 2] ?? 0);
|
|
570
593
|
out[at + LIGHT_RECORD.channels] = floatBits(DEFAULT_LIGHT_CHANNELS);
|
|
594
|
+
out[at + LIGHT_RECORD.falloffExponent] = 0;
|
|
595
|
+
out[at + LIGHT_RECORD.barnDoorCos] = floatBits(areas?.barnDoors?.[a * 2] ?? 0);
|
|
596
|
+
out[at + LIGHT_RECORD.barnDoorLength] = floatBits(areas?.barnDoors?.[a * 2 + 1] ?? 0);
|
|
597
|
+
out[at + LIGHT_RECORD.barnDoorLength + 1] = 0;
|
|
571
598
|
}
|
|
572
599
|
/** A clustered rectangle's reach, which its record carries negated. See `AreaLightSource.range`. */
|
|
573
600
|
function areaReach(lights, light) {
|
|
@@ -26,6 +26,8 @@ export interface FieldLight extends LightReach {
|
|
|
26
26
|
readonly b: number;
|
|
27
27
|
/** The emitter's physical radius, which widens its penumbra. */
|
|
28
28
|
readonly sourceRadius: number;
|
|
29
|
+
/** Its own falloff exponent, overriding the frame's above zero. See `pointLightShape`. */
|
|
30
|
+
readonly falloffExponent?: number;
|
|
29
31
|
}
|
|
30
32
|
/**
|
|
31
33
|
* Fill `light` and `direction`, `BRICK_TEXELS * 4` floats each, x fastest, for brick `brick`.
|
|
@@ -66,7 +66,7 @@ export function sampleFieldLight(px, py, pz, lights, candidates, first, last, fa
|
|
|
66
66
|
const dist = Math.hypot(dx, dy, dz);
|
|
67
67
|
if (dist >= source.radius)
|
|
68
68
|
continue;
|
|
69
|
-
const shape = pointLightShape(dist, source.radius, falloff);
|
|
69
|
+
const shape = pointLightShape(dist, source.radius, falloff, source.falloffExponent);
|
|
70
70
|
if (shape <= 0)
|
|
71
71
|
continue;
|
|
72
72
|
const seen = distance === null
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Both of the renderer's falloffs: `smooth`, the default, is a linear ramp squared, reaching zero at
|
|
11
11
|
* the radius; `inverseSquare` is physical, windowed so it still reaches zero at the radius, and
|
|
12
|
-
* floored at ten centimetres so a surface touching a light is not infinitely bright.
|
|
12
|
+
* floored at ten centimetres so a surface touching a light is not infinitely bright. And a light's
|
|
13
|
+
* own, which overrides both: `(1 - (d/R)^2)^n` with no distance term, for an exponent `n` above zero
|
|
14
|
+
* (`PointLightSource.falloffExponent`).
|
|
13
15
|
*/
|
|
14
16
|
export type PointLightFalloff = 'smooth' | 'inverseSquare';
|
|
15
17
|
/** The shader's `shape` for an unshaped point light: no cone, no photometric profile. */
|
|
16
|
-
export declare function pointLightShape(dist: number, radius: number, falloff: PointLightFalloff): number;
|
|
18
|
+
export declare function pointLightShape(dist: number, radius: number, falloff: PointLightFalloff, exponent?: number): number;
|
|
@@ -9,10 +9,16 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Both of the renderer's falloffs: `smooth`, the default, is a linear ramp squared, reaching zero at
|
|
11
11
|
* the radius; `inverseSquare` is physical, windowed so it still reaches zero at the radius, and
|
|
12
|
-
* floored at ten centimetres so a surface touching a light is not infinitely bright.
|
|
12
|
+
* floored at ten centimetres so a surface touching a light is not infinitely bright. And a light's
|
|
13
|
+
* own, which overrides both: `(1 - (d/R)^2)^n` with no distance term, for an exponent `n` above zero
|
|
14
|
+
* (`PointLightSource.falloffExponent`).
|
|
13
15
|
*/
|
|
14
16
|
/** The shader's `shape` for an unshaped point light: no cone, no photometric profile. */
|
|
15
|
-
export function pointLightShape(dist, radius, falloff) {
|
|
17
|
+
export function pointLightShape(dist, radius, falloff, exponent = 0) {
|
|
18
|
+
if (exponent > 0) {
|
|
19
|
+
const reach = dist / Math.max(radius, 1e-4);
|
|
20
|
+
return Math.min(1, Math.max(0, 1 - reach * reach)) ** exponent;
|
|
21
|
+
}
|
|
16
22
|
if (falloff === 'inverseSquare') {
|
|
17
23
|
const ratio = dist / Math.max(radius, 1e-4);
|
|
18
24
|
const window = Math.min(1, Math.max(0, 1 - ratio ** 4));
|
|
@@ -26,9 +26,10 @@ import type { PointLightSet } from '../lightBudget.ts';
|
|
|
26
26
|
export declare const MAX_BOUNCE_LIGHTS = 32;
|
|
27
27
|
/**
|
|
28
28
|
* Floats a light takes: where it is and how far it reaches; its colour at its weight and the cosine
|
|
29
|
-
* of its inner cone; the way it points and the cosine of its outer cone
|
|
29
|
+
* of its inner cone; the way it points and the cosine of its outer cone; and its own falloff
|
|
30
|
+
* exponent, 0 for the frame's, in a fourth row of its own (`PointLightSource.falloffExponent`).
|
|
30
31
|
*/
|
|
31
|
-
export declare const BOUNCE_LIGHT_FLOATS =
|
|
32
|
+
export declare const BOUNCE_LIGHT_FLOATS = 16;
|
|
32
33
|
export declare function createBounceLights(): Float32Array;
|
|
33
34
|
/** Pack the lights of `lights` the bounce reads into `out`, and answer how many. */
|
|
34
35
|
export declare function resolveBounceLights(lights: PointLightSet | null, out: Float32Array): number;
|
|
@@ -21,14 +21,15 @@
|
|
|
21
21
|
* What it gives up against the frame: a photometric profile and a cookie, which the bounce reads as
|
|
22
22
|
* a plain cone, and a light's specular, which a diffuse bounce has no use for.
|
|
23
23
|
*/
|
|
24
|
-
import { POINT_LIGHT_COS_INNER, POINT_LIGHT_COS_OUTER } from '../clusteredLights.js';
|
|
24
|
+
import { POINT_LIGHT_COS_INNER, POINT_LIGHT_COS_OUTER, lightFalloffExponentOf, } from '../clusteredLights.js';
|
|
25
25
|
/** Lights the probe bake shades a hit with. */
|
|
26
26
|
export const MAX_BOUNCE_LIGHTS = 32;
|
|
27
27
|
/**
|
|
28
28
|
* Floats a light takes: where it is and how far it reaches; its colour at its weight and the cosine
|
|
29
|
-
* of its inner cone; the way it points and the cosine of its outer cone
|
|
29
|
+
* of its inner cone; the way it points and the cosine of its outer cone; and its own falloff
|
|
30
|
+
* exponent, 0 for the frame's, in a fourth row of its own (`PointLightSource.falloffExponent`).
|
|
30
31
|
*/
|
|
31
|
-
export const BOUNCE_LIGHT_FLOATS =
|
|
32
|
+
export const BOUNCE_LIGHT_FLOATS = 16;
|
|
32
33
|
export function createBounceLights() {
|
|
33
34
|
return new Float32Array(MAX_BOUNCE_LIGHTS * BOUNCE_LIGHT_FLOATS);
|
|
34
35
|
}
|
|
@@ -60,6 +61,10 @@ export function resolveBounceLights(lights, out) {
|
|
|
60
61
|
out[at + 9] = lights.lightDirections?.[i * 3 + 1] ?? 0;
|
|
61
62
|
out[at + 10] = lights.lightDirections?.[i * 3 + 2] ?? 0;
|
|
62
63
|
out[at + 11] = lights.lightConeCos?.[i * 2 + 1] ?? POINT_LIGHT_COS_OUTER;
|
|
64
|
+
out[at + 12] = lightFalloffExponentOf(lights.lightFalloffExponents?.[i]);
|
|
65
|
+
out[at + 13] = 0;
|
|
66
|
+
out[at + 14] = 0;
|
|
67
|
+
out[at + 15] = 0;
|
|
63
68
|
count++;
|
|
64
69
|
}
|
|
65
70
|
return count;
|
|
@@ -51,6 +51,11 @@ export function cullInstances(data, local, frustum, out) {
|
|
|
51
51
|
for (let k = 0; k < 4; k++)
|
|
52
52
|
out.lightmapRegions[kept * 4 + k] = regions[i * 4 + k];
|
|
53
53
|
}
|
|
54
|
+
const cells = data.uvRegions;
|
|
55
|
+
if (cells !== undefined && out.uvRegions !== undefined) {
|
|
56
|
+
for (let k = 0; k < 4; k++)
|
|
57
|
+
out.uvRegions[kept * 4 + k] = cells[i * 4 + k];
|
|
58
|
+
}
|
|
54
59
|
kept++;
|
|
55
60
|
}
|
|
56
61
|
out.count = kept;
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
*
|
|
13
13
|
* **What it gives up** is a matrix per instance where a scatter spends five floats: 80 bytes
|
|
14
14
|
* against 44, and no wind. **What would make it wrong** is a caller wanting per-instance
|
|
15
|
-
* anything beyond an opacity — a morph weight,
|
|
16
|
-
* and the sixteen WebGL2 guarantees are already spent. The opacity rides the float the
|
|
17
|
-
* padded with, which is why
|
|
15
|
+
* anything beyond an opacity and a texture cell — a morph weight, say — which wants another
|
|
16
|
+
* attribute, and the sixteen WebGL2 guarantees are already spent. The opacity rides the float the
|
|
17
|
+
* stride was padded with and the cell the matrix's bottom row, which is why neither costs a byte.
|
|
18
18
|
*/
|
|
19
19
|
export interface MeshInstances {
|
|
20
20
|
/**
|
|
@@ -51,6 +51,20 @@ export interface MeshInstances {
|
|
|
51
51
|
* either mismatch is said once on the console.
|
|
52
52
|
*/
|
|
53
53
|
readonly lightmapRegions?: Float32Array;
|
|
54
|
+
/**
|
|
55
|
+
* Four floats each, the instance's cell of its texture: `[scaleU, scaleV, offsetU, offsetV]`,
|
|
56
|
+
* applied to the mesh's coordinates before the material's own scale and offset, in every pass
|
|
57
|
+
* that reads them — so one draw carries particles in different cells of a flipbook, or props
|
|
58
|
+
* wearing different tiles of one atlas. **Absent, or past `count`, is the whole texture**,
|
|
59
|
+
* `[1, 1, 0, 0]`, which is what every instance read before 4.8.7.
|
|
60
|
+
*
|
|
61
|
+
* **It rides the matrix's bottom row**, which an instance's placement — a turn, a scale and a
|
|
62
|
+
* move — leaves at `[0, 0, 0, 1]`, and which every stage drawing an instance rebuilds as that.
|
|
63
|
+
* So a placement must be affine, as every placement of a solid object is; a projective one is
|
|
64
|
+
* drawn as its affine part. A culled batch (`cullInstances`) carries the cells only into a
|
|
65
|
+
* target that has the array.
|
|
66
|
+
*/
|
|
67
|
+
readonly uvRegions?: Float32Array;
|
|
54
68
|
/** How many instances the arrays hold. Fixed at creation; the buffers are sized from it. */
|
|
55
69
|
readonly capacity: number;
|
|
56
70
|
/** How many are live. The rest of the buffer is neither uploaded nor drawn. */
|
|
@@ -65,8 +79,8 @@ export interface MeshInstances {
|
|
|
65
79
|
*/
|
|
66
80
|
export declare function createMeshInstances(capacity: number): MeshInstances;
|
|
67
81
|
/**
|
|
68
|
-
* Floats one instance occupies in the interleaved vertex buffer: sixteen of matrix,
|
|
69
|
-
* tint, and the opacity.
|
|
82
|
+
* Floats one instance occupies in the interleaved vertex buffer: sixteen of matrix, the bottom row
|
|
83
|
+
* of which carries the texture cell, three of tint, and the opacity.
|
|
70
84
|
*
|
|
71
85
|
* **Twenty so the stride is 80 bytes rather than 76.** A vertex buffer's stride must be a multiple
|
|
72
86
|
* of four on both backends, which 76 already is — the twentieth float was padding for the
|
package/dist/render/instances.js
CHANGED
|
@@ -15,8 +15,8 @@ export function createMeshInstances(capacity) {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Floats one instance occupies in the interleaved vertex buffer: sixteen of matrix,
|
|
19
|
-
* tint, and the opacity.
|
|
18
|
+
* Floats one instance occupies in the interleaved vertex buffer: sixteen of matrix, the bottom row
|
|
19
|
+
* of which carries the texture cell, three of tint, and the opacity.
|
|
20
20
|
*
|
|
21
21
|
* **Twenty so the stride is 80 bytes rather than 76.** A vertex buffer's stride must be a multiple
|
|
22
22
|
* of four on both backends, which 76 already is — the twentieth float was padding for the
|
|
@@ -39,13 +39,19 @@ export const INSTANCE_STRIDE = INSTANCE_FLOATS * 4;
|
|
|
39
39
|
* Writes nothing beyond `count`, and allocates nothing: `out` is the caller's staging array.
|
|
40
40
|
*/
|
|
41
41
|
export function packInstances(instances, out) {
|
|
42
|
-
const { models, tints, alphas, lightmapRegions } = instances;
|
|
42
|
+
const { models, tints, alphas, lightmapRegions, uvRegions } = instances;
|
|
43
43
|
const count = Math.min(instances.count, instances.capacity);
|
|
44
44
|
for (let i = 0; i < count; i += 1) {
|
|
45
45
|
const at = i * INSTANCE_FLOATS;
|
|
46
46
|
const m = i * 16;
|
|
47
47
|
for (let c = 0; c < 16; c += 1)
|
|
48
48
|
out[at + c] = models[m + c];
|
|
49
|
+
/* The texture cell in the bottom row an affine placement leaves free. See `uvRegions`. */
|
|
50
|
+
const cell = i * 4;
|
|
51
|
+
out[at + 3] = uvRegions?.[cell] ?? 1;
|
|
52
|
+
out[at + 7] = uvRegions?.[cell + 1] ?? 1;
|
|
53
|
+
out[at + 11] = uvRegions?.[cell + 2] ?? 0;
|
|
54
|
+
out[at + 15] = uvRegions?.[cell + 3] ?? 0;
|
|
49
55
|
if (lightmapRegions !== undefined) {
|
|
50
56
|
/* The region in the tint's and the opacity's place. See `lightmapRegions`. */
|
|
51
57
|
for (let c = 0; c < 4; c += 1)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** Fixed point-light budget shared by CPU selection and the flat shader. */
|
|
2
1
|
import type { AreaLightBuffer } from './areaLights.ts';
|
|
3
2
|
import type { ResolvedAreaShadows } from './areaShadowSet.ts';
|
|
4
3
|
/**
|
|
@@ -368,6 +367,12 @@ export interface PointLightSet {
|
|
|
368
367
|
* character's own key and rim lights, which the floor around the character must not take.
|
|
369
368
|
*/
|
|
370
369
|
readonly lightChannels?: Float32Array;
|
|
370
|
+
/**
|
|
371
|
+
* One per light: its own falloff exponent, `(1 - (d/R)^2)^n` with no distance term, or 0 for the
|
|
372
|
+
* frame's falloff. **Absent, or a short array, is 0 for every light**, so a scene that names none
|
|
373
|
+
* falls off as it did. See `PointLightSource.falloffExponent`.
|
|
374
|
+
*/
|
|
375
|
+
readonly lightFalloffExponents?: Float32Array;
|
|
371
376
|
}
|
|
372
377
|
/**
|
|
373
378
|
* A cone that admits every direction, for every light.
|
|
@@ -441,6 +446,12 @@ export interface ResolvedPointLights {
|
|
|
441
446
|
* array of their own, which would cost a row a light of a budget that has none to give.
|
|
442
447
|
*/
|
|
443
448
|
readonly axesAndChannels: Float32Array;
|
|
449
|
+
/**
|
|
450
|
+
* Owned, four a light: the cone's two cosines, then the light's own falloff exponent, as
|
|
451
|
+
* `uLightCone` carries them — a `vec2` array costs a whole row a light on WebGL2 anyway, so the
|
|
452
|
+
* exponent rides the half it left rather than a row of its own.
|
|
453
|
+
*/
|
|
454
|
+
readonly conesAndFalloffs: Float32Array;
|
|
444
455
|
/**
|
|
445
456
|
* Owned, `MAX_POINT_LIGHTS` long: the emitter sizes and weights as the froxel record carries
|
|
446
457
|
* them, at half precision. `sourceRadii` and `weights` point here once a light set resolves, so
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/** Fixed point-light budget shared by CPU selection and the flat shader. */
|
|
2
|
+
import { MAX_AREA_LIGHTS, packAreaSizesAndDoors } from './areaLights.js';
|
|
3
|
+
import { POINT_LIGHT_COS_INNER, POINT_LIGHT_COS_OUTER, lampSourceRadius, lampWeight, lightChannelsOf, lightFalloffExponentOf, } from './clusteredLights.js';
|
|
2
4
|
/**
|
|
3
5
|
* How many point lights the shading pass shades against at once.
|
|
4
6
|
*
|
|
@@ -302,6 +304,8 @@ export const SDF_TEXT_TEXTURE_UNIT = SURFACE_TEXTURE_UNIT;
|
|
|
302
304
|
export const POINT_SHADOW_POOL = MAX_POINT_LIGHTS + 4;
|
|
303
305
|
/** Weights are optional per consumer; a short array means every light is fully present. */
|
|
304
306
|
const FULLY_PRESENT = new Float32Array(MAX_POINT_LIGHTS).fill(1);
|
|
307
|
+
/** The fixed arm's rectangles as `uAreaLightSize` holds them; refilled per bind, allocated once. */
|
|
308
|
+
const AREA_SIZES_AND_DOORS = new Float32Array(MAX_AREA_LIGHTS * 4);
|
|
305
309
|
/** No size at all, which is the behaviour every scene had before a light could have one. */
|
|
306
310
|
const POINT_SOURCES = new Float32Array(MAX_POINT_LIGHTS);
|
|
307
311
|
/**
|
|
@@ -394,7 +398,8 @@ export function bindPointLights(gl, uniforms, lights, falloff) {
|
|
|
394
398
|
* every lamp switched off.
|
|
395
399
|
*/
|
|
396
400
|
gl.uniform3fv(uniforms['uLightDir[0]'] ?? null, r.directions);
|
|
397
|
-
|
|
401
|
+
/* The cone and, in z, the light's own falloff exponent. See `conesAndFalloffs`. */
|
|
402
|
+
gl.uniform4fv(uniforms['uLightCone[0]'] ?? null, r.conesAndFalloffs);
|
|
398
403
|
gl.uniform1fv(uniforms['uLightIesProfile[0]'] ?? null, r.iesProfiles);
|
|
399
404
|
/* The azimuth and, in w, the channels. See `axesAndChannels`. */
|
|
400
405
|
gl.uniform4fv(uniforms['uLightIesAxis[0]'] ?? null, r.axesAndChannels);
|
|
@@ -421,7 +426,9 @@ export function bindAreaLights(gl, uniforms, lights) {
|
|
|
421
426
|
gl.uniform3fv(uniforms['uAreaLightColor[0]'] ?? null, lights.colors);
|
|
422
427
|
gl.uniform3fv(uniforms['uAreaLightRight[0]'] ?? null, lights.right);
|
|
423
428
|
gl.uniform3fv(uniforms['uAreaLightUp[0]'] ?? null, lights.up);
|
|
424
|
-
|
|
429
|
+
/* The half extents and, in zw, the barn doors. See `packAreaSizesAndDoors`. */
|
|
430
|
+
packAreaSizesAndDoors(lights, AREA_SIZES_AND_DOORS);
|
|
431
|
+
gl.uniform4fv(uniforms['uAreaLightSize[0]'] ?? null, AREA_SIZES_AND_DOORS);
|
|
425
432
|
gl.uniform1fv(uniforms['uAreaLightTwoSided[0]'] ?? null, lights.twoSided);
|
|
426
433
|
}
|
|
427
434
|
/**
|
|
@@ -463,6 +470,7 @@ const glLights = {
|
|
|
463
470
|
iesAxes: NO_IES_AXES,
|
|
464
471
|
cookies: NO_COOKIES,
|
|
465
472
|
axesAndChannels: new Float32Array(MAX_POINT_LIGHTS * 4),
|
|
473
|
+
conesAndFalloffs: new Float32Array(MAX_POINT_LIGHTS * 4),
|
|
466
474
|
heldSourceRadii: new Float32Array(MAX_POINT_LIGHTS),
|
|
467
475
|
heldWeights: new Float32Array(MAX_POINT_LIGHTS),
|
|
468
476
|
};
|
|
@@ -493,6 +501,7 @@ export function resolvePointLights(lights, falloff, out) {
|
|
|
493
501
|
out.iesAxes = NO_IES_AXES;
|
|
494
502
|
out.cookies = NO_COOKIES;
|
|
495
503
|
packAxesAndChannels(NO_IES_AXES, undefined, out.axesAndChannels);
|
|
504
|
+
packConesAndFalloffs(OPEN_CONES, undefined, out.conesAndFalloffs);
|
|
496
505
|
return out;
|
|
497
506
|
}
|
|
498
507
|
/*
|
|
@@ -574,8 +583,19 @@ export function resolvePointLights(lights, falloff, out) {
|
|
|
574
583
|
out.iesAxes = iesAxes;
|
|
575
584
|
out.cookies = cookies;
|
|
576
585
|
packAxesAndChannels(iesAxes, lights.lightChannels, out.axesAndChannels);
|
|
586
|
+
packConesAndFalloffs(coneCos, lights.lightFalloffExponents, out.conesAndFalloffs);
|
|
577
587
|
return out;
|
|
578
588
|
}
|
|
589
|
+
/** The cone and the falloff exponent, four a light, as `uLightCone` holds them. */
|
|
590
|
+
function packConesAndFalloffs(cones, exponents, out) {
|
|
591
|
+
const named = (exponents?.length ?? 0) >= MAX_POINT_LIGHTS ? exponents : undefined;
|
|
592
|
+
for (let light = 0; light < MAX_POINT_LIGHTS; light++) {
|
|
593
|
+
out[light * 4] = cones[light * 2] ?? POINT_LIGHT_COS_INNER;
|
|
594
|
+
out[light * 4 + 1] = cones[light * 2 + 1] ?? POINT_LIGHT_COS_OUTER;
|
|
595
|
+
out[light * 4 + 2] = lightFalloffExponentOf(named?.[light]);
|
|
596
|
+
out[light * 4 + 3] = 0;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
579
599
|
/** The azimuth and the channels, four a light, as `uLightIesAxis` holds them. */
|
|
580
600
|
function packAxesAndChannels(axes, channels, out) {
|
|
581
601
|
const named = (channels?.length ?? 0) >= MAX_POINT_LIGHTS ? channels : undefined;
|
|
@@ -35,10 +35,12 @@ export interface LightmapPage {
|
|
|
35
35
|
readonly width: number;
|
|
36
36
|
readonly height: number;
|
|
37
37
|
/**
|
|
38
|
-
* Linear irradiance,
|
|
39
|
-
*
|
|
38
|
+
* Linear irradiance, row-major from the top, in the same units every other light here is in — a
|
|
39
|
+
* bake from another engine scaled by that engine's exposure, as its lights are. Either three
|
|
40
|
+
* floats a texel, or one word a texel already packed as rgb9e5 (`rgb9e5.ts`), which is how such a
|
|
41
|
+
* bake usually arrives and which uploads with no conversion at all.
|
|
40
42
|
*/
|
|
41
|
-
readonly irradiance: Float32Array;
|
|
43
|
+
readonly irradiance: Float32Array | Uint32Array;
|
|
42
44
|
/**
|
|
43
45
|
* The direction the light arrives from, four bytes a texel: a first-order spherical harmonic in
|
|
44
46
|
* this engine's world axes, each value stored as `v * 0.5 + 0.5` — xyz the axis, w the constant.
|
|
@@ -54,20 +56,32 @@ export declare const LIGHTMAP_LAYERS = 2;
|
|
|
54
56
|
export type LightmapRegion = readonly [number, number, number, number];
|
|
55
57
|
/** The whole page. */
|
|
56
58
|
export declare const WHOLE_PAGE: LightmapRegion;
|
|
57
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* A page as both backends upload it: two layers of four bytes a texel, row-major from the top. The
|
|
61
|
+
* first is the irradiance's rgb9e5 word, byte by byte from its lowest; the second is the direction.
|
|
62
|
+
*
|
|
63
|
+
* **Eight bytes a texel, and the shader filters it.** Half floats were sixteen: a stage of 49 pages
|
|
64
|
+
* and 21.8 million texels held 348 MB, where this holds 174. The price is that the irradiance cannot
|
|
65
|
+
* be filtered as it is stored — a bilinear blend of two words with different exponents is not the
|
|
66
|
+
* blend of the colours they hold — so the lit stage fetches a texel's four neighbours, decodes each
|
|
67
|
+
* and blends the colours (`modelBaked` in `shaders/flat/models.ts`): four fetches a layer where one
|
|
68
|
+
* filtered sample was, on lightmapped surfaces alone. **What would make it wrong** is every device
|
|
69
|
+
* a page reaches sampling `rgb9e5ufloat`, which WebGL2 does as `RGB9_E5` too; it would then be a
|
|
70
|
+
* texture of its own, filtered by the device, and a binding the lit stage has no room for.
|
|
71
|
+
*/
|
|
58
72
|
export interface LightmapTexels {
|
|
59
73
|
readonly kind: 'lightmap-texels';
|
|
60
74
|
readonly width: number;
|
|
61
75
|
readonly height: number;
|
|
62
|
-
|
|
63
|
-
readonly
|
|
76
|
+
/** Each layer's bytes, in layer order: the irradiance, then the direction. */
|
|
77
|
+
readonly layers: readonly Uint8Array[];
|
|
64
78
|
}
|
|
65
79
|
/** Whether a surface texture's source is a page's texels rather than an image or blocks. */
|
|
66
80
|
export declare function isLightmapTexels(source: unknown): source is LightmapTexels;
|
|
67
81
|
/**
|
|
68
|
-
* A page as both backends upload it
|
|
69
|
-
*
|
|
70
|
-
*
|
|
82
|
+
* A page as both backends upload it (see `LightmapTexels`). Refuses a page whose arrays are not its
|
|
83
|
+
* size, naming the one that is short. Packed irradiance is read where it lies, with no copy; float
|
|
84
|
+
* irradiance is packed once, here.
|
|
71
85
|
*/
|
|
72
86
|
export declare function lightmapTexels(page: LightmapPage): LightmapTexels;
|
|
73
87
|
/**
|
package/dist/render/lightmap.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { packRgb9e5 } from './rgb9e5.js';
|
|
2
2
|
/** Layers a page is uploaded as: the irradiance, then the direction. */
|
|
3
3
|
export const LIGHTMAP_LAYERS = 2;
|
|
4
4
|
/** The whole page. */
|
|
@@ -10,9 +10,9 @@ export function isLightmapTexels(source) {
|
|
|
10
10
|
source.kind === 'lightmap-texels');
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* A page as both backends upload it
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* A page as both backends upload it (see `LightmapTexels`). Refuses a page whose arrays are not its
|
|
14
|
+
* size, naming the one that is short. Packed irradiance is read where it lies, with no copy; float
|
|
15
|
+
* irradiance is packed once, here.
|
|
16
16
|
*/
|
|
17
17
|
export function lightmapTexels(page) {
|
|
18
18
|
const { width, height } = page;
|
|
@@ -20,32 +20,54 @@ export function lightmapTexels(page) {
|
|
|
20
20
|
throw new Error(`createLightmap: a ${width} by ${height} page has nothing in it`);
|
|
21
21
|
}
|
|
22
22
|
const texels = width * height;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const irradiance = page.irradiance;
|
|
24
|
+
const packed = irradiance instanceof Uint32Array;
|
|
25
|
+
const wanted = packed ? texels : texels * 3;
|
|
26
|
+
if (irradiance.length !== wanted) {
|
|
27
|
+
throw new Error(`createLightmap: ${irradiance.length} irradiance ${packed ? 'words' : 'floats'} for a ` +
|
|
28
|
+
`${width} by ${height} page, which wants ${wanted}: ` +
|
|
29
|
+
(packed ? 'one rgb9e5 word a texel' : 'three a texel'));
|
|
26
30
|
}
|
|
27
31
|
if (page.direction.length !== texels * 4) {
|
|
28
32
|
throw new Error(`createLightmap: ${page.direction.length} direction bytes for a ${width} by ${height} ` +
|
|
29
33
|
`page, which wants ${texels * 4}: four a texel`);
|
|
30
34
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
let words;
|
|
36
|
+
if (packed) {
|
|
37
|
+
words = irradiance;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
words = new Uint32Array(texels);
|
|
41
|
+
for (let t = 0; t < texels; t++) {
|
|
42
|
+
words[t] = packRgb9e5(irradiance[t * 3], irradiance[t * 3 + 1], irradiance[t * 3 + 2]);
|
|
43
|
+
}
|
|
40
44
|
}
|
|
41
45
|
return {
|
|
42
46
|
kind: 'lightmap-texels',
|
|
43
47
|
width,
|
|
44
48
|
height,
|
|
45
|
-
layers:
|
|
46
|
-
texels: toHalfFloats(floats),
|
|
49
|
+
layers: [littleEndianBytes(words), page.direction],
|
|
47
50
|
};
|
|
48
51
|
}
|
|
52
|
+
/** Whether this platform lays a word out lowest byte first, which the shader's decode assumes. */
|
|
53
|
+
const LITTLE_ENDIAN = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;
|
|
54
|
+
/**
|
|
55
|
+
* A page's words as bytes, lowest first: a view of the same memory on every little-endian platform,
|
|
56
|
+
* which is every one a browser runs on, and a reordered copy anywhere else.
|
|
57
|
+
*/
|
|
58
|
+
function littleEndianBytes(words) {
|
|
59
|
+
const bytes = new Uint8Array(words.buffer, words.byteOffset, words.length * 4);
|
|
60
|
+
if (LITTLE_ENDIAN)
|
|
61
|
+
return bytes;
|
|
62
|
+
const swapped = new Uint8Array(bytes.length);
|
|
63
|
+
for (let i = 0; i < bytes.length; i += 4) {
|
|
64
|
+
swapped[i] = bytes[i + 3];
|
|
65
|
+
swapped[i + 1] = bytes[i + 2];
|
|
66
|
+
swapped[i + 2] = bytes[i + 1];
|
|
67
|
+
swapped[i + 3] = bytes[i];
|
|
68
|
+
}
|
|
69
|
+
return swapped;
|
|
70
|
+
}
|
|
49
71
|
/**
|
|
50
72
|
* A mesh with its second coordinates moved onto the grain and relief streams they ride, or the
|
|
51
73
|
* mesh itself where it has none. Called by both backends' `createMesh`, before anything records
|