@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
@@ -4,11 +4,17 @@ import { FOG_OFF, FOG_RECEDE, fogModeOf } from '../drawFog.js';
4
4
  import { blendCovering, blendKeeping } from '../../sceneCoverage.js';
5
5
  import { mat4 } from 'gl-matrix';
6
6
  import { GL_DEPTH_REMAP, MAX_DEPTH_LAYER, depthClearFor, depthOffsetForLayer, GL_SHADOW_REMAP, REVERSED_DEPTH, SHADOW_DEPTH_CLEAR, glDepthFuncEqual, glShadowDepthFunc, } from '../../depthConvention.js';
7
+ import { maskOf } from '../../frame/resources.js';
8
+ /** The two copies a registered pass may declare it reads. See `drawPass`. */
9
+ const COLOR_SNAPSHOT = maskOf('colorSnapshot');
10
+ const DEPTH_SNAPSHOT = maskOf('depthSnapshot');
7
11
  import { resolveGlass } from '../../glass.js';
8
12
  import { PickableSet } from '../../pickable.js';
9
13
  import { TEMPORAL_HISTORY_BLEND, TemporalHistory, jitterOffset, jitterProjection, } from '../../temporalAa.js';
10
14
  import { DecalPass } from '../../decalPass.js';
11
15
  import { SsrPass } from '../../ssrPass.js';
16
+ import { ReflectionSurfaceTargets } from '../../reflectionSurfaceTargets.js';
17
+ import { frameReflectionsRefused, resolveFrameReflections, } from '../../frameReflections.js';
12
18
  import { GlobalMediumPass } from '../../globalMediumPass.js';
13
19
  import { DEFAULT_GLOBAL_MEDIUM, WEAK_GPU_MEDIUM_STEPS, mediumActive, resolveGlobalMedium, } from '../../globalMedium.js';
14
20
  import { REFLECTION_EDGE_FADE, ReflectionQueue } from '../../screenSpaceReflection.js';
@@ -28,7 +34,8 @@ import { attachContextLoss } from '../../contextLoss.js';
28
34
  import { isWeakGpuFamily, webgl2RendererName } from '../../gpuCapability.js';
29
35
  import { GpuTimer } from '../../gpuTimer.js';
30
36
  import { compileProgram, uniformLocations } from '../../shader.js';
31
- import { FULL_LIGHT_BUDGET, nextLightBudget, planLightBudget, } from '../../uniformVectorBudget.js';
37
+ import { countUniformVectors, FULL_LIGHT_BUDGET, nextLightBudget, planLightBudget, } from '../../uniformVectorBudget.js';
38
+ import { OVERLAY_FLOATS, overlayLays, packSurfaceOverlay, } from '../../surfaceOverlay.js';
32
39
  import { FILM_FRAG, FILM_VERT } from '../../shaders/film.js';
33
40
  import { SceneTarget } from '../../sceneTarget.js';
34
41
  import { FILM_LOOK_WITHOUT_COMPOSITE } from '../../shaders/filmLook.js';
@@ -41,7 +48,7 @@ import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/un
41
48
  import { flatFrag, flatVert } from '../../shaders/flat/index.js';
42
49
  import { skinEight } from '../../shaders/skinning.js';
43
50
  import { MODEL_MAP_TEXTURE_UNIT, MORPH_DELTA_TEXTURE_UNIT, SKIN_PALETTE_TEXTURE_UNIT, } from '../../lightBudget.js';
44
- import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel } from '../../surfaceModel.js';
51
+ import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel, skinProfileDistance, } from '../../surfaceModel.js';
45
52
  import { lightmapRegionsBound, modelBound, skinHalfBound } from '../../shaders/flat/models.js';
46
53
  import { lightmapTexels, warnRegionsWithoutPage, warnUnpagedLightmap, withLightmapUvs, } from '../../lightmap.js';
47
54
  import { GlSkinScatter } from './skinScatter.js';
@@ -86,6 +93,8 @@ import { resolveRenderQuality } from '../../renderQuality.js';
86
93
  import { drawingBufferSize } from '../../drawingBuffer.js';
87
94
  import { Mesh, createMeshIncremental } from '../../mesh.js';
88
95
  import { SurfaceTexture } from '../../surfaceTexture.js';
96
+ import { sceneCaptureTexels } from '../../sceneCapture.js';
97
+ import { GlSceneCaptureTarget } from './sceneCaptureTarget.js';
89
98
  import { glCompressedFormats } from '../../glCompressed.js';
90
99
  import { PlumeRenderer } from '../../plumeRenderer.js';
91
100
  import { createScatterDeform, resolveScatterDeform } from '../../scatterDeform.js';
@@ -102,8 +111,9 @@ import { SCATTER_DEPTH_VERT, SCATTER_FRAG, SCATTER_VERT } from '../../shaders/sc
102
111
  import { FlockRenderer } from '../../flockRenderer.js';
103
112
  import { TextRenderer } from '../../textRenderer.js';
104
113
  import { SdfTextRenderer } from '../../sdfTextRenderer.js';
105
- import { MOTION_BLUR_MAX_UV, RUSH_REACH_UV } from '../../vertexDefaults.js';
106
- import { OUTPUT_TRANSFORM_CODE } from '../../vertexDefaults.js';
114
+ import { MOTION_BLUR_MAX_UV, OBJECT_BLUR_UNKEPT, RUSH_REACH_UV, clampMotionShare, } from '../../vertexDefaults.js';
115
+ import { OUTPUT_TRANSFORM_CODE, forwardTransformCode } from '../../vertexDefaults.js';
116
+ import { DEFAULT_FILMIC_CURVE, FILMIC_CLAMPED, FILMIC_CONSTANTS, FILMIC_WITHOUT_COMPOSITE, resolveFilmicCurve, } from '../../filmicCurve.js';
107
117
  import { deviceSnappedCellSize } from '../../textLayout.js';
108
118
  import { WindStreakRenderer } from '../../windStreakRenderer.js';
109
119
  import { InstancedBatch } from './instanced.js';
@@ -602,6 +612,10 @@ export class WebGL2Renderer {
602
612
  reflectionQueue = new ReflectionQueue();
603
613
  /** Built on the first frame that submits a surface, and never for a scene with none. */
604
614
  ssr = null;
615
+ /** The frame's materials' reflections, resolved; null where off. See `frameReflections.ts`. */
616
+ frameReflections;
617
+ /** Their two maps on the scene's framebuffer, null where they cannot be drawn. */
618
+ reflectionTargets;
605
619
  /** Said once where a reflection cannot be traced at all. */
606
620
  reflectionsRefused = false;
607
621
  /** Where the frame's camera stands, which is what gives a reconstructed normal its sign. */
@@ -672,6 +686,8 @@ export class WebGL2Renderer {
672
686
  * exactly as it asked to be and nothing about it changes.
673
687
  */
674
688
  exposure;
689
+ /** The `filmic` curve's eight constants, held until `setFilmicCurve` is called again. */
690
+ filmConstants = new Float32Array(FILMIC_CONSTANTS);
675
691
  /**
676
692
  * This frame's veil colour, and how much of it to composite. See `setFrameVeil`.
677
693
  *
@@ -873,8 +889,26 @@ export class WebGL2Renderer {
873
889
  warnedNoPointShadowArray = false;
874
890
  /** Said once, for the retired ambient dial. */
875
891
  warnedEnvironmentAmbient = false;
892
+ /** The overlay the draws wear, `uOverlay`: see `setSurfaceOverlay`. */
893
+ overlayFloats = new Float32Array(OVERLAY_FLOATS);
894
+ /** Its atlas, and the 2D copy of it bound where the refraction copy goes. */
895
+ overlayMaps = null;
896
+ overlayTexture = null;
897
+ /** Whether this device's lit stage has room for the overlay's vectors: asked once. */
898
+ overlayRoom = null;
899
+ warnedOverlayRoom = false;
900
+ warnedOverlayBlocks = false;
876
901
  /** Whether a probe is being baked, so the passes that would recurse into one can tell. */
877
902
  probePassActive = false;
903
+ /**
904
+ * Whether a scene capture is being drawn (`captureScene`): an off-screen pass like a bake, so the
905
+ * frame's own view, its refraction copy and skin's spread are left alone, but lit as the frame is.
906
+ */
907
+ capturePassActive = false;
908
+ /** Each capture's framebuffer, beside the texture a material binds. See `createSceneCapture`. */
909
+ captureTargets = new WeakMap();
910
+ /** Said once, where a driver refuses to draw into a capture. */
911
+ warnedCapture = false;
878
912
  /** Whether the bake under way lights its faces by the grid. `ProbeBakeOptions.bounce`. */
879
913
  probeBounce = false;
880
914
  /** The clip-control extension, or null where this context does not offer it. */
@@ -941,7 +975,8 @@ export class WebGL2Renderer {
941
975
  * the camera's matrix unchanged and multiplies nothing.
942
976
  */
943
977
  viewProjFor(camera) {
944
- const scene = this.probePassActive
978
+ /* A capture's rows are flipped as a probe face's are, so it reads as an image does. */
979
+ const scene = this.probePassActive || this.capturePassActive
945
980
  ? cubeFaceProjection(this.cubeFaceViewProj, this.sceneMatrix(camera.viewProjection))
946
981
  : this.sceneMatrix(camera.viewProjection);
947
982
  /*
@@ -1491,6 +1526,8 @@ export class WebGL2Renderer {
1491
1526
  * call. `init` runs now rather than at the first frame, because building a pipeline in the
1492
1527
  * frame loop is the allocation the house rules are about.
1493
1528
  */
1529
+ /** Each registered pass's declared reads, as a mask, resolved once at registration. */
1530
+ passReads = new Map();
1494
1531
  registerPass(definition) {
1495
1532
  const handle = registerIn(this.passes, definition);
1496
1533
  /*
@@ -1510,6 +1547,8 @@ export class WebGL2Renderer {
1510
1547
  };
1511
1548
  if (definition.prepare !== undefined)
1512
1549
  this.preparingPasses++;
1550
+ /* Resolved once: `maskOf` builds an array, and the frame path may not. */
1551
+ this.passReads.set(handle, maskOf(...(definition.reads ?? [])));
1513
1552
  definition.init?.(this.passDevice);
1514
1553
  return handle;
1515
1554
  }
@@ -1561,10 +1600,28 @@ export class WebGL2Renderer {
1561
1600
  gl: this.gl,
1562
1601
  outputTransform: 0,
1563
1602
  outputExposure: 1,
1603
+ sceneColor: null,
1604
+ sceneDepth: null,
1564
1605
  };
1565
1606
  const context = this.passContext;
1566
1607
  context.outputTransform = this.gradeCode();
1567
1608
  context.outputExposure = this.gradeExposure();
1609
+ /*
1610
+ * The copies a pass declared it reads, taken here with everything drawn before it: colour
1611
+ * afresh at each reading pass, as WebGPU's `takePassColour` does, so a full-screen effect drawn
1612
+ * last reads the whole frame; depth afresh too, since a blit is the whole cost of either here.
1613
+ * Not in a mirror, a probe's face or a capture, whose pictures are not the frame's.
1614
+ */
1615
+ const reads = this.passReads.get(handle) ?? 0;
1616
+ const own = !this.reflectionPassActive && !this.probePassActive && !this.capturePassActive;
1617
+ context.sceneColor =
1618
+ own && (reads & COLOR_SNAPSHOT) !== 0
1619
+ ? (this.sceneTarget?.snapshotColor(true) ?? null)
1620
+ : null;
1621
+ context.sceneDepth =
1622
+ own && (reads & DEPTH_SNAPSHOT) !== 0
1623
+ ? (this.sceneTarget?.snapshotDepth(true) ?? null)
1624
+ : null;
1568
1625
  definition.draw(context);
1569
1626
  }
1570
1627
  /** Let go of a pass. A handle kept past this draws nothing; see `PassHandle`. */
@@ -1572,6 +1629,7 @@ export class WebGL2Renderer {
1572
1629
  const definition = unregisterIn(this.passes, handle);
1573
1630
  if (definition?.prepare !== undefined)
1574
1631
  this.preparingPasses--;
1632
+ this.passReads.delete(handle);
1575
1633
  if (definition !== undefined && this.passDevice !== null)
1576
1634
  definition.dispose?.(this.passDevice);
1577
1635
  }
@@ -1722,10 +1780,10 @@ export class WebGL2Renderer {
1722
1780
  gl.bindTexture(gl.TEXTURE_2D, this.clusterTexture);
1723
1781
  gl.uniform1i(u['uClusterTable'] ?? null, CLUSTER_TABLE_TEXTURE_UNIT);
1724
1782
  gl.uniform1i(u['uClustered'] ?? null, on ? 1 : 0);
1725
- if (!on)
1726
- return;
1727
1783
  /*
1728
- * The frustum, read off the projection rather than taken as a parameter.
1784
+ * The frustum, read off the projection rather than taken as a parameter — **and with the view,
1785
+ * written whether or not the lights are clustered**: a surface overlay's rim finds its place on
1786
+ * the screen through the same two (`overlayScreen`).
1729
1787
  *
1730
1788
  * A perspective matrix carries `1 / tan(fovY/2)` at [5] and that over the aspect at [0], and
1731
1789
  * the near and far planes in [14] and [10]. Deriving them here means the binner and the shader
@@ -1743,6 +1801,8 @@ export class WebGL2Renderer {
1743
1801
  this.clusterFrustum[3] = aspect;
1744
1802
  gl.uniform4fv(u['uClusterFrustum'] ?? null, this.clusterFrustum);
1745
1803
  gl.uniformMatrix4fv(u['uView'] ?? null, false, camera.view);
1804
+ if (!on)
1805
+ return;
1746
1806
  this.clusterTable ??= createClusterTable();
1747
1807
  /* The rectangles past the fixed arm's go into the table. See `ClusterLightSet.areas`. */
1748
1808
  const lights = fillClusterLightSet(env, this.clusterLights, this.shadedAreaLights);
@@ -1816,6 +1876,11 @@ export class WebGL2Renderer {
1816
1876
  this.quality = resolveRenderQuality(qualityOptions);
1817
1877
  /* The grade this world asked for, until a frame says otherwise. See `setOutputExposure`. */
1818
1878
  this.exposure = this.quality.outputExposure;
1879
+ resolveFilmicCurve(DEFAULT_FILMIC_CURVE, this.filmConstants);
1880
+ if (this.quality.outputTransform === 'filmic' &&
1881
+ !(this.quality.screenEffects && this.quality.hdrScene)) {
1882
+ console.warn(FILMIC_WITHOUT_COMPOSITE);
1883
+ }
1819
1884
  /*
1820
1885
  * Multisampling, but only when anything can use it.
1821
1886
  *
@@ -2074,6 +2139,29 @@ export class WebGL2Renderer {
2074
2139
  this.sceneTarget = this.quality.screenEffects
2075
2140
  ? new SceneTarget(gl, this.quality.sceneSamples, this.quality.hdrScene, this.quality.discardResolvedAttachments)
2076
2141
  : null;
2142
+ /*
2143
+ * The frame's materials' reflections, where the frame can carry them: scene light to swap the
2144
+ * probe's share in, and one sample, which the trace's depth copy and both maps are. Said once
2145
+ * where it cannot, in the words the other backend uses. See `frameReflections.ts`.
2146
+ */
2147
+ this.frameReflections = resolveFrameReflections(this.quality.screenSpaceReflections);
2148
+ const reflectionsRefusal = this.frameReflections === null
2149
+ ? null
2150
+ : this.sceneTarget === null || !this.sceneTarget.keepsRange
2151
+ ? 'the frame keeps no scene light to swap the probe in (`screenEffects` and `hdrScene`)'
2152
+ : this.sceneTarget.sampleCount > 1
2153
+ ? 'a multisampled frame has no single depth or colour to march through'
2154
+ : null;
2155
+ if (reflectionsRefusal !== null) {
2156
+ console.warn(`WebGL2: ${frameReflectionsRefused(reflectionsRefusal)}`);
2157
+ }
2158
+ this.reflectionTargets =
2159
+ this.frameReflections !== null && reflectionsRefusal === null
2160
+ ? new ReflectionSurfaceTargets(gl)
2161
+ : null;
2162
+ /* The lit programs write the maps from the first frame they are rebuilt in. See `litOn`. */
2163
+ if (this.reflectionTargets !== null)
2164
+ this.askLit('REFLECTION_MAPS');
2077
2165
  /* Screen-space skin needs the composite to add into; without one it is pre-integrated, said once. */
2078
2166
  if (this.quality.skinScattering === 'screen-space' && this.sceneTarget === null) {
2079
2167
  console.warn("WebGL2: skinScattering 'screen-space' needs screenEffects to add into; skin is pre-integrated");
@@ -2545,6 +2633,76 @@ export class WebGL2Renderer {
2545
2633
  createLightmap(page) {
2546
2634
  return new SurfaceTexture(this.gl, lightmapTexels(page), { wrap: 'clamp', mipmap: false });
2547
2635
  }
2636
+ /**
2637
+ * A texture the scene can be drawn into, for a material to show. See `RendererApi` and
2638
+ * `sceneCapture.ts`. Half floats where the frame keeps range, as a probe does.
2639
+ */
2640
+ createSceneCapture(width, height) {
2641
+ const float = this.quality.hdrScene && this.gl.getExtension('EXT_color_buffer_float') !== null;
2642
+ const texture = new SurfaceTexture(this.gl, sceneCaptureTexels(width, height, float), {
2643
+ wrap: 'clamp',
2644
+ mipmap: false,
2645
+ });
2646
+ this.captureTargets.set(texture, new GlSceneCaptureTarget(this.gl, texture, width, height, this.sceneTarget?.sampleCount ?? 1, float));
2647
+ return texture;
2648
+ }
2649
+ /**
2650
+ * Draw the scene into a capture from `camera`: `draw` is handed the camera, its matrices updated
2651
+ * for the capture's shape, and submits the scene as it would to the screen. See `RendererApi`.
2652
+ *
2653
+ * **Radiance, as a probe bake stores it**: the output transform and exposure are held off for the
2654
+ * pass, so the frame grades a screen showing it once. No temporal jitter, which a capture has no
2655
+ * resolve to undo. Returns whether it drew: false for a texture that is not a capture, or one the
2656
+ * driver will not draw into.
2657
+ */
2658
+ captureScene(capture, camera, clearColor, draw) {
2659
+ if (this.contextLost)
2660
+ return false;
2661
+ const target = this.captureTargets.get(capture);
2662
+ if (target === undefined)
2663
+ return false;
2664
+ if (!target.usable) {
2665
+ if (!this.warnedCapture) {
2666
+ this.warnedCapture = true;
2667
+ console.warn('Renderer: this driver will not draw into a scene capture, so it keeps its clear. ' +
2668
+ 'Nothing else is affected.');
2669
+ }
2670
+ return false;
2671
+ }
2672
+ camera.updateMatrices(target.width / target.height);
2673
+ const { gl } = this;
2674
+ const jittering = this.temporalJittering;
2675
+ this.temporalJittering = false;
2676
+ this.capturePassActive = true;
2677
+ this.useFlatProgram();
2678
+ gl.uniform1i(this.flatUniforms['uOutputTransform'] ?? null, 0);
2679
+ gl.uniform1f(this.flatUniforms['uOutputExposure'] ?? null, 1);
2680
+ this.materials.dirty();
2681
+ target.begin(gl, clearColor, this.depthClear);
2682
+ /*
2683
+ * **Rows flipped, as a probe face's are.** A GL framebuffer's first row is its bottom and an
2684
+ * image's is its top, so a capture drawn upright would be shown upside down by every material
2685
+ * reading it as the image it is; `viewProjFor` negates clip y for the pass, and negating one axis
2686
+ * reverses every triangle's winding, which the front face follows. WebGPU's rows already run
2687
+ * the image's way.
2688
+ */
2689
+ gl.frontFace(gl.CW);
2690
+ try {
2691
+ draw(camera);
2692
+ }
2693
+ finally {
2694
+ target.end(gl);
2695
+ gl.frontFace(gl.CCW);
2696
+ this.useFlatProgram();
2697
+ gl.uniform1i(this.flatUniforms['uOutputTransform'] ?? null, this.gradeCode());
2698
+ gl.uniform1f(this.flatUniforms['uOutputExposure'] ?? null, this.gradeExposure());
2699
+ this.materials.dirty();
2700
+ this.capturePassActive = false;
2701
+ this.temporalJittering = jittering;
2702
+ this.restoreViewport();
2703
+ }
2704
+ return true;
2705
+ }
2548
2706
  /**
2549
2707
  * Replace a surface texture's pixels, keeping the GPU object and its sampler state.
2550
2708
  *
@@ -2561,11 +2719,18 @@ export class WebGL2Renderer {
2561
2719
  if (this.contextLost)
2562
2720
  return;
2563
2721
  texture.update(this.gl, source);
2722
+ /* New pixels are a new copy for the overlay's slot: see `SurfaceTexture.flatLayer`. */
2723
+ if (texture === this.overlayMaps)
2724
+ this.bindOverlayMaps(texture);
2564
2725
  }
2565
2726
  disposeSurfaceTexture(texture) {
2566
2727
  if (this.contextLost)
2567
2728
  return;
2729
+ if (texture === this.overlayMaps)
2730
+ this.bindOverlayMaps(null);
2568
2731
  texture.dispose(this.gl);
2732
+ this.captureTargets.get(texture)?.dispose(this.gl);
2733
+ this.captureTargets.delete(texture);
2569
2734
  }
2570
2735
  /**
2571
2736
  * Choose the surface texture the following `drawMesh` calls sample, or `null` for none.
@@ -2768,7 +2933,7 @@ export class WebGL2Renderer {
2768
2933
  const model = material?.model;
2769
2934
  warnUnpagedLightmap(model, material?.modelMap);
2770
2935
  if (model?.kind === 'skin' && this.skinScatter !== null) {
2771
- this.skinScatter.setProfile(model.profile, model.scatterColor[0] * model.radius, model.scatterColor[1] * model.radius, model.scatterColor[2] * model.radius);
2936
+ this.skinScatter.setProfile(model.profile, skinProfileDistance(model, 0), skinProfileDistance(model, 1), skinProfileDistance(model, 2));
2772
2937
  }
2773
2938
  /*
2774
2939
  * Written to every flat program, because material state persists across draws and a skinned
@@ -2892,8 +3057,13 @@ export class WebGL2Renderer {
2892
3057
  if (modelMap !== null)
2893
3058
  modelMap.bind(gl, MODEL_MAP_TEXTURE_UNIT);
2894
3059
  gl.uniform1i(u['uModelMap'] ?? null, MODEL_MAP_TEXTURE_UNIT);
2895
- packModel(material?.model ?? null, modelMap !== null, this.modelParams);
3060
+ packModel(material?.model ?? null, modelMap !== null, this.modelParams, material?.physicalSpecular === true);
3061
+ /* The first physical highlight asked for is when the lit programs start carrying one. */
3062
+ if (material?.physicalSpecular === true)
3063
+ this.askLit('PHYSICAL_SPECULAR');
2896
3064
  gl.uniform4fv(u['uModelParams'] ?? null, this.modelParams);
3065
+ /* The draw's overlay, which a program born mid-pass must wear too: see `setSurfaceOverlay`. */
3066
+ gl.uniform4fv(u['uOverlay'] ?? null, this.overlayFloats);
2897
3067
  /* The albedo's effects table, or the stand-in the shader reads as none. Before the early
2898
3068
  return, so a material with no albedo still leaves a complete texture on the unit. */
2899
3069
  if (albedo === null) {
@@ -2952,18 +3122,6 @@ export class WebGL2Renderer {
2952
3122
  gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
2953
3123
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
2954
3124
  }
2955
- /**
2956
- * A pass-level scale over the grain the geometry itself declared, 0 to 1.
2957
- *
2958
- * Which surfaces are mineral is now `MeshData.grain`, stated per vertex through
2959
- * `MeshBuilder.setGrain` — because it is a property of a material, and absent means none.
2960
- * This stays as the override for the case that has no answer to state: an imported model
2961
- * arrives with no grain information at all, and a caller drawing one decides for it.
2962
- *
2963
- * Pass state, like `setSurfaceTexture`, because a material covers many draws. The default
2964
- * is 1 and `bindMeshPass` restores it, so it scales geometry that declared grain and
2965
- * leaves geometry that did not exactly as it is.
2966
- */
2967
3125
  /**
2968
3126
  * The ambient the following draws take, as nine spherical-harmonic coefficients of incoming
2969
3127
  * radiance — red, green and blue of each, twenty-seven numbers — or `null` for the frame's own:
@@ -2975,16 +3133,6 @@ export class WebGL2Renderer {
2975
3133
  * only — a glossy surface still reflects the probes — and it is pass state like the material,
2976
3134
  * which `bindMeshPass` clears, so a mirror or a probe bake drawn after does not inherit it.
2977
3135
  */
2978
- /**
2979
- * How much of the environment the following draws mirror, 0 to 1.
2980
- *
2981
- * Pass state beside the grain, and the counterpart to it: grain is a surface being uneven,
2982
- * this is a surface being smooth enough to carry an image. Polished paint, glass, chrome
2983
- * and still water want it; plaster and stone want none.
2984
- *
2985
- * Defaults to zero and bindMeshPass restores it, so a scene that never calls this looks
2986
- * exactly as it did.
2987
- */
2988
3136
  setAmbientSH(coefficients) {
2989
3137
  if (this.contextLost)
2990
3138
  return;
@@ -2996,6 +3144,16 @@ export class WebGL2Renderer {
2996
3144
  }
2997
3145
  this.useFlatProgram();
2998
3146
  }
3147
+ /**
3148
+ * How much of the environment the following draws mirror, 0 to 1.
3149
+ *
3150
+ * Pass state beside the grain, and the counterpart to it: grain is a surface being uneven,
3151
+ * this is a surface being smooth enough to carry an image. Polished paint, glass, chrome
3152
+ * and still water want it; plaster and stone want none.
3153
+ *
3154
+ * Defaults to zero and bindMeshPass restores it, so a scene that never calls this looks
3155
+ * exactly as it did.
3156
+ */
2999
3157
  setSurfaceReflectivity(amount) {
3000
3158
  if (this.contextLost)
3001
3159
  return;
@@ -3003,6 +3161,67 @@ export class WebGL2Renderer {
3003
3161
  this.environmentDials[1] = Math.min(1, Math.max(0, amount));
3004
3162
  this.writeFlatVector('uEnvironmentDials', this.environmentDials);
3005
3163
  }
3164
+ /**
3165
+ * The overlay the following draws wear — a rim of light, a dissolve, wrinkles by region — or null
3166
+ * for none. Pass state, as the material is: `bindMeshPass` puts it back to none. See
3167
+ * `surfaceOverlay.ts` for what it lays and `shaders/flat/overlay.ts` for how.
3168
+ *
3169
+ * **Refused, said once, where the lit stage has no room for it.** Fifteen vectors more than the
3170
+ * program this device fits: counted from the source as `fitFlatProgram` counts, and on a device
3171
+ * whose limit is the 256 an Adreno 740 reports, the eight-light build at 254 has none. The draws
3172
+ * then wear nothing, which is the picture a game without the feature draws. Its atlas is bound as
3173
+ * a 2D copy of its first layer, in the refraction copy's unit (`SurfaceTexture.flatLayer`); a
3174
+ * compressed atlas has no copy and is read as none, also said once.
3175
+ */
3176
+ setSurfaceOverlay(overlay) {
3177
+ if (this.contextLost)
3178
+ return;
3179
+ let wanted = overlay;
3180
+ if (overlayLays(wanted) && !this.overlayFits()) {
3181
+ if (!this.warnedOverlayRoom) {
3182
+ this.warnedOverlayRoom = true;
3183
+ console.warn('Renderer: setSurfaceOverlay is refused on this device. The overlay takes fifteen ' +
3184
+ 'fragment uniform vectors and the lit shader already uses all but ' +
3185
+ `${this.overlayRoomLeft()} of the ${this.fragmentVectorLimit()} this GPU offers; ` +
3186
+ 'draws wear no overlay. A lower `maxLights` makes the room, or WebGPU has it.');
3187
+ }
3188
+ wanted = null;
3189
+ }
3190
+ packSurfaceOverlay(wanted, this.overlayFloats);
3191
+ if (overlayLays(wanted))
3192
+ this.askLit('SURFACE_OVERLAY');
3193
+ this.materials.dirty();
3194
+ this.writeFlatVector('uOverlay', this.overlayFloats);
3195
+ this.bindOverlayMaps((wanted?.maps ?? null));
3196
+ }
3197
+ /** The overlay's atlas into the refraction copy's unit, as a 2D copy, or the empty texture. */
3198
+ bindOverlayMaps(maps) {
3199
+ const { gl } = this;
3200
+ this.overlayMaps = maps;
3201
+ this.overlayTexture = maps === null ? null : maps.flatLayer(gl);
3202
+ if (maps !== null && this.overlayTexture === null && !this.warnedOverlayBlocks) {
3203
+ this.warnedOverlayBlocks = true;
3204
+ console.warn("Renderer: a surface overlay's maps are a compressed texture, which WebGL2 cannot copy " +
3205
+ 'into the 2D image the lit stage reads them from. The overlay is drawn without its ' +
3206
+ 'images; upload the atlas from an image instead.');
3207
+ }
3208
+ gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
3209
+ gl.bindTexture(gl.TEXTURE_2D, this.overlayTexture ?? this.emptyTexture2D);
3210
+ }
3211
+ /** The most fragment uniform vectors this GPU offers a stage. */
3212
+ fragmentVectorLimit() {
3213
+ return this.gl.getParameter(this.gl.MAX_FRAGMENT_UNIFORM_VECTORS);
3214
+ }
3215
+ /** What the lit shader at this renderer's budget leaves of the limit, with no overlay. */
3216
+ overlayRoomLeft() {
3217
+ return (this.fragmentVectorLimit() - countUniformVectors(this.flatFragAt(this.lightBudget, false)));
3218
+ }
3219
+ /** Whether the lit shader with the overlay compiled in fits this GPU: counted once. */
3220
+ overlayFits() {
3221
+ this.overlayRoom ??=
3222
+ countUniformVectors(this.flatFragAt(this.lightBudget, true)) <= this.fragmentVectorLimit();
3223
+ return this.overlayRoom;
3224
+ }
3006
3225
  /**
3007
3226
  * How bright the environment the surfaces drawn next reflect is. 1 is the default and identity.
3008
3227
  *
@@ -3043,6 +3262,18 @@ export class WebGL2Renderer {
3043
3262
  console.warn('Renderer: setEnvironmentAmbient no longer does anything. A baked probe now lights a ' +
3044
3263
  'surface through projected irradiance, and there is nothing left to tune.');
3045
3264
  }
3265
+ /**
3266
+ * A pass-level scale over the grain the geometry itself declared, 0 to 1.
3267
+ *
3268
+ * Which surfaces are mineral is now `MeshData.grain`, stated per vertex through
3269
+ * `MeshBuilder.setGrain` — because it is a property of a material, and absent means none.
3270
+ * This stays as the override for the case that has no answer to state: an imported model
3271
+ * arrives with no grain information at all, and a caller drawing one decides for it.
3272
+ *
3273
+ * Pass state, like `setSurfaceTexture`, because a material covers many draws. The default
3274
+ * is 1 and `bindMeshPass` restores it, so it scales geometry that declared grain and
3275
+ * leaves geometry that did not exactly as it is.
3276
+ */
3046
3277
  setSurfaceGrain(amount) {
3047
3278
  if (this.contextLost)
3048
3279
  return;
@@ -3409,10 +3640,15 @@ export class WebGL2Renderer {
3409
3640
  gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE);
3410
3641
  if (ditheredBlend)
3411
3642
  gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, true));
3643
+ const keeps = !blend && this.reflectionKeeps();
3644
+ if (keeps)
3645
+ this.reflectionTargets?.on();
3412
3646
  if (culling && cullsInstances(count, mesh.indexCount))
3413
3647
  batch.drawCulled(gl, data, this.frustum);
3414
3648
  else
3415
3649
  batch.draw(gl, count);
3650
+ if (keeps)
3651
+ this.reflectionTargets?.off();
3416
3652
  if (coverage)
3417
3653
  gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
3418
3654
  if (ditheredBlend)
@@ -3464,7 +3700,7 @@ export class WebGL2Renderer {
3464
3700
  const glassy = resolveGlass(options.glass, this.glassScratch);
3465
3701
  if (refraction <= 0 && !glassy)
3466
3702
  return false;
3467
- if (this.probePassActive || this.reflectionPassActive)
3703
+ if (this.probePassActive || this.reflectionPassActive || this.capturePassActive)
3468
3704
  return false;
3469
3705
  const snapshot = this.sceneTarget?.snapshotColor(false, glassy) ?? null;
3470
3706
  if (snapshot === null)
@@ -3483,7 +3719,8 @@ export class WebGL2Renderer {
3483
3719
  const { gl } = this;
3484
3720
  gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
3485
3721
  gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
3486
- gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
3722
+ /* The overlay's atlas, where one is held: it shares the unit. See `setSurfaceOverlay`. */
3723
+ gl.bindTexture(gl.TEXTURE_2D, this.overlayTexture ?? this.emptyTexture2D);
3487
3724
  }
3488
3725
  disposeMesh(mesh) {
3489
3726
  /*
@@ -3694,7 +3931,7 @@ export class WebGL2Renderer {
3694
3931
  */
3695
3932
  gradeCode() {
3696
3933
  return this.sceneTarget === null || !this.quality.hdrScene
3697
- ? OUTPUT_TRANSFORM_CODE[this.quality.outputTransform]
3934
+ ? forwardTransformCode(this.quality.outputTransform)
3698
3935
  : 0;
3699
3936
  }
3700
3937
  gradeExposure() {
@@ -3763,6 +4000,20 @@ export class WebGL2Renderer {
3763
4000
  createParticles(capacity, options) {
3764
4001
  return new ParticleBatch(this.gl, capacity, options);
3765
4002
  }
4003
+ /**
4004
+ * Particles a caller's own compute wrote: WebGPU's alone, since this backend has no compute stage
4005
+ * to have written them (`computeSupported`). Said once, and nothing is drawn.
4006
+ */
4007
+ drawDeviceParticles(_batch, _particles, _camera, _env, _timeSeconds) {
4008
+ if (this.warnedDeviceParticles)
4009
+ return;
4010
+ this.warnedDeviceParticles = true;
4011
+ console.warn('Renderer: drawDeviceParticles draws particles a compute shader wrote, and WebGL2 has no ' +
4012
+ 'compute stage, so nothing is drawn. Check `computeSupported`, and draw a pool with ' +
4013
+ '`drawParticles` where it is false.');
4014
+ }
4015
+ /** Said once: see `drawDeviceParticles`. */
4016
+ warnedDeviceParticles = false;
3766
4017
  /**
3767
4018
  * Draw a live particle pool.
3768
4019
  *
@@ -3820,7 +4071,10 @@ export class WebGL2Renderer {
3820
4071
  const u = batch.uniforms;
3821
4072
  const scene = this.sceneTarget;
3822
4073
  let depth = null;
3823
- if (batch.softDepth > 0 && !this.reflectionPassActive && !this.probePassActive) {
4074
+ if (batch.softDepth > 0 &&
4075
+ !this.reflectionPassActive &&
4076
+ !this.probePassActive &&
4077
+ !this.capturePassActive) {
3824
4078
  depth = scene?.snapshotDepth(true) ?? null;
3825
4079
  if (depth === null && !this.warnedSpriteDepth) {
3826
4080
  this.warnedSpriteDepth = true;
@@ -4680,7 +4934,7 @@ export class WebGL2Renderer {
4680
4934
  * throws a sentence with the device's numbers in it rather than a bare driver string.
4681
4935
  */
4682
4936
  /** The lit fragment source this renderer's profile compiles, at a light budget. */
4683
- flatFragAt(budget) {
4937
+ flatFragAt(budget, overlay = this.litOn.SURFACE_OVERLAY) {
4684
4938
  return flatFrag({
4685
4939
  pointShadows: this.quality.pointShadows,
4686
4940
  directionalShadows: this.quality.directionalShadows,
@@ -4692,6 +4946,9 @@ export class WebGL2Renderer {
4692
4946
  lightFixtures: this.litOn.LIGHT_FIXTURES,
4693
4947
  surfaceEffects: this.litOn.SURFACE_EFFECTS,
4694
4948
  driftLight: this.litOn.DRIFT_LIGHT,
4949
+ physicalSpecular: this.litOn.PHYSICAL_SPECULAR,
4950
+ surfaceOverlay: overlay,
4951
+ reflectionMaps: this.litOn.REFLECTION_MAPS,
4695
4952
  maxLights: budget.maxLights,
4696
4953
  maxAreaLights: budget.maxAreaLights,
4697
4954
  });
@@ -5363,10 +5620,20 @@ export class WebGL2Renderer {
5363
5620
  * Set before `beginFrame` and held until changed, like `setSpeedRush`, which it sits
5364
5621
  * beside for the same reason: both describe how the finished image is presented rather
5365
5622
  * than anything in the world.
5623
+ *
5624
+ * `maxShare` is the longest smear, as a share of the frame (0.03 unless given, at most half): a
5625
+ * fast turn softens the frame rather than erasing it. **Each drawn object's own motion is
5626
+ * followed only under a reconstruction on WebGPU**, which draws the motion target it reads; here,
5627
+ * and on WebGPU without one, the camera's motion alone smears the frame, and a draw that names
5628
+ * where it was last frame is told so once.
5366
5629
  */
5367
- setCameraMotionBlur(scale) {
5630
+ setCameraMotionBlur(scale, maxShare = MOTION_BLUR_MAX_UV) {
5368
5631
  this.motionBlurScale = Math.min(Math.max(scale, 0), 1);
5632
+ this.motionBlurMax = clampMotionShare(maxShare);
5369
5633
  }
5634
+ /** The longest smear, as a share of the frame. See `setCameraMotionBlur`. */
5635
+ motionBlurMax = MOTION_BLUR_MAX_UV;
5636
+ warnedObjectBlur = false;
5370
5637
  /**
5371
5638
  * The camera has cut: this frame is a new shot, not the next moment of the last one.
5372
5639
  *
@@ -5566,6 +5833,42 @@ export class WebGL2Renderer {
5566
5833
  ? Math.max(exposure, 1e-3)
5567
5834
  : this.quality.outputExposure;
5568
5835
  }
5836
+ /**
5837
+ * The `filmic` curve this and every later frame is graded through, until it is set again: a
5838
+ * grading volume's five numbers, solved once here into the constants the resolve reads. Ignored
5839
+ * unless `outputTransform` is `filmic`. A number out of range is clamped into it and said once,
5840
+ * since a frame loop must not throw. See `filmicCurve.ts`.
5841
+ */
5842
+ setFilmicCurve(curve) {
5843
+ if (resolveFilmicCurve(curve, this.filmConstants) && !this.warnedFilmClamped) {
5844
+ this.warnedFilmClamped = true;
5845
+ console.warn(FILMIC_CLAMPED);
5846
+ }
5847
+ }
5848
+ warnedFilmClamped = false;
5849
+ /**
5850
+ * The range the frame goes out in: `'high'` where the profile asked for `highDynamicRange`, the
5851
+ * frame holds light past white and the display shows it — on WebGPU — and `'standard'` otherwise.
5852
+ * **Always standard here**: this backend draws into a canvas of eight bits a channel, which has
5853
+ * nothing above white to show. Typed as the question rather than this backend's answer, because
5854
+ * the shared surface is derived from this class.
5855
+ */
5856
+ displayRange = 'standard';
5857
+ /** Why it is that range, in words, for a game choosing its look and for a bug report. */
5858
+ get displayRangeReason() {
5859
+ return this.quality.highDynamicRange
5860
+ ? 'WebGL2 draws into an eight-bit canvas, which has no range above white'
5861
+ : 'highDynamicRange was not asked for';
5862
+ }
5863
+ /**
5864
+ * How bright paper white and the display's peak are, in one unit, held until changed: their
5865
+ * ratio is how far above white the highlights may run where `displayRange` is `'high'`, and
5866
+ * nothing changes where it is standard, which is always here.
5867
+ */
5868
+ setDisplayLuminance(paperWhite, peak) {
5869
+ void paperWhite;
5870
+ void peak;
5871
+ }
5569
5872
  /**
5570
5873
  * Composite a flat colour over the finished frame — for a cut dipping to white or to black.
5571
5874
  *
@@ -5652,6 +5955,20 @@ export class WebGL2Renderer {
5652
5955
  }
5653
5956
  this.sceneTarget.setVignette(strength);
5654
5957
  }
5958
+ /**
5959
+ * The lens's colour fringe, held until changed: `intensity` is a percentage, 0 is none, and red
5960
+ * and green are pulled toward the centre by their wavelength's distance from blue, growing from
5961
+ * `start` — a share of the half-frame, 0 the centre — to the whole of it at the edge. Applied to
5962
+ * scene light at the composite's first read. Needs `screenEffects`, and says so once rather than
5963
+ * doing nothing. See `shaders/fringe.ts`.
5964
+ */
5965
+ setChromaticAberration(intensity, start = 0) {
5966
+ if (this.sceneTarget === null) {
5967
+ this.warnLookWithoutComposite(intensity);
5968
+ return;
5969
+ }
5970
+ this.sceneTarget.setChromaticAberration(intensity, start);
5971
+ }
5655
5972
  /**
5656
5973
  * Film grain, held until changed: an amplitude in display values (0 is none, 0.03 is a fine
5657
5974
  * grain) and this frame's seed. **The seed is the caller's**, because the engine takes time from
@@ -5832,6 +6149,8 @@ export class WebGL2Renderer {
5832
6149
  beginFrame(clearColor) {
5833
6150
  if (this.contextLost)
5834
6151
  return;
6152
+ /* One frame of the jitter's sequence however many times the frame binds its camera. */
6153
+ this.temporalHistory.nextFrame();
5835
6154
  this.skinScattered = false;
5836
6155
  this.skinScatter?.reset();
5837
6156
  if (this.litAsked) {
@@ -5898,6 +6217,8 @@ export class WebGL2Renderer {
5898
6217
  }
5899
6218
  }
5900
6219
  this.sceneTarget?.begin(this.canvas.width, this.canvas.height);
6220
+ /* The materials' maps on the scene's framebuffer, cleared and disabled: `reflectionKeeps`. */
6221
+ this.reflectionTargets?.begin(this.canvas.width, this.canvas.height);
5901
6222
  gl.clearColor(clearColor[0], clearColor[1], clearColor[2], 1);
5902
6223
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
5903
6224
  }
@@ -5947,7 +6268,7 @@ export class WebGL2Renderer {
5947
6268
  motion = {
5948
6269
  reprojection: this.reprojection,
5949
6270
  strength: blur,
5950
- max: MOTION_BLUR_MAX_UV,
6271
+ max: this.motionBlurMax,
5951
6272
  };
5952
6273
  }
5953
6274
  }
@@ -6027,7 +6348,11 @@ export class WebGL2Renderer {
6027
6348
  : undefined;
6028
6349
  /* The grade travels to the resolve only where the mesh pass gave it up. See `bindMeshPass`. */
6029
6350
  const lateGrade = this.quality.hdrScene
6030
- ? { transform: OUTPUT_TRANSFORM_CODE[this.quality.outputTransform], exposure: this.exposure }
6351
+ ? {
6352
+ transform: OUTPUT_TRANSFORM_CODE[this.quality.outputTransform] ?? 0,
6353
+ exposure: this.exposure,
6354
+ film: this.filmConstants,
6355
+ }
6031
6356
  : { transform: 0, exposure: 1 };
6032
6357
  /*
6033
6358
  * Depth of field, on the same ceiling-and-dial split as bloom above: `depthOfField` decides
@@ -6089,6 +6414,30 @@ export class WebGL2Renderer {
6089
6414
  * drawn over a reflective floor rather than under it, and before the post chain so bloom and
6090
6415
  * the grade see a reflection exactly as they would see any other part of the picture.
6091
6416
  */
6417
+ /*
6418
+ * **The frame's materials' reflections first**, on the same terms: every pixel the opaque lit
6419
+ * draws left a reflection for, marched and swapped for the probe's share. The maps leave the
6420
+ * scene's framebuffer before the resolve draws into it while sampling them.
6421
+ */
6422
+ const reflectionTargets = this.reflectionTargets;
6423
+ const probeMap = reflectionTargets?.probeTexture ?? null;
6424
+ const tintMap = reflectionTargets?.tintTexture ?? null;
6425
+ if (reflectionTargets !== null &&
6426
+ this.frameReflections !== null &&
6427
+ probeMap !== null &&
6428
+ tintMap !== null &&
6429
+ this.frameViewProj !== null &&
6430
+ this.sceneTarget !== null) {
6431
+ const scene = this.sceneTarget.colorAttachment();
6432
+ const depth = this.sceneTarget.snapshotDepth(true);
6433
+ if (scene !== null && depth !== null) {
6434
+ this.ssr ??= new SsrPass(this.gl, this.sceneTarget.keepsRange);
6435
+ this.ssr.traceMaterials(scene, depth, probeMap, tintMap, this.frameViewProj, this.frameEye, this.canvas.width, this.canvas.height, REFLECTION_EDGE_FADE, this.frameReflections);
6436
+ this.sceneTarget.bind();
6437
+ reflectionTargets.detach();
6438
+ this.ssr.resolveMaterials(probeMap, tintMap, this.canvas.width, this.canvas.height, this.frameReflections);
6439
+ }
6440
+ }
6092
6441
  if (this.reflectionQueue.length > 0 && this.frameViewProj !== null) {
6093
6442
  const scene = this.sceneTarget?.colorAttachment() ?? null;
6094
6443
  const depth = this.sceneTarget?.snapshotDepth(true) ?? null;
@@ -6328,7 +6677,10 @@ export class WebGL2Renderer {
6328
6677
  */
6329
6678
  if (this.framePresented)
6330
6679
  this.temporalJittering = false;
6331
- if (!this.reflectionPassActive && !this.probePassActive && !this.framePresented) {
6680
+ if (!this.reflectionPassActive &&
6681
+ !this.probePassActive &&
6682
+ !this.capturePassActive &&
6683
+ !this.framePresented) {
6332
6684
  this.frameViewProj = camera.viewProjection;
6333
6685
  /* Copied rather than referenced: a caller moves its camera in place, and the decal pass
6334
6686
  reads this at the end of the frame. */
@@ -6423,6 +6775,10 @@ export class WebGL2Renderer {
6423
6775
  this.materialHasMaps = false;
6424
6776
  this.currentMaterial = null;
6425
6777
  this.currentSurfaceTexture = null;
6778
+ /* Nor an overlay: the atlas's unit is given the empty texture with the refraction copy's. */
6779
+ this.overlayFloats.fill(0);
6780
+ this.overlayMaps = null;
6781
+ this.overlayTexture = null;
6426
6782
  /* Nor an ambient of its own: the frame's, until a draw asks. See `setAmbientSH`. */
6427
6783
  this.ambientSH.fill(0);
6428
6784
  /* No reflection and an environment at its own brightness; the emission the world asks for. */
@@ -6548,8 +6904,10 @@ export class WebGL2Renderer {
6548
6904
  * unless a draw sets the strength above zero.
6549
6905
  */
6550
6906
  gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
6551
- gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
6907
+ gl.bindTexture(gl.TEXTURE_2D, this.overlayTexture ?? this.emptyTexture2D);
6552
6908
  gl.uniform1i(u['uRefractScene'] ?? null, REFRACT_SCENE_TEXTURE_UNIT);
6909
+ /* The draws' overlay, none at a pass's start, and its atlas above: see `setSurfaceOverlay`. */
6910
+ gl.uniform4fv(u['uOverlay'] ?? null, this.overlayFloats);
6553
6911
  /* Neither refracting nor glass, which is every draw that does not say otherwise. See
6554
6912
  `bindSeeThrough` for the four numbers in the one vector. */
6555
6913
  gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
@@ -6928,9 +7286,16 @@ export class WebGL2Renderer {
6928
7286
  * here, so there is no motion target for this to be written into, and a mover passed here is
6929
7287
  * neither read nor advanced.
6930
7288
  */
6931
- _previousModel = null) {
7289
+ previousModel = null) {
6932
7290
  if (this.contextLost)
6933
7291
  return;
7292
+ /* A mover's own motion is not blurred here: see `setCameraMotionBlur`. Said once. */
7293
+ if (previousModel !== null &&
7294
+ !this.warnedObjectBlur &&
7295
+ this.quality.cameraMotionBlur * this.motionBlurScale > 0) {
7296
+ this.warnedObjectBlur = true;
7297
+ console.warn(OBJECT_BLUR_UNKEPT);
7298
+ }
6934
7299
  /* Geometry that has not all arrived is not drawn, nor a mesh with none. See `Mesh.complete`
6935
7300
  and `Mesh.indexCount`. */
6936
7301
  if (!mesh.complete || mesh.indexCount === 0)
@@ -6963,7 +7328,13 @@ export class WebGL2Renderer {
6963
7328
  * light and its colour, each into the blur's own target.
6964
7329
  */
6965
7330
  const splits = this.skinSplits();
7331
+ /* An opaque lit draw into the frame writes its material's reflection too: see `reflectionKeeps`. */
7332
+ const keeps = this.reflectionKeeps();
7333
+ if (keeps)
7334
+ this.reflectionTargets?.on();
6966
7335
  this.drawMeshHalf(mesh, model, depthLayer, tint, splits ? 'scene' : 'whole');
7336
+ if (keeps)
7337
+ this.reflectionTargets?.off();
6967
7338
  if (splits && this.skinScatter !== null) {
6968
7339
  this.skinScatter.begin();
6969
7340
  this.drawMeshHalf(mesh, model, depthLayer, tint, 'diffuse');
@@ -6977,6 +7348,18 @@ export class WebGL2Renderer {
6977
7348
  * skin yet, this is the frame's own picture — not a mirror's or a probe's, nor an overlay after
6978
7349
  * the frame — and the blur's target can be had.
6979
7350
  */
7351
+ /**
7352
+ * Whether a draw now is the frame's own, opaque and lit, and so writes the two maps beside its
7353
+ * colour: not a mirror's, a probe's or a capture's, whose framebuffers have no maps, and not after
7354
+ * the frame. See `reflectionSurfaceTargets.ts`.
7355
+ */
7356
+ reflectionKeeps() {
7357
+ return (this.reflectionTargets !== null &&
7358
+ !this.reflectionPassActive &&
7359
+ !this.probePassActive &&
7360
+ !this.capturePassActive &&
7361
+ !this.framePresented);
7362
+ }
6980
7363
  skinSplits() {
6981
7364
  const skin = this.skinScatter;
6982
7365
  const scene = this.sceneTarget;
@@ -6986,6 +7369,7 @@ export class WebGL2Renderer {
6986
7369
  !this.skinScattered &&
6987
7370
  !this.reflectionPassActive &&
6988
7371
  !this.probePassActive &&
7372
+ !this.capturePassActive &&
6989
7373
  !this.framePresented &&
6990
7374
  skin.ready(scene));
6991
7375
  }
@@ -7002,11 +7386,19 @@ export class WebGL2Renderer {
7002
7386
  const projection = this.frameProjection;
7003
7387
  if (skin === null || scene === null || this.skinScattered)
7004
7388
  return;
7389
+ /*
7390
+ * **Not the frame's picture, so not the frame's moment**: a blended draw in a mirror, a probe
7391
+ * bake or a scene capture lands over none of the frame's skin. Latching there switched the
7392
+ * spread off for the frame, so a capture with a glowing sign in it left every face drawn after
7393
+ * it flat. `scripts/capture-check.mjs`.
7394
+ */
7395
+ if (this.reflectionPassActive || this.probePassActive || this.capturePassActive)
7396
+ return;
7005
7397
  /* Latched whether or not skin was drawn: a skin drawn after this point is the whole surface. */
7006
7398
  this.skinScattered = true;
7007
7399
  if (!skin.pending)
7008
7400
  return;
7009
- if (this.reflectionPassActive || this.probePassActive || projection === null)
7401
+ if (projection === null)
7010
7402
  return;
7011
7403
  const depth = scene.snapshotDepth(true);
7012
7404
  mat4.invert(this.skinInverseProjection, projection);