@driftengine/core 4.8.5 → 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 +4392 -4250
- package/README.md +1 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.js +12 -2
- package/dist/render/ambientHarmonics.d.ts +46 -0
- package/dist/render/ambientHarmonics.js +80 -0
- package/dist/render/anisotropicLobe.d.ts +10 -0
- package/dist/render/anisotropicLobe.js +19 -0
- package/dist/render/areaLights.d.ts +54 -0
- package/dist/render/areaLights.js +55 -0
- package/dist/render/backend/api.d.ts +43 -2
- package/dist/render/backend/webgl2/instanced.d.ts +2 -0
- package/dist/render/backend/webgl2/instanced.js +11 -2
- package/dist/render/backend/webgl2/renderer.d.ts +120 -11
- package/dist/render/backend/webgl2/renderer.js +356 -53
- package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
- package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
- package/dist/render/backend/webgl2/skinScatter.d.ts +14 -0
- package/dist/render/backend/webgl2/skinScatter.js +88 -26
- package/dist/render/backend/webgpu/buffers.js +2 -1
- package/dist/render/backend/webgpu/clusterBinner.js +3 -2
- package/dist/render/backend/webgpu/flatPass.d.ts +7 -5
- package/dist/render/backend/webgpu/flatPass.js +15 -13
- package/dist/render/backend/webgpu/particlePass.d.ts +37 -4
- package/dist/render/backend/webgpu/particlePass.js +81 -12
- package/dist/render/backend/webgpu/postPass.d.ts +6 -1
- package/dist/render/backend/webgpu/postPass.js +7 -2
- package/dist/render/backend/webgpu/renderer.d.ts +93 -8
- package/dist/render/backend/webgpu/renderer.js +483 -93
- 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/shaders/clusterBin.wgsl.js +41 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +40 -28
- package/dist/render/backend/webgpu/skinPaletteRing.js +69 -32
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +10 -2
- package/dist/render/backend/webgpu/skinScatterPass.js +59 -9
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +45 -5
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/bloomChain.d.ts +44 -0
- package/dist/render/bloomChain.js +28 -0
- package/dist/render/bloomPass.d.ts +3 -1
- package/dist/render/bloomPass.js +16 -2
- package/dist/render/clusteredLights.d.ts +78 -6
- package/dist/render/clusteredLights.js +214 -13
- 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/fixtureAtlas.js +1 -1
- package/dist/render/gi/bounceLights.d.ts +3 -2
- package/dist/render/gi/bounceLights.js +8 -3
- package/dist/render/instanceCull.js +12 -0
- package/dist/render/instances.d.ts +47 -11
- package/dist/render/instances.js +24 -9
- package/dist/render/lightBudget.d.ts +38 -2
- package/dist/render/lightBudget.js +48 -5
- package/dist/render/lightmap.d.ts +116 -0
- package/dist/render/lightmap.js +140 -0
- package/dist/render/mesh.js +2 -1
- package/dist/render/particleBatch.d.ts +59 -3
- package/dist/render/particleBatch.js +53 -2
- package/dist/render/particleMaterial.d.ts +6 -1
- package/dist/render/particleMaterial.js +10 -2
- package/dist/render/particlePool.d.ts +32 -0
- package/dist/render/particlePool.js +9 -0
- package/dist/render/particleSort.d.ts +34 -0
- package/dist/render/particleSort.js +96 -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/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +8 -1
- package/dist/render/screenSpaceReflection.d.ts +27 -1
- package/dist/render/screenSpaceReflection.js +9 -0
- package/dist/render/shaders/bloom.d.ts +8 -4
- package/dist/render/shaders/bloom.js +20 -3
- 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 +10 -0
- package/dist/render/shaders/flat/areaLight.js +142 -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 +46 -6
- 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 +158 -121
- package/dist/render/shaders/flat/models.d.ts +21 -6
- package/dist/render/shaders/flat/models.js +161 -17
- package/dist/render/shaders/flat/normalMap.d.ts +10 -0
- package/dist/render/shaders/flat/normalMap.js +25 -0
- package/dist/render/shaders/flat/preamble.js +84 -40
- package/dist/render/shaders/flat/skin.js +6 -5
- package/dist/render/shaders/generated/bloom.wgsl.d.ts +13 -3
- package/dist/render/shaders/generated/bloom.wgsl.js +13 -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 +1647 -2044
- package/dist/render/shaders/generated/flat.wgsl.js +1685 -2082
- package/dist/render/shaders/generated/particle.wgsl.d.ts +115 -1
- package/dist/render/shaders/generated/particle.wgsl.js +115 -1
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/skinBlur.wgsl.js +12 -2
- 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/particle.d.ts +17 -1
- package/dist/render/shaders/particle.js +133 -1
- 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/skinBlur.js +25 -3
- 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/skinBlur.d.ts +8 -5
- package/dist/render/skinBlur.js +8 -5
- 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 +49 -8
- package/dist/render/surfaceModel.js +54 -8
- package/dist/render/surfaceTexture.d.ts +38 -1
- package/dist/render/surfaceTexture.js +37 -2
- package/package.json +6 -6
- package/src/index.ts +26 -3
- package/src/render/ambientHarmonics.ts +92 -0
- package/src/render/anisotropicLobe.ts +29 -0
- package/src/render/areaLights.ts +97 -0
- package/src/render/backend/api.ts +61 -1
- package/src/render/backend/webgl2/instanced.ts +12 -2
- package/src/render/backend/webgl2/renderer.ts +470 -59
- package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
- package/src/render/backend/webgl2/skinScatter.ts +120 -43
- package/src/render/backend/webgpu/buffers.ts +2 -1
- package/src/render/backend/webgpu/clusterBinner.ts +3 -1
- package/src/render/backend/webgpu/flatPass.ts +18 -13
- package/src/render/backend/webgpu/particlePass.ts +119 -12
- package/src/render/backend/webgpu/postPass.ts +7 -2
- package/src/render/backend/webgpu/renderer.ts +589 -113
- package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
- package/src/render/backend/webgpu/shaders/clusterBin.wgsl.ts +41 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +65 -33
- package/src/render/backend/webgpu/skinScatterPass.ts +59 -9
- package/src/render/backend/webgpu/surfaceTexturePass.ts +57 -6
- package/src/render/bloomChain.ts +69 -0
- package/src/render/bloomPass.ts +26 -2
- package/src/render/clusteredLights.ts +259 -12
- package/src/render/driftLight/bake.ts +3 -1
- package/src/render/driftLight/falloff.ts +13 -2
- package/src/render/fixtureAtlas.ts +1 -1
- package/src/render/gi/bounceLights.ts +12 -3
- package/src/render/instanceCull.ts +9 -0
- package/src/render/instances.ts +61 -12
- package/src/render/lightBudget.ts +88 -4
- package/src/render/lightmap.ts +243 -0
- package/src/render/mesh.ts +2 -1
- package/src/render/particleBatch.ts +95 -4
- package/src/render/particleMaterial.ts +11 -1
- package/src/render/particlePool.ts +36 -0
- package/src/render/particleSort.ts +133 -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/sceneTarget.ts +18 -1
- package/src/render/screenSpaceReflection.ts +33 -1
- package/src/render/shaders/bloom.ts +20 -3
- 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 +142 -0
- package/src/render/shaders/flat/glassTint.ts +9 -2
- package/src/render/shaders/flat/index.ts +51 -6
- package/src/render/shaders/flat/lobes.ts +50 -43
- package/src/render/shaders/flat/main.ts +158 -121
- package/src/render/shaders/flat/models.ts +170 -21
- package/src/render/shaders/flat/normalMap.ts +25 -0
- package/src/render/shaders/flat/preamble.ts +84 -40
- package/src/render/shaders/flat/skin.ts +6 -5
- package/src/render/shaders/generated/bloom.wgsl.ts +13 -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 +1685 -2082
- package/src/render/shaders/generated/particle.wgsl.ts +116 -1
- package/src/render/shaders/generated/skinBlur.wgsl.ts +12 -2
- 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/particle.ts +134 -1
- package/src/render/shaders/recon/motion.wgsl.ts +13 -2
- package/src/render/shaders/skinBlur.ts +25 -3
- package/src/render/shaders/ssrTrace.ts +20 -1
- package/src/render/shaders/water.ts +12 -3
- package/src/render/skinBlur.ts +8 -5
- package/src/render/skinScatter.ts +3 -1
- package/src/render/ssrPass.ts +1 -0
- package/src/render/surfaceModel.ts +82 -10
- package/src/render/surfaceTexture.ts +101 -3
package/src/render/bloomPass.ts
CHANGED
|
@@ -159,7 +159,14 @@ export class BloomPass {
|
|
|
159
159
|
* `threshold` is in scene units, so it only separates a light from white paint on a target
|
|
160
160
|
* that kept the range. The caller owns that decision and the warning that goes with it.
|
|
161
161
|
*/
|
|
162
|
-
run(
|
|
162
|
+
run(
|
|
163
|
+
scene: WebGLTexture,
|
|
164
|
+
width: number,
|
|
165
|
+
height: number,
|
|
166
|
+
threshold: number,
|
|
167
|
+
/** The ramp, then each level's tint: `resolveBloomResponse`'s floats. */
|
|
168
|
+
response: Float32Array,
|
|
169
|
+
): WebGLTexture | null {
|
|
163
170
|
if (!this.ensureSize(width, height)) return null;
|
|
164
171
|
const { gl } = this;
|
|
165
172
|
const levels = this.levels;
|
|
@@ -182,6 +189,7 @@ export class BloomPass {
|
|
|
182
189
|
gl.bindTexture(gl.TEXTURE_2D, scene);
|
|
183
190
|
gl.uniform1i(this.prefilterUniforms['uSource'] ?? null, 0);
|
|
184
191
|
gl.uniform1f(this.prefilterUniforms['uThreshold'] ?? null, threshold);
|
|
192
|
+
gl.uniform1f(this.prefilterUniforms['uRamp'] ?? null, response[0] ?? 0);
|
|
185
193
|
this.uploadTexel(this.prefilterUniforms, width, height);
|
|
186
194
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
187
195
|
|
|
@@ -204,8 +212,13 @@ export class BloomPass {
|
|
|
204
212
|
* every octave at once. Additive blending rather than a second target to ping-pong through:
|
|
205
213
|
* a level is only ever read while a *different* level is bound, so there is no feedback.
|
|
206
214
|
*/
|
|
215
|
+
/*
|
|
216
|
+
* Each level's tint rides the blend: the level being added onto is multiplied by its own as the
|
|
217
|
+
* octave below is added, and the deepest by `uScale` on the first draw, the only one reading it
|
|
218
|
+
* before anything has been added to it. White everywhere is `ONE, ONE` exactly.
|
|
219
|
+
*/
|
|
207
220
|
gl.enable(gl.BLEND);
|
|
208
|
-
gl.blendFunc(gl.ONE, gl.
|
|
221
|
+
gl.blendFunc(gl.ONE, gl.CONSTANT_COLOR);
|
|
209
222
|
gl.useProgram(this.upsampleProgram);
|
|
210
223
|
gl.uniform1i(this.upsampleUniforms['uSource'] ?? null, 0);
|
|
211
224
|
gl.uniform1f(this.upsampleUniforms['uRadius'] ?? null, BLOOM_FILTER_RADIUS_UV);
|
|
@@ -213,12 +226,23 @@ export class BloomPass {
|
|
|
213
226
|
const target = levels[index - 1];
|
|
214
227
|
const source = levels[index];
|
|
215
228
|
if (target === undefined || source === undefined) break;
|
|
229
|
+
const deepest = index === levels.length - 1;
|
|
230
|
+
const into = 1 + (index - 1) * 3;
|
|
231
|
+
const from = 1 + index * 3;
|
|
232
|
+
gl.blendColor(response[into] ?? 1, response[into + 1] ?? 1, response[into + 2] ?? 1, 1);
|
|
233
|
+
gl.uniform3f(
|
|
234
|
+
this.upsampleUniforms['uScale'] ?? null,
|
|
235
|
+
deepest ? (response[from] ?? 1) : 1,
|
|
236
|
+
deepest ? (response[from + 1] ?? 1) : 1,
|
|
237
|
+
deepest ? (response[from + 2] ?? 1) : 1,
|
|
238
|
+
);
|
|
216
239
|
gl.bindFramebuffer(gl.FRAMEBUFFER, target.framebuffer);
|
|
217
240
|
gl.viewport(0, 0, target.width, target.height);
|
|
218
241
|
gl.bindTexture(gl.TEXTURE_2D, source.texture);
|
|
219
242
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
220
243
|
}
|
|
221
244
|
|
|
245
|
+
gl.blendColor(0, 0, 0, 0);
|
|
222
246
|
gl.disable(gl.BLEND);
|
|
223
247
|
gl.bindVertexArray(null);
|
|
224
248
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AreaLightBuffer } from './areaLights.ts';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The froxel table: what it looks like in memory, and the CPU binner that fills it.
|
|
3
5
|
*
|
|
@@ -90,7 +92,7 @@ export const MAX_CLUSTERED_LIGHTS = 320;
|
|
|
90
92
|
* it cannot. That is what lets a light index the shadow uniforms by a number read from a texture,
|
|
91
93
|
* and it is the same fact the octahedral array texture was built on.
|
|
92
94
|
*/
|
|
93
|
-
export const LIGHT_TEXELS =
|
|
95
|
+
export const LIGHT_TEXELS = 6;
|
|
94
96
|
|
|
95
97
|
/**
|
|
96
98
|
* Where each field of a light's record sits, as an offset in `uint`s from `lightBase`.
|
|
@@ -121,7 +123,11 @@ export const LIGHT_TEXELS = 5;
|
|
|
121
123
|
* **Reordered 2026-10-02 so a plain light's fields are the first eight**, which is what lets the
|
|
122
124
|
* shader stop after two texels. Nine values are needed by every light and eight fit in two
|
|
123
125
|
* texels, so the emitter size and the weight share slot 7 as two halves; see `packSizeAndWeight`.
|
|
124
|
-
*
|
|
126
|
+
*
|
|
127
|
+
* **A sixth texel arrived in 4.8.7 for a light's own falloff exponent**, the fifth's last slot
|
|
128
|
+
* having gone to the channels: 6 KB more of a table near 300, read only for a light whose record
|
|
129
|
+
* carries a fixture, which a light with an exponent does. Slots 21 and 22 are a rectangle's barn
|
|
130
|
+
* doors and spare on a lamp; slot 23 is spare.
|
|
125
131
|
*/
|
|
126
132
|
export const LIGHT_RECORD = {
|
|
127
133
|
positionX: 0,
|
|
@@ -146,13 +152,48 @@ export const LIGHT_RECORD = {
|
|
|
146
152
|
iesAxisX: 16,
|
|
147
153
|
iesAxisY: 17,
|
|
148
154
|
iesAxisZ: 18,
|
|
155
|
+
/**
|
|
156
|
+
* The light's lighting channels, a mask (`PointLightSet.lightChannels`), in the slot that was
|
|
157
|
+
* spare. A light naming channels other than the default is read as carrying a fixture, so its
|
|
158
|
+
* last texels are fetched; a plain light's are not, and the shader takes the default for it.
|
|
159
|
+
*/
|
|
160
|
+
channels: 19,
|
|
161
|
+
/** The light's own falloff exponent, or 0 for the frame's falloff. `PointLightSource.falloffExponent`. */
|
|
162
|
+
falloffExponent: 20,
|
|
163
|
+
/** A rectangle's barn doors: the cosine of their angle from its normal, then their length in metres. */
|
|
164
|
+
barnDoorCos: 21,
|
|
165
|
+
barnDoorLength: 22,
|
|
149
166
|
} as const;
|
|
150
167
|
|
|
168
|
+
/** The lighting channel every light and every surface is on unless it says otherwise. */
|
|
169
|
+
export const DEFAULT_LIGHT_CHANNELS = 1;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* A light's channels as the shader compares them: a whole mask from 1 to 255, or the default where
|
|
173
|
+
* none was named or what was named is not one. The froxel record and the uniform path both write
|
|
174
|
+
* this, so a light named badly is on channel 1 in both rather than on two different guesses.
|
|
175
|
+
*/
|
|
176
|
+
export function lightChannelsOf(value: number | undefined): number {
|
|
177
|
+
if (value === undefined || !Number.isInteger(value) || value < 1 || value > 255) {
|
|
178
|
+
return DEFAULT_LIGHT_CHANNELS;
|
|
179
|
+
}
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* A light's own falloff exponent as the shader reads it: a positive finite number, or 0 for the
|
|
185
|
+
* frame's falloff where none was named or what was named is not one. The record and the uniform
|
|
186
|
+
* path both write this, so a light named badly falls off by the frame's rule in both.
|
|
187
|
+
*/
|
|
188
|
+
export function lightFalloffExponentOf(value: number | undefined): number {
|
|
189
|
+
return value !== undefined && Number.isFinite(value) && value > 0 ? value : 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
151
192
|
/** Texels the lit pass reads for a light whose record carries no fixture. */
|
|
152
193
|
export const PLAIN_LIGHT_TEXELS = 2;
|
|
153
194
|
|
|
154
195
|
/**
|
|
155
|
-
* The bit of `sizeAndWeight` that says the record's last
|
|
196
|
+
* The bit of `sizeAndWeight` that says the record's last four texels hold something: the sign of
|
|
156
197
|
* the emitter size's half, which a size never needs.
|
|
157
198
|
*/
|
|
158
199
|
export const LIGHT_FIXTURE_FLAG = 0x8000;
|
|
@@ -401,6 +442,18 @@ export interface ClusterLightSet {
|
|
|
401
442
|
readonly cookies?: Float32Array;
|
|
402
443
|
/** One per light: an index into the IES atlas, or −1. */
|
|
403
444
|
readonly iesProfiles?: Float32Array;
|
|
445
|
+
/** One per light: its lighting channels. See `PointLightSet.lightChannels`. */
|
|
446
|
+
readonly channels?: Float32Array;
|
|
447
|
+
/** One per light: its own falloff exponent, or 0. See `PointLightSource.falloffExponent`. */
|
|
448
|
+
readonly falloffExponents?: Float32Array;
|
|
449
|
+
/**
|
|
450
|
+
* The rectangles shaded through the table, after the `count` point lights: those of `areas` from
|
|
451
|
+
* `areaFrom` on, `areaCount` of them. The ones before `areaFrom` are the fixed arm's, which is
|
|
452
|
+
* where a rectangle's shadow lives. Absent, there are none.
|
|
453
|
+
*/
|
|
454
|
+
readonly areas?: AreaLightBuffer | null;
|
|
455
|
+
readonly areaFrom?: number;
|
|
456
|
+
readonly areaCount?: number;
|
|
404
457
|
}
|
|
405
458
|
|
|
406
459
|
/** Where `fillClusterLightSet` reads a frame's lights from: the arrays `Environment` carries. */
|
|
@@ -416,6 +469,9 @@ export interface ClusterLightArrays {
|
|
|
416
469
|
readonly lightIesProfiles?: Float32Array;
|
|
417
470
|
readonly lightIesAxes?: Float32Array;
|
|
418
471
|
readonly lightCookies?: Float32Array;
|
|
472
|
+
readonly lightChannels?: Float32Array;
|
|
473
|
+
readonly lightFalloffExponents?: Float32Array;
|
|
474
|
+
readonly areaLights?: AreaLightBuffer | null;
|
|
419
475
|
}
|
|
420
476
|
|
|
421
477
|
/** A `ClusterLightSet` a renderer refills every frame. */
|
|
@@ -453,8 +509,17 @@ export function createClusterLightSet(): ClusterLightScratch {
|
|
|
453
509
|
export function fillClusterLightSet(
|
|
454
510
|
env: ClusterLightArrays,
|
|
455
511
|
out: ClusterLightScratch,
|
|
512
|
+
/** How many rectangles the fixed arm shades; the rest of the buffer is clustered. */
|
|
513
|
+
areaFrom = 0,
|
|
456
514
|
): ClusterLightSet {
|
|
457
515
|
out.count = Math.min(env.lightCount ?? 0, MAX_CLUSTERED_LIGHTS);
|
|
516
|
+
const areas = env.areaLights ?? null;
|
|
517
|
+
out.areas = areas;
|
|
518
|
+
out.areaFrom = areaFrom;
|
|
519
|
+
out.areaCount = Math.max(
|
|
520
|
+
0,
|
|
521
|
+
Math.min((areas?.count ?? 0) - areaFrom, MAX_CLUSTERED_LIGHTS - out.count),
|
|
522
|
+
);
|
|
458
523
|
out.positions = env.lightPositions;
|
|
459
524
|
out.colors = env.lightColors;
|
|
460
525
|
out.radii = env.lightRadii;
|
|
@@ -465,6 +530,8 @@ export function fillClusterLightSet(
|
|
|
465
530
|
out.iesProfiles = env.lightIesProfiles;
|
|
466
531
|
out.iesAxes = env.lightIesAxes;
|
|
467
532
|
out.cookies = env.lightCookies;
|
|
533
|
+
out.channels = env.lightChannels;
|
|
534
|
+
out.falloffExponents = env.lightFalloffExponents;
|
|
468
535
|
return out;
|
|
469
536
|
}
|
|
470
537
|
|
|
@@ -514,8 +581,8 @@ function distanceSqToBounds(x: number, y: number, z: number, b: Float32Array): n
|
|
|
514
581
|
}
|
|
515
582
|
|
|
516
583
|
/**
|
|
517
|
-
* Whether a light's record needs its last
|
|
518
|
-
* or a
|
|
584
|
+
* Whether a light's record needs its last four texels: it holds a shadow slot, a cone, a profile,
|
|
585
|
+
* a cookie, channels of its own or a falloff exponent of its own. Everything else is a plain light, read in `PLAIN_LIGHT_TEXELS`.
|
|
519
586
|
*
|
|
520
587
|
* **`shadowSlots` is zero where the lit shader has no point shadows**, and both binners are handed
|
|
521
588
|
* it that way: a slot is only read under `POINT_SHADOWS`, so a light holding one in a build without
|
|
@@ -526,13 +593,17 @@ export function lightHasFixture(
|
|
|
526
593
|
light: number,
|
|
527
594
|
shadowSlots: number,
|
|
528
595
|
): boolean {
|
|
596
|
+
/* A rectangle's record always carries its axes and extents past the second texel. */
|
|
597
|
+
if (light >= lights.count) return true;
|
|
529
598
|
const cone = lights.coneCos;
|
|
530
599
|
return (
|
|
531
600
|
light < shadowSlots ||
|
|
532
601
|
(cone?.[light * 2] ?? POINT_LIGHT_COS_INNER) !== POINT_LIGHT_COS_INNER ||
|
|
533
602
|
(cone?.[light * 2 + 1] ?? POINT_LIGHT_COS_OUTER) !== POINT_LIGHT_COS_OUTER ||
|
|
534
603
|
(lights.iesProfiles?.[light] ?? NO_IES_PROFILE) >= 0 ||
|
|
535
|
-
(lights.cookies?.[light] ?? NO_IES_PROFILE) >= 0
|
|
604
|
+
(lights.cookies?.[light] ?? NO_IES_PROFILE) >= 0 ||
|
|
605
|
+
lightChannelsOf(lights.channels?.[light]) !== DEFAULT_LIGHT_CHANNELS ||
|
|
606
|
+
lightFalloffExponentOf(lights.falloffExponents?.[light]) > 0
|
|
536
607
|
);
|
|
537
608
|
}
|
|
538
609
|
|
|
@@ -576,6 +647,10 @@ export function writeLightRecord(
|
|
|
576
647
|
out: Uint32Array,
|
|
577
648
|
at: number,
|
|
578
649
|
): void {
|
|
650
|
+
if (light >= lights.count) {
|
|
651
|
+
writeAreaRecord(lights, light, out, at);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
579
654
|
const directions = lights.directions;
|
|
580
655
|
const cone = lights.coneCos;
|
|
581
656
|
const axes = lights.iesAxes;
|
|
@@ -612,7 +687,82 @@ export function writeLightRecord(
|
|
|
612
687
|
out[at + LIGHT_RECORD.iesAxisX] = floatBits(axes?.[light * 3] ?? 0);
|
|
613
688
|
out[at + LIGHT_RECORD.iesAxisY] = floatBits(axes?.[light * 3 + 1] ?? 0);
|
|
614
689
|
out[at + LIGHT_RECORD.iesAxisZ] = floatBits(axes?.[light * 3 + 2] ?? 0);
|
|
615
|
-
out[at +
|
|
690
|
+
out[at + LIGHT_RECORD.channels] = floatBits(lightChannelsOf(lights.channels?.[light]));
|
|
691
|
+
out[at + LIGHT_RECORD.falloffExponent] = floatBits(
|
|
692
|
+
lightFalloffExponentOf(lights.falloffExponents?.[light]),
|
|
693
|
+
);
|
|
694
|
+
out[at + LIGHT_RECORD.barnDoorCos] = 0;
|
|
695
|
+
out[at + LIGHT_RECORD.barnDoorLength] = 0;
|
|
696
|
+
out[at + LIGHT_RECORD.barnDoorLength + 1] = 0;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Which rectangle of the area buffer clustered light `light` is, for a light past the point
|
|
701
|
+
* lights. See `ClusterLightSet.areas`.
|
|
702
|
+
*/
|
|
703
|
+
function areaSlot(lights: ClusterLightSet, light: number): number {
|
|
704
|
+
return (lights.areaFrom ?? 0) + light - lights.count;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* A rectangle's record, which the shader tells from a lamp's by its **negative radius** — its
|
|
709
|
+
* reach, negated — and reads to its sixth texel: sidedness where a lamp keeps its shadow slot,
|
|
710
|
+
* the right axis where a spot keeps its direction, the half extents where its cone is, the up
|
|
711
|
+
* axis where the azimuth is, the channels, and the barn doors in the sixth. Never shadowed: a rectangle's occlusion is
|
|
712
|
+
* the fixed arm's, which is why the first `maxAreaLights` stay there.
|
|
713
|
+
*/
|
|
714
|
+
function writeAreaRecord(
|
|
715
|
+
lights: ClusterLightSet,
|
|
716
|
+
light: number,
|
|
717
|
+
out: Uint32Array,
|
|
718
|
+
at: number,
|
|
719
|
+
): void {
|
|
720
|
+
const areas = lights.areas;
|
|
721
|
+
const a = areaSlot(lights, light);
|
|
722
|
+
const pos = areas?.positions;
|
|
723
|
+
const color = areas?.colors;
|
|
724
|
+
const right = areas?.right;
|
|
725
|
+
const up = areas?.up;
|
|
726
|
+
const sizes = areas?.sizes;
|
|
727
|
+
out[at + LIGHT_RECORD.positionX] = floatBits(pos?.[a * 3] ?? 0);
|
|
728
|
+
out[at + LIGHT_RECORD.positionY] = floatBits(pos?.[a * 3 + 1] ?? 0);
|
|
729
|
+
out[at + LIGHT_RECORD.positionZ] = floatBits(pos?.[a * 3 + 2] ?? 0);
|
|
730
|
+
out[at + LIGHT_RECORD.radius] = floatBits(-areaReach(lights, light));
|
|
731
|
+
out[at + LIGHT_RECORD.colorR] = floatBits(color?.[a * 3] ?? 0);
|
|
732
|
+
out[at + LIGHT_RECORD.colorG] = floatBits(color?.[a * 3 + 1] ?? 0);
|
|
733
|
+
out[at + LIGHT_RECORD.colorB] = floatBits(color?.[a * 3 + 2] ?? 0);
|
|
734
|
+
out[at + LIGHT_RECORD.sizeAndWeight] = packSizeAndWeight(0, 1, true);
|
|
735
|
+
out[at + LIGHT_RECORD.shadowSlot] = floatBits(areas?.twoSided[a] ?? 0);
|
|
736
|
+
out[at + LIGHT_RECORD.directionX] = floatBits(right?.[a * 3] ?? 1);
|
|
737
|
+
out[at + LIGHT_RECORD.directionY] = floatBits(right?.[a * 3 + 1] ?? 0);
|
|
738
|
+
out[at + LIGHT_RECORD.directionZ] = floatBits(right?.[a * 3 + 2] ?? 0);
|
|
739
|
+
out[at + LIGHT_RECORD.cosInner] = floatBits(sizes?.[a * 2] ?? 0);
|
|
740
|
+
out[at + LIGHT_RECORD.cosOuter] = floatBits(sizes?.[a * 2 + 1] ?? 0);
|
|
741
|
+
out[at + LIGHT_RECORD.iesProfile] = floatBits(NO_IES_PROFILE);
|
|
742
|
+
out[at + LIGHT_RECORD.cookie] = floatBits(NO_IES_PROFILE);
|
|
743
|
+
out[at + LIGHT_RECORD.iesAxisX] = floatBits(up?.[a * 3] ?? 0);
|
|
744
|
+
out[at + LIGHT_RECORD.iesAxisY] = floatBits(up?.[a * 3 + 1] ?? 1);
|
|
745
|
+
out[at + LIGHT_RECORD.iesAxisZ] = floatBits(up?.[a * 3 + 2] ?? 0);
|
|
746
|
+
out[at + LIGHT_RECORD.channels] = floatBits(DEFAULT_LIGHT_CHANNELS);
|
|
747
|
+
out[at + LIGHT_RECORD.falloffExponent] = 0;
|
|
748
|
+
out[at + LIGHT_RECORD.barnDoorCos] = floatBits(areas?.barnDoors?.[a * 2] ?? 0);
|
|
749
|
+
out[at + LIGHT_RECORD.barnDoorLength] = floatBits(areas?.barnDoors?.[a * 2 + 1] ?? 0);
|
|
750
|
+
out[at + LIGHT_RECORD.barnDoorLength + 1] = 0;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/** A clustered rectangle's reach, which its record carries negated. See `AreaLightSource.range`. */
|
|
754
|
+
function areaReach(lights: ClusterLightSet, light: number): number {
|
|
755
|
+
const areas = lights.areas;
|
|
756
|
+
const a = areaSlot(lights, light);
|
|
757
|
+
const named = areas?.ranges?.[a] ?? 0;
|
|
758
|
+
if (named > 0) return named;
|
|
759
|
+
const sizes = areas?.sizes;
|
|
760
|
+
return Math.hypot(sizes?.[a * 2] ?? 0, sizes?.[a * 2 + 1] ?? 0);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/** How many lights the table carries: the point lights and the clustered rectangles after them. */
|
|
764
|
+
export function clusteredLightTotal(lights: ClusterLightSet): number {
|
|
765
|
+
return Math.min(lights.count + (lights.areaCount ?? 0), MAX_CLUSTERED_LIGHTS);
|
|
616
766
|
}
|
|
617
767
|
|
|
618
768
|
/**
|
|
@@ -670,7 +820,7 @@ export function buildLightClusters(
|
|
|
670
820
|
table: Uint32Array,
|
|
671
821
|
shadowSlots: number,
|
|
672
822
|
): number {
|
|
673
|
-
const count =
|
|
823
|
+
const count = clusteredLightTotal(lights);
|
|
674
824
|
|
|
675
825
|
/*
|
|
676
826
|
* The whole cluster region, not just the counts.
|
|
@@ -686,10 +836,14 @@ export function buildLightClusters(
|
|
|
686
836
|
for (let k = 0; k <= CLUSTER_Z; k++) sliceDepths[k] = sliceNearDepth(k, near, far);
|
|
687
837
|
|
|
688
838
|
for (let light = 0; light < count; light++) {
|
|
689
|
-
|
|
690
|
-
const
|
|
691
|
-
const
|
|
692
|
-
const
|
|
839
|
+
/* A rectangle past the lamps is placed by its centre and binned by its reach. */
|
|
840
|
+
const area = light >= lights.count;
|
|
841
|
+
const from = area ? (lights.areas?.positions ?? lights.positions) : lights.positions;
|
|
842
|
+
const slot = area ? areaSlot(lights, light) : light;
|
|
843
|
+
const x = from[slot * 3] ?? 0;
|
|
844
|
+
const y = from[slot * 3 + 1] ?? 0;
|
|
845
|
+
const z = from[slot * 3 + 2] ?? 0;
|
|
846
|
+
const radius = area ? areaReach(lights, light) : (lights.radii[light] ?? 0);
|
|
693
847
|
|
|
694
848
|
/* The record, as the shader reads it. World space; see the note above. */
|
|
695
849
|
writeLightRecord(lights, light, shadowSlots, table, lightBase(light));
|
|
@@ -706,6 +860,40 @@ export function buildLightClusters(
|
|
|
706
860
|
/* Entirely behind the camera, so no cluster can reach it. */
|
|
707
861
|
if (depth + radius <= 0) continue;
|
|
708
862
|
|
|
863
|
+
/*
|
|
864
|
+
* **A spot is binned by its cone as well as its sphere.** A 10° spot reaching thirty metres
|
|
865
|
+
* touches every froxel its sphere does and lights a sliver of them, and a stage rigged with
|
|
866
|
+
* hundreds of such fixtures put every one of them in nearly every froxel near the floor: 313
|
|
867
|
+
* lights cost 11.7 ms of a 1600x900 frame. The cone is tested against each froxel's bounding
|
|
868
|
+
* sphere in view space, which is conservative, by the construction `coneBoxGap` describes.
|
|
869
|
+
*/
|
|
870
|
+
const cosOuter = lights.coneCos?.[light * 2 + 1] ?? POINT_LIGHT_COS_OUTER;
|
|
871
|
+
const directions = lights.directions;
|
|
872
|
+
let coneCos = POINT_LIGHT_COS_OUTER;
|
|
873
|
+
let coneSin = 0;
|
|
874
|
+
let axisX = 0;
|
|
875
|
+
let axisY = 0;
|
|
876
|
+
let axisZ = 0;
|
|
877
|
+
if (!area && directions !== undefined && cosOuter > -1) {
|
|
878
|
+
const dx = directions[light * 3] ?? 0;
|
|
879
|
+
const dy = directions[light * 3 + 1] ?? 0;
|
|
880
|
+
const dz = directions[light * 3 + 2] ?? 0;
|
|
881
|
+
const ax = (view[0] ?? 0) * dx + (view[4] ?? 0) * dy + (view[8] ?? 0) * dz;
|
|
882
|
+
const ay = (view[1] ?? 0) * dx + (view[5] ?? 0) * dy + (view[9] ?? 0) * dz;
|
|
883
|
+
const az = -((view[2] ?? 0) * dx + (view[6] ?? 0) * dy + (view[10] ?? 0) * dz);
|
|
884
|
+
const length = Math.sqrt(ax * ax + ay * ay + az * az);
|
|
885
|
+
/* The shader measures the cone against the direction as given, so a long one is a wider
|
|
886
|
+
cone: `dot(L, d) >= c` is `dot(L, d / |d|) >= c / |d|`. */
|
|
887
|
+
if (length > 1e-6 && cosOuter / length > -1) {
|
|
888
|
+
coneCos = cosOuter / length;
|
|
889
|
+
coneSin = Math.sqrt(Math.max(0, 1 - coneCos * coneCos));
|
|
890
|
+
axisX = ax / length;
|
|
891
|
+
axisY = ay / length;
|
|
892
|
+
axisZ = az / length;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
const spot = coneCos > -1;
|
|
896
|
+
|
|
709
897
|
/*
|
|
710
898
|
* The slices the sphere could possibly touch. The tiles are derived per slice, below.
|
|
711
899
|
*
|
|
@@ -765,6 +953,12 @@ export function buildLightClusters(
|
|
|
765
953
|
for (let i = iLo; i <= iHi; i++) {
|
|
766
954
|
sliceBounds(i, j, zNear, zFar, halfWNear, halfWFar, halfHNear, halfHFar, bounds);
|
|
767
955
|
if (distanceSqToBounds(vx, vy, depth, bounds) > radiusSq) continue;
|
|
956
|
+
if (
|
|
957
|
+
spot &&
|
|
958
|
+
coneBoxGap(vx, vy, depth, axisX, axisY, axisZ, coneCos, coneSin, bounds) > 0
|
|
959
|
+
) {
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
768
962
|
insert(table, i + j * CLUSTER_X + k * CLUSTER_X * CLUSTER_Y, light, bounds);
|
|
769
963
|
}
|
|
770
964
|
}
|
|
@@ -773,6 +967,59 @@ export function buildLightClusters(
|
|
|
773
967
|
return count;
|
|
774
968
|
}
|
|
775
969
|
|
|
970
|
+
/**
|
|
971
|
+
* How far a froxel lies outside a spot's cone, in metres, or a negative number where the cone
|
|
972
|
+
* reaches it: positive is a froxel the light cannot shade. Everything in view space with depth
|
|
973
|
+
* positive, as the binner holds it; `axis` is unit length and `cosA`, `sinA` are the outer cone's.
|
|
974
|
+
*
|
|
975
|
+
* **The froxel's bounding sphere against the infinite cone**, after Wronski's cone test for
|
|
976
|
+
* clustered spots. With `V` from the apex to the sphere's centre at an angle `θ` from the axis and
|
|
977
|
+
* `A` the cone's half-angle, the centre's distance from the cone is `|V| sin(θ − A)` — which is
|
|
978
|
+
* `cosA |V⊥| − (V·axis) sinA` — while `θ − A` is under a right angle, and `|V|` past it, where the
|
|
979
|
+
* nearest point of the cone is its apex. Inside the cone it is negative. So the gap is exact for
|
|
980
|
+
* the sphere, and the sphere holds the froxel: the test can only keep a light, never drop one the
|
|
981
|
+
* shader would draw. The apex case is what drops a froxel behind a spot whose sphere reaches it,
|
|
982
|
+
* which the formula alone underestimates.
|
|
983
|
+
*
|
|
984
|
+
* **What it gives up** is tightness at the near end of the frustum, where a froxel is a long thin
|
|
985
|
+
* box whose sphere is much larger than it: a spot grazing such a froxel is kept. **What would make
|
|
986
|
+
* it wrong** is a profile showing those near froxels hold the cost; testing the box's corners is
|
|
987
|
+
* the tight version, at eight dot products a pair.
|
|
988
|
+
*
|
|
989
|
+
* Exported so the conformance page can tell a disagreement at the cone's edge from a real one; the
|
|
990
|
+
* GPU binner writes the same arithmetic in WGSL.
|
|
991
|
+
*/
|
|
992
|
+
export function coneBoxGap(
|
|
993
|
+
px: number,
|
|
994
|
+
py: number,
|
|
995
|
+
pz: number,
|
|
996
|
+
axisX: number,
|
|
997
|
+
axisY: number,
|
|
998
|
+
axisZ: number,
|
|
999
|
+
cosA: number,
|
|
1000
|
+
sinA: number,
|
|
1001
|
+
box: Float32Array,
|
|
1002
|
+
): number {
|
|
1003
|
+
const b0 = box[0] ?? 0;
|
|
1004
|
+
const b1 = box[1] ?? 0;
|
|
1005
|
+
const b2 = box[2] ?? 0;
|
|
1006
|
+
const b3 = box[3] ?? 0;
|
|
1007
|
+
const b4 = box[4] ?? 0;
|
|
1008
|
+
const b5 = box[5] ?? 0;
|
|
1009
|
+
const ex = (b3 - b0) * 0.5;
|
|
1010
|
+
const ey = (b4 - b1) * 0.5;
|
|
1011
|
+
const ez = (b5 - b2) * 0.5;
|
|
1012
|
+
const vx = (b0 + b3) * 0.5 - px;
|
|
1013
|
+
const vy = (b1 + b4) * 0.5 - py;
|
|
1014
|
+
const vz = (b2 + b5) * 0.5 - pz;
|
|
1015
|
+
const along = vx * axisX + vy * axisY + vz * axisZ;
|
|
1016
|
+
const lengthSq = vx * vx + vy * vy + vz * vz;
|
|
1017
|
+
const across = Math.sqrt(Math.max(lengthSq - along * along, 0));
|
|
1018
|
+
const reach = Math.sqrt(lengthSq);
|
|
1019
|
+
const distance = along < -sinA * reach ? reach : cosA * across - along * sinA;
|
|
1020
|
+
return distance - Math.sqrt(ex * ex + ey * ey + ez * ez);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
776
1023
|
/** An NDC coordinate to a tile index, clamped. */
|
|
777
1024
|
function tileOf(ndc: number, tiles: number): number {
|
|
778
1025
|
const tile = Math.floor((ndc + 1) * 0.5 * tiles);
|
|
@@ -32,6 +32,8 @@ export interface FieldLight extends LightReach {
|
|
|
32
32
|
readonly b: number;
|
|
33
33
|
/** The emitter's physical radius, which widens its penumbra. */
|
|
34
34
|
readonly sourceRadius: number;
|
|
35
|
+
/** Its own falloff exponent, overriding the frame's above zero. See `pointLightShape`. */
|
|
36
|
+
readonly falloffExponent?: number;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
/**
|
|
@@ -115,7 +117,7 @@ export function sampleFieldLight(
|
|
|
115
117
|
const dz = source.z - pz;
|
|
116
118
|
const dist = Math.hypot(dx, dy, dz);
|
|
117
119
|
if (dist >= source.radius) continue;
|
|
118
|
-
const shape = pointLightShape(dist, source.radius, falloff);
|
|
120
|
+
const shape = pointLightShape(dist, source.radius, falloff, source.falloffExponent);
|
|
119
121
|
if (shape <= 0) continue;
|
|
120
122
|
const seen =
|
|
121
123
|
distance === null
|
|
@@ -9,13 +9,24 @@
|
|
|
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
|
|
|
15
17
|
export type PointLightFalloff = 'smooth' | 'inverseSquare';
|
|
16
18
|
|
|
17
19
|
/** The shader's `shape` for an unshaped point light: no cone, no photometric profile. */
|
|
18
|
-
export function pointLightShape(
|
|
20
|
+
export function pointLightShape(
|
|
21
|
+
dist: number,
|
|
22
|
+
radius: number,
|
|
23
|
+
falloff: PointLightFalloff,
|
|
24
|
+
exponent = 0,
|
|
25
|
+
): number {
|
|
26
|
+
if (exponent > 0) {
|
|
27
|
+
const reach = dist / Math.max(radius, 1e-4);
|
|
28
|
+
return Math.min(1, Math.max(0, 1 - reach * reach)) ** exponent;
|
|
29
|
+
}
|
|
19
30
|
if (falloff === 'inverseSquare') {
|
|
20
31
|
const ratio = dist / Math.max(radius, 1e-4);
|
|
21
32
|
const window = Math.min(1, Math.max(0, 1 - ratio ** 4));
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* only ever reaches forward — to the next plane, and from the last to the repeat of the first — so
|
|
13
13
|
* past its last row it meets the texture's edge and the clamp, never a cookie. A cookie's own
|
|
14
14
|
* half-texel inset keeps it inside its band on both axes. The shader finds the bands from
|
|
15
|
-
* `textureSize` and `
|
|
15
|
+
* `textureSize` and `uFixtureShape.x`, so the fold cost no uniform.
|
|
16
16
|
*
|
|
17
17
|
* **What it gives up**: every texel is four half floats where a profile needed one, a few kilobytes
|
|
18
18
|
* a scene; and setting either table rebuilds the whole texture, which a consumer does once per set.
|
|
@@ -23,15 +23,20 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import type { PointLightSet } from '../lightBudget.ts';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
POINT_LIGHT_COS_INNER,
|
|
28
|
+
POINT_LIGHT_COS_OUTER,
|
|
29
|
+
lightFalloffExponentOf,
|
|
30
|
+
} from '../clusteredLights.ts';
|
|
27
31
|
|
|
28
32
|
/** Lights the probe bake shades a hit with. */
|
|
29
33
|
export const MAX_BOUNCE_LIGHTS = 32;
|
|
30
34
|
/**
|
|
31
35
|
* Floats a light takes: where it is and how far it reaches; its colour at its weight and the cosine
|
|
32
|
-
* of its inner cone; the way it points and the cosine of its outer cone
|
|
36
|
+
* of its inner cone; the way it points and the cosine of its outer cone; and its own falloff
|
|
37
|
+
* exponent, 0 for the frame's, in a fourth row of its own (`PointLightSource.falloffExponent`).
|
|
33
38
|
*/
|
|
34
|
-
export const BOUNCE_LIGHT_FLOATS =
|
|
39
|
+
export const BOUNCE_LIGHT_FLOATS = 16;
|
|
35
40
|
|
|
36
41
|
export function createBounceLights(): Float32Array {
|
|
37
42
|
return new Float32Array(MAX_BOUNCE_LIGHTS * BOUNCE_LIGHT_FLOATS);
|
|
@@ -62,6 +67,10 @@ export function resolveBounceLights(lights: PointLightSet | null, out: Float32Ar
|
|
|
62
67
|
out[at + 9] = lights.lightDirections?.[i * 3 + 1] ?? 0;
|
|
63
68
|
out[at + 10] = lights.lightDirections?.[i * 3 + 2] ?? 0;
|
|
64
69
|
out[at + 11] = lights.lightConeCos?.[i * 2 + 1] ?? POINT_LIGHT_COS_OUTER;
|
|
70
|
+
out[at + 12] = lightFalloffExponentOf(lights.lightFalloffExponents?.[i]);
|
|
71
|
+
out[at + 13] = 0;
|
|
72
|
+
out[at + 14] = 0;
|
|
73
|
+
out[at + 15] = 0;
|
|
65
74
|
count++;
|
|
66
75
|
}
|
|
67
76
|
return count;
|
|
@@ -71,6 +71,15 @@ export function cullInstances(
|
|
|
71
71
|
out.tints[kept * 3] = tints[i * 3] as number;
|
|
72
72
|
out.tints[kept * 3 + 1] = tints[i * 3 + 1] as number;
|
|
73
73
|
out.tints[kept * 3 + 2] = tints[i * 3 + 2] as number;
|
|
74
|
+
if (out.alphas !== undefined) out.alphas[kept] = data.alphas?.[i] ?? 1;
|
|
75
|
+
const regions = data.lightmapRegions;
|
|
76
|
+
if (regions !== undefined && out.lightmapRegions !== undefined) {
|
|
77
|
+
for (let k = 0; k < 4; k++) out.lightmapRegions[kept * 4 + k] = regions[i * 4 + k] as number;
|
|
78
|
+
}
|
|
79
|
+
const cells = data.uvRegions;
|
|
80
|
+
if (cells !== undefined && out.uvRegions !== undefined) {
|
|
81
|
+
for (let k = 0; k < 4; k++) out.uvRegions[kept * 4 + k] = cells[i * 4 + k] as number;
|
|
82
|
+
}
|
|
74
83
|
kept++;
|
|
75
84
|
}
|
|
76
85
|
out.count = kept;
|