@driftengine/core 4.7.2 → 4.7.3
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 +16 -0
- package/dist/render/backend/webgl2/renderer.js +7 -17
- package/dist/render/backend/webgpu/clusterBinner.js +5 -41
- package/dist/render/backend/webgpu/renderer.js +9 -17
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.js +9 -5
- package/dist/render/clusteredLights.d.ts +136 -13
- package/dist/render/clusteredLights.js +225 -51
- package/dist/render/lightBudget.d.ts +8 -0
- package/dist/render/lightBudget.js +10 -3
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +77 -38
- package/dist/render/shaders/flat/preamble.js +2 -1
- package/dist/render/shaders/generated/flat.wgsl.js +1 -1
- package/package.json +6 -6
- package/src/render/backend/webgl2/renderer.ts +10 -26
- package/src/render/backend/webgpu/clusterBinner.ts +11 -44
- package/src/render/backend/webgpu/renderer.ts +14 -25
- package/src/render/backend/webgpu/shaders/clusterBin.wgsl.ts +9 -5
- package/src/render/clusteredLights.ts +272 -58
- package/src/render/lightBudget.ts +23 -3
- package/src/render/shaders/flat/main.ts +77 -38
- package/src/render/shaders/flat/preamble.ts +2 -0
- package/src/render/shaders/generated/flat.wgsl.ts +1 -1
|
@@ -938,7 +938,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
938
938
|
int clusterBaseTexel = LIGHT_REGION_TEXELS
|
|
939
939
|
+ (clusterTileX + clusterTileY * CLUSTER_X + clusterSlice * CLUSTER_X * CLUSTER_Y)
|
|
940
940
|
* CLUSTER_TEXELS;
|
|
941
|
-
int clusterLights = uClustered
|
|
941
|
+
int clusterLights = uClustered != 0 ? int(clusterTexel(clusterBaseTexel).x) : 0;
|
|
942
942
|
|
|
943
943
|
/* The fixture atlas's size, asked once: its cookie band and its profile rows are found from it. */
|
|
944
944
|
vec2 fixtureSize = vec2(textureSize(uFixtureAtlas, 0));
|
|
@@ -967,7 +967,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
967
967
|
float iesProfile;
|
|
968
968
|
vec3 iesAxis;
|
|
969
969
|
float cookie;
|
|
970
|
-
if (uClustered
|
|
970
|
+
if (uClustered != 0) {
|
|
971
971
|
if (slot >= clusterLights) break;
|
|
972
972
|
/*
|
|
973
973
|
* Four indices to a texel. Selected with compares rather than by indexing the vector,
|
|
@@ -982,32 +982,65 @@ export const MAIN_GLSL = `void main() {
|
|
|
982
982
|
i = int(packed);
|
|
983
983
|
int record = i * LIGHT_TEXELS;
|
|
984
984
|
uvec4 rec0 = clusterTexel(record);
|
|
985
|
-
uvec4 rec1 = clusterTexel(record + 1);
|
|
986
|
-
uvec4 rec2 = clusterTexel(record + 2);
|
|
987
985
|
lightPos = vec3(
|
|
988
986
|
uintBitsToFloat(rec0.x), uintBitsToFloat(rec0.y), uintBitsToFloat(rec0.z));
|
|
989
987
|
lightRadius = uintBitsToFloat(rec0.w);
|
|
988
|
+
/*
|
|
989
|
+
* **Out of reach is decided on the first texel, before the rest of the record is read.**
|
|
990
|
+
* A froxel holds every light whose sphere touches its box, so most of the lights a fragment
|
|
991
|
+
* walks never reach it, and each one used to cost all five texels before the falloff below
|
|
992
|
+
* said so. Both falloffs are zero at and past the radius, so this skips only lights the
|
|
993
|
+
* \`continue\` after the falloff would have skipped anyway: 17.5 ms of a 4K main pass with
|
|
994
|
+
* 320 lamps was 12.3 with this and the profile guard below, byte for byte the same picture.
|
|
995
|
+
*/
|
|
996
|
+
if (length(lightPos - vWorldPos) >= lightRadius) continue;
|
|
997
|
+
/*
|
|
998
|
+
* **The second texel, and for a plain point light the last one.** The emitter size and the
|
|
999
|
+
* weight are two halves in one word, which is how nine numbers fit in two texels;
|
|
1000
|
+
* \`unpackHalf2x16\` widens them exactly, and the fixed arm reads the same two values rounded
|
|
1001
|
+
* the same way, so the arms stay one picture. See \`packSizeAndWeight\`.
|
|
1002
|
+
*/
|
|
1003
|
+
uvec4 rec1 = clusterTexel(record + 1);
|
|
990
1004
|
lightColor = vec3(
|
|
991
1005
|
uintBitsToFloat(rec1.x), uintBitsToFloat(rec1.y), uintBitsToFloat(rec1.z));
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1006
|
+
vec2 sizeAndWeight = unpackHalf2x16(rec1.w);
|
|
1007
|
+
lightSourceRadius = abs(sizeAndWeight.x);
|
|
1008
|
+
lightWeight = sizeAndWeight.y;
|
|
995
1009
|
/*
|
|
996
|
-
* The
|
|
997
|
-
*
|
|
998
|
-
*
|
|
1010
|
+
* **The fixture, read only for a light that has one.** What a plain light's record holds in
|
|
1011
|
+
* the last three texels is exactly these values, so a light carrying no shadow slot, cone,
|
|
1012
|
+
* profile or cookie shades the same whether they are read or assumed, and at heavy overlap
|
|
1013
|
+
* reading them was a third of the pass. A branch on a value from a texture around
|
|
1014
|
+
* \`texelFetch\` is not the 2026-08-07 case: an integer fetch takes no derivative.
|
|
1015
|
+
*
|
|
1016
|
+
* **Inside a branch on \`uClustered\` first**, which is 2 only in a frame where some light
|
|
1017
|
+
* carries a fixture. The test on each light's flag is a branch on a value read from a
|
|
1018
|
+
* texture, which a compiler may run both sides of, and it cost 1.3 ms of a 4K pass whose 320
|
|
1019
|
+
* lamps were all plain; a uniform branch costs nothing, so such a frame never reaches it.
|
|
999
1020
|
*/
|
|
1000
|
-
|
|
1001
|
-
lightDir = vec3(
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1021
|
+
shadowSlot = -1;
|
|
1022
|
+
lightDir = vec3(0.0);
|
|
1023
|
+
lightCone = vec2(-1.0, -2.0);
|
|
1024
|
+
iesProfile = -1.0;
|
|
1025
|
+
iesAxis = vec3(0.0);
|
|
1026
|
+
cookie = -1.0;
|
|
1027
|
+
if (uClustered == 2) {
|
|
1028
|
+
if ((rec1.w & LIGHT_FIXTURE_FLAG) != 0u) {
|
|
1029
|
+
uvec4 rec2 = clusterTexel(record + 2);
|
|
1030
|
+
uvec4 rec3 = clusterTexel(record + 3);
|
|
1031
|
+
uvec4 rec4 = clusterTexel(record + 4);
|
|
1032
|
+
shadowSlot = int(uintBitsToFloat(rec2.x));
|
|
1033
|
+
lightDir = vec3(
|
|
1034
|
+
uintBitsToFloat(rec2.y), uintBitsToFloat(rec2.z), uintBitsToFloat(rec2.w));
|
|
1035
|
+
lightCone = vec2(uintBitsToFloat(rec3.x), uintBitsToFloat(rec3.y));
|
|
1036
|
+
iesProfile = uintBitsToFloat(rec3.z);
|
|
1037
|
+
cookie = uintBitsToFloat(rec3.w);
|
|
1038
|
+
/* The photometric azimuth. See LIGHT_RECORD in clusteredLights.ts for why it costs a
|
|
1039
|
+
texel rather than being derived from the direction. */
|
|
1040
|
+
iesAxis = vec3(
|
|
1041
|
+
uintBitsToFloat(rec4.x), uintBitsToFloat(rec4.y), uintBitsToFloat(rec4.z));
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1011
1044
|
} else {
|
|
1012
1045
|
i = slot;
|
|
1013
1046
|
if (i >= uLightCount || i >= MAX_LIGHTS) break;
|
|
@@ -1226,20 +1259,25 @@ export const MAIN_GLSL = `void main() {
|
|
|
1226
1259
|
* and the result is selected afterwards with a \`mix\` on a compare — which is arithmetic
|
|
1227
1260
|
* rather than control flow.
|
|
1228
1261
|
*
|
|
1229
|
-
* **
|
|
1230
|
-
*
|
|
1231
|
-
*
|
|
1232
|
-
*
|
|
1233
|
-
*
|
|
1262
|
+
* **And skipped outright where nothing loaded a profile**, behind \`uIesAtlasRows\`, which is
|
|
1263
|
+
* one while the atlas holds only its row of ones. A uniform is provably uniform control
|
|
1264
|
+
* flow, so this branch is not the one the rule forbids. It was paid on every scene until a
|
|
1265
|
+
* 4K frame of 320 lamps and no shadows measured it at 0.7 ms of the main pass, rising to
|
|
1266
|
+
* 2.3 ms with the lamps overlapping, which is the scene this note used to say would make
|
|
1267
|
+
* the fetch wrong. Every light then reads 1, which is what \`step(0.0, iesProfile)\` gave
|
|
1268
|
+
* each of them anyway.
|
|
1234
1269
|
*/
|
|
1235
1270
|
float iesRow = max(iesProfile, 0.0);
|
|
1236
1271
|
/* The direction the fixture throws toward this fragment, which both angles are measured on. */
|
|
1237
1272
|
vec3 iesRay = -toLight / max(dist, 1e-4);
|
|
1238
1273
|
/* The angle from the fixture's axis, as a fraction of the 0-to-180 arc every row spans. */
|
|
1239
|
-
float
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1274
|
+
float iesU = 0.0;
|
|
1275
|
+
if (uIesAtlasRows > 1.0) {
|
|
1276
|
+
float iesAngle = acos(clamp(dot(iesRay, lightDir), -1.0, 1.0));
|
|
1277
|
+
/* Clamped to the row's first and last texel centres, which is what the edge clamp of a
|
|
1278
|
+
texture exactly one row wide gave; the atlas can be wider now, with cookies beside it. */
|
|
1279
|
+
iesU = clamp(iesAngle / PI_IES * IES_WIDTH, 0.5, IES_WIDTH - 0.5) / fixtureSize.x;
|
|
1280
|
+
}
|
|
1243
1281
|
/*
|
|
1244
1282
|
* **The azimuth about the fixture's own axis**, behind a branch on a uniform so a scene whose
|
|
1245
1283
|
* profiles are all axially symmetric pays none of this arithmetic. A \`uniform float\` is
|
|
@@ -1314,14 +1352,15 @@ export const MAIN_GLSL = `void main() {
|
|
|
1314
1352
|
* version this replaced took two explicit fetches and a \`mix\`, and cost **18,315 gzipped
|
|
1315
1353
|
* bytes on \`core-only\`** across the sixteen fragment permutations.
|
|
1316
1354
|
*/
|
|
1317
|
-
float
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1355
|
+
float photometric = 1.0;
|
|
1356
|
+
if (uIesAtlasRows > 1.0) {
|
|
1357
|
+
float iesBase = iesRow * (uIesPlaneCount + 1.0);
|
|
1358
|
+
/* The profiles are the atlas's bottom rows, below any cookies. */
|
|
1359
|
+
float iesTop = fixtureSize.y - uIesAtlasRows;
|
|
1360
|
+
float iesV = (iesTop + iesBase + iesTurns * uIesPlaneCount + 0.5) / fixtureSize.y;
|
|
1361
|
+
float iesGain = textureLod(uFixtureAtlas, vec2(iesU, iesV), 0.0).r;
|
|
1362
|
+
photometric = mix(1.0, iesGain, step(0.0, iesProfile));
|
|
1363
|
+
}
|
|
1325
1364
|
|
|
1326
1365
|
float shape = (uLightFalloff == 1 ? falloff : falloff * falloff) * coneFalloff * photometric;
|
|
1327
1366
|
glassGlow += lightColor * backNdl * shape * lightWeight * shaded * lampGlass;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Version, inputs, uniforms and the defines the rest of the fragment shader is written against. */
|
|
2
2
|
import { COOKIE_TILE } from '../../lightBudget.js';
|
|
3
3
|
import { IES_ATLAS_WIDTH } from '../../iesProfile.js';
|
|
4
|
-
import { CLUSTER_TEXELS, CLUSTER_X, CLUSTER_Y, CLUSTER_Z, LIGHT_REGION_TEXELS, LIGHT_TEXELS, MAX_LIGHTS_PER_CLUSTER, TABLE_WIDTH, } from '../../clusteredLights.js';
|
|
4
|
+
import { CLUSTER_TEXELS, CLUSTER_X, CLUSTER_Y, CLUSTER_Z, LIGHT_FIXTURE_FLAG, LIGHT_REGION_TEXELS, LIGHT_TEXELS, MAX_LIGHTS_PER_CLUSTER, TABLE_WIDTH, } from '../../clusteredLights.js';
|
|
5
5
|
/**
|
|
6
6
|
* How much roughness a relieved or normal-mapped surface gains per unit of relief.
|
|
7
7
|
*
|
|
@@ -647,6 +647,7 @@ uniform float uHighlightGain;
|
|
|
647
647
|
#define CLUSTER_TEXELS ${CLUSTER_TEXELS}
|
|
648
648
|
#define MAX_LIGHTS_PER_CLUSTER ${MAX_LIGHTS_PER_CLUSTER}
|
|
649
649
|
#define LIGHT_TEXELS ${LIGHT_TEXELS}
|
|
650
|
+
#define LIGHT_FIXTURE_FLAG ${LIGHT_FIXTURE_FLAG}u
|
|
650
651
|
#define LIGHT_REGION_TEXELS ${LIGHT_REGION_TEXELS}
|
|
651
652
|
#define CLUSTER_TABLE_WIDTH ${TABLE_WIDTH}
|
|
652
653
|
uniform highp usampler2D uClusterTable; // wgsl:share shadow
|