@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.
Files changed (139) hide show
  1. package/CHANGELOG.json +71 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +1 -1
  5. package/dist/render/anisotropicLobe.d.ts +10 -0
  6. package/dist/render/anisotropicLobe.js +19 -0
  7. package/dist/render/areaLights.d.ts +35 -0
  8. package/dist/render/areaLights.js +35 -0
  9. package/dist/render/backend/api.d.ts +33 -3
  10. package/dist/render/backend/webgl2/renderer.d.ts +50 -14
  11. package/dist/render/backend/webgl2/renderer.js +141 -30
  12. package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
  13. package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
  14. package/dist/render/backend/webgpu/particlePass.js +2 -1
  15. package/dist/render/backend/webgpu/renderer.d.ts +35 -1
  16. package/dist/render/backend/webgpu/renderer.js +155 -16
  17. package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
  18. package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
  19. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
  20. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
  21. package/dist/render/backend/webgpu/surfaceTexturePass.js +34 -8
  22. package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
  23. package/dist/render/clusteredLights.d.ts +23 -5
  24. package/dist/render/clusteredLights.js +35 -8
  25. package/dist/render/driftLight/bake.d.ts +2 -0
  26. package/dist/render/driftLight/bake.js +1 -1
  27. package/dist/render/driftLight/falloff.d.ts +4 -2
  28. package/dist/render/driftLight/falloff.js +8 -2
  29. package/dist/render/gi/bounceLights.d.ts +3 -2
  30. package/dist/render/gi/bounceLights.js +8 -3
  31. package/dist/render/instanceCull.js +5 -0
  32. package/dist/render/instances.d.ts +19 -5
  33. package/dist/render/instances.js +9 -3
  34. package/dist/render/lightBudget.d.ts +12 -1
  35. package/dist/render/lightBudget.js +23 -3
  36. package/dist/render/lightmap.d.ts +23 -9
  37. package/dist/render/lightmap.js +40 -18
  38. package/dist/render/particlePool.d.ts +21 -0
  39. package/dist/render/particlePool.js +7 -0
  40. package/dist/render/pointLightSelection.d.ts +13 -0
  41. package/dist/render/pointLightSelection.js +3 -1
  42. package/dist/render/renderQuality.d.ts +8 -7
  43. package/dist/render/rendererHarness.js +3 -0
  44. package/dist/render/rgb9e5.d.ts +22 -0
  45. package/dist/render/rgb9e5.js +47 -0
  46. package/dist/render/sceneCapture.d.ts +29 -0
  47. package/dist/render/sceneCapture.js +36 -0
  48. package/dist/render/screenSpaceReflection.d.ts +27 -1
  49. package/dist/render/screenSpaceReflection.js +9 -0
  50. package/dist/render/shaders/bolt.d.ts +1 -1
  51. package/dist/render/shaders/bolt.js +7 -1
  52. package/dist/render/shaders/depth.js +11 -0
  53. package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
  54. package/dist/render/shaders/flat/anisotropic.js +37 -3
  55. package/dist/render/shaders/flat/areaLight.d.ts +1 -1
  56. package/dist/render/shaders/flat/areaLight.js +46 -0
  57. package/dist/render/shaders/flat/glassTint.d.ts +3 -2
  58. package/dist/render/shaders/flat/glassTint.js +3 -1
  59. package/dist/render/shaders/flat/index.d.ts +5 -0
  60. package/dist/render/shaders/flat/index.js +16 -2
  61. package/dist/render/shaders/flat/lobes.d.ts +6 -0
  62. package/dist/render/shaders/flat/lobes.js +49 -43
  63. package/dist/render/shaders/flat/main.d.ts +1 -1
  64. package/dist/render/shaders/flat/main.js +57 -22
  65. package/dist/render/shaders/flat/models.d.ts +5 -2
  66. package/dist/render/shaders/flat/models.js +100 -16
  67. package/dist/render/shaders/flat/preamble.js +10 -3
  68. package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
  69. package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
  70. package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
  71. package/dist/render/shaders/generated/depth.wgsl.js +4 -4
  72. package/dist/render/shaders/generated/flat.wgsl.d.ts +16 -0
  73. package/dist/render/shaders/generated/flat.wgsl.js +54 -38
  74. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +11 -6
  75. package/dist/render/shaders/generated/ssrTrace.wgsl.js +11 -6
  76. package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
  77. package/dist/render/shaders/generated/water.wgsl.js +23 -16
  78. package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
  79. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  80. package/dist/render/shaders/recon/motion.wgsl.js +13 -2
  81. package/dist/render/shaders/ssrTrace.js +20 -1
  82. package/dist/render/shaders/water.d.ts +1 -1
  83. package/dist/render/shaders/water.js +12 -3
  84. package/dist/render/skinScatter.d.ts +3 -1
  85. package/dist/render/skinScatter.js +3 -1
  86. package/dist/render/ssrPass.js +1 -0
  87. package/dist/render/surfaceModel.d.ts +29 -8
  88. package/dist/render/surfaceModel.js +33 -9
  89. package/dist/render/surfaceTexture.d.ts +25 -1
  90. package/dist/render/surfaceTexture.js +27 -5
  91. package/package.json +6 -6
  92. package/src/index.ts +6 -2
  93. package/src/render/anisotropicLobe.ts +29 -0
  94. package/src/render/areaLights.ts +58 -0
  95. package/src/render/backend/api.ts +49 -2
  96. package/src/render/backend/webgl2/renderer.ts +192 -38
  97. package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
  98. package/src/render/backend/webgpu/particlePass.ts +2 -1
  99. package/src/render/backend/webgpu/renderer.ts +194 -19
  100. package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
  101. package/src/render/backend/webgpu/surfaceTexturePass.ts +44 -14
  102. package/src/render/clusteredLights.ts +41 -8
  103. package/src/render/driftLight/bake.ts +3 -1
  104. package/src/render/driftLight/falloff.ts +13 -2
  105. package/src/render/gi/bounceLights.ts +12 -3
  106. package/src/render/instanceCull.ts +4 -0
  107. package/src/render/instances.ts +26 -6
  108. package/src/render/lightBudget.ts +40 -2
  109. package/src/render/lightmap.ts +64 -23
  110. package/src/render/particlePool.ts +23 -0
  111. package/src/render/pointLightSelection.ts +20 -1
  112. package/src/render/renderQuality.ts +8 -7
  113. package/src/render/rendererHarness.ts +3 -0
  114. package/src/render/rgb9e5.ts +52 -0
  115. package/src/render/sceneCapture.ts +56 -0
  116. package/src/render/screenSpaceReflection.ts +33 -1
  117. package/src/render/shaders/bolt.ts +7 -1
  118. package/src/render/shaders/depth.ts +11 -0
  119. package/src/render/shaders/flat/anisotropic.ts +37 -3
  120. package/src/render/shaders/flat/areaLight.ts +46 -0
  121. package/src/render/shaders/flat/glassTint.ts +9 -2
  122. package/src/render/shaders/flat/index.ts +21 -2
  123. package/src/render/shaders/flat/lobes.ts +50 -43
  124. package/src/render/shaders/flat/main.ts +57 -22
  125. package/src/render/shaders/flat/models.ts +100 -16
  126. package/src/render/shaders/flat/preamble.ts +10 -3
  127. package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
  128. package/src/render/shaders/generated/depth.wgsl.ts +4 -4
  129. package/src/render/shaders/generated/flat.wgsl.ts +54 -38
  130. package/src/render/shaders/generated/ssrTrace.wgsl.ts +11 -6
  131. package/src/render/shaders/generated/water.wgsl.ts +23 -16
  132. package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
  133. package/src/render/shaders/recon/motion.wgsl.ts +13 -2
  134. package/src/render/shaders/ssrTrace.ts +20 -1
  135. package/src/render/shaders/water.ts +12 -3
  136. package/src/render/skinScatter.ts +3 -1
  137. package/src/render/ssrPass.ts +1 -0
  138. package/src/render/surfaceModel.ts +47 -10
  139. package/src/render/surfaceTexture.ts +69 -9
@@ -542,7 +542,7 @@ export const MAIN_GLSL = `void main() {
542
542
  * specularLobe's header argues for at length.
543
543
  */
544
544
  float sunVoH = max(dot(normalize(uCameraPos - vWorldPos), halfway), 0.0);
545
- vec3 sunSpec = mix(specColor, vec3(1.0), pow(1.0 - sunVoH, 5.0) * metal);
545
+ vec3 sunSpec = highlightTint(specColor, sunVoH, metal);
546
546
  /*
547
547
  * **Held back until after the environment blend, because that blend is a mix and would
548
548
  * otherwise erase this.**
@@ -561,8 +561,12 @@ export const MAIN_GLSL = `void main() {
561
561
  * Zero for the whole world that carries no specular attribute and no metalness, exactly as
562
562
  * before: \`sunSpec\` is \`vec3(vSpecular)\` at metal 0 and \`vSpecular\` defaults to 0.
563
563
  */
564
- vec3 sunHighlight =
565
- sunColor * specularLobe(max(dot(n, halfway), 0.0), surfaceRoughness) * sunSpec * sunShade * sunFacing;
564
+ /* GGX's own where the surface asks (\`physicalSpecular\`): the sun is a point, so nothing widens it. */
565
+ float sunNdh = max(dot(n, halfway), 0.0);
566
+ float sunLobe = physicalSpecular()
567
+ ? physicalLobe(sunNdh, ndl, dot(n, normalize(uCameraPos - vWorldPos)), surfaceRoughness, 0.0, 1.0)
568
+ : specularLobe(sunNdh, surfaceRoughness);
569
+ vec3 sunHighlight = sunColor * sunLobe * sunSpec * sunShade * sunFacing;
566
570
  if (modelled) sunHighlight = sunModelSpecular;
567
571
  /*
568
572
  * The dielectric's share goes in here, where it has always gone, so the environment blend
@@ -1066,6 +1070,10 @@ export const MAIN_GLSL = `void main() {
1066
1070
  float cookie;
1067
1071
  /* The light's lighting channels, a mask: 1 unless it names others. See \`uLightIesAxis\`. */
1068
1072
  float lightChannels = 1.0;
1073
+ /* The light's own falloff exponent, or 0 for the frame's falloff. See \`uLightCone\`. */
1074
+ float lightExponent = 0.0;
1075
+ /* A clustered rectangle's barn doors: their cosine and length, none at 0. See \`areaBarnDoors\`. */
1076
+ vec2 areaDoors = vec2(0.0);
1069
1077
  /*
1070
1078
  * **A rectangle in the froxel table** — one past the fixed arm's \`maxAreaLights\` — which
1071
1079
  * its record says with a negative radius, its reach negated. Its fields come out of the
@@ -1116,8 +1124,8 @@ export const MAIN_GLSL = `void main() {
1116
1124
  lightWeight = sizeAndWeight.y;
1117
1125
  /*
1118
1126
  * **The fixture, read only for a light that has one.** What a plain light's record holds in
1119
- * the last three texels is exactly these values, so a light carrying no shadow slot, cone,
1120
- * profile or cookie shades the same whether they are read or assumed, and at heavy overlap
1127
+ * the last four texels is exactly these values, so a light carrying no shadow slot, cone,
1128
+ * profile, cookie, channels or falloff of its own shades the same whether they are read or assumed, and at heavy overlap
1121
1129
  * reading them was a third of the pass. A branch on a value from a texture around
1122
1130
  * \`texelFetch\` is not the 2026-08-07 case: an integer fetch takes no derivative.
1123
1131
  *
@@ -1137,6 +1145,7 @@ export const MAIN_GLSL = `void main() {
1137
1145
  uvec4 rec2 = clusterTexel(record + 2);
1138
1146
  uvec4 rec3 = clusterTexel(record + 3);
1139
1147
  uvec4 rec4 = clusterTexel(record + 4);
1148
+ uvec4 rec5 = clusterTexel(record + 5);
1140
1149
  shadowSlot = clusteredArea ? -1 : int(uintBitsToFloat(rec2.x));
1141
1150
  areaTwoSided = uintBitsToFloat(rec2.x);
1142
1151
  lightDir = vec3(
@@ -1149,6 +1158,8 @@ export const MAIN_GLSL = `void main() {
1149
1158
  iesAxis = vec3(
1150
1159
  uintBitsToFloat(rec4.x), uintBitsToFloat(rec4.y), uintBitsToFloat(rec4.z));
1151
1160
  lightChannels = uintBitsToFloat(rec4.w);
1161
+ lightExponent = uintBitsToFloat(rec5.x);
1162
+ areaDoors = vec2(uintBitsToFloat(rec5.y), uintBitsToFloat(rec5.z));
1152
1163
  }
1153
1164
  }
1154
1165
  } else {
@@ -1161,7 +1172,8 @@ export const MAIN_GLSL = `void main() {
1161
1172
  lightWeight = uLightWeight[i];
1162
1173
  shadowSlot = i;
1163
1174
  lightDir = uLightDir[i];
1164
- lightCone = uLightCone[i];
1175
+ lightCone = uLightCone[i].xy;
1176
+ lightExponent = uLightCone[i].z;
1165
1177
  iesProfile = uLightIesProfile[i];
1166
1178
  iesAxis = uLightIesAxis[i].xyz;
1167
1179
  lightChannels = uLightIesAxis[i].w;
@@ -1187,12 +1199,15 @@ export const MAIN_GLSL = `void main() {
1187
1199
  * froxel it is binned into has no edge in the picture.
1188
1200
  */
1189
1201
  if (clusteredArea) {
1190
- float areaForm = areaSignedForm(n, lightPos, lightDir, iesAxis, lightCone);
1202
+ vec3 seenCentre = lightPos;
1203
+ vec2 seenHalf = lightCone;
1204
+ if (!areaBarnDoors(seenCentre, lightDir, iesAxis, seenHalf, areaDoors)) continue;
1205
+ float areaForm = areaSignedForm(n, seenCentre, lightDir, iesAxis, seenHalf);
1191
1206
  float areaWindow = clamp(1.0 - pow(length(lightPos - vWorldPos) / max(lightRadius, 1e-4), 4.0), 0.0, 1.0);
1192
1207
  areaForm = mix(max(0.0, areaForm), abs(areaForm), areaTwoSided);
1193
1208
  if (areaForm > 0.0) {
1194
1209
  areaLightAdd(
1195
- lightPos, lightDir, iesAxis, lightCone, lightColor * (areaWindow * areaWindow), areaForm,
1210
+ seenCentre, lightDir, iesAxis, seenHalf, lightColor * (areaWindow * areaWindow), areaForm,
1196
1211
  1.0, vec3(1.0), n, albedo, metal, specColor, surfaceRoughness, modelled,
1197
1212
  lampOpen, lampShadowed
1198
1213
  );
@@ -1243,7 +1258,16 @@ export const MAIN_GLSL = `void main() {
1243
1258
  * same way three.js windows its own, so the light still ends where it says it does.
1244
1259
  */
1245
1260
  float falloff;
1246
- if (uLightFalloff == 1) {
1261
+ /*
1262
+ * **A light may name its own falloff, and then it is Unreal's for a light without
1263
+ * inverse-square falloff**: \`(1 - (d/R)^2)^n\` with no distance term, so a fill authored to
1264
+ * light a whole hall across a hundred metres does, where the inverse square would have it
1265
+ * gone a few metres out. Zero is the frame's own rule below, which every light was before.
1266
+ */
1267
+ if (lightExponent > 0.0) {
1268
+ float reach = dist / max(lightRadius, 1e-4);
1269
+ falloff = pow(clamp(1.0 - reach * reach, 0.0, 1.0), lightExponent);
1270
+ } else if (uLightFalloff == 1) {
1247
1271
  float window = clamp(1.0 - pow(dist / max(lightRadius, 1e-4), 4.0), 0.0, 1.0);
1248
1272
  falloff = window * window / max(dist * dist, 0.01);
1249
1273
  } else {
@@ -1506,7 +1530,11 @@ export const MAIN_GLSL = `void main() {
1506
1530
  photometric = mix(1.0, iesGain, step(0.0, iesProfile));
1507
1531
  }
1508
1532
 
1509
- float shape = (uLightFalloff == 1 ? falloff : falloff * falloff) * coneFalloff * photometric;
1533
+ /* A light's own exponent already is its whole curve; the frame's smooth rule squares its own. */
1534
+ float shape =
1535
+ (lightExponent > 0.0 || uLightFalloff == 1 ? falloff : falloff * falloff) *
1536
+ coneFalloff *
1537
+ photometric;
1510
1538
  glassGlow += lightColor * backNdl * shape * lightWeight * shaded * lampGlass;
1511
1539
  if (ndl <= 0.0 && !modelledBehind) continue;
1512
1540
  /*
@@ -1583,16 +1611,15 @@ export const MAIN_GLSL = `void main() {
1583
1611
  if (vSpecular > 0.0 || metal > 0.0) {
1584
1612
  vec3 toEyeLamp = normalize(uCameraPos - vWorldPos);
1585
1613
  vec3 lampHalfway = normalize(toLight / max(dist, 1e-4) + toEyeLamp);
1586
- /* The same grazing whitening the sun's highlight takes, and zero at metal 0. */
1587
- vec3 lampSpec = mix(
1588
- specColor,
1589
- vec3(1.0),
1590
- pow(1.0 - max(dot(toEyeLamp, lampHalfway), 0.0), 5.0) * metal
1591
- );
1614
+ /* The same grazing whitening the sun's highlight takes, and zero at metal 0 for the look. */
1615
+ vec3 lampSpec = highlightTint(specColor, max(dot(toEyeLamp, lampHalfway), 0.0), metal);
1616
+ /* The look's lobe, or GGX's own where the surface asks for it: see \`physicalLobe\`. */
1617
+ float lampNdh = max(dot(n, lampHalfway), 0.0);
1618
+ float lampLobe = physicalSpecular()
1619
+ ? physicalLobe(lampNdh, ndl, dot(n, toEyeLamp), surfaceRoughness, lightSourceRadius, dist)
1620
+ : sphereLobe(lampNdh, surfaceRoughness, lightSourceRadius, dist);
1592
1621
  /* Held back with the diffuse half, and shadowed by the same term it always was. */
1593
- vec3 lampHighlight =
1594
- lightColor * sphereLobe(max(dot(n, lampHalfway), 0.0), surfaceRoughness, lightSourceRadius, dist)
1595
- * lampSpec * shape * lightWeight;
1622
+ vec3 lampHighlight = lightColor * lampLobe * lampSpec * shape * lightWeight;
1596
1623
  lampOpen += lampHighlight;
1597
1624
  lampShadowed += lampHighlight * shaded * lampGlass;
1598
1625
  }
@@ -1636,10 +1663,18 @@ export const MAIN_GLSL = `void main() {
1636
1663
  vec3 centre = uAreaLightPos[a];
1637
1664
  vec3 right = uAreaLightRight[a];
1638
1665
  vec3 up = uAreaLightUp[a];
1639
- vec2 halfSize = uAreaLightSize[a];
1666
+ vec2 halfSize = uAreaLightSize[a].xy;
1667
+ /*
1668
+ * **The part of the rectangle this fragment sees past its barn doors** (\`areaBarnDoors\`),
1669
+ * which its light and highlight come from. Its shadow keeps the whole rectangle's centre and
1670
+ * size below, because that is what its map was baked from.
1671
+ */
1672
+ vec3 seenCentre = centre;
1673
+ vec2 seenHalf = halfSize;
1674
+ if (!areaBarnDoors(seenCentre, right, up, seenHalf, uAreaLightSize[a].zw)) continue;
1640
1675
 
1641
1676
  /* The form factor and its sign, which is the sidedness: see \`areaSignedForm\`. */
1642
- float signedForm = areaSignedForm(n, centre, right, up, halfSize);
1677
+ float signedForm = areaSignedForm(n, seenCentre, right, up, seenHalf);
1643
1678
  float form = mix(max(0.0, signedForm), abs(signedForm), uAreaLightTwoSided[a]);
1644
1679
  /* The form factor is linear in the normal, so the one seen from behind is its negation. */
1645
1680
  float backForm = glassTransmission > 0.0
@@ -1756,7 +1791,7 @@ export const MAIN_GLSL = `void main() {
1756
1791
  if (form <= 0.0) continue;
1757
1792
  /* The diffuse, the model's answer or the lobe integrated over the rectangle: see areaLight.ts. */
1758
1793
  areaLightAdd(
1759
- centre, right, up, halfSize, uAreaLightColor[a], form, areaOccl, areaGlass,
1794
+ seenCentre, right, up, seenHalf, uAreaLightColor[a], form, areaOccl, areaGlass,
1760
1795
  n, albedo, metal, specColor, surfaceRoughness, modelled, lampOpen, lampShadowed
1761
1796
  );
1762
1797
  }
@@ -1,6 +1,9 @@
1
1
  import type { SurfaceModelKind } from '../../surfaceModel.ts';
2
- /** The models' whole chunk, at depth zero before `main`: see `flatFrag`. */
3
- export declare const MODELS_GLSL: string;
2
+ /**
3
+ * The models' whole chunk, at depth zero before `main`: see `flatFrag`. `physicalSpecular` is the
4
+ * \`PHYSICAL_SPECULAR\` lit switch's value, written in as \`litSwitchesGlsl\` writes the others.
5
+ */
6
+ export declare function modelsGlsl(physicalSpecular: boolean): string;
4
7
  /**
5
8
  * Which half of a skin a lit pipeline draws under `skinScattering: 'screen-space'`: the whole
6
9
  * surface (every other pipeline), the frame's half without its diffuse, the diffuse's light alone
@@ -46,6 +46,12 @@ const bool SKIN_ALBEDO = false; // wgsl:override
46
46
  * Declared after skin's halves so that every switch before it keeps the id it shipped with.
47
47
  */
48
48
  const bool MODEL_LIGHTMAP = false; // wgsl:override
49
+ /*
50
+ * Not a model either: whether this program carries GGX's own highlight for a material that asks
51
+ * (\`SurfaceMaterial.physicalSpecular\`). A lit switch, off until a material first asks and then
52
+ * on for good, as \`litSwitchesGlsl\`'s are; declared last so every switch before it keeps its id.
53
+ */
54
+ const bool PHYSICAL_SPECULAR = PHYSICAL_SPECULAR_ON; // wgsl:override
49
55
 
50
56
  /*
51
57
  * A model's numbers, two vectors a material — the lit stage's uniform budget has no more room
@@ -124,15 +130,57 @@ float smithMasking(float ndl, float ndv, float alpha) {
124
130
  );
125
131
  }
126
132
 
133
+ /*
134
+ * Whether this surface's highlights are GGX's own (\`SurfaceMaterial.physicalSpecular\`), from the
135
+ * lane the standard, lightmap and anisotropic models leave free (\`packModel\`). Skin, hair and the
136
+ * eye shade their own highlights, and the eye's axis holds that lane, so they are never asked. The
137
+ * lit switch first, so a program built before any material asked carries none of it.
138
+ */
139
+ bool physicalSpecular() {
140
+ if (PHYSICAL_SPECULAR) {
141
+ if (MODEL_HAIR) return false;
142
+ if (MODEL_SKIN) return false;
143
+ if (MODEL_EYE) return false;
144
+ return uModelParams[1].z > 0.5;
145
+ }
146
+ return false;
147
+ }
148
+
149
+ /**
150
+ * GGX as a BRDF in this engine's light units, \`π · D · Vis · N·L\` with Fresnel left to the caller:
151
+ * the term skin's and the eye's highlights are, for a surface that asks for it. Its peak is
152
+ * \`1 / (4 α²)\` of the light head-on, against the look's 1. A lamp's size widens α as \`sphereLobe\`
153
+ * widens it, and a normalised distribution keeps its energy as it widens.
154
+ */
155
+ float physicalLobe(float ndh, float ndl, float ndv, float roughness, float sourceRadius, float dist) {
156
+ float grow = sourceRadius / max(2.0 * dist, 1e-3);
157
+ float alpha = min(max(roughness * roughness, MIN_LOBE_ALPHA) + grow, 1.0);
158
+ return 3.14159265 * ggxLobe(ndh, alpha) * smithMasking(ndl, max(ndv, 1e-4), alpha) * ndl;
159
+ }
160
+
161
+ /**
162
+ * A highlight's colour: Schlick's Fresnel from the surface's reflectance toward white at a grazing
163
+ * \`voh\`. On a metal alone for the look, whose dielectric attribute is a strength rather than a
164
+ * reflectance; on every surface where the highlight is physical, whose attribute is its F0 — but
165
+ * one reflecting nothing head-on keeps nothing, as the lamps' gate on that attribute has it.
166
+ */
167
+ vec3 highlightTint(vec3 specColor, float voh, float metal) {
168
+ float edge = pow(1.0 - voh, 5.0);
169
+ bool reflects = max(max(specColor.r, specColor.g), specColor.b) > 0.0;
170
+ return mix(specColor, vec3(1.0), physicalSpecular() && reflects ? edge : edge * metal);
171
+ }
172
+
127
173
  /** The standard model's answer to one light, which a model falls back to for what it does not change. */
128
174
  void standardLight(vec3 l, float sourceRadius, float dist) {
129
175
  float ndl = max(dot(mNormal, l), 0.0);
130
176
  vec3 h = normalize(l + mToEye);
131
177
  float ndh = max(dot(mNormal, h), 0.0);
132
- float lobe = sourceRadius > 0.0
133
- ? sphereLobe(ndh, mRoughness, sourceRadius, dist)
134
- : specularLobe(ndh, mRoughness);
135
- vec3 tint = mix(mSpecColor, vec3(1.0), pow(1.0 - max(dot(mToEye, h), 0.0), 5.0) * mMetal);
178
+ float lobe = physicalSpecular()
179
+ ? physicalLobe(ndh, ndl, dot(mNormal, mToEye), mRoughness, sourceRadius, dist)
180
+ : sourceRadius > 0.0
181
+ ? sphereLobe(ndh, mRoughness, sourceRadius, dist)
182
+ : specularLobe(ndh, mRoughness);
183
+ vec3 tint = highlightTint(mSpecColor, max(dot(mToEye, h), 0.0), mMetal);
136
184
  mDiffuse = mAlbedo * ndl * (1.0 - mMetal);
137
185
  mSpecular = ndl > 0.0 ? tint * lobe : vec3(0.0);
138
186
  }
@@ -250,20 +298,51 @@ vec3 modelEnvironment(vec3 environment) {
250
298
  return environment;
251
299
  }
252
300
 
301
+ /**
302
+ * One texel of a page's irradiance layer: its four bytes put back into the rgb9e5 word they are,
303
+ * lowest first, and decoded — three 9-bit mantissas over the exponent in the top five bits.
304
+ */
305
+ vec3 bakedIrradianceAt(ivec3 texel) {
306
+ uvec4 bytes = uvec4(texelFetch(uModelMap, texel, 0) * 255.0 + 0.5);
307
+ uint word = bytes.x | (bytes.y << 8u) | (bytes.z << 16u) | (bytes.w << 24u);
308
+ float scale = exp2(float(word >> 27u) - 24.0);
309
+ return vec3(float(word & 511u), float((word >> 9u) & 511u), float((word >> 18u) & 511u)) * scale;
310
+ }
311
+
253
312
  /**
254
313
  * What a baked page adds to this surface's diffuse: nothing in any pipeline but a lightmapped one,
255
314
  * nor in one whose page is missing. The page's two layers at the surface's second coordinates, which
256
315
  * ride the grain and relief lanes as \`-1 - uv\` (lightmap.ts) and are placed by the material's
257
316
  * region: the irradiance, and the first-order harmonic its direction is, answered by the shading
258
317
  * normal.
259
- * **An explicit level**, because the page has one and nothing here needs a derivative.
318
+ *
319
+ * **Filtered here, because the page cannot be filtered as it is stored**: the irradiance is a
320
+ * shared-exponent word in four bytes, and a blend of two words is not the blend of their colours.
321
+ * So the four texels around the sample are fetched, decoded and blended, as a bilinear sampler
322
+ * clamped at the page's edge would. Fetches need no derivative, so the branch costs nothing there.
260
323
  */
261
324
  vec3 modelBaked(vec3 n, vec3 albedo, float metal) {
262
325
  if (MODEL_LIGHTMAP) {
263
326
  if (uModelParams[1].w <= 0.0) return vec3(0.0);
264
327
  vec2 at = (vec2(-1.0) - vec2(vGrain, vRelief)) * uModelParams[0].xy + uModelParams[0].zw;
265
- vec3 irradiance = textureLod(uModelMap, vec3(at, 0.0), 0.0).rgb;
266
- vec4 d = textureLod(uModelMap, vec3(at, 1.0), 0.0) * 2.0 - 1.0;
328
+ ivec2 size = textureSize(uModelMap, 0).xy;
329
+ vec2 texel = at * vec2(size) - 0.5;
330
+ vec2 corner = floor(texel);
331
+ vec2 f = texel - corner;
332
+ ivec2 last = size - 1;
333
+ ivec2 lo = clamp(ivec2(corner), ivec2(0), last);
334
+ ivec2 hi = clamp(ivec2(corner) + 1, ivec2(0), last);
335
+ vec3 irradiance = mix(
336
+ mix(bakedIrradianceAt(ivec3(lo.x, lo.y, 0)), bakedIrradianceAt(ivec3(hi.x, lo.y, 0)), f.x),
337
+ mix(bakedIrradianceAt(ivec3(lo.x, hi.y, 0)), bakedIrradianceAt(ivec3(hi.x, hi.y, 0)), f.x),
338
+ f.y
339
+ );
340
+ vec4 direction = mix(
341
+ mix(texelFetch(uModelMap, ivec3(lo.x, lo.y, 1), 0), texelFetch(uModelMap, ivec3(hi.x, lo.y, 1), 0), f.x),
342
+ mix(texelFetch(uModelMap, ivec3(lo.x, hi.y, 1), 0), texelFetch(uModelMap, ivec3(hi.x, hi.y, 1), 0), f.x),
343
+ f.y
344
+ );
345
+ vec4 d = direction * 2.0 - 1.0;
267
346
  return albedo * (1.0 - metal) * irradiance * max(dot(d.xyz, n) + d.w, 0.0);
268
347
  }
269
348
  return vec3(0.0);
@@ -285,15 +364,20 @@ float modelReflectance(float standard) {
285
364
  return standard;
286
365
  }
287
366
  `;
288
- /** The models' whole chunk, at depth zero before `main`: see `flatFrag`. */
289
- export const MODELS_GLSL = [
290
- MODELS_HEAD,
291
- ANISOTROPIC_GLSL,
292
- HAIR_GLSL,
293
- SKIN_GLSL,
294
- EYE_GLSL,
295
- MODELS_DISPATCH,
296
- ].join('\n');
367
+ /**
368
+ * The models' whole chunk, at depth zero before `main`: see `flatFrag`. `physicalSpecular` is the
369
+ * \`PHYSICAL_SPECULAR\` lit switch's value, written in as \`litSwitchesGlsl\` writes the others.
370
+ */
371
+ export function modelsGlsl(physicalSpecular) {
372
+ return [
373
+ MODELS_HEAD.replace('PHYSICAL_SPECULAR_ON', physicalSpecular ? 'true' : 'false'),
374
+ ANISOTROPIC_GLSL,
375
+ HAIR_GLSL,
376
+ SKIN_GLSL,
377
+ EYE_GLSL,
378
+ MODELS_DISPATCH,
379
+ ].join('\n');
380
+ }
297
381
  /** The constant each half of a skin turns on. See `SkinHalf`. */
298
382
  export const SKIN_HALF_SWITCH = {
299
383
  scene: 'SKIN_SCREEN',
@@ -771,13 +771,15 @@ uniform float uLightWeight[MAX_LIGHTS];
771
771
  */
772
772
  uniform vec3 uLightDir[MAX_LIGHTS];
773
773
  /**
774
- * The cosine of each light's inner cone angle, then of its outer.
774
+ * The cosine of each light's inner cone angle, then of its outer, and in z the light's own falloff
775
+ * exponent, 0 for the frame's falloff (\`PointLightSource.falloffExponent\`): a \`vec2\` array cost
776
+ * a whole row a light anyway, so the exponent spends no row of its own.
775
777
  *
776
778
  * A light with no cone carries -1 and -2, which makes \`smoothstep(outer, inner, dot)\` exactly 1
777
779
  * for every direction on the sphere. See \`POINT_LIGHT_COS_OUTER\`: the pair is chosen so a point
778
780
  * light collapses to the arithmetic it had rather than to something very close to it.
779
781
  */
780
- uniform vec2 uLightCone[MAX_LIGHTS];
782
+ uniform vec4 uLightCone[MAX_LIGHTS];
781
783
  /**
782
784
  * Which row of the photometric atlas each light uses, or a negative index for none.
783
785
  *
@@ -882,7 +884,12 @@ uniform vec3 uAreaLightColor[MAX_AREA_LIGHTS];
882
884
  /** The rectangle's in-plane axes, unit length, and its half extents along each. */
883
885
  uniform vec3 uAreaLightRight[MAX_AREA_LIGHTS];
884
886
  uniform vec3 uAreaLightUp[MAX_AREA_LIGHTS];
885
- uniform vec2 uAreaLightSize[MAX_AREA_LIGHTS];
887
+ /*
888
+ * Each rectangle's half extents, and in zw its barn doors: the cosine of their angle from its normal
889
+ * and their length, 0 for none (\`areaBarnDoors\`). A \`vec2\` array costs a whole row a rectangle
890
+ * anyway, so the doors spend no row of their own.
891
+ */
892
+ uniform vec4 uAreaLightSize[MAX_AREA_LIGHTS];
886
893
  /**
887
894
  * Whether the rectangle emits from both faces, 1 or 0.
888
895
  *
@@ -1,4 +1,4 @@
1
- export declare const BOLT_FRAG_WGSL = "struct Uniforms {\n uTime: f32,\n uCoreColor: vec3<f32>,\n uEdgeColor: vec3<f32>,\n uCoreGain: f32,\n uFogColor: vec3<f32>,\n uFogDensity: f32,\n uFogHeightFalloff: f32,\n uFogEyeY: f32,\n uUnderwaterColor: vec3<f32>,\n uUnderwaterFogDensity: f32,\n uUnderwaterFactor: f32,\n uFogMode: i32,\n uFogNear: f32,\n uFogFar: f32,\n uCameraPos: vec3<f32>,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\nvar<private> vSide_1: f32;\nvar<private> vSeed_1: f32;\nvar<private> vAlong_1: f32;\nvar<private> vFade_1: f32;\nvar<private> vGain_1: f32;\nvar<private> vWorldPos_1: vec3<f32>;\nvar<private> outColor: vec4<f32>;\n\nfn mediumFog_u0028_f1_u003b_f1_u003b(dist: ptr<function, f32>, pointY: ptr<function, f32>) -> f32 {\n var span: f32;\n var ramp: f32;\n var wetLinear: f32;\n var t: f32;\n var denom: f32;\n var air: f32;\n var wet: f32;\n\n let _e48 = unnamed.uFogMode;\n if (_e48 == 1i) {\n let _e51 = unnamed.uFogFar;\n let _e53 = unnamed.uFogNear;\n span = max((_e51 - _e53), 0.0001f);\n let _e56 = (*dist);\n let _e58 = unnamed.uFogNear;\n let _e60 = span;\n ramp = clamp(((_e56 - _e58) / _e60), 0f, 1f);\n let _e64 = unnamed.uUnderwaterFactor;\n if (_e64 <= 0f) {\n let _e66 = ramp;\n return _e66;\n }\n let _e68 = unnamed.uUnderwaterFogDensity;\n let _e69 = (*dist);\n wetLinear = (_e68 * _e69);\n let _e71 = ramp;\n let _e72 = wetLinear;\n let _e74 = wetLinear;\n let _e80 = unnamed.uUnderwaterFactor;\n return mix(_e71, (1f - exp2(((-(_e72) * _e74) * 1.442695f))), _e80);\n }\n let _e82 = (*pointY);\n let _e84 = unnamed.uFogEyeY;\n let _e87 = unnamed.uFogHeightFalloff;\n t = ((_e82 - _e84) * _e87);\n let _e89 = t;\n let _e92 = t;\n denom = select(_e92, 0.0001f, (abs(_e89) < 0.0001f));\n let _e95 = unnamed.uFogDensity;\n let _e97 = (*dist);\n let _e99 = denom;\n let _e104 = denom;\n air = (1f - exp((((-(_e95) * _e97) * (1f - exp(-(_e99)))) / _e104)));\n let _e109 = unnamed.uUnderwaterFactor;\n if (_e109 <= 0f) {\n let _e111 = air;\n return _e111;\n }\n let _e113 = unnamed.uUnderwaterFogDensity;\n let _e114 = (*dist);\n wet = (_e113 * _e114);\n let _e116 = air;\n let _e117 = wet;\n let _e119 = wet;\n let _e125 = unnamed.uUnderwaterFactor;\n return mix(_e116, (1f - exp2(((-(_e117) * _e119) * 1.442695f))), _e125);\n}\n\nfn hash11_u0028_f1_u003b(p: ptr<function, f32>) -> f32 {\n let _e39 = (*p);\n return fract((sin((_e39 * 12.9898f)) * 43758.547f));\n}\n\nfn main_1() {\n var across: f32;\n var core: f32;\n var glow: f32;\n var t_1: f32;\n var stutter: f32;\n var param: f32;\n var param_1: f32;\n var taper: f32;\n var energy: f32;\n var lit: vec3<f32>;\n var fog: f32;\n var param_2: f32;\n var param_3: f32;\n\n let _e51 = vSide_1;\n across = (1f - abs(_e51));\n let _e54 = across;\n core = pow(_e54, 10f);\n let _e56 = across;\n glow = pow(_e56, 1.1f);\n let _e59 = unnamed.uTime;\n let _e61 = vSeed_1;\n t_1 = ((_e59 * 47f) + (_e61 * 17f));\n let _e64 = t_1;\n param = floor(_e64);\n let _e66 = hash11_u0028_f1_u003b((&param));\n let _e68 = t_1;\n param_1 = floor((_e68 * 2.37f));\n let _e71 = hash11_u0028_f1_u003b((&param_1));\n stutter = (0.78f + (0.22f * ((_e66 * 0.6f) + (_e71 * 0.4f))));\n let _e76 = vAlong_1;\n taper = (1f - (_e76 * 0.55f));\n let _e79 = vFade_1;\n let _e80 = stutter;\n let _e82 = taper;\n let _e84 = vGain_1;\n energy = (((_e79 * _e80) * _e82) * _e84);\n let _e87 = unnamed.uEdgeColor;\n let _e88 = glow;\n let _e91 = unnamed.uCoreColor;\n let _e92 = core;\n let _e95 = unnamed.uCoreGain;\n lit = ((_e87 * _e88) + ((_e91 * _e92) * _e95));\n let _e98 = vWorldPos_1;\n let _e100 = unnamed.uCameraPos;\n param_2 = distance(_e98, _e100);\n let _e103 = vWorldPos_1[1u];\n param_3 = _e103;\n let _e104 = mediumFog_u0028_f1_u003b_f1_u003b((&param_2), (&param_3));\n fog = _e104;\n let _e105 = lit;\n let _e106 = energy;\n let _e108 = fog;\n let _e110 = ((_e105 * _e106) * (1f - _e108));\n outColor = vec4<f32>(_e110.x, _e110.y, _e110.z, 1f);\n return;\n}\n\n@fragment \nfn main(@location(0) vSide: f32, @location(3) vSeed: f32, @location(1) vAlong: f32, @location(2) vFade: f32, @location(4) vGain: f32, @location(5) vWorldPos: vec3<f32>) -> @location(0) vec4<f32> {\n vSide_1 = vSide;\n vSeed_1 = vSeed;\n vAlong_1 = vAlong;\n vFade_1 = vFade;\n vGain_1 = vGain;\n vWorldPos_1 = vWorldPos;\n main_1();\n let _e13 = outColor;\n return _e13;\n}\n";
1
+ export declare const BOLT_FRAG_WGSL = "struct Uniforms {\n uTime: f32,\n uCoreColor: vec3<f32>,\n uEdgeColor: vec3<f32>,\n uCoreGain: f32,\n uFogColor: vec3<f32>,\n uFogDensity: f32,\n uFogHeightFalloff: f32,\n uFogEyeY: f32,\n uUnderwaterColor: vec3<f32>,\n uUnderwaterFogDensity: f32,\n uUnderwaterFactor: f32,\n uFogMode: i32,\n uFogNear: f32,\n uFogFar: f32,\n uCameraPos: vec3<f32>,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\nvar<private> vSide_1: f32;\nvar<private> vSeed_1: f32;\nvar<private> vAlong_1: f32;\nvar<private> vFade_1: f32;\nvar<private> vGain_1: f32;\nvar<private> vWorldPos_1: vec3<f32>;\nvar<private> outColor: vec4<f32>;\n\nfn mediumFog_u0028_f1_u003b_f1_u003b(dist: ptr<function, f32>, pointY: ptr<function, f32>) -> f32 {\n var span: f32;\n var ramp: f32;\n var wetLinear: f32;\n var t: f32;\n var denom: f32;\n var air: f32;\n var wet: f32;\n\n let _e48 = unnamed.uFogMode;\n if (_e48 == 1i) {\n let _e51 = unnamed.uFogFar;\n let _e53 = unnamed.uFogNear;\n span = max((_e51 - _e53), 0.0001f);\n let _e56 = (*dist);\n let _e58 = unnamed.uFogNear;\n let _e60 = span;\n ramp = clamp(((_e56 - _e58) / _e60), 0f, 1f);\n let _e64 = unnamed.uUnderwaterFactor;\n if (_e64 <= 0f) {\n let _e66 = ramp;\n return _e66;\n }\n let _e68 = unnamed.uUnderwaterFogDensity;\n let _e69 = (*dist);\n wetLinear = (_e68 * _e69);\n let _e71 = ramp;\n let _e72 = wetLinear;\n let _e74 = wetLinear;\n let _e80 = unnamed.uUnderwaterFactor;\n return mix(_e71, (1f - exp2(((-(_e72) * _e74) * 1.442695f))), _e80);\n }\n let _e82 = (*pointY);\n let _e84 = unnamed.uFogEyeY;\n let _e87 = unnamed.uFogHeightFalloff;\n t = ((_e82 - _e84) * _e87);\n let _e89 = t;\n let _e92 = t;\n denom = select(_e92, 0.0001f, (abs(_e89) < 0.0001f));\n let _e95 = unnamed.uFogDensity;\n let _e97 = (*dist);\n let _e99 = denom;\n let _e104 = denom;\n air = (1f - exp((((-(_e95) * _e97) * (1f - exp(-(_e99)))) / _e104)));\n let _e109 = unnamed.uUnderwaterFactor;\n if (_e109 <= 0f) {\n let _e111 = air;\n return _e111;\n }\n let _e113 = unnamed.uUnderwaterFogDensity;\n let _e114 = (*dist);\n wet = (_e113 * _e114);\n let _e116 = air;\n let _e117 = wet;\n let _e119 = wet;\n let _e125 = unnamed.uUnderwaterFactor;\n return mix(_e116, (1f - exp2(((-(_e117) * _e119) * 1.442695f))), _e125);\n}\n\nfn hash11_u0028_f1_u003b(p: ptr<function, f32>) -> f32 {\n let _e39 = (*p);\n return fract((sin((_e39 * 12.9898f)) * 43758.547f));\n}\n\nfn main_1() {\n var across: f32;\n var core: f32;\n var glow: f32;\n var t_1: f32;\n var stutter: f32;\n var param: f32;\n var param_1: f32;\n var taper: f32;\n var energy: f32;\n var lit: vec3<f32>;\n var fog: f32;\n var param_2: f32;\n var param_3: f32;\n\n let _e51 = vSide_1;\n across = clamp((1f - abs(_e51)), 0f, 1f);\n let _e55 = across;\n core = pow(_e55, 10f);\n let _e57 = across;\n glow = pow(_e57, 1.1f);\n let _e60 = unnamed.uTime;\n let _e62 = vSeed_1;\n t_1 = ((_e60 * 47f) + (_e62 * 17f));\n let _e65 = t_1;\n param = floor(_e65);\n let _e67 = hash11_u0028_f1_u003b((&param));\n let _e69 = t_1;\n param_1 = floor((_e69 * 2.37f));\n let _e72 = hash11_u0028_f1_u003b((&param_1));\n stutter = (0.78f + (0.22f * ((_e67 * 0.6f) + (_e72 * 0.4f))));\n let _e77 = vAlong_1;\n taper = (1f - (_e77 * 0.55f));\n let _e80 = vFade_1;\n let _e81 = stutter;\n let _e83 = taper;\n let _e85 = vGain_1;\n energy = (((_e80 * _e81) * _e83) * _e85);\n let _e88 = unnamed.uEdgeColor;\n let _e89 = glow;\n let _e92 = unnamed.uCoreColor;\n let _e93 = core;\n let _e96 = unnamed.uCoreGain;\n lit = ((_e88 * _e89) + ((_e92 * _e93) * _e96));\n let _e99 = vWorldPos_1;\n let _e101 = unnamed.uCameraPos;\n param_2 = distance(_e99, _e101);\n let _e104 = vWorldPos_1[1u];\n param_3 = _e104;\n let _e105 = mediumFog_u0028_f1_u003b_f1_u003b((&param_2), (&param_3));\n fog = _e105;\n let _e106 = lit;\n let _e107 = energy;\n let _e109 = fog;\n let _e111 = ((_e106 * _e107) * (1f - _e109));\n outColor = vec4<f32>(_e111.x, _e111.y, _e111.z, 1f);\n return;\n}\n\n@fragment \nfn main(@location(0) vSide: f32, @location(3) vSeed: f32, @location(1) vAlong: f32, @location(2) vFade: f32, @location(4) vGain: f32, @location(5) vWorldPos: vec3<f32>) -> @location(0) vec4<f32> {\n vSide_1 = vSide;\n vSeed_1 = vSeed;\n vAlong_1 = vAlong;\n vFade_1 = vFade;\n vGain_1 = vGain;\n vWorldPos_1 = vWorldPos;\n main_1();\n let _e13 = outColor;\n return _e13;\n}\n";
2
2
  export declare const BOLT_VERT_WGSL = "struct Uniforms {\n uViewProj: mat4x4<f32>,\n uCameraPos: vec3<f32>,\n uWidth: f32,\n uMinWidthPerMetre: f32,\n}\n\nstruct gl_PerVertex {\n @builtin(position) gl_Position: vec4<f32>,\n gl_PointSize: f32,\n}\n\nstruct VertexOutput {\n @location(0) member: f32,\n @location(1) member_1: f32,\n @location(2) member_2: f32,\n @location(3) member_3: f32,\n @location(4) member_4: f32,\n @location(5) member_5: vec3<f32>,\n @builtin(position) gl_Position: vec4<f32>,\n}\n\nvar<private> aFrom_1: vec3<f32>;\nvar<private> aTo_1: vec3<f32>;\nvar<private> aCorner_1: vec2<f32>;\n@group(0) @binding(0) \nvar<uniform> unnamed: Uniforms;\nvar<private> vSide: f32;\nvar<private> vAlong: f32;\nvar<private> aArc_1: vec4<f32>;\nvar<private> vFade: f32;\nvar<private> vSeed: f32;\nvar<private> vGain: f32;\nvar<private> vWorldPos: vec3<f32>;\nvar<private> unnamed_1: gl_PerVertex = gl_PerVertex(vec4<f32>(0f, 0f, 0f, 1f), 1f);\n\nfn main_1() {\n var mid: vec3<f32>;\n var dir: vec3<f32>;\n var len: f32;\n var local: vec3<f32>;\n var view: vec3<f32>;\n var dist: f32;\n var local_1: vec3<f32>;\n var across: vec3<f32>;\n var span: f32;\n var local_2: vec3<f32>;\n var halfWidth: f32;\n var world: vec3<f32>;\n\n let _e40 = aFrom_1;\n let _e41 = aTo_1;\n let _e43 = aCorner_1[0u];\n mid = mix(_e40, _e41, vec3(_e43));\n let _e46 = aTo_1;\n let _e47 = aFrom_1;\n dir = (_e46 - _e47);\n let _e49 = dir;\n len = length(_e49);\n let _e51 = len;\n if (_e51 > 0.000001f) {\n let _e53 = dir;\n let _e54 = len;\n local = (_e53 / vec3(_e54));\n } else {\n local = vec3<f32>(0f, 1f, 0f);\n }\n let _e57 = local;\n dir = _e57;\n let _e58 = mid;\n let _e60 = unnamed.uCameraPos;\n view = (_e58 - _e60);\n let _e62 = view;\n dist = length(_e62);\n let _e64 = dist;\n if (_e64 > 0.000001f) {\n let _e66 = view;\n let _e67 = dist;\n local_1 = (_e66 / vec3(_e67));\n } else {\n local_1 = vec3<f32>(0f, 0f, 1f);\n }\n let _e70 = local_1;\n view = _e70;\n let _e71 = dir;\n let _e72 = view;\n across = cross(_e71, _e72);\n let _e74 = across;\n span = length(_e74);\n let _e76 = span;\n if (_e76 > 0.0001f) {\n let _e78 = across;\n let _e79 = span;\n local_2 = (_e78 / vec3(_e79));\n } else {\n let _e82 = dir;\n local_2 = normalize((cross(_e82, vec3<f32>(0f, 1f, 0f)) + vec3<f32>(0.001f, 0.001f, 0.001f)));\n }\n let _e86 = local_2;\n across = _e86;\n let _e88 = unnamed.uWidth;\n let _e89 = dist;\n let _e91 = unnamed.uMinWidthPerMetre;\n halfWidth = max(_e88, (_e89 * _e91));\n let _e94 = mid;\n let _e95 = across;\n let _e97 = aCorner_1[1u];\n let _e99 = halfWidth;\n world = (_e94 + ((_e95 * _e97) * _e99));\n let _e103 = aCorner_1[1u];\n vSide = _e103;\n let _e105 = aArc_1[0u];\n vAlong = _e105;\n let _e107 = aArc_1[1u];\n vFade = _e107;\n let _e109 = aArc_1[2u];\n vSeed = _e109;\n let _e111 = aArc_1[3u];\n vGain = _e111;\n let _e112 = world;\n vWorldPos = _e112;\n let _e114 = unnamed.uViewProj;\n let _e115 = world;\n unnamed_1.gl_Position = (_e114 * vec4<f32>(_e115.x, _e115.y, _e115.z, 1f));\n return;\n}\n\n@vertex \nfn main(@location(0) aFrom: vec3<f32>, @location(1) aTo: vec3<f32>, @location(2) aCorner: vec2<f32>, @location(3) aArc: vec4<f32>) -> VertexOutput {\n aFrom_1 = aFrom;\n aTo_1 = aTo;\n aCorner_1 = aCorner;\n aArc_1 = aArc;\n main_1();\n let _e17 = unnamed_1.gl_Position.y;\n unnamed_1.gl_Position.y = -(_e17);\n let _e19 = vSide;\n let _e20 = vAlong;\n let _e21 = vFade;\n let _e22 = vSeed;\n let _e23 = vGain;\n let _e24 = vWorldPos;\n let _e25 = unnamed_1.gl_Position;\n return VertexOutput(_e19, _e20, _e21, _e22, _e23, _e24, _e25);\n}\n";
3
3
  /**
4
4
  * What the transform assigned, so the renderer binds the same numbers.
@@ -4,7 +4,7 @@
4
4
  * The GLSL beside this file is the source of truth. A hand edit here is discarded by
5
5
  * the next generation, and `npm run wgsl:check` fails the build when this is stale.
6
6
  */
7
- export const BOLT_FRAG_WGSL = "struct Uniforms {\n uTime: f32,\n uCoreColor: vec3<f32>,\n uEdgeColor: vec3<f32>,\n uCoreGain: f32,\n uFogColor: vec3<f32>,\n uFogDensity: f32,\n uFogHeightFalloff: f32,\n uFogEyeY: f32,\n uUnderwaterColor: vec3<f32>,\n uUnderwaterFogDensity: f32,\n uUnderwaterFactor: f32,\n uFogMode: i32,\n uFogNear: f32,\n uFogFar: f32,\n uCameraPos: vec3<f32>,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\nvar<private> vSide_1: f32;\nvar<private> vSeed_1: f32;\nvar<private> vAlong_1: f32;\nvar<private> vFade_1: f32;\nvar<private> vGain_1: f32;\nvar<private> vWorldPos_1: vec3<f32>;\nvar<private> outColor: vec4<f32>;\n\nfn mediumFog_u0028_f1_u003b_f1_u003b(dist: ptr<function, f32>, pointY: ptr<function, f32>) -> f32 {\n var span: f32;\n var ramp: f32;\n var wetLinear: f32;\n var t: f32;\n var denom: f32;\n var air: f32;\n var wet: f32;\n\n let _e48 = unnamed.uFogMode;\n if (_e48 == 1i) {\n let _e51 = unnamed.uFogFar;\n let _e53 = unnamed.uFogNear;\n span = max((_e51 - _e53), 0.0001f);\n let _e56 = (*dist);\n let _e58 = unnamed.uFogNear;\n let _e60 = span;\n ramp = clamp(((_e56 - _e58) / _e60), 0f, 1f);\n let _e64 = unnamed.uUnderwaterFactor;\n if (_e64 <= 0f) {\n let _e66 = ramp;\n return _e66;\n }\n let _e68 = unnamed.uUnderwaterFogDensity;\n let _e69 = (*dist);\n wetLinear = (_e68 * _e69);\n let _e71 = ramp;\n let _e72 = wetLinear;\n let _e74 = wetLinear;\n let _e80 = unnamed.uUnderwaterFactor;\n return mix(_e71, (1f - exp2(((-(_e72) * _e74) * 1.442695f))), _e80);\n }\n let _e82 = (*pointY);\n let _e84 = unnamed.uFogEyeY;\n let _e87 = unnamed.uFogHeightFalloff;\n t = ((_e82 - _e84) * _e87);\n let _e89 = t;\n let _e92 = t;\n denom = select(_e92, 0.0001f, (abs(_e89) < 0.0001f));\n let _e95 = unnamed.uFogDensity;\n let _e97 = (*dist);\n let _e99 = denom;\n let _e104 = denom;\n air = (1f - exp((((-(_e95) * _e97) * (1f - exp(-(_e99)))) / _e104)));\n let _e109 = unnamed.uUnderwaterFactor;\n if (_e109 <= 0f) {\n let _e111 = air;\n return _e111;\n }\n let _e113 = unnamed.uUnderwaterFogDensity;\n let _e114 = (*dist);\n wet = (_e113 * _e114);\n let _e116 = air;\n let _e117 = wet;\n let _e119 = wet;\n let _e125 = unnamed.uUnderwaterFactor;\n return mix(_e116, (1f - exp2(((-(_e117) * _e119) * 1.442695f))), _e125);\n}\n\nfn hash11_u0028_f1_u003b(p: ptr<function, f32>) -> f32 {\n let _e39 = (*p);\n return fract((sin((_e39 * 12.9898f)) * 43758.547f));\n}\n\nfn main_1() {\n var across: f32;\n var core: f32;\n var glow: f32;\n var t_1: f32;\n var stutter: f32;\n var param: f32;\n var param_1: f32;\n var taper: f32;\n var energy: f32;\n var lit: vec3<f32>;\n var fog: f32;\n var param_2: f32;\n var param_3: f32;\n\n let _e51 = vSide_1;\n across = (1f - abs(_e51));\n let _e54 = across;\n core = pow(_e54, 10f);\n let _e56 = across;\n glow = pow(_e56, 1.1f);\n let _e59 = unnamed.uTime;\n let _e61 = vSeed_1;\n t_1 = ((_e59 * 47f) + (_e61 * 17f));\n let _e64 = t_1;\n param = floor(_e64);\n let _e66 = hash11_u0028_f1_u003b((&param));\n let _e68 = t_1;\n param_1 = floor((_e68 * 2.37f));\n let _e71 = hash11_u0028_f1_u003b((&param_1));\n stutter = (0.78f + (0.22f * ((_e66 * 0.6f) + (_e71 * 0.4f))));\n let _e76 = vAlong_1;\n taper = (1f - (_e76 * 0.55f));\n let _e79 = vFade_1;\n let _e80 = stutter;\n let _e82 = taper;\n let _e84 = vGain_1;\n energy = (((_e79 * _e80) * _e82) * _e84);\n let _e87 = unnamed.uEdgeColor;\n let _e88 = glow;\n let _e91 = unnamed.uCoreColor;\n let _e92 = core;\n let _e95 = unnamed.uCoreGain;\n lit = ((_e87 * _e88) + ((_e91 * _e92) * _e95));\n let _e98 = vWorldPos_1;\n let _e100 = unnamed.uCameraPos;\n param_2 = distance(_e98, _e100);\n let _e103 = vWorldPos_1[1u];\n param_3 = _e103;\n let _e104 = mediumFog_u0028_f1_u003b_f1_u003b((&param_2), (&param_3));\n fog = _e104;\n let _e105 = lit;\n let _e106 = energy;\n let _e108 = fog;\n let _e110 = ((_e105 * _e106) * (1f - _e108));\n outColor = vec4<f32>(_e110.x, _e110.y, _e110.z, 1f);\n return;\n}\n\n@fragment \nfn main(@location(0) vSide: f32, @location(3) vSeed: f32, @location(1) vAlong: f32, @location(2) vFade: f32, @location(4) vGain: f32, @location(5) vWorldPos: vec3<f32>) -> @location(0) vec4<f32> {\n vSide_1 = vSide;\n vSeed_1 = vSeed;\n vAlong_1 = vAlong;\n vFade_1 = vFade;\n vGain_1 = vGain;\n vWorldPos_1 = vWorldPos;\n main_1();\n let _e13 = outColor;\n return _e13;\n}\n";
7
+ export const BOLT_FRAG_WGSL = "struct Uniforms {\n uTime: f32,\n uCoreColor: vec3<f32>,\n uEdgeColor: vec3<f32>,\n uCoreGain: f32,\n uFogColor: vec3<f32>,\n uFogDensity: f32,\n uFogHeightFalloff: f32,\n uFogEyeY: f32,\n uUnderwaterColor: vec3<f32>,\n uUnderwaterFogDensity: f32,\n uUnderwaterFactor: f32,\n uFogMode: i32,\n uFogNear: f32,\n uFogFar: f32,\n uCameraPos: vec3<f32>,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\nvar<private> vSide_1: f32;\nvar<private> vSeed_1: f32;\nvar<private> vAlong_1: f32;\nvar<private> vFade_1: f32;\nvar<private> vGain_1: f32;\nvar<private> vWorldPos_1: vec3<f32>;\nvar<private> outColor: vec4<f32>;\n\nfn mediumFog_u0028_f1_u003b_f1_u003b(dist: ptr<function, f32>, pointY: ptr<function, f32>) -> f32 {\n var span: f32;\n var ramp: f32;\n var wetLinear: f32;\n var t: f32;\n var denom: f32;\n var air: f32;\n var wet: f32;\n\n let _e48 = unnamed.uFogMode;\n if (_e48 == 1i) {\n let _e51 = unnamed.uFogFar;\n let _e53 = unnamed.uFogNear;\n span = max((_e51 - _e53), 0.0001f);\n let _e56 = (*dist);\n let _e58 = unnamed.uFogNear;\n let _e60 = span;\n ramp = clamp(((_e56 - _e58) / _e60), 0f, 1f);\n let _e64 = unnamed.uUnderwaterFactor;\n if (_e64 <= 0f) {\n let _e66 = ramp;\n return _e66;\n }\n let _e68 = unnamed.uUnderwaterFogDensity;\n let _e69 = (*dist);\n wetLinear = (_e68 * _e69);\n let _e71 = ramp;\n let _e72 = wetLinear;\n let _e74 = wetLinear;\n let _e80 = unnamed.uUnderwaterFactor;\n return mix(_e71, (1f - exp2(((-(_e72) * _e74) * 1.442695f))), _e80);\n }\n let _e82 = (*pointY);\n let _e84 = unnamed.uFogEyeY;\n let _e87 = unnamed.uFogHeightFalloff;\n t = ((_e82 - _e84) * _e87);\n let _e89 = t;\n let _e92 = t;\n denom = select(_e92, 0.0001f, (abs(_e89) < 0.0001f));\n let _e95 = unnamed.uFogDensity;\n let _e97 = (*dist);\n let _e99 = denom;\n let _e104 = denom;\n air = (1f - exp((((-(_e95) * _e97) * (1f - exp(-(_e99)))) / _e104)));\n let _e109 = unnamed.uUnderwaterFactor;\n if (_e109 <= 0f) {\n let _e111 = air;\n return _e111;\n }\n let _e113 = unnamed.uUnderwaterFogDensity;\n let _e114 = (*dist);\n wet = (_e113 * _e114);\n let _e116 = air;\n let _e117 = wet;\n let _e119 = wet;\n let _e125 = unnamed.uUnderwaterFactor;\n return mix(_e116, (1f - exp2(((-(_e117) * _e119) * 1.442695f))), _e125);\n}\n\nfn hash11_u0028_f1_u003b(p: ptr<function, f32>) -> f32 {\n let _e39 = (*p);\n return fract((sin((_e39 * 12.9898f)) * 43758.547f));\n}\n\nfn main_1() {\n var across: f32;\n var core: f32;\n var glow: f32;\n var t_1: f32;\n var stutter: f32;\n var param: f32;\n var param_1: f32;\n var taper: f32;\n var energy: f32;\n var lit: vec3<f32>;\n var fog: f32;\n var param_2: f32;\n var param_3: f32;\n\n let _e51 = vSide_1;\n across = clamp((1f - abs(_e51)), 0f, 1f);\n let _e55 = across;\n core = pow(_e55, 10f);\n let _e57 = across;\n glow = pow(_e57, 1.1f);\n let _e60 = unnamed.uTime;\n let _e62 = vSeed_1;\n t_1 = ((_e60 * 47f) + (_e62 * 17f));\n let _e65 = t_1;\n param = floor(_e65);\n let _e67 = hash11_u0028_f1_u003b((&param));\n let _e69 = t_1;\n param_1 = floor((_e69 * 2.37f));\n let _e72 = hash11_u0028_f1_u003b((&param_1));\n stutter = (0.78f + (0.22f * ((_e67 * 0.6f) + (_e72 * 0.4f))));\n let _e77 = vAlong_1;\n taper = (1f - (_e77 * 0.55f));\n let _e80 = vFade_1;\n let _e81 = stutter;\n let _e83 = taper;\n let _e85 = vGain_1;\n energy = (((_e80 * _e81) * _e83) * _e85);\n let _e88 = unnamed.uEdgeColor;\n let _e89 = glow;\n let _e92 = unnamed.uCoreColor;\n let _e93 = core;\n let _e96 = unnamed.uCoreGain;\n lit = ((_e88 * _e89) + ((_e92 * _e93) * _e96));\n let _e99 = vWorldPos_1;\n let _e101 = unnamed.uCameraPos;\n param_2 = distance(_e99, _e101);\n let _e104 = vWorldPos_1[1u];\n param_3 = _e104;\n let _e105 = mediumFog_u0028_f1_u003b_f1_u003b((&param_2), (&param_3));\n fog = _e105;\n let _e106 = lit;\n let _e107 = energy;\n let _e109 = fog;\n let _e111 = ((_e106 * _e107) * (1f - _e109));\n outColor = vec4<f32>(_e111.x, _e111.y, _e111.z, 1f);\n return;\n}\n\n@fragment \nfn main(@location(0) vSide: f32, @location(3) vSeed: f32, @location(1) vAlong: f32, @location(2) vFade: f32, @location(4) vGain: f32, @location(5) vWorldPos: vec3<f32>) -> @location(0) vec4<f32> {\n vSide_1 = vSide;\n vSeed_1 = vSeed;\n vAlong_1 = vAlong;\n vFade_1 = vFade;\n vGain_1 = vGain;\n vWorldPos_1 = vWorldPos;\n main_1();\n let _e13 = outColor;\n return _e13;\n}\n";
8
8
  export const BOLT_VERT_WGSL = "struct Uniforms {\n uViewProj: mat4x4<f32>,\n uCameraPos: vec3<f32>,\n uWidth: f32,\n uMinWidthPerMetre: f32,\n}\n\nstruct gl_PerVertex {\n @builtin(position) gl_Position: vec4<f32>,\n gl_PointSize: f32,\n}\n\nstruct VertexOutput {\n @location(0) member: f32,\n @location(1) member_1: f32,\n @location(2) member_2: f32,\n @location(3) member_3: f32,\n @location(4) member_4: f32,\n @location(5) member_5: vec3<f32>,\n @builtin(position) gl_Position: vec4<f32>,\n}\n\nvar<private> aFrom_1: vec3<f32>;\nvar<private> aTo_1: vec3<f32>;\nvar<private> aCorner_1: vec2<f32>;\n@group(0) @binding(0) \nvar<uniform> unnamed: Uniforms;\nvar<private> vSide: f32;\nvar<private> vAlong: f32;\nvar<private> aArc_1: vec4<f32>;\nvar<private> vFade: f32;\nvar<private> vSeed: f32;\nvar<private> vGain: f32;\nvar<private> vWorldPos: vec3<f32>;\nvar<private> unnamed_1: gl_PerVertex = gl_PerVertex(vec4<f32>(0f, 0f, 0f, 1f), 1f);\n\nfn main_1() {\n var mid: vec3<f32>;\n var dir: vec3<f32>;\n var len: f32;\n var local: vec3<f32>;\n var view: vec3<f32>;\n var dist: f32;\n var local_1: vec3<f32>;\n var across: vec3<f32>;\n var span: f32;\n var local_2: vec3<f32>;\n var halfWidth: f32;\n var world: vec3<f32>;\n\n let _e40 = aFrom_1;\n let _e41 = aTo_1;\n let _e43 = aCorner_1[0u];\n mid = mix(_e40, _e41, vec3(_e43));\n let _e46 = aTo_1;\n let _e47 = aFrom_1;\n dir = (_e46 - _e47);\n let _e49 = dir;\n len = length(_e49);\n let _e51 = len;\n if (_e51 > 0.000001f) {\n let _e53 = dir;\n let _e54 = len;\n local = (_e53 / vec3(_e54));\n } else {\n local = vec3<f32>(0f, 1f, 0f);\n }\n let _e57 = local;\n dir = _e57;\n let _e58 = mid;\n let _e60 = unnamed.uCameraPos;\n view = (_e58 - _e60);\n let _e62 = view;\n dist = length(_e62);\n let _e64 = dist;\n if (_e64 > 0.000001f) {\n let _e66 = view;\n let _e67 = dist;\n local_1 = (_e66 / vec3(_e67));\n } else {\n local_1 = vec3<f32>(0f, 0f, 1f);\n }\n let _e70 = local_1;\n view = _e70;\n let _e71 = dir;\n let _e72 = view;\n across = cross(_e71, _e72);\n let _e74 = across;\n span = length(_e74);\n let _e76 = span;\n if (_e76 > 0.0001f) {\n let _e78 = across;\n let _e79 = span;\n local_2 = (_e78 / vec3(_e79));\n } else {\n let _e82 = dir;\n local_2 = normalize((cross(_e82, vec3<f32>(0f, 1f, 0f)) + vec3<f32>(0.001f, 0.001f, 0.001f)));\n }\n let _e86 = local_2;\n across = _e86;\n let _e88 = unnamed.uWidth;\n let _e89 = dist;\n let _e91 = unnamed.uMinWidthPerMetre;\n halfWidth = max(_e88, (_e89 * _e91));\n let _e94 = mid;\n let _e95 = across;\n let _e97 = aCorner_1[1u];\n let _e99 = halfWidth;\n world = (_e94 + ((_e95 * _e97) * _e99));\n let _e103 = aCorner_1[1u];\n vSide = _e103;\n let _e105 = aArc_1[0u];\n vAlong = _e105;\n let _e107 = aArc_1[1u];\n vFade = _e107;\n let _e109 = aArc_1[2u];\n vSeed = _e109;\n let _e111 = aArc_1[3u];\n vGain = _e111;\n let _e112 = world;\n vWorldPos = _e112;\n let _e114 = unnamed.uViewProj;\n let _e115 = world;\n unnamed_1.gl_Position = (_e114 * vec4<f32>(_e115.x, _e115.y, _e115.z, 1f));\n return;\n}\n\n@vertex \nfn main(@location(0) aFrom: vec3<f32>, @location(1) aTo: vec3<f32>, @location(2) aCorner: vec2<f32>, @location(3) aArc: vec4<f32>) -> VertexOutput {\n aFrom_1 = aFrom;\n aTo_1 = aTo;\n aCorner_1 = aCorner;\n aArc_1 = aArc;\n main_1();\n let _e17 = unnamed_1.gl_Position.y;\n unnamed_1.gl_Position.y = -(_e17);\n let _e19 = vSide;\n let _e20 = vAlong;\n let _e21 = vFade;\n let _e22 = vSeed;\n let _e23 = vGain;\n let _e24 = vWorldPos;\n let _e25 = unnamed_1.gl_Position;\n return VertexOutput(_e19, _e20, _e21, _e22, _e23, _e24, _e25);\n}\n";
9
9
  /**
10
10
  * What the transform assigned, so the renderer binds the same numbers.