@driftengine/core 4.8.6 → 4.9.0

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 (253) hide show
  1. package/CHANGELOG.json +132 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +7 -2
  4. package/dist/index.js +8 -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/atmosphere.d.ts +13 -0
  10. package/dist/render/atmosphere.js +9 -1
  11. package/dist/render/backend/api.d.ts +41 -3
  12. package/dist/render/backend/createRenderer.d.ts +6 -0
  13. package/dist/render/backend/createRenderer.js +4 -1
  14. package/dist/render/backend/webgl2/renderer.d.ts +140 -16
  15. package/dist/render/backend/webgl2/renderer.js +436 -44
  16. package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
  17. package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
  18. package/dist/render/backend/webgpu/device.d.ts +7 -1
  19. package/dist/render/backend/webgpu/device.js +31 -3
  20. package/dist/render/backend/webgpu/flatPass.d.ts +16 -1
  21. package/dist/render/backend/webgpu/flatPass.js +58 -26
  22. package/dist/render/backend/webgpu/particlePass.js +2 -1
  23. package/dist/render/backend/webgpu/postPass.d.ts +51 -9
  24. package/dist/render/backend/webgpu/postPass.js +16 -2
  25. package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
  26. package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
  27. package/dist/render/backend/webgpu/renderer.d.ts +166 -3
  28. package/dist/render/backend/webgpu/renderer.js +738 -83
  29. package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
  30. package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
  31. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
  32. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
  33. package/dist/render/backend/webgpu/surfaceTexturePass.js +34 -8
  34. package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
  35. package/dist/render/clusteredLights.d.ts +23 -5
  36. package/dist/render/clusteredLights.js +35 -8
  37. package/dist/render/displayRange.d.ts +31 -0
  38. package/dist/render/displayRange.js +47 -0
  39. package/dist/render/driftLight/bake.d.ts +2 -0
  40. package/dist/render/driftLight/bake.js +1 -1
  41. package/dist/render/driftLight/falloff.d.ts +4 -2
  42. package/dist/render/driftLight/falloff.js +8 -2
  43. package/dist/render/filmicCurve.d.ts +74 -0
  44. package/dist/render/filmicCurve.js +203 -0
  45. package/dist/render/fog.d.ts +4 -1
  46. package/dist/render/fog.js +12 -2
  47. package/dist/render/frame/resources.d.ts +2 -2
  48. package/dist/render/frame/resources.js +6 -0
  49. package/dist/render/frameReflections.d.ts +40 -0
  50. package/dist/render/frameReflections.js +48 -0
  51. package/dist/render/gi/bounceLights.d.ts +3 -2
  52. package/dist/render/gi/bounceLights.js +8 -3
  53. package/dist/render/instanceCull.js +5 -0
  54. package/dist/render/instances.d.ts +19 -5
  55. package/dist/render/instances.js +9 -3
  56. package/dist/render/lightBudget.d.ts +12 -1
  57. package/dist/render/lightBudget.js +23 -3
  58. package/dist/render/lightmap.d.ts +23 -9
  59. package/dist/render/lightmap.js +40 -18
  60. package/dist/render/particlePool.d.ts +21 -0
  61. package/dist/render/particlePool.js +7 -0
  62. package/dist/render/pass.d.ts +49 -0
  63. package/dist/render/pointLightSelection.d.ts +13 -0
  64. package/dist/render/pointLightSelection.js +3 -1
  65. package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
  66. package/dist/render/reflectionSurfaceTargets.js +99 -0
  67. package/dist/render/renderQuality.d.ts +47 -15
  68. package/dist/render/renderQuality.js +4 -0
  69. package/dist/render/rendererHarness.js +3 -0
  70. package/dist/render/rgb9e5.d.ts +22 -0
  71. package/dist/render/rgb9e5.js +47 -0
  72. package/dist/render/sceneCapture.d.ts +29 -0
  73. package/dist/render/sceneCapture.js +36 -0
  74. package/dist/render/sceneTarget.d.ts +5 -0
  75. package/dist/render/sceneTarget.js +24 -1
  76. package/dist/render/screenSpaceReflection.d.ts +32 -1
  77. package/dist/render/screenSpaceReflection.js +14 -0
  78. package/dist/render/shaders/arcane.d.ts +1 -1
  79. package/dist/render/shaders/bolt.d.ts +1 -1
  80. package/dist/render/shaders/bolt.js +7 -1
  81. package/dist/render/shaders/caustics.d.ts +1 -1
  82. package/dist/render/shaders/depth.js +11 -0
  83. package/dist/render/shaders/film.d.ts +1 -1
  84. package/dist/render/shaders/filmLook.js +2 -1
  85. package/dist/render/shaders/fire.d.ts +1 -1
  86. package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
  87. package/dist/render/shaders/flat/anisotropic.js +37 -3
  88. package/dist/render/shaders/flat/areaLight.d.ts +1 -1
  89. package/dist/render/shaders/flat/areaLight.js +46 -0
  90. package/dist/render/shaders/flat/glassTint.d.ts +3 -2
  91. package/dist/render/shaders/flat/glassTint.js +5 -1
  92. package/dist/render/shaders/flat/index.d.ts +15 -0
  93. package/dist/render/shaders/flat/index.js +22 -2
  94. package/dist/render/shaders/flat/lobes.d.ts +6 -0
  95. package/dist/render/shaders/flat/lobes.js +49 -43
  96. package/dist/render/shaders/flat/main.d.ts +1 -1
  97. package/dist/render/shaders/flat/main.js +85 -22
  98. package/dist/render/shaders/flat/models.d.ts +5 -2
  99. package/dist/render/shaders/flat/models.js +100 -16
  100. package/dist/render/shaders/flat/overlay.d.ts +16 -0
  101. package/dist/render/shaders/flat/overlay.js +117 -0
  102. package/dist/render/shaders/flat/pointShadow.js +1 -1
  103. package/dist/render/shaders/flat/preamble.js +10 -3
  104. package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
  105. package/dist/render/shaders/flat/reflectionSurface.js +61 -0
  106. package/dist/render/shaders/fog.d.ts +1 -1
  107. package/dist/render/shaders/fog.js +9 -2
  108. package/dist/render/shaders/fringe.d.ts +29 -0
  109. package/dist/render/shaders/fringe.js +55 -0
  110. package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
  111. package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
  112. package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
  113. package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
  114. package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
  115. package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
  116. package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
  117. package/dist/render/shaders/generated/depth.wgsl.js +4 -4
  118. package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
  119. package/dist/render/shaders/generated/film.wgsl.js +1 -1
  120. package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
  121. package/dist/render/shaders/generated/fire.wgsl.js +1 -1
  122. package/dist/render/shaders/generated/flat.wgsl.d.ts +192 -16
  123. package/dist/render/shaders/generated/flat.wgsl.js +230 -54
  124. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  125. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  126. package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
  127. package/dist/render/shaders/generated/particle.wgsl.js +4 -4
  128. package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
  129. package/dist/render/shaders/generated/rush.wgsl.js +56 -26
  130. package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
  131. package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
  132. package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
  133. package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
  134. package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
  135. package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
  136. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +32 -7
  137. package/dist/render/shaders/generated/ssrTrace.wgsl.js +32 -7
  138. package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
  139. package/dist/render/shaders/generated/water.wgsl.js +23 -16
  140. package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
  141. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
  142. package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
  143. package/dist/render/shaders/line.d.ts +1 -1
  144. package/dist/render/shaders/particle.d.ts +3 -3
  145. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  146. package/dist/render/shaders/recon/motion.wgsl.js +13 -2
  147. package/dist/render/shaders/rush.js +54 -11
  148. package/dist/render/shaders/scatter.d.ts +1 -1
  149. package/dist/render/shaders/smoke.d.ts +1 -1
  150. package/dist/render/shaders/ssrResolve.d.ts +17 -0
  151. package/dist/render/shaders/ssrResolve.js +51 -0
  152. package/dist/render/shaders/ssrTrace.js +46 -1
  153. package/dist/render/shaders/water.d.ts +1 -1
  154. package/dist/render/shaders/water.js +12 -3
  155. package/dist/render/skinScatter.d.ts +3 -1
  156. package/dist/render/skinScatter.js +3 -1
  157. package/dist/render/ssrPass.d.ts +16 -0
  158. package/dist/render/ssrPass.js +107 -1
  159. package/dist/render/surfaceModel.d.ts +29 -8
  160. package/dist/render/surfaceModel.js +33 -9
  161. package/dist/render/surfaceOverlay.d.ts +132 -0
  162. package/dist/render/surfaceOverlay.js +85 -0
  163. package/dist/render/surfaceTexture.d.ts +44 -1
  164. package/dist/render/surfaceTexture.js +96 -5
  165. package/dist/render/temporalAa.d.ts +14 -0
  166. package/dist/render/temporalAa.js +20 -0
  167. package/dist/render/vertexDefaults.d.ts +17 -0
  168. package/dist/render/vertexDefaults.js +24 -0
  169. package/package.json +6 -6
  170. package/src/index.ts +22 -2
  171. package/src/render/anisotropicLobe.ts +29 -0
  172. package/src/render/areaLights.ts +58 -0
  173. package/src/render/atmosphere.ts +22 -1
  174. package/src/render/backend/api.ts +58 -2
  175. package/src/render/backend/createRenderer.ts +18 -1
  176. package/src/render/backend/webgl2/renderer.ts +548 -50
  177. package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
  178. package/src/render/backend/webgpu/device.ts +42 -3
  179. package/src/render/backend/webgpu/flatPass.ts +63 -26
  180. package/src/render/backend/webgpu/particlePass.ts +2 -1
  181. package/src/render/backend/webgpu/postPass.ts +18 -0
  182. package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
  183. package/src/render/backend/webgpu/renderer.ts +927 -100
  184. package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
  185. package/src/render/backend/webgpu/surfaceTexturePass.ts +44 -14
  186. package/src/render/clusteredLights.ts +41 -8
  187. package/src/render/displayRange.ts +61 -0
  188. package/src/render/driftLight/bake.ts +3 -1
  189. package/src/render/driftLight/falloff.ts +13 -2
  190. package/src/render/filmicCurve.ts +229 -0
  191. package/src/render/fog.ts +19 -3
  192. package/src/render/frame/resources.ts +6 -0
  193. package/src/render/frameReflections.ts +72 -0
  194. package/src/render/gi/bounceLights.ts +12 -3
  195. package/src/render/instanceCull.ts +4 -0
  196. package/src/render/instances.ts +26 -6
  197. package/src/render/lightBudget.ts +40 -2
  198. package/src/render/lightmap.ts +64 -23
  199. package/src/render/particlePool.ts +23 -0
  200. package/src/render/pass.ts +53 -1
  201. package/src/render/pointLightSelection.ts +20 -1
  202. package/src/render/reflectionSurfaceTargets.ts +105 -0
  203. package/src/render/renderQuality.ts +52 -15
  204. package/src/render/rendererHarness.ts +3 -0
  205. package/src/render/rgb9e5.ts +52 -0
  206. package/src/render/sceneCapture.ts +56 -0
  207. package/src/render/sceneTarget.ts +30 -2
  208. package/src/render/screenSpaceReflection.ts +38 -1
  209. package/src/render/shaders/bolt.ts +7 -1
  210. package/src/render/shaders/depth.ts +11 -0
  211. package/src/render/shaders/filmLook.ts +2 -1
  212. package/src/render/shaders/flat/anisotropic.ts +37 -3
  213. package/src/render/shaders/flat/areaLight.ts +46 -0
  214. package/src/render/shaders/flat/glassTint.ts +13 -2
  215. package/src/render/shaders/flat/index.ts +37 -2
  216. package/src/render/shaders/flat/lobes.ts +50 -43
  217. package/src/render/shaders/flat/main.ts +85 -22
  218. package/src/render/shaders/flat/models.ts +100 -16
  219. package/src/render/shaders/flat/overlay.ts +117 -0
  220. package/src/render/shaders/flat/pointShadow.ts +1 -1
  221. package/src/render/shaders/flat/preamble.ts +10 -3
  222. package/src/render/shaders/flat/reflectionSurface.ts +61 -0
  223. package/src/render/shaders/fog.ts +9 -2
  224. package/src/render/shaders/fringe.ts +59 -0
  225. package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
  226. package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
  227. package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
  228. package/src/render/shaders/generated/depth.wgsl.ts +4 -4
  229. package/src/render/shaders/generated/film.wgsl.ts +1 -1
  230. package/src/render/shaders/generated/fire.wgsl.ts +1 -1
  231. package/src/render/shaders/generated/flat.wgsl.ts +230 -54
  232. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  233. package/src/render/shaders/generated/particle.wgsl.ts +4 -4
  234. package/src/render/shaders/generated/rush.wgsl.ts +56 -26
  235. package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
  236. package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
  237. package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
  238. package/src/render/shaders/generated/ssrTrace.wgsl.ts +32 -7
  239. package/src/render/shaders/generated/water.wgsl.ts +23 -16
  240. package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
  241. package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
  242. package/src/render/shaders/recon/motion.wgsl.ts +13 -2
  243. package/src/render/shaders/rush.ts +54 -11
  244. package/src/render/shaders/ssrResolve.ts +52 -0
  245. package/src/render/shaders/ssrTrace.ts +46 -1
  246. package/src/render/shaders/water.ts +12 -3
  247. package/src/render/skinScatter.ts +3 -1
  248. package/src/render/ssrPass.ts +139 -1
  249. package/src/render/surfaceModel.ts +47 -10
  250. package/src/render/surfaceOverlay.ts +195 -0
  251. package/src/render/surfaceTexture.ts +136 -9
  252. package/src/render/temporalAa.ts +20 -0
  253. package/src/render/vertexDefaults.ts +28 -0
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The `filmic` output transform: a parametric film curve, its five numbers, and the eight
3
+ * constants the resolve reads, decided once here for both backends.
4
+ *
5
+ * **Why a second filmic curve beside `aces`.** `aces` is the reference RRT and ODT fit, which
6
+ * holds a mid grey of 0.18 at about 0.11 of display linear: graded for a dark cinema and kept dark.
7
+ * The curve here holds 0.18 at 0.18 by construction, and is shaped by five numbers a grading
8
+ * volume carries — a slope through mid grey, a toe and a shoulder, and how far black and white are
9
+ * clipped past the ends — so a look authored against such a curve is matched by passing its numbers
10
+ * rather than by baking the difference into a colour lookup table, which has the fewest steps
11
+ * exactly where two curves differ most: in the shadows.
12
+ *
13
+ * **What the curve is.** In the ACEScg working space, after the ACES reference's glow and red
14
+ * modifier and a 4% desaturation: a straight line of `slope` through mid grey in log10, joined to a
15
+ * logistic toe below `toeMatch` and a logistic shoulder above `shoulderMatch`, blended by a smooth
16
+ * step between the two, then desaturated by 7% and taken back to linear sRGB. The toe's position is
17
+ * solved so that 0.18 in is 0.18 out.
18
+ *
19
+ * **What it gives up.** It needs the composite to grade: `screenEffects` and `hdrScene`, since the
20
+ * forward passes that grade a frame without one carry two uniforms between them and not eight.
21
+ * Without them every pass grades with `aces` and the renderer says so once at construction.
22
+ * **What would make it wrong** is a profile wanting this curve without a scene target, which would
23
+ * then pay six floats in every forward program to have it.
24
+ */
25
+ /** A film curve's five numbers, as a grading volume states them. */
26
+ export interface FilmicCurve {
27
+ /** The straight segment's slope in log10, through mid grey. Positive. */
28
+ readonly slope: number;
29
+ /** How much of the curve below mid grey the toe takes, 0 to 1. Larger is a longer, softer toe. */
30
+ readonly toe: number;
31
+ /** How much of the curve above mid grey the shoulder takes, 0 to 1. Larger rolls off sooner. */
32
+ readonly shoulder: number;
33
+ /** How far below 0 the toe's asymptote sits: 0 holds black at black, more crushes it. */
34
+ readonly blackClip: number;
35
+ /** How far above 1 the shoulder's asymptote sits: 0 never reaches white, more lets it clip. */
36
+ readonly whiteClip: number;
37
+ }
38
+ /** The defaults the curve ships with where a volume states nothing. */
39
+ export declare const DEFAULT_FILMIC_CURVE: FilmicCurve;
40
+ /** How many floats `resolveFilmicCurve` fills: two vectors of the resolve's uniforms. */
41
+ export declare const FILMIC_CONSTANTS = 8;
42
+ /**
43
+ * The eight constants the resolve reads, into `out`: `uFilmA` = (slope, black clip, white clip,
44
+ * toe scale) and `uFilmB` = (shoulder scale, toe match, straight match, shoulder match). Returns
45
+ * whether any of the five numbers had to be brought into range.
46
+ *
47
+ * Here rather than in the shader, because they are one solve a curve and would be one per pixel
48
+ * there. **Clamped rather than refused**, because a grading volume is set from inside a frame and
49
+ * the frame loop never throws: a slope below 0.01, a toe, shoulder or clip outside 0 to 1, and a
50
+ * toe or shoulder of exactly 1 — whose scale is then zero and divides the curve by it — are each
51
+ * a picture made of NaN otherwise. A number that is not finite takes the default's. The renderer
52
+ * says once when anything was clamped.
53
+ */
54
+ export declare function resolveFilmicCurve(curve: FilmicCurve, out: Float32Array,
55
+ /**
56
+ * The display's peak over paper white (`displayHeadroom`), 1 for a standard range. Above 1 the
57
+ * white clip is raised by the difference, so the shoulder rolls off toward the display's peak
58
+ * rather than toward white, and the toe and mid grey stay exactly where they were.
59
+ */
60
+ headroom?: number): boolean;
61
+ /** Said once a renderer, the first time `setFilmicCurve` was handed a number out of range. */
62
+ export declare const FILMIC_CLAMPED: string;
63
+ /**
64
+ * Said once at construction where `filmic` was asked for and the frame has no composite that
65
+ * grades: the passes grade with `aces`, which is the nearest curve they carry.
66
+ */
67
+ export declare const FILMIC_WITHOUT_COMPOSITE: string;
68
+ /**
69
+ * `filmicCurve(c)`: linear sRGB in, display-linear sRGB out, clamped to 0 to 1 — or, on a high
70
+ * range display, to the shoulder's own asymptote. Reads `uFilmA`, `uFilmB` and `uDisplayHeadroom`,
71
+ * which the including stage declares. The ACES helpers are the reference's own:
72
+ * saturation, `yc`, the glow's sigmoid, and hue in degrees.
73
+ */
74
+ export declare const FILMIC_GLSL = "\n/* sRGB to ACEScg (through D60), ACEScg back, and ACEScg to and from ACES2065-1. Columns. */\nconst mat3 FILM_SRGB_TO_AP1 = mat3(\n 0.61319, 0.07021, 0.02062,\n 0.33951, 0.91634, 0.10957,\n 0.04737, 0.01345, 0.86961\n);\n/* The inverse of the one above, computed rather than copied, so a round trip is the identity. */\nconst mat3 FILM_AP1_TO_SRGB = mat3(\n 1.70480, -0.13027, -0.02401,\n -0.62168, 1.14082, -0.12900,\n -0.08325, -0.01055, 1.15324\n);\nconst mat3 FILM_AP1_TO_AP0 = mat3(\n 0.6954522414, 0.0447945634, -0.0055258826,\n 0.1406786965, 0.8596711185, 0.0040252103,\n 0.1638690622, 0.0955343182, 1.0015006723\n);\nconst mat3 FILM_AP0_TO_AP1 = mat3(\n 1.4514393161, -0.0765537734, 0.0083161484,\n -0.2365107469, 1.1762296998, -0.0060324498,\n -0.2149285693, -0.0996759264, 0.9977163014\n);\nconst vec3 FILM_AP1_LUMA = vec3(0.2722287168, 0.6740817658, 0.0536895174);\n\nfloat filmSaturation(vec3 c) {\n float hi = max(c.r, max(c.g, c.b));\n float lo = min(c.r, min(c.g, c.b));\n return (max(hi, 1e-10) - max(lo, 1e-10)) / max(hi, 1e-2);\n}\n\nfloat filmYc(vec3 c) {\n float chroma = sqrt(max(c.b * (c.b - c.g) + c.g * (c.g - c.r) + c.r * (c.r - c.b), 0.0));\n return (c.b + c.g + c.r + 1.75 * chroma) / 3.0;\n}\n\nfloat filmSigmoid(float x) {\n float t = max(1.0 - abs(x / 2.0), 0.0);\n return (1.0 + sign(x) * (1.0 - t * t)) / 2.0;\n}\n\nfloat filmGlow(float yc, float gain, float mid) {\n if (yc <= 2.0 / 3.0 * mid) return gain;\n if (yc >= 2.0 * mid) return 0.0;\n return gain * (mid / yc - 0.5);\n}\n\n/* Hue in degrees, -180 to 180 about red; 0 for a grey, which then takes no red modifier. */\nfloat filmCentredHue(vec3 c) {\n float y = sqrt(3.0) * (c.g - c.b);\n float x = 2.0 * c.r - c.g - c.b;\n if (abs(x) < 1e-10 && abs(y) < 1e-10) return 0.0;\n return degrees(atan(y, x));\n}\n\nvec3 filmicCurve(vec3 linear) {\n vec3 ap0 = FILM_AP1_TO_AP0 * (FILM_SRGB_TO_AP1 * linear);\n /* The reference's glow, lifting dark saturated colours a little. */\n float saturation = filmSaturation(ap0);\n ap0 *= 1.0 + filmGlow(filmYc(ap0), 0.05 * filmSigmoid((saturation - 0.4) / 0.2), 0.08);\n /* And its red modifier, pulling saturated reds toward a pivot so they do not run to orange. */\n float weight = smoothstep(0.0, 1.0, 1.0 - abs(2.0 * filmCentredHue(ap0) / 135.0));\n ap0.r += weight * weight * saturation * (0.03 - ap0.r) * (1.0 - 0.82);\n vec3 work = max(FILM_AP0_TO_AP1 * ap0, vec3(0.0));\n work = mix(vec3(dot(work, FILM_AP1_LUMA)), work, 0.96);\n\n float slope = uFilmA.x;\n float black = uFilmA.y;\n float white = uFilmA.z;\n float toeScale = uFilmA.w;\n float shoulderScale = uFilmB.x;\n float toeMatch = uFilmB.y;\n float straightMatch = uFilmB.z;\n float shoulderMatch = uFilmB.w;\n /* log10, through log2: GLSL ES has no log10. */\n vec3 logc = log2(max(work, vec3(1e-10))) * 0.30102999566;\n vec3 straight = slope * (logc + straightMatch);\n vec3 toeC = -black + (2.0 * toeScale) / (1.0 + exp((-2.0 * slope / toeScale) * (logc - toeMatch)));\n vec3 shoulderC =\n (1.0 + white) - (2.0 * shoulderScale) / (1.0 + exp((2.0 * slope / shoulderScale) * (logc - shoulderMatch)));\n toeC = mix(straight, toeC, vec3(lessThan(logc, vec3(toeMatch))));\n shoulderC = mix(straight, shoulderC, vec3(greaterThan(logc, vec3(shoulderMatch))));\n vec3 t = clamp((logc - toeMatch) / (shoulderMatch - toeMatch), 0.0, 1.0);\n if (shoulderMatch < toeMatch) t = 1.0 - t;\n t = (3.0 - 2.0 * t) * t * t;\n vec3 tone = mix(toeC, shoulderC, t);\n tone = mix(vec3(dot(tone, FILM_AP1_LUMA)), tone, 0.93);\n /* White is the ceiling on a standard display; on a high range one the shoulder's asymptote is. */\n float ceiling = uDisplayHeadroom > 1.0 ? 1.0 + white : 1.0;\n return clamp(FILM_AP1_TO_SRGB * max(tone, vec3(0.0)), 0.0, ceiling);\n}\n";
@@ -0,0 +1,203 @@
1
+ /**
2
+ * The `filmic` output transform: a parametric film curve, its five numbers, and the eight
3
+ * constants the resolve reads, decided once here for both backends.
4
+ *
5
+ * **Why a second filmic curve beside `aces`.** `aces` is the reference RRT and ODT fit, which
6
+ * holds a mid grey of 0.18 at about 0.11 of display linear: graded for a dark cinema and kept dark.
7
+ * The curve here holds 0.18 at 0.18 by construction, and is shaped by five numbers a grading
8
+ * volume carries — a slope through mid grey, a toe and a shoulder, and how far black and white are
9
+ * clipped past the ends — so a look authored against such a curve is matched by passing its numbers
10
+ * rather than by baking the difference into a colour lookup table, which has the fewest steps
11
+ * exactly where two curves differ most: in the shadows.
12
+ *
13
+ * **What the curve is.** In the ACEScg working space, after the ACES reference's glow and red
14
+ * modifier and a 4% desaturation: a straight line of `slope` through mid grey in log10, joined to a
15
+ * logistic toe below `toeMatch` and a logistic shoulder above `shoulderMatch`, blended by a smooth
16
+ * step between the two, then desaturated by 7% and taken back to linear sRGB. The toe's position is
17
+ * solved so that 0.18 in is 0.18 out.
18
+ *
19
+ * **What it gives up.** It needs the composite to grade: `screenEffects` and `hdrScene`, since the
20
+ * forward passes that grade a frame without one carry two uniforms between them and not eight.
21
+ * Without them every pass grades with `aces` and the renderer says so once at construction.
22
+ * **What would make it wrong** is a profile wanting this curve without a scene target, which would
23
+ * then pay six floats in every forward program to have it.
24
+ */
25
+ /** The defaults the curve ships with where a volume states nothing. */
26
+ export const DEFAULT_FILMIC_CURVE = {
27
+ slope: 0.88,
28
+ toe: 0.55,
29
+ shoulder: 0.26,
30
+ blackClip: 0,
31
+ whiteClip: 0.04,
32
+ };
33
+ /** How many floats `resolveFilmicCurve` fills: two vectors of the resolve's uniforms. */
34
+ export const FILMIC_CONSTANTS = 8;
35
+ /** Mid grey, which the curve takes to itself. */
36
+ const MID_GREY = 0.18;
37
+ /** The largest toe or shoulder kept: at 1 the toe's or the shoulder's scale is zero. */
38
+ const MOST = 0.999;
39
+ /** The shallowest slope kept, which is still a curve rather than a flat line. */
40
+ const LEAST_SLOPE = 0.01;
41
+ /**
42
+ * The eight constants the resolve reads, into `out`: `uFilmA` = (slope, black clip, white clip,
43
+ * toe scale) and `uFilmB` = (shoulder scale, toe match, straight match, shoulder match). Returns
44
+ * whether any of the five numbers had to be brought into range.
45
+ *
46
+ * Here rather than in the shader, because they are one solve a curve and would be one per pixel
47
+ * there. **Clamped rather than refused**, because a grading volume is set from inside a frame and
48
+ * the frame loop never throws: a slope below 0.01, a toe, shoulder or clip outside 0 to 1, and a
49
+ * toe or shoulder of exactly 1 — whose scale is then zero and divides the curve by it — are each
50
+ * a picture made of NaN otherwise. A number that is not finite takes the default's. The renderer
51
+ * says once when anything was clamped.
52
+ */
53
+ export function resolveFilmicCurve(curve, out,
54
+ /**
55
+ * The display's peak over paper white (`displayHeadroom`), 1 for a standard range. Above 1 the
56
+ * white clip is raised by the difference, so the shoulder rolls off toward the display's peak
57
+ * rather than toward white, and the toe and mid grey stay exactly where they were.
58
+ */
59
+ headroom = 1) {
60
+ let clamped = false;
61
+ const within = (value, low, high, fallback) => {
62
+ const finite = Number.isFinite(value) ? value : fallback;
63
+ const kept = Math.min(Math.max(finite, low), high);
64
+ if (kept !== value)
65
+ clamped = true;
66
+ return kept;
67
+ };
68
+ const slope = within(curve.slope, LEAST_SLOPE, Number.MAX_VALUE, DEFAULT_FILMIC_CURVE.slope);
69
+ const toe = within(curve.toe, 0, MOST, DEFAULT_FILMIC_CURVE.toe);
70
+ const shoulder = within(curve.shoulder, 0, MOST, DEFAULT_FILMIC_CURVE.shoulder);
71
+ const blackClip = within(curve.blackClip, 0, 1, DEFAULT_FILMIC_CURVE.blackClip);
72
+ const whiteClip = within(curve.whiteClip, 0, 1, DEFAULT_FILMIC_CURVE.whiteClip) + Math.max(headroom, 1) - 1;
73
+ const toeScale = 1 + blackClip - toe;
74
+ const shoulderScale = 1 + whiteClip - shoulder;
75
+ let toeMatch;
76
+ if (toe > 0.8) {
77
+ /* Mid grey falls on the straight segment. */
78
+ toeMatch = (1 - toe - MID_GREY) / slope + Math.log10(MID_GREY);
79
+ }
80
+ else {
81
+ /* Mid grey falls on the toe: solve the toe's logistic for 0.18 in, 0.18 out. */
82
+ const bt = (MID_GREY + blackClip) / toeScale - 1;
83
+ toeMatch = Math.log10(MID_GREY) - 0.5 * Math.log((1 + bt) / (1 - bt)) * (toeScale / slope);
84
+ }
85
+ const straightMatch = (1 - toe) / slope - toeMatch;
86
+ const shoulderMatch = shoulder / slope - straightMatch;
87
+ out[0] = slope;
88
+ out[1] = blackClip;
89
+ out[2] = whiteClip;
90
+ out[3] = toeScale;
91
+ out[4] = shoulderScale;
92
+ out[5] = toeMatch;
93
+ out[6] = straightMatch;
94
+ out[7] = shoulderMatch;
95
+ return clamped;
96
+ }
97
+ /** Said once a renderer, the first time `setFilmicCurve` was handed a number out of range. */
98
+ export const FILMIC_CLAMPED = '[driftengine] setFilmicCurve was handed a number outside its range — a slope under 0.01, a ' +
99
+ 'toe, shoulder or clip outside 0 to 1, or one that is not a number — and drew with it clamped.';
100
+ /**
101
+ * Said once at construction where `filmic` was asked for and the frame has no composite that
102
+ * grades: the passes grade with `aces`, which is the nearest curve they carry.
103
+ */
104
+ export const FILMIC_WITHOUT_COMPOSITE = "[driftengine] outputTransform 'filmic' is applied by the composite, which grades only with " +
105
+ "`screenEffects` and `hdrScene` both on: without them every pass grades itself, with 'aces'.";
106
+ /**
107
+ * `filmicCurve(c)`: linear sRGB in, display-linear sRGB out, clamped to 0 to 1 — or, on a high
108
+ * range display, to the shoulder's own asymptote. Reads `uFilmA`, `uFilmB` and `uDisplayHeadroom`,
109
+ * which the including stage declares. The ACES helpers are the reference's own:
110
+ * saturation, `yc`, the glow's sigmoid, and hue in degrees.
111
+ */
112
+ export const FILMIC_GLSL = `
113
+ /* sRGB to ACEScg (through D60), ACEScg back, and ACEScg to and from ACES2065-1. Columns. */
114
+ const mat3 FILM_SRGB_TO_AP1 = mat3(
115
+ 0.61319, 0.07021, 0.02062,
116
+ 0.33951, 0.91634, 0.10957,
117
+ 0.04737, 0.01345, 0.86961
118
+ );
119
+ /* The inverse of the one above, computed rather than copied, so a round trip is the identity. */
120
+ const mat3 FILM_AP1_TO_SRGB = mat3(
121
+ 1.70480, -0.13027, -0.02401,
122
+ -0.62168, 1.14082, -0.12900,
123
+ -0.08325, -0.01055, 1.15324
124
+ );
125
+ const mat3 FILM_AP1_TO_AP0 = mat3(
126
+ 0.6954522414, 0.0447945634, -0.0055258826,
127
+ 0.1406786965, 0.8596711185, 0.0040252103,
128
+ 0.1638690622, 0.0955343182, 1.0015006723
129
+ );
130
+ const mat3 FILM_AP0_TO_AP1 = mat3(
131
+ 1.4514393161, -0.0765537734, 0.0083161484,
132
+ -0.2365107469, 1.1762296998, -0.0060324498,
133
+ -0.2149285693, -0.0996759264, 0.9977163014
134
+ );
135
+ const vec3 FILM_AP1_LUMA = vec3(0.2722287168, 0.6740817658, 0.0536895174);
136
+
137
+ float filmSaturation(vec3 c) {
138
+ float hi = max(c.r, max(c.g, c.b));
139
+ float lo = min(c.r, min(c.g, c.b));
140
+ return (max(hi, 1e-10) - max(lo, 1e-10)) / max(hi, 1e-2);
141
+ }
142
+
143
+ float filmYc(vec3 c) {
144
+ float chroma = sqrt(max(c.b * (c.b - c.g) + c.g * (c.g - c.r) + c.r * (c.r - c.b), 0.0));
145
+ return (c.b + c.g + c.r + 1.75 * chroma) / 3.0;
146
+ }
147
+
148
+ float filmSigmoid(float x) {
149
+ float t = max(1.0 - abs(x / 2.0), 0.0);
150
+ return (1.0 + sign(x) * (1.0 - t * t)) / 2.0;
151
+ }
152
+
153
+ float filmGlow(float yc, float gain, float mid) {
154
+ if (yc <= 2.0 / 3.0 * mid) return gain;
155
+ if (yc >= 2.0 * mid) return 0.0;
156
+ return gain * (mid / yc - 0.5);
157
+ }
158
+
159
+ /* Hue in degrees, -180 to 180 about red; 0 for a grey, which then takes no red modifier. */
160
+ float filmCentredHue(vec3 c) {
161
+ float y = sqrt(3.0) * (c.g - c.b);
162
+ float x = 2.0 * c.r - c.g - c.b;
163
+ if (abs(x) < 1e-10 && abs(y) < 1e-10) return 0.0;
164
+ return degrees(atan(y, x));
165
+ }
166
+
167
+ vec3 filmicCurve(vec3 linear) {
168
+ vec3 ap0 = FILM_AP1_TO_AP0 * (FILM_SRGB_TO_AP1 * linear);
169
+ /* The reference's glow, lifting dark saturated colours a little. */
170
+ float saturation = filmSaturation(ap0);
171
+ ap0 *= 1.0 + filmGlow(filmYc(ap0), 0.05 * filmSigmoid((saturation - 0.4) / 0.2), 0.08);
172
+ /* And its red modifier, pulling saturated reds toward a pivot so they do not run to orange. */
173
+ float weight = smoothstep(0.0, 1.0, 1.0 - abs(2.0 * filmCentredHue(ap0) / 135.0));
174
+ ap0.r += weight * weight * saturation * (0.03 - ap0.r) * (1.0 - 0.82);
175
+ vec3 work = max(FILM_AP0_TO_AP1 * ap0, vec3(0.0));
176
+ work = mix(vec3(dot(work, FILM_AP1_LUMA)), work, 0.96);
177
+
178
+ float slope = uFilmA.x;
179
+ float black = uFilmA.y;
180
+ float white = uFilmA.z;
181
+ float toeScale = uFilmA.w;
182
+ float shoulderScale = uFilmB.x;
183
+ float toeMatch = uFilmB.y;
184
+ float straightMatch = uFilmB.z;
185
+ float shoulderMatch = uFilmB.w;
186
+ /* log10, through log2: GLSL ES has no log10. */
187
+ vec3 logc = log2(max(work, vec3(1e-10))) * 0.30102999566;
188
+ vec3 straight = slope * (logc + straightMatch);
189
+ vec3 toeC = -black + (2.0 * toeScale) / (1.0 + exp((-2.0 * slope / toeScale) * (logc - toeMatch)));
190
+ vec3 shoulderC =
191
+ (1.0 + white) - (2.0 * shoulderScale) / (1.0 + exp((2.0 * slope / shoulderScale) * (logc - shoulderMatch)));
192
+ toeC = mix(straight, toeC, vec3(lessThan(logc, vec3(toeMatch))));
193
+ shoulderC = mix(straight, shoulderC, vec3(greaterThan(logc, vec3(shoulderMatch))));
194
+ vec3 t = clamp((logc - toeMatch) / (shoulderMatch - toeMatch), 0.0, 1.0);
195
+ if (shoulderMatch < toeMatch) t = 1.0 - t;
196
+ t = (3.0 - 2.0 * t) * t * t;
197
+ vec3 tone = mix(toeC, shoulderC, t);
198
+ tone = mix(vec3(dot(tone, FILM_AP1_LUMA)), tone, 0.93);
199
+ /* White is the ceiling on a standard display; on a high range one the shoulder's asymptote is. */
200
+ float ceiling = uDisplayHeadroom > 1.0 ? 1.0 + white : 1.0;
201
+ return clamp(FILM_AP1_TO_SRGB * max(tone, vec3(0.0)), 0.0, ceiling);
202
+ }
203
+ `;
@@ -36,7 +36,10 @@ export interface FogOptions {
36
36
  readonly underwaterFactor: number;
37
37
  /** `MEDIUM_FOG` integrates a medium; `LINEAR_FOG` ramps between near and far. */
38
38
  readonly mode: number;
39
- /** Linear mode only: where haze begins, and where it is total. */
39
+ /**
40
+ * Where haze begins: the ramp's start in linear mode, and in the medium the distance along the
41
+ * ray it is integrated from (`Atmosphere.fogStart`), 0 at the camera.
42
+ */
40
43
  readonly near: number;
41
44
  readonly far: number;
42
45
  }
@@ -62,13 +62,23 @@ export function mediumFog(fog, dist, pointY) {
62
62
  const wetLinear = fog.underwaterDensity * dist;
63
63
  return mix(ramp, 1 - Math.pow(2, -wetLinear * wetLinear * 1.442695), fog.underwaterFactor);
64
64
  }
65
+ /*
66
+ * **Where the medium begins along the ray**, `near` — the linear mode's slot, which this mode
67
+ * never read before `Atmosphere.fogStart`. The ray is integrated from that point on: the density
68
+ * there, carried over what is left of the ray. At a start of 0 every term below is the one it
69
+ * always was, so a world that sets none draws the same bits.
70
+ */
71
+ const start = Math.min(fog.near, dist);
72
+ const before = dist > 0 ? start / dist : 0;
65
73
  const t = (pointY - fog.eyeY) * fog.heightFalloff;
74
+ const rest = t * (1 - before);
66
75
  /*
67
76
  * `(1 - e^-t)/t`, which tends to 1 as the ray levels out. **Guarded rather than branched**: at
68
77
  * `t = 0` the quotient is 0/0, and 1e-4 of a scale height is 8 mm.
69
78
  */
70
- const denom = Math.abs(t) < 1e-4 ? 1e-4 : t;
71
- const air = 1 - Math.exp((-fog.density * dist * (1 - Math.exp(-denom))) / denom);
79
+ const denom = Math.abs(rest) < 1e-4 ? 1e-4 : rest;
80
+ const air = 1 -
81
+ Math.exp((-fog.density * Math.exp(-t * before) * (dist - start) * (1 - Math.exp(-denom))) / denom);
72
82
  if (fog.underwaterFactor <= 0)
73
83
  return air;
74
84
  /* Water is its own medium and keeps its own curve, so crossing the surface changes nothing
@@ -9,9 +9,9 @@
9
9
  * A mask is a `number` and not a bitset object. Thirty-one bits is more than the table needs,
10
10
  * a number compares and combines in one instruction, and the scheduler does this per node.
11
11
  */
12
- export declare const FRAME_RESOURCES: readonly ['sceneColor', 'sceneDepth', 'canvas', 'cascade0', 'cascade1', 'cascade2', 'pointShadows', 'probeCube', 'mirrorColor', 'mirrorDepth', 'bloomChain', 'aoTarget', 'depthSnapshot', 'inset'];
12
+ export declare const FRAME_RESOURCES: readonly ['sceneColor', 'sceneDepth', 'canvas', 'cascade0', 'cascade1', 'cascade2', 'pointShadows', 'probeCube', 'mirrorColor', 'mirrorDepth', 'bloomChain', 'aoTarget', 'depthSnapshot', 'inset', 'colorSnapshot'];
13
13
  export type FrameResource = (typeof FRAME_RESOURCES)[number];
14
- export declare const RESOURCE_COUNT: 14;
14
+ export declare const RESOURCE_COUNT: 15;
15
15
  /** The single bit that stands for one resource. */
16
16
  export declare function resourceBit(name: FrameResource): number;
17
17
  /** A mask of the named resources, for declaring what a node reads or writes. */
@@ -30,6 +30,12 @@ export const FRAME_RESOURCES = [
30
30
  'aoTarget',
31
31
  'depthSnapshot',
32
32
  'inset',
33
+ /*
34
+ * The frame's colour as a registered pass reads it: a copy taken at that pass's draw, so reading
35
+ * it constrains no attachment's store, as `depthSnapshot` does not. Last, so no bit before it
36
+ * moved when it arrived.
37
+ */
38
+ 'colorSnapshot',
33
39
  ];
34
40
  export const RESOURCE_COUNT = FRAME_RESOURCES.length;
35
41
  const BITS = new Map(FRAME_RESOURCES.map((name, i) => [name, 1 << i]));
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Reflections traced through the frame's own materials: `RenderQuality.screenSpaceReflections`.
3
+ *
4
+ * **Every opaque lit surface reflects the frame by its own material**, where `ReflectiveSurface`
5
+ * reflects it by a box's: the lit stage leaves, per pixel, how much of the environment it showed and
6
+ * at what roughness (`shaders/flat/reflectionSurface.ts`), and a ray marched against the frame's
7
+ * depth swaps that share for what it finds — so a glossy panel mirrors the figures on it and the
8
+ * matte panel beside it barely does, in one mesh with one material map. Past `maxRoughness` a
9
+ * surface keeps the probes' reflection alone, which is what a march cannot do better than: a rough
10
+ * lobe wants many rays, and one ray blurred is only a fair stand-in for a narrow one.
11
+ *
12
+ * **What a surface reflects is what the lit stage already reflected**: a metal by its metalness, a
13
+ * dielectric where `setSurfaceReflectivity` or its material asks for one. A surface that reflects
14
+ * no environment reflects no frame either, which is what keeps the two from disagreeing.
15
+ */
16
+ /** How the frame's materials reflect it. Every field optional; see `DEFAULT_FRAME_REFLECTIONS`. */
17
+ export interface FrameReflections {
18
+ /** The roughness past which a surface reflects the probes alone, faded over the last quarter. */
19
+ readonly maxRoughness?: number;
20
+ /** How far a ray travels before it gives up, in metres. */
21
+ readonly reachM?: number;
22
+ /** How deep a depth sample is believed to be, in metres. See `ScreenSpaceMarch.thicknessM`. */
23
+ readonly thicknessM?: number;
24
+ /** Samples along a ray, at most 32. */
25
+ readonly steps?: number;
26
+ /** How far a fully rough reflection is smeared, as a share of the frame's height. */
27
+ readonly blur?: number;
28
+ }
29
+ export type ResolvedFrameReflections = Required<FrameReflections>;
30
+ /** The march `ReflectiveSurface` defaults to, a ceiling at satin, and a smear of 3% of the frame. */
31
+ export declare const DEFAULT_FRAME_REFLECTIONS: ResolvedFrameReflections;
32
+ /**
33
+ * The option as the renderers read it: null where it is off, and every number held where the
34
+ * shader can use it — a ceiling above zero, a reach and a thickness above zero, a whole number of
35
+ * steps from 1 to 32, and a blur from none to a fifth of the frame. Clamped rather than refused,
36
+ * since a quality option is read at construction and a renderer that threw there would draw nothing.
37
+ */
38
+ export declare function resolveFrameReflections(asked: boolean | FrameReflections | undefined): ResolvedFrameReflections | null;
39
+ /** What a renderer says once where the frame cannot carry these reflections, and why. */
40
+ export declare function frameReflectionsRefused(reason: string): string;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Reflections traced through the frame's own materials: `RenderQuality.screenSpaceReflections`.
3
+ *
4
+ * **Every opaque lit surface reflects the frame by its own material**, where `ReflectiveSurface`
5
+ * reflects it by a box's: the lit stage leaves, per pixel, how much of the environment it showed and
6
+ * at what roughness (`shaders/flat/reflectionSurface.ts`), and a ray marched against the frame's
7
+ * depth swaps that share for what it finds — so a glossy panel mirrors the figures on it and the
8
+ * matte panel beside it barely does, in one mesh with one material map. Past `maxRoughness` a
9
+ * surface keeps the probes' reflection alone, which is what a march cannot do better than: a rough
10
+ * lobe wants many rays, and one ray blurred is only a fair stand-in for a narrow one.
11
+ *
12
+ * **What a surface reflects is what the lit stage already reflected**: a metal by its metalness, a
13
+ * dielectric where `setSurfaceReflectivity` or its material asks for one. A surface that reflects
14
+ * no environment reflects no frame either, which is what keeps the two from disagreeing.
15
+ */
16
+ /** The march `ReflectiveSurface` defaults to, a ceiling at satin, and a smear of 3% of the frame. */
17
+ export const DEFAULT_FRAME_REFLECTIONS = Object.freeze({
18
+ maxRoughness: 0.6,
19
+ reachM: 8,
20
+ thicknessM: 0.25,
21
+ steps: 24,
22
+ blur: 0.03,
23
+ });
24
+ const finite = (value, fallback) => value !== undefined && Number.isFinite(value) ? value : fallback;
25
+ /**
26
+ * The option as the renderers read it: null where it is off, and every number held where the
27
+ * shader can use it — a ceiling above zero, a reach and a thickness above zero, a whole number of
28
+ * steps from 1 to 32, and a blur from none to a fifth of the frame. Clamped rather than refused,
29
+ * since a quality option is read at construction and a renderer that threw there would draw nothing.
30
+ */
31
+ export function resolveFrameReflections(asked) {
32
+ if (asked === undefined || asked === false)
33
+ return null;
34
+ const given = asked === true ? {} : asked;
35
+ const d = DEFAULT_FRAME_REFLECTIONS;
36
+ return {
37
+ maxRoughness: Math.min(Math.max(finite(given.maxRoughness, d.maxRoughness), 0.01), 1),
38
+ reachM: Math.max(finite(given.reachM, d.reachM), 0.01),
39
+ thicknessM: Math.max(finite(given.thicknessM, d.thicknessM), 0.001),
40
+ steps: Math.min(Math.max(Math.round(finite(given.steps, d.steps)), 1), 32),
41
+ blur: Math.min(Math.max(finite(given.blur, d.blur), 0), 0.2),
42
+ };
43
+ }
44
+ /** What a renderer says once where the frame cannot carry these reflections, and why. */
45
+ export function frameReflectionsRefused(reason) {
46
+ return (`screenSpaceReflections is not drawn: ${reason}. Surfaces keep the probes' reflection; ` +
47
+ 'the trace needs `screenEffects` and `hdrScene`, and a frame of one sample.');
48
+ }
@@ -26,9 +26,10 @@ import type { PointLightSet } from '../lightBudget.ts';
26
26
  export declare const MAX_BOUNCE_LIGHTS = 32;
27
27
  /**
28
28
  * Floats a light takes: where it is and how far it reaches; its colour at its weight and the cosine
29
- * of its inner cone; the way it points and the cosine of its outer cone.
29
+ * of its inner cone; the way it points and the cosine of its outer cone; and its own falloff
30
+ * exponent, 0 for the frame's, in a fourth row of its own (`PointLightSource.falloffExponent`).
30
31
  */
31
- export declare const BOUNCE_LIGHT_FLOATS = 12;
32
+ export declare const BOUNCE_LIGHT_FLOATS = 16;
32
33
  export declare function createBounceLights(): Float32Array;
33
34
  /** Pack the lights of `lights` the bounce reads into `out`, and answer how many. */
34
35
  export declare function resolveBounceLights(lights: PointLightSet | null, out: Float32Array): number;
@@ -21,14 +21,15 @@
21
21
  * What it gives up against the frame: a photometric profile and a cookie, which the bounce reads as
22
22
  * a plain cone, and a light's specular, which a diffuse bounce has no use for.
23
23
  */
24
- import { POINT_LIGHT_COS_INNER, POINT_LIGHT_COS_OUTER } from '../clusteredLights.js';
24
+ import { POINT_LIGHT_COS_INNER, POINT_LIGHT_COS_OUTER, lightFalloffExponentOf, } from '../clusteredLights.js';
25
25
  /** Lights the probe bake shades a hit with. */
26
26
  export const MAX_BOUNCE_LIGHTS = 32;
27
27
  /**
28
28
  * Floats a light takes: where it is and how far it reaches; its colour at its weight and the cosine
29
- * of its inner cone; the way it points and the cosine of its outer cone.
29
+ * of its inner cone; the way it points and the cosine of its outer cone; and its own falloff
30
+ * exponent, 0 for the frame's, in a fourth row of its own (`PointLightSource.falloffExponent`).
30
31
  */
31
- export const BOUNCE_LIGHT_FLOATS = 12;
32
+ export const BOUNCE_LIGHT_FLOATS = 16;
32
33
  export function createBounceLights() {
33
34
  return new Float32Array(MAX_BOUNCE_LIGHTS * BOUNCE_LIGHT_FLOATS);
34
35
  }
@@ -60,6 +61,10 @@ export function resolveBounceLights(lights, out) {
60
61
  out[at + 9] = lights.lightDirections?.[i * 3 + 1] ?? 0;
61
62
  out[at + 10] = lights.lightDirections?.[i * 3 + 2] ?? 0;
62
63
  out[at + 11] = lights.lightConeCos?.[i * 2 + 1] ?? POINT_LIGHT_COS_OUTER;
64
+ out[at + 12] = lightFalloffExponentOf(lights.lightFalloffExponents?.[i]);
65
+ out[at + 13] = 0;
66
+ out[at + 14] = 0;
67
+ out[at + 15] = 0;
63
68
  count++;
64
69
  }
65
70
  return count;
@@ -51,6 +51,11 @@ export function cullInstances(data, local, frustum, out) {
51
51
  for (let k = 0; k < 4; k++)
52
52
  out.lightmapRegions[kept * 4 + k] = regions[i * 4 + k];
53
53
  }
54
+ const cells = data.uvRegions;
55
+ if (cells !== undefined && out.uvRegions !== undefined) {
56
+ for (let k = 0; k < 4; k++)
57
+ out.uvRegions[kept * 4 + k] = cells[i * 4 + k];
58
+ }
54
59
  kept++;
55
60
  }
56
61
  out.count = kept;
@@ -12,9 +12,9 @@
12
12
  *
13
13
  * **What it gives up** is a matrix per instance where a scatter spends five floats: 80 bytes
14
14
  * against 44, and no wind. **What would make it wrong** is a caller wanting per-instance
15
- * anything beyond an opacity — a morph weight, a texture cell — which wants another attribute,
16
- * and the sixteen WebGL2 guarantees are already spent. The opacity rides the float the stride was
17
- * padded with, which is why it costs nothing.
15
+ * anything beyond an opacity and a texture cell — a morph weight, say — which wants another
16
+ * attribute, and the sixteen WebGL2 guarantees are already spent. The opacity rides the float the
17
+ * stride was padded with and the cell the matrix's bottom row, which is why neither costs a byte.
18
18
  */
19
19
  export interface MeshInstances {
20
20
  /**
@@ -51,6 +51,20 @@ export interface MeshInstances {
51
51
  * either mismatch is said once on the console.
52
52
  */
53
53
  readonly lightmapRegions?: Float32Array;
54
+ /**
55
+ * Four floats each, the instance's cell of its texture: `[scaleU, scaleV, offsetU, offsetV]`,
56
+ * applied to the mesh's coordinates before the material's own scale and offset, in every pass
57
+ * that reads them — so one draw carries particles in different cells of a flipbook, or props
58
+ * wearing different tiles of one atlas. **Absent, or past `count`, is the whole texture**,
59
+ * `[1, 1, 0, 0]`, which is what every instance read before 4.8.7.
60
+ *
61
+ * **It rides the matrix's bottom row**, which an instance's placement — a turn, a scale and a
62
+ * move — leaves at `[0, 0, 0, 1]`, and which every stage drawing an instance rebuilds as that.
63
+ * So a placement must be affine, as every placement of a solid object is; a projective one is
64
+ * drawn as its affine part. A culled batch (`cullInstances`) carries the cells only into a
65
+ * target that has the array.
66
+ */
67
+ readonly uvRegions?: Float32Array;
54
68
  /** How many instances the arrays hold. Fixed at creation; the buffers are sized from it. */
55
69
  readonly capacity: number;
56
70
  /** How many are live. The rest of the buffer is neither uploaded nor drawn. */
@@ -65,8 +79,8 @@ export interface MeshInstances {
65
79
  */
66
80
  export declare function createMeshInstances(capacity: number): MeshInstances;
67
81
  /**
68
- * Floats one instance occupies in the interleaved vertex buffer: sixteen of matrix, three of
69
- * tint, and the opacity.
82
+ * Floats one instance occupies in the interleaved vertex buffer: sixteen of matrix, the bottom row
83
+ * of which carries the texture cell, three of tint, and the opacity.
70
84
  *
71
85
  * **Twenty so the stride is 80 bytes rather than 76.** A vertex buffer's stride must be a multiple
72
86
  * of four on both backends, which 76 already is — the twentieth float was padding for the
@@ -15,8 +15,8 @@ export function createMeshInstances(capacity) {
15
15
  };
16
16
  }
17
17
  /**
18
- * Floats one instance occupies in the interleaved vertex buffer: sixteen of matrix, three of
19
- * tint, and the opacity.
18
+ * Floats one instance occupies in the interleaved vertex buffer: sixteen of matrix, the bottom row
19
+ * of which carries the texture cell, three of tint, and the opacity.
20
20
  *
21
21
  * **Twenty so the stride is 80 bytes rather than 76.** A vertex buffer's stride must be a multiple
22
22
  * of four on both backends, which 76 already is — the twentieth float was padding for the
@@ -39,13 +39,19 @@ export const INSTANCE_STRIDE = INSTANCE_FLOATS * 4;
39
39
  * Writes nothing beyond `count`, and allocates nothing: `out` is the caller's staging array.
40
40
  */
41
41
  export function packInstances(instances, out) {
42
- const { models, tints, alphas, lightmapRegions } = instances;
42
+ const { models, tints, alphas, lightmapRegions, uvRegions } = instances;
43
43
  const count = Math.min(instances.count, instances.capacity);
44
44
  for (let i = 0; i < count; i += 1) {
45
45
  const at = i * INSTANCE_FLOATS;
46
46
  const m = i * 16;
47
47
  for (let c = 0; c < 16; c += 1)
48
48
  out[at + c] = models[m + c];
49
+ /* The texture cell in the bottom row an affine placement leaves free. See `uvRegions`. */
50
+ const cell = i * 4;
51
+ out[at + 3] = uvRegions?.[cell] ?? 1;
52
+ out[at + 7] = uvRegions?.[cell + 1] ?? 1;
53
+ out[at + 11] = uvRegions?.[cell + 2] ?? 0;
54
+ out[at + 15] = uvRegions?.[cell + 3] ?? 0;
49
55
  if (lightmapRegions !== undefined) {
50
56
  /* The region in the tint's and the opacity's place. See `lightmapRegions`. */
51
57
  for (let c = 0; c < 4; c += 1)
@@ -1,4 +1,3 @@
1
- /** Fixed point-light budget shared by CPU selection and the flat shader. */
2
1
  import type { AreaLightBuffer } from './areaLights.ts';
3
2
  import type { ResolvedAreaShadows } from './areaShadowSet.ts';
4
3
  /**
@@ -368,6 +367,12 @@ export interface PointLightSet {
368
367
  * character's own key and rim lights, which the floor around the character must not take.
369
368
  */
370
369
  readonly lightChannels?: Float32Array;
370
+ /**
371
+ * One per light: its own falloff exponent, `(1 - (d/R)^2)^n` with no distance term, or 0 for the
372
+ * frame's falloff. **Absent, or a short array, is 0 for every light**, so a scene that names none
373
+ * falls off as it did. See `PointLightSource.falloffExponent`.
374
+ */
375
+ readonly lightFalloffExponents?: Float32Array;
371
376
  }
372
377
  /**
373
378
  * A cone that admits every direction, for every light.
@@ -441,6 +446,12 @@ export interface ResolvedPointLights {
441
446
  * array of their own, which would cost a row a light of a budget that has none to give.
442
447
  */
443
448
  readonly axesAndChannels: Float32Array;
449
+ /**
450
+ * Owned, four a light: the cone's two cosines, then the light's own falloff exponent, as
451
+ * `uLightCone` carries them — a `vec2` array costs a whole row a light on WebGL2 anyway, so the
452
+ * exponent rides the half it left rather than a row of its own.
453
+ */
454
+ readonly conesAndFalloffs: Float32Array;
444
455
  /**
445
456
  * Owned, `MAX_POINT_LIGHTS` long: the emitter sizes and weights as the froxel record carries
446
457
  * them, at half precision. `sourceRadii` and `weights` point here once a light set resolves, so