@driftengine/core 4.8.7 → 4.10.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 (263) hide show
  1. package/CHANGELOG.json +123 -11
  2. package/README.md +43 -38
  3. package/dist/index.d.ts +8 -1
  4. package/dist/index.js +11 -3
  5. package/dist/render/ambientHarmonics.d.ts +1 -1
  6. package/dist/render/ambientHarmonics.js +1 -1
  7. package/dist/render/areaLights.d.ts +4 -4
  8. package/dist/render/areaLights.js +1 -1
  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 +19 -4
  12. package/dist/render/backend/createRenderer.d.ts +6 -0
  13. package/dist/render/backend/createRenderer.js +4 -1
  14. package/dist/render/backend/select.js +11 -5
  15. package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
  16. package/dist/render/backend/webgl2/boneAnimations.js +80 -0
  17. package/dist/render/backend/webgl2/instanced.d.ts +5 -1
  18. package/dist/render/backend/webgl2/instanced.js +13 -1
  19. package/dist/render/backend/webgl2/renderer.d.ts +180 -4
  20. package/dist/render/backend/webgl2/renderer.js +550 -40
  21. package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
  22. package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
  23. package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
  24. package/dist/render/backend/webgpu/boneAnimations.js +76 -0
  25. package/dist/render/backend/webgpu/buffers.d.ts +19 -4
  26. package/dist/render/backend/webgpu/buffers.js +18 -11
  27. package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
  28. package/dist/render/backend/webgpu/compressedUpload.js +53 -8
  29. package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
  30. package/dist/render/backend/webgpu/depthPass.js +49 -14
  31. package/dist/render/backend/webgpu/device.d.ts +7 -1
  32. package/dist/render/backend/webgpu/device.js +31 -3
  33. package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
  34. package/dist/render/backend/webgpu/drawCommand.js +10 -3
  35. package/dist/render/backend/webgpu/flatPass.d.ts +100 -42
  36. package/dist/render/backend/webgpu/flatPass.js +191 -43
  37. package/dist/render/backend/webgpu/instanced.d.ts +14 -1
  38. package/dist/render/backend/webgpu/instanced.js +24 -1
  39. package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
  40. package/dist/render/backend/webgpu/materialStore.js +265 -0
  41. package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
  42. package/dist/render/backend/webgpu/pipelineCache.js +7 -0
  43. package/dist/render/backend/webgpu/postPass.d.ts +51 -9
  44. package/dist/render/backend/webgpu/postPass.js +16 -2
  45. package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
  46. package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
  47. package/dist/render/backend/webgpu/renderer.d.ts +283 -37
  48. package/dist/render/backend/webgpu/renderer.js +1363 -202
  49. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
  50. package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
  51. package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
  52. package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
  53. package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
  54. package/dist/render/backend/webgpu/staticBundles.js +241 -0
  55. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
  56. package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
  57. package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
  58. package/dist/render/backend/webgpu/uniformRing.js +9 -1
  59. package/dist/render/bloomChain.d.ts +3 -3
  60. package/dist/render/boneAnimation.d.ts +92 -0
  61. package/dist/render/boneAnimation.js +133 -0
  62. package/dist/render/compressedSource.d.ts +33 -13
  63. package/dist/render/compressedSource.js +70 -25
  64. package/dist/render/displayRange.d.ts +31 -0
  65. package/dist/render/displayRange.js +47 -0
  66. package/dist/render/filmicCurve.d.ts +74 -0
  67. package/dist/render/filmicCurve.js +203 -0
  68. package/dist/render/fog.d.ts +4 -1
  69. package/dist/render/fog.js +12 -2
  70. package/dist/render/frame/resources.d.ts +2 -2
  71. package/dist/render/frame/resources.js +6 -0
  72. package/dist/render/frameReflections.d.ts +40 -0
  73. package/dist/render/frameReflections.js +48 -0
  74. package/dist/render/glCompressed.js +83 -0
  75. package/dist/render/instances.d.ts +26 -0
  76. package/dist/render/lightBudget.d.ts +10 -0
  77. package/dist/render/lightBudget.js +10 -0
  78. package/dist/render/lightmap.d.ts +1 -1
  79. package/dist/render/mesh.d.ts +5 -0
  80. package/dist/render/mesh.js +6 -0
  81. package/dist/render/pass.d.ts +49 -0
  82. package/dist/render/pointLightSelection.d.ts +4 -4
  83. package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
  84. package/dist/render/reflectionSurfaceTargets.js +99 -0
  85. package/dist/render/renderQuality.d.ts +39 -8
  86. package/dist/render/renderQuality.js +4 -0
  87. package/dist/render/sceneTarget.d.ts +5 -0
  88. package/dist/render/sceneTarget.js +24 -1
  89. package/dist/render/screenSpaceReflection.d.ts +7 -1
  90. package/dist/render/screenSpaceReflection.js +5 -0
  91. package/dist/render/shaders/arcane.d.ts +1 -1
  92. package/dist/render/shaders/bolt.d.ts +1 -1
  93. package/dist/render/shaders/boneAnimation.d.ts +19 -0
  94. package/dist/render/shaders/boneAnimation.js +63 -0
  95. package/dist/render/shaders/caustics.d.ts +1 -1
  96. package/dist/render/shaders/depth.d.ts +7 -0
  97. package/dist/render/shaders/depth.js +35 -10
  98. package/dist/render/shaders/film.d.ts +1 -1
  99. package/dist/render/shaders/filmLook.js +2 -1
  100. package/dist/render/shaders/fire.d.ts +1 -1
  101. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  102. package/dist/render/shaders/flat/glassTint.js +2 -0
  103. package/dist/render/shaders/flat/index.d.ts +16 -0
  104. package/dist/render/shaders/flat/index.js +33 -5
  105. package/dist/render/shaders/flat/main.d.ts +1 -1
  106. package/dist/render/shaders/flat/main.js +43 -5
  107. package/dist/render/shaders/flat/models.js +1 -1
  108. package/dist/render/shaders/flat/overlay.d.ts +16 -0
  109. package/dist/render/shaders/flat/overlay.js +117 -0
  110. package/dist/render/shaders/flat/pointShadow.js +3 -3
  111. package/dist/render/shaders/flat/preamble.js +38 -24
  112. package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
  113. package/dist/render/shaders/flat/reflectionSurface.js +61 -0
  114. package/dist/render/shaders/fog.d.ts +1 -1
  115. package/dist/render/shaders/fog.js +9 -2
  116. package/dist/render/shaders/fringe.d.ts +29 -0
  117. package/dist/render/shaders/fringe.js +55 -0
  118. package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
  119. package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
  120. package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
  121. package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
  122. package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
  123. package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
  124. package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
  125. package/dist/render/shaders/generated/depth.wgsl.js +93 -0
  126. package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
  127. package/dist/render/shaders/generated/film.wgsl.js +1 -1
  128. package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
  129. package/dist/render/shaders/generated/fire.wgsl.js +1 -1
  130. package/dist/render/shaders/generated/flat.wgsl.d.ts +3254 -2920
  131. package/dist/render/shaders/generated/flat.wgsl.js +3292 -2957
  132. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  133. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  134. package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
  135. package/dist/render/shaders/generated/particle.wgsl.js +4 -4
  136. package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
  137. package/dist/render/shaders/generated/rush.wgsl.js +56 -26
  138. package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
  139. package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
  140. package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
  141. package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
  142. package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
  143. package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
  144. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +22 -2
  145. package/dist/render/shaders/generated/ssrTrace.wgsl.js +22 -2
  146. package/dist/render/shaders/generated/water.wgsl.d.ts +1 -1
  147. package/dist/render/shaders/generated/water.wgsl.js +1 -1
  148. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
  149. package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
  150. package/dist/render/shaders/line.d.ts +1 -1
  151. package/dist/render/shaders/outputTransform.d.ts +7 -0
  152. package/dist/render/shaders/outputTransform.js +28 -14
  153. package/dist/render/shaders/particle.d.ts +3 -3
  154. package/dist/render/shaders/rush.js +54 -11
  155. package/dist/render/shaders/scatter.d.ts +1 -1
  156. package/dist/render/shaders/smoke.d.ts +1 -1
  157. package/dist/render/shaders/ssrResolve.d.ts +17 -0
  158. package/dist/render/shaders/ssrResolve.js +51 -0
  159. package/dist/render/shaders/ssrTrace.js +26 -0
  160. package/dist/render/shaders/vertexChannel.d.ts +7 -0
  161. package/dist/render/shaders/vertexChannel.js +26 -0
  162. package/dist/render/shaders/water.d.ts +1 -1
  163. package/dist/render/ssrPass.d.ts +16 -0
  164. package/dist/render/ssrPass.js +106 -1
  165. package/dist/render/staticDraws.d.ts +47 -0
  166. package/dist/render/staticDraws.js +43 -0
  167. package/dist/render/surfaceOverlay.d.ts +132 -0
  168. package/dist/render/surfaceOverlay.js +85 -0
  169. package/dist/render/surfaceTexture.d.ts +57 -7
  170. package/dist/render/surfaceTexture.js +125 -17
  171. package/dist/render/temporalAa.d.ts +14 -0
  172. package/dist/render/temporalAa.js +20 -0
  173. package/dist/render/vertexDefaults.d.ts +17 -0
  174. package/dist/render/vertexDefaults.js +24 -0
  175. package/package.json +6 -6
  176. package/src/index.ts +24 -2
  177. package/src/render/ambientHarmonics.ts +1 -1
  178. package/src/render/areaLights.ts +4 -4
  179. package/src/render/atmosphere.ts +22 -1
  180. package/src/render/backend/api.ts +22 -3
  181. package/src/render/backend/createRenderer.ts +18 -1
  182. package/src/render/backend/select.ts +10 -5
  183. package/src/render/backend/webgl2/boneAnimations.ts +105 -0
  184. package/src/render/backend/webgl2/instanced.ts +20 -1
  185. package/src/render/backend/webgl2/renderer.ts +672 -37
  186. package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
  187. package/src/render/backend/webgpu/boneAnimations.ts +97 -0
  188. package/src/render/backend/webgpu/buffers.ts +42 -16
  189. package/src/render/backend/webgpu/compressedUpload.ts +59 -9
  190. package/src/render/backend/webgpu/depthPass.ts +51 -10
  191. package/src/render/backend/webgpu/device.ts +42 -3
  192. package/src/render/backend/webgpu/drawCommand.ts +22 -4
  193. package/src/render/backend/webgpu/flatPass.ts +250 -40
  194. package/src/render/backend/webgpu/instanced.ts +32 -1
  195. package/src/render/backend/webgpu/materialStore.ts +276 -0
  196. package/src/render/backend/webgpu/pipelineCache.ts +7 -0
  197. package/src/render/backend/webgpu/postPass.ts +18 -0
  198. package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
  199. package/src/render/backend/webgpu/renderer.ts +1655 -230
  200. package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
  201. package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
  202. package/src/render/backend/webgpu/staticBundles.ts +363 -0
  203. package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
  204. package/src/render/backend/webgpu/uniformRing.ts +10 -1
  205. package/src/render/bloomChain.ts +3 -3
  206. package/src/render/boneAnimation.ts +223 -0
  207. package/src/render/compressedSource.ts +121 -29
  208. package/src/render/displayRange.ts +61 -0
  209. package/src/render/filmicCurve.ts +229 -0
  210. package/src/render/fog.ts +19 -3
  211. package/src/render/frame/resources.ts +6 -0
  212. package/src/render/frameReflections.ts +72 -0
  213. package/src/render/glCompressed.ts +83 -0
  214. package/src/render/instances.ts +27 -0
  215. package/src/render/lightBudget.ts +11 -0
  216. package/src/render/lightmap.ts +1 -1
  217. package/src/render/mesh.ts +6 -0
  218. package/src/render/pass.ts +53 -1
  219. package/src/render/pointLightSelection.ts +4 -4
  220. package/src/render/reflectionSurfaceTargets.ts +105 -0
  221. package/src/render/renderQuality.ts +44 -8
  222. package/src/render/sceneTarget.ts +30 -2
  223. package/src/render/screenSpaceReflection.ts +7 -1
  224. package/src/render/shaders/boneAnimation.ts +63 -0
  225. package/src/render/shaders/depth.ts +45 -10
  226. package/src/render/shaders/filmLook.ts +2 -1
  227. package/src/render/shaders/flat/glassTint.ts +5 -1
  228. package/src/render/shaders/flat/index.ts +49 -5
  229. package/src/render/shaders/flat/main.ts +43 -5
  230. package/src/render/shaders/flat/models.ts +1 -1
  231. package/src/render/shaders/flat/overlay.ts +117 -0
  232. package/src/render/shaders/flat/pointShadow.ts +3 -3
  233. package/src/render/shaders/flat/preamble.ts +38 -24
  234. package/src/render/shaders/flat/reflectionSurface.ts +61 -0
  235. package/src/render/shaders/fog.ts +9 -2
  236. package/src/render/shaders/fringe.ts +59 -0
  237. package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
  238. package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
  239. package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
  240. package/src/render/shaders/generated/depth.wgsl.ts +95 -0
  241. package/src/render/shaders/generated/film.wgsl.ts +1 -1
  242. package/src/render/shaders/generated/fire.wgsl.ts +1 -1
  243. package/src/render/shaders/generated/flat.wgsl.ts +3292 -2957
  244. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  245. package/src/render/shaders/generated/particle.wgsl.ts +4 -4
  246. package/src/render/shaders/generated/rush.wgsl.ts +56 -26
  247. package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
  248. package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
  249. package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
  250. package/src/render/shaders/generated/ssrTrace.wgsl.ts +22 -2
  251. package/src/render/shaders/generated/water.wgsl.ts +1 -1
  252. package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
  253. package/src/render/shaders/outputTransform.ts +30 -14
  254. package/src/render/shaders/rush.ts +54 -11
  255. package/src/render/shaders/ssrResolve.ts +52 -0
  256. package/src/render/shaders/ssrTrace.ts +26 -0
  257. package/src/render/shaders/vertexChannel.ts +27 -0
  258. package/src/render/ssrPass.ts +138 -1
  259. package/src/render/staticDraws.ts +95 -0
  260. package/src/render/surfaceOverlay.ts +195 -0
  261. package/src/render/surfaceTexture.ts +161 -27
  262. package/src/render/temporalAa.ts +20 -0
  263. 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';
@@ -70,7 +77,7 @@ import { MAX_AREA_LIGHTS } from '../../areaLights.js';
70
77
  import { areaShadowLayerCount, castingRange, createResolvedAreaShadows, firstAreaShadowLayer, AreaShadowSet, } from '../../areaShadowSet.js';
71
78
  import { equirectToCubeFaces } from '../../equirectToCube.js';
72
79
  import { SKY_FRAG, skyVertFor } from '../../shaders/sky.js';
73
- import { DEPTH_CUTOUT_FRAG, DEPTH_CUTOUT_VERT, DEPTH_FRAG, DEPTH_INSTANCED_CUTOUT_VERT, DEPTH_INSTANCED_VERT, DEPTH_SKINNED_VERT, DEPTH_VERT, } from '../../shaders/depth.js';
80
+ import { DEPTH_CUTOUT_FRAG, DEPTH_CUTOUT_VERT, DEPTH_FRAG, DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT, DEPTH_INSTANCED_ANIMATED_VERT, DEPTH_INSTANCED_CUTOUT_VERT, DEPTH_INSTANCED_VERT, DEPTH_SKINNED_VERT, DEPTH_VERT, } from '../../shaders/depth.js';
74
81
  import { OCTAHEDRAL_EDGE, PointShadowArray, strideLayers } from '../../pointShadowArray.js';
75
82
  import { SunShadowArray } from '../../shadowMap.js';
76
83
  import { POINT_SHADOW_SAMPLER, PointShadowSystem } from '../../pointShadowSystem.js';
@@ -78,6 +85,7 @@ import { castsDepth, glassOf } from '../../shadowCasters.js';
78
85
  import { GlassCasterList } from '../../glassCasters.js';
79
86
  import { SunGlassTint } from '../../sunGlassTint.js';
80
87
  import { GlassTintPrograms } from './glassTintPrograms.js';
88
+ import { captureStaticDraws, replayStaticDraws } from '../../staticDraws.js';
81
89
  import { FACE_COUNT, PointShadowMap } from '../../pointShadowMap.js';
82
90
  import { createResolvedPointShadows, DEFAULT_SOURCE_RADIUS } from '../../pointShadowImage.js';
83
91
  import { createBakeScratch, runPointShadowBakes, selectCastingLights, } from '../../pointShadowBudget.js';
@@ -104,11 +112,15 @@ import { SCATTER_DEPTH_VERT, SCATTER_FRAG, SCATTER_VERT } from '../../shaders/sc
104
112
  import { FlockRenderer } from '../../flockRenderer.js';
105
113
  import { TextRenderer } from '../../textRenderer.js';
106
114
  import { SdfTextRenderer } from '../../sdfTextRenderer.js';
107
- import { MOTION_BLUR_MAX_UV, RUSH_REACH_UV } from '../../vertexDefaults.js';
108
- import { OUTPUT_TRANSFORM_CODE } from '../../vertexDefaults.js';
115
+ import { MOTION_BLUR_MAX_UV, OBJECT_BLUR_UNKEPT, RUSH_REACH_UV, clampMotionShare, } from '../../vertexDefaults.js';
116
+ import { OUTPUT_TRANSFORM_CODE, forwardTransformCode } from '../../vertexDefaults.js';
117
+ import { DEFAULT_FILMIC_CURVE, FILMIC_CLAMPED, FILMIC_CONSTANTS, FILMIC_WITHOUT_COMPOSITE, resolveFilmicCurve, } from '../../filmicCurve.js';
109
118
  import { deviceSnappedCellSize } from '../../textLayout.js';
110
119
  import { WindStreakRenderer } from '../../windStreakRenderer.js';
111
120
  import { InstancedBatch } from './instanced.js';
121
+ import { GlBoneAnimation } from './boneAnimations.js';
122
+ import { packBoneAnimation } from '../../boneAnimation.js';
123
+ import { BONE_PLACES_TEXTURE_UNIT, BONE_TURNS_TEXTURE_UNIT, INSTANCE_CLOCKS_TEXTURE_UNIT, } from '../../lightBudget.js';
112
124
  import { cutoutOf } from '../../cutoutCaster.js';
113
125
  import { CUTOUT_RESOLVE_CODE, resolveCutout } from '../../cutoutDither.js';
114
126
  import { materialHasMaps, noteMapsWithoutUvs } from '../../mapsWithoutUvs.js';
@@ -604,6 +616,10 @@ export class WebGL2Renderer {
604
616
  reflectionQueue = new ReflectionQueue();
605
617
  /** Built on the first frame that submits a surface, and never for a scene with none. */
606
618
  ssr = null;
619
+ /** The frame's materials' reflections, resolved; null where off. See `frameReflections.ts`. */
620
+ frameReflections;
621
+ /** Their two maps on the scene's framebuffer, null where they cannot be drawn. */
622
+ reflectionTargets;
607
623
  /** Said once where a reflection cannot be traced at all. */
608
624
  reflectionsRefused = false;
609
625
  /** Where the frame's camera stands, which is what gives a reconstructed normal its sign. */
@@ -674,6 +690,8 @@ export class WebGL2Renderer {
674
690
  * exactly as it asked to be and nothing about it changes.
675
691
  */
676
692
  exposure;
693
+ /** The `filmic` curve's eight constants, held until `setFilmicCurve` is called again. */
694
+ filmConstants = new Float32Array(FILMIC_CONSTANTS);
677
695
  /**
678
696
  * This frame's veil colour, and how much of it to composite. See `setFrameVeil`.
679
697
  *
@@ -788,6 +806,11 @@ export class WebGL2Renderer {
788
806
  depthCutoutUniforms;
789
807
  depthInstancedCutoutProgram;
790
808
  depthInstancedCutoutUniforms;
809
+ /** A crowd's casters: the instanced depth programs that play a bone animation. */
810
+ depthAnimatedProgram;
811
+ depthAnimatedUniforms;
812
+ depthAnimatedCutoutProgram;
813
+ depthAnimatedCutoutUniforms;
791
814
  scatterDepthProgram;
792
815
  scatterDepthUniforms;
793
816
  /** The sun's static, moving and peeled maps, as layers of one array. See `shadowMap.ts`. */
@@ -875,6 +898,15 @@ export class WebGL2Renderer {
875
898
  warnedNoPointShadowArray = false;
876
899
  /** Said once, for the retired ambient dial. */
877
900
  warnedEnvironmentAmbient = false;
901
+ /** The overlay the draws wear, `uOverlay`: see `setSurfaceOverlay`. */
902
+ overlayFloats = new Float32Array(OVERLAY_FLOATS);
903
+ /** Its atlas, and the 2D copy of it bound where the refraction copy goes. */
904
+ overlayMaps = null;
905
+ overlayTexture = null;
906
+ /** Whether this device's lit stage has room for the overlay's vectors: asked once. */
907
+ overlayRoom = null;
908
+ warnedOverlayRoom = false;
909
+ warnedOverlayBlocks = false;
878
910
  /** Whether a probe is being baked, so the passes that would recurse into one can tell. */
879
911
  probePassActive = false;
880
912
  /**
@@ -1182,14 +1214,21 @@ export class WebGL2Renderer {
1182
1214
  const count = Math.min(data.count, gpuBatch.capacity);
1183
1215
  if (count === 0)
1184
1216
  return;
1217
+ const clip = gpuBatch.animation;
1218
+ if (clip !== null && clip.disposed)
1219
+ return;
1185
1220
  this.shadowDrawBudget.ask();
1186
1221
  const twoSided = material?.doubleSided === true;
1187
1222
  if (twoSided)
1188
1223
  gl.disable(gl.CULL_FACE);
1189
1224
  const cutout = cutoutOf(material);
1190
1225
  if (cutout !== null) {
1191
- gl.useProgram(this.depthInstancedCutoutProgram);
1192
- this.bindCutoutDepth(this.depthInstancedCutoutUniforms, cutout);
1226
+ /* A crowd casts where its clip has moved it, through the animated variant. */
1227
+ const uniforms = clip === null ? this.depthInstancedCutoutUniforms : this.depthAnimatedCutoutUniforms;
1228
+ gl.useProgram(clip === null ? this.depthInstancedCutoutProgram : this.depthAnimatedCutoutProgram);
1229
+ this.bindCutoutDepth(uniforms, cutout);
1230
+ if (clip !== null)
1231
+ this.bindAnimation(uniforms, gpuBatch);
1193
1232
  gpuBatch.draw(gl, count);
1194
1233
  if (twoSided && this.depthPassCullsFaces)
1195
1234
  gl.enable(gl.CULL_FACE);
@@ -1197,12 +1236,14 @@ export class WebGL2Renderer {
1197
1236
  this.restoreSurfaceTextureUnit();
1198
1237
  return;
1199
1238
  }
1200
- const u = this.depthInstancedUniforms;
1201
- gl.useProgram(this.depthInstancedProgram);
1239
+ const u = clip === null ? this.depthInstancedUniforms : this.depthAnimatedUniforms;
1240
+ gl.useProgram(clip === null ? this.depthInstancedProgram : this.depthAnimatedProgram);
1202
1241
  gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
1203
1242
  gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
1204
1243
  if (this.activeDepthPeel)
1205
1244
  gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
1245
+ if (clip !== null)
1246
+ this.bindAnimation(u, gpuBatch);
1206
1247
  gpuBatch.draw(gl, count);
1207
1248
  if (twoSided && this.depthPassCullsFaces)
1208
1249
  gl.enable(gl.CULL_FACE);
@@ -1503,6 +1544,8 @@ export class WebGL2Renderer {
1503
1544
  * call. `init` runs now rather than at the first frame, because building a pipeline in the
1504
1545
  * frame loop is the allocation the house rules are about.
1505
1546
  */
1547
+ /** Each registered pass's declared reads, as a mask, resolved once at registration. */
1548
+ passReads = new Map();
1506
1549
  registerPass(definition) {
1507
1550
  const handle = registerIn(this.passes, definition);
1508
1551
  /*
@@ -1522,6 +1565,8 @@ export class WebGL2Renderer {
1522
1565
  };
1523
1566
  if (definition.prepare !== undefined)
1524
1567
  this.preparingPasses++;
1568
+ /* Resolved once: `maskOf` builds an array, and the frame path may not. */
1569
+ this.passReads.set(handle, maskOf(...(definition.reads ?? [])));
1525
1570
  definition.init?.(this.passDevice);
1526
1571
  return handle;
1527
1572
  }
@@ -1573,10 +1618,28 @@ export class WebGL2Renderer {
1573
1618
  gl: this.gl,
1574
1619
  outputTransform: 0,
1575
1620
  outputExposure: 1,
1621
+ sceneColor: null,
1622
+ sceneDepth: null,
1576
1623
  };
1577
1624
  const context = this.passContext;
1578
1625
  context.outputTransform = this.gradeCode();
1579
1626
  context.outputExposure = this.gradeExposure();
1627
+ /*
1628
+ * The copies a pass declared it reads, taken here with everything drawn before it: colour
1629
+ * afresh at each reading pass, as WebGPU's `takePassColour` does, so a full-screen effect drawn
1630
+ * last reads the whole frame; depth afresh too, since a blit is the whole cost of either here.
1631
+ * Not in a mirror, a probe's face or a capture, whose pictures are not the frame's.
1632
+ */
1633
+ const reads = this.passReads.get(handle) ?? 0;
1634
+ const own = !this.reflectionPassActive && !this.probePassActive && !this.capturePassActive;
1635
+ context.sceneColor =
1636
+ own && (reads & COLOR_SNAPSHOT) !== 0
1637
+ ? (this.sceneTarget?.snapshotColor(true) ?? null)
1638
+ : null;
1639
+ context.sceneDepth =
1640
+ own && (reads & DEPTH_SNAPSHOT) !== 0
1641
+ ? (this.sceneTarget?.snapshotDepth(true) ?? null)
1642
+ : null;
1580
1643
  definition.draw(context);
1581
1644
  }
1582
1645
  /** Let go of a pass. A handle kept past this draws nothing; see `PassHandle`. */
@@ -1584,6 +1647,7 @@ export class WebGL2Renderer {
1584
1647
  const definition = unregisterIn(this.passes, handle);
1585
1648
  if (definition?.prepare !== undefined)
1586
1649
  this.preparingPasses--;
1650
+ this.passReads.delete(handle);
1587
1651
  if (definition !== undefined && this.passDevice !== null)
1588
1652
  definition.dispose?.(this.passDevice);
1589
1653
  }
@@ -1734,10 +1798,10 @@ export class WebGL2Renderer {
1734
1798
  gl.bindTexture(gl.TEXTURE_2D, this.clusterTexture);
1735
1799
  gl.uniform1i(u['uClusterTable'] ?? null, CLUSTER_TABLE_TEXTURE_UNIT);
1736
1800
  gl.uniform1i(u['uClustered'] ?? null, on ? 1 : 0);
1737
- if (!on)
1738
- return;
1739
1801
  /*
1740
- * The frustum, read off the projection rather than taken as a parameter.
1802
+ * The frustum, read off the projection rather than taken as a parameter — **and with the view,
1803
+ * written whether or not the lights are clustered**: a surface overlay's rim finds its place on
1804
+ * the screen through the same two (`overlayScreen`).
1741
1805
  *
1742
1806
  * A perspective matrix carries `1 / tan(fovY/2)` at [5] and that over the aspect at [0], and
1743
1807
  * the near and far planes in [14] and [10]. Deriving them here means the binner and the shader
@@ -1755,6 +1819,8 @@ export class WebGL2Renderer {
1755
1819
  this.clusterFrustum[3] = aspect;
1756
1820
  gl.uniform4fv(u['uClusterFrustum'] ?? null, this.clusterFrustum);
1757
1821
  gl.uniformMatrix4fv(u['uView'] ?? null, false, camera.view);
1822
+ if (!on)
1823
+ return;
1758
1824
  this.clusterTable ??= createClusterTable();
1759
1825
  /* The rectangles past the fixed arm's go into the table. See `ClusterLightSet.areas`. */
1760
1826
  const lights = fillClusterLightSet(env, this.clusterLights, this.shadedAreaLights);
@@ -1828,6 +1894,11 @@ export class WebGL2Renderer {
1828
1894
  this.quality = resolveRenderQuality(qualityOptions);
1829
1895
  /* The grade this world asked for, until a frame says otherwise. See `setOutputExposure`. */
1830
1896
  this.exposure = this.quality.outputExposure;
1897
+ resolveFilmicCurve(DEFAULT_FILMIC_CURVE, this.filmConstants);
1898
+ if (this.quality.outputTransform === 'filmic' &&
1899
+ !(this.quality.screenEffects && this.quality.hdrScene)) {
1900
+ console.warn(FILMIC_WITHOUT_COMPOSITE);
1901
+ }
1831
1902
  /*
1832
1903
  * Multisampling, but only when anything can use it.
1833
1904
  *
@@ -2086,6 +2157,29 @@ export class WebGL2Renderer {
2086
2157
  this.sceneTarget = this.quality.screenEffects
2087
2158
  ? new SceneTarget(gl, this.quality.sceneSamples, this.quality.hdrScene, this.quality.discardResolvedAttachments)
2088
2159
  : null;
2160
+ /*
2161
+ * The frame's materials' reflections, where the frame can carry them: scene light to swap the
2162
+ * probe's share in, and one sample, which the trace's depth copy and both maps are. Said once
2163
+ * where it cannot, in the words the other backend uses. See `frameReflections.ts`.
2164
+ */
2165
+ this.frameReflections = resolveFrameReflections(this.quality.screenSpaceReflections);
2166
+ const reflectionsRefusal = this.frameReflections === null
2167
+ ? null
2168
+ : this.sceneTarget === null || !this.sceneTarget.keepsRange
2169
+ ? 'the frame keeps no scene light to swap the probe in (`screenEffects` and `hdrScene`)'
2170
+ : this.sceneTarget.sampleCount > 1
2171
+ ? 'a multisampled frame has no single depth or colour to march through'
2172
+ : null;
2173
+ if (reflectionsRefusal !== null) {
2174
+ console.warn(`WebGL2: ${frameReflectionsRefused(reflectionsRefusal)}`);
2175
+ }
2176
+ this.reflectionTargets =
2177
+ this.frameReflections !== null && reflectionsRefusal === null
2178
+ ? new ReflectionSurfaceTargets(gl)
2179
+ : null;
2180
+ /* The lit programs write the maps from the first frame they are rebuilt in. See `litOn`. */
2181
+ if (this.reflectionTargets !== null)
2182
+ this.askLit('REFLECTION_MAPS');
2089
2183
  /* Screen-space skin needs the composite to add into; without one it is pre-integrated, said once. */
2090
2184
  if (this.quality.skinScattering === 'screen-space' && this.sceneTarget === null) {
2091
2185
  console.warn("WebGL2: skinScattering 'screen-space' needs screenEffects to add into; skin is pre-integrated");
@@ -2154,10 +2248,15 @@ export class WebGL2Renderer {
2154
2248
  this.depthCutoutUniforms = uniformLocations(gl, this.depthCutoutProgram, 'depth-cutout');
2155
2249
  this.depthInstancedCutoutProgram = compileProgram(gl, DEPTH_INSTANCED_CUTOUT_VERT, DEPTH_CUTOUT_FRAG, 'depth-instanced-cutout');
2156
2250
  this.depthInstancedCutoutUniforms = uniformLocations(gl, this.depthInstancedCutoutProgram, 'depth-instanced-cutout');
2251
+ this.depthAnimatedProgram = compileProgram(gl, DEPTH_INSTANCED_ANIMATED_VERT, DEPTH_FRAG, 'depth-instanced-animated');
2252
+ this.depthAnimatedUniforms = uniformLocations(gl, this.depthAnimatedProgram, 'depth-instanced-animated');
2253
+ this.depthAnimatedCutoutProgram = compileProgram(gl, DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT, DEPTH_CUTOUT_FRAG, 'depth-instanced-animated-cutout');
2254
+ this.depthAnimatedCutoutUniforms = uniformLocations(gl, this.depthAnimatedCutoutProgram, 'depth-instanced-animated-cutout');
2157
2255
  /* The cutout map is always read from the surface unit, which the draw borrows and gives back. */
2158
2256
  for (const [program, uniforms] of [
2159
2257
  [this.depthCutoutProgram, this.depthCutoutUniforms],
2160
2258
  [this.depthInstancedCutoutProgram, this.depthInstancedCutoutUniforms],
2259
+ [this.depthAnimatedCutoutProgram, this.depthAnimatedCutoutUniforms],
2161
2260
  ]) {
2162
2261
  gl.useProgram(program);
2163
2262
  gl.uniform1i(uniforms['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
@@ -2325,6 +2424,8 @@ export class WebGL2Renderer {
2325
2424
  this.lightVolumeProgram,
2326
2425
  this.depthCutoutProgram,
2327
2426
  this.depthInstancedCutoutProgram,
2427
+ this.depthAnimatedProgram,
2428
+ this.depthAnimatedCutoutProgram,
2328
2429
  ]) {
2329
2430
  gl.deleteProgram(program);
2330
2431
  }
@@ -2642,11 +2743,16 @@ export class WebGL2Renderer {
2642
2743
  updateSurfaceTexture(texture, source) {
2643
2744
  if (this.contextLost)
2644
2745
  return;
2645
- texture.update(this.gl, source);
2746
+ texture.update(this.gl, source, this.compressedFormats);
2747
+ /* New pixels are a new copy for the overlay's slot: see `SurfaceTexture.flatLayer`. */
2748
+ if (texture === this.overlayMaps)
2749
+ this.bindOverlayMaps(texture);
2646
2750
  }
2647
2751
  disposeSurfaceTexture(texture) {
2648
2752
  if (this.contextLost)
2649
2753
  return;
2754
+ if (texture === this.overlayMaps)
2755
+ this.bindOverlayMaps(null);
2650
2756
  texture.dispose(this.gl);
2651
2757
  this.captureTargets.get(texture)?.dispose(this.gl);
2652
2758
  this.captureTargets.delete(texture);
@@ -2848,6 +2954,13 @@ export class WebGL2Renderer {
2848
2954
  this.materialDoubleSided = material?.doubleSided === true;
2849
2955
  this.materialHasMaps = materialHasMaps(material);
2850
2956
  this.currentMaterial = material;
2957
+ const reflectivity = material?.reflectivity;
2958
+ const environmentGain = material?.environmentGain;
2959
+ this.materialReflectivity =
2960
+ reflectivity === undefined ? null : Math.min(1, Math.max(0, reflectivity));
2961
+ this.materialEnvironmentGain =
2962
+ environmentGain === undefined ? null : Math.max(0, environmentGain);
2963
+ this.writeEnvironmentDials();
2851
2964
  /* What the blur spreads this skin by: the last material to name a profile says. */
2852
2965
  const model = material?.model;
2853
2966
  warnUnpagedLightmap(model, material?.modelMap);
@@ -2885,7 +2998,8 @@ export class WebGL2Renderer {
2885
2998
  const v = this.cutoutVector;
2886
2999
  v[0] = material?.cutout ?? 0;
2887
3000
  v[1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveNow(material, translucent)];
2888
- v[2] = this.cutoutFrame;
3001
+ /* z unused: the dither's frame is the pass's, in `uHighlightMin.w`. See `uCutout`. */
3002
+ v[2] = 0;
2889
3003
  /* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
2890
3004
  v[3] = lightChannelsOf(material?.lightChannels);
2891
3005
  return v;
@@ -2981,6 +3095,8 @@ export class WebGL2Renderer {
2981
3095
  if (material?.physicalSpecular === true)
2982
3096
  this.askLit('PHYSICAL_SPECULAR');
2983
3097
  gl.uniform4fv(u['uModelParams'] ?? null, this.modelParams);
3098
+ /* The draw's overlay, which a program born mid-pass must wear too: see `setSurfaceOverlay`. */
3099
+ gl.uniform4fv(u['uOverlay'] ?? null, this.overlayFloats);
2984
3100
  /* The albedo's effects table, or the stand-in the shader reads as none. Before the early
2985
3101
  return, so a material with no albedo still leaves a complete texture on the unit. */
2986
3102
  if (albedo === null) {
@@ -3074,10 +3190,85 @@ export class WebGL2Renderer {
3074
3190
  setSurfaceReflectivity(amount) {
3075
3191
  if (this.contextLost)
3076
3192
  return;
3193
+ this.passReflectivity = Math.min(1, Math.max(0, amount));
3194
+ this.writeEnvironmentDials();
3195
+ }
3196
+ /**
3197
+ * The reflectivity and environment gain the setters gave the pass, and the ones the material
3198
+ * set now states, or null where it states none (`SurfaceMaterial.reflectivity`). A draw wears the
3199
+ * material's where it has one and the pass's otherwise.
3200
+ */
3201
+ passReflectivity = 0;
3202
+ passEnvironmentGain = 1;
3203
+ materialReflectivity = null;
3204
+ materialEnvironmentGain = null;
3205
+ writeEnvironmentDials() {
3077
3206
  this.materials.dirty();
3078
- this.environmentDials[1] = Math.min(1, Math.max(0, amount));
3207
+ this.environmentDials[1] = this.materialReflectivity ?? this.passReflectivity;
3208
+ this.environmentDials[2] = this.materialEnvironmentGain ?? this.passEnvironmentGain;
3079
3209
  this.writeFlatVector('uEnvironmentDials', this.environmentDials);
3080
3210
  }
3211
+ /**
3212
+ * The overlay the following draws wear — a rim of light, a dissolve, wrinkles by region — or null
3213
+ * for none. Pass state, as the material is: `bindMeshPass` puts it back to none. See
3214
+ * `surfaceOverlay.ts` for what it lays and `shaders/flat/overlay.ts` for how.
3215
+ *
3216
+ * **Refused, said once, where the lit stage has no room for it.** Fifteen vectors more than the
3217
+ * program this device fits: counted from the source as `fitFlatProgram` counts, and on a device
3218
+ * whose limit is the 256 an Adreno 740 reports, the eight-light build at 254 has none. The draws
3219
+ * then wear nothing, which is the picture a game without the feature draws. Its atlas is bound as
3220
+ * a 2D copy of its first layer, in the refraction copy's unit (`SurfaceTexture.flatLayer`); a
3221
+ * compressed atlas has no copy and is read as none, also said once.
3222
+ */
3223
+ setSurfaceOverlay(overlay) {
3224
+ if (this.contextLost)
3225
+ return;
3226
+ let wanted = overlay;
3227
+ if (overlayLays(wanted) && !this.overlayFits()) {
3228
+ if (!this.warnedOverlayRoom) {
3229
+ this.warnedOverlayRoom = true;
3230
+ console.warn('Renderer: setSurfaceOverlay is refused on this device. The overlay takes fifteen ' +
3231
+ 'fragment uniform vectors and the lit shader already uses all but ' +
3232
+ `${this.overlayRoomLeft()} of the ${this.fragmentVectorLimit()} this GPU offers; ` +
3233
+ 'draws wear no overlay. A lower `maxLights` makes the room, or WebGPU has it.');
3234
+ }
3235
+ wanted = null;
3236
+ }
3237
+ packSurfaceOverlay(wanted, this.overlayFloats);
3238
+ if (overlayLays(wanted))
3239
+ this.askLit('SURFACE_OVERLAY');
3240
+ this.materials.dirty();
3241
+ this.writeFlatVector('uOverlay', this.overlayFloats);
3242
+ this.bindOverlayMaps((wanted?.maps ?? null));
3243
+ }
3244
+ /** The overlay's atlas into the refraction copy's unit, as a 2D copy, or the empty texture. */
3245
+ bindOverlayMaps(maps) {
3246
+ const { gl } = this;
3247
+ this.overlayMaps = maps;
3248
+ this.overlayTexture = maps === null ? null : maps.flatLayer(gl);
3249
+ if (maps !== null && this.overlayTexture === null && !this.warnedOverlayBlocks) {
3250
+ this.warnedOverlayBlocks = true;
3251
+ console.warn("Renderer: a surface overlay's maps are a compressed texture, which WebGL2 cannot copy " +
3252
+ 'into the 2D image the lit stage reads them from. The overlay is drawn without its ' +
3253
+ 'images; upload the atlas from an image instead.');
3254
+ }
3255
+ gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
3256
+ gl.bindTexture(gl.TEXTURE_2D, this.overlayTexture ?? this.emptyTexture2D);
3257
+ }
3258
+ /** The most fragment uniform vectors this GPU offers a stage. */
3259
+ fragmentVectorLimit() {
3260
+ return this.gl.getParameter(this.gl.MAX_FRAGMENT_UNIFORM_VECTORS);
3261
+ }
3262
+ /** What the lit shader at this renderer's budget leaves of the limit, with no overlay. */
3263
+ overlayRoomLeft() {
3264
+ return (this.fragmentVectorLimit() - countUniformVectors(this.flatFragAt(this.lightBudget, false)));
3265
+ }
3266
+ /** Whether the lit shader with the overlay compiled in fits this GPU: counted once. */
3267
+ overlayFits() {
3268
+ this.overlayRoom ??=
3269
+ countUniformVectors(this.flatFragAt(this.lightBudget, true)) <= this.fragmentVectorLimit();
3270
+ return this.overlayRoom;
3271
+ }
3081
3272
  /**
3082
3273
  * How bright the environment the surfaces drawn next reflect is. 1 is the default and identity.
3083
3274
  *
@@ -3093,9 +3284,8 @@ export class WebGL2Renderer {
3093
3284
  setEnvironmentGain(gain) {
3094
3285
  if (this.contextLost)
3095
3286
  return;
3096
- this.materials.dirty();
3097
- this.environmentDials[2] = Math.max(0, gain);
3098
- this.writeFlatVector('uEnvironmentDials', this.environmentDials);
3287
+ this.passEnvironmentGain = Math.max(0, gain);
3288
+ this.writeEnvironmentDials();
3099
3289
  }
3100
3290
  /**
3101
3291
  * Kept as a no-op that says so once, because it was a stand-in for a term that now exists.
@@ -3364,8 +3554,95 @@ export class WebGL2Renderer {
3364
3554
  throw new Error('WebGL2: a skinned mesh cannot be instanced — an instanced draw binds the two attribute ' +
3365
3555
  'locations the joint indices and weights occupy, and this mesh supplies them.');
3366
3556
  }
3557
+ const animation = (options.animation ?? null);
3558
+ /* The bone a vertex follows is its second coordinates' u, which a mesh carries in its grain
3559
+ lane; a mesh with none has nothing to name a bone by. See `boneAnimation.ts`. */
3560
+ if (animation !== null && !mesh.hasGrain) {
3561
+ throw new Error('WebGL2: a bone animation reads each vertex’s bone from the mesh’s second coordinates ' +
3562
+ '(`MeshData.lightmapUvs`), and this mesh has none.');
3563
+ }
3367
3564
  this.ensureInstancedProgram();
3368
- return new InstancedBatch(this.gl, mesh, capacity, options.cull ?? false);
3565
+ if (animation !== null)
3566
+ this.ensureAnimatedProgram();
3567
+ return new InstancedBatch(this.gl, mesh, capacity, options.cull ?? false, animation);
3568
+ }
3569
+ /**
3570
+ * A bone animation an instanced batch can play (`InstancedOptions.animation`): per frame, a turn
3571
+ * and a place for each bone, which every vertex of the batch's mesh follows — the bone its second
3572
+ * coordinates' u names, times `boneScale` — at each instance's own moment of the clip. What a
3573
+ * crowd is made of: every instance moves at the frame's rate on its own clock, in the colour pass
3574
+ * and in every shadow, with nothing on the processor after this. See `boneAnimation.ts` for the
3575
+ * clip's layout and the arithmetic, which both backends run.
3576
+ *
3577
+ * What it gives up against skinning is the blend: a vertex follows one bone. Refused, with what
3578
+ * was wrong, for a clip whose numbers are not its size, or past 2,048 bones or frames.
3579
+ */
3580
+ createBoneAnimation(clip) {
3581
+ return new GlBoneAnimation(this.gl, packBoneAnimation(clip));
3582
+ }
3583
+ /** Release a clip. A batch still playing it draws nothing, and casts nothing, after. */
3584
+ disposeBoneAnimation(animation) {
3585
+ const clip = animation;
3586
+ if (this.contextLost || clip.disposed)
3587
+ return;
3588
+ clip.dispose(this.gl);
3589
+ }
3590
+ /**
3591
+ * The clock a bone animation plays by this frame, in seconds on the caller's clock: an instance's
3592
+ * moment is this times its rate plus its phase (`MeshInstances.clocks`). **Set it before the
3593
+ * shadows are drawn**, once a frame, as the wind is set: a shadow drawn under one clock and its
3594
+ * caster under another come apart at the frame's rate. 0 until set, which holds every crowd at
3595
+ * the moment its phases name.
3596
+ */
3597
+ setAnimationTime(seconds) {
3598
+ if (Number.isFinite(seconds))
3599
+ this.animationTime = seconds;
3600
+ }
3601
+ /** The clock `setAnimationTime` set. */
3602
+ animationTime = 0;
3603
+ /**
3604
+ * An animated batch's clip, clocks and numbers into whichever program is bound: the three vertex
3605
+ * textures on units of their own (`lightBudget.ts`), the clip's speed and bone scale, the clock.
3606
+ */
3607
+ bindAnimation(u, batch) {
3608
+ const clip = batch.animation;
3609
+ const clocks = batch.clocks;
3610
+ if (clip === null || clocks === null)
3611
+ return;
3612
+ const { gl } = this;
3613
+ gl.activeTexture(gl.TEXTURE0 + BONE_PLACES_TEXTURE_UNIT);
3614
+ gl.bindTexture(gl.TEXTURE_2D, clip.places);
3615
+ gl.activeTexture(gl.TEXTURE0 + BONE_TURNS_TEXTURE_UNIT);
3616
+ gl.bindTexture(gl.TEXTURE_2D, clip.turns);
3617
+ gl.activeTexture(gl.TEXTURE0 + INSTANCE_CLOCKS_TEXTURE_UNIT);
3618
+ gl.bindTexture(gl.TEXTURE_2D, clocks.texture);
3619
+ gl.uniform1i(u['uBonePlaces'] ?? null, BONE_PLACES_TEXTURE_UNIT);
3620
+ gl.uniform1i(u['uBoneTurns'] ?? null, BONE_TURNS_TEXTURE_UNIT);
3621
+ gl.uniform1i(u['uInstanceClocks'] ?? null, INSTANCE_CLOCKS_TEXTURE_UNIT);
3622
+ gl.uniform2f(u['uBoneClip'] ?? null, clip.framesPerSecond, clip.boneScale);
3623
+ gl.uniform1f(u['uSceneTime'] ?? null, this.animationTime);
3624
+ }
3625
+ /** The animated instanced program, compiled on first use. See `ensureInstancedProgram`. */
3626
+ flatAnimatedProgram = null;
3627
+ flatAnimatedUniforms = null;
3628
+ /** Compile the animated variant, once, and feed it this frame's pass state. */
3629
+ ensureAnimatedProgram() {
3630
+ if (this.contextLost || this.flatAnimatedProgram !== null)
3631
+ return;
3632
+ const { gl } = this;
3633
+ const program = compileProgram(gl, flatVert({ skinned: false, morphed: false, instanced: true, animated: true }), this.flatFragSource, 'flat.animated');
3634
+ const uniforms = uniformLocations(gl, program, 'flat.animated');
3635
+ this.flatAnimatedProgram = program;
3636
+ this.flatAnimatedUniforms = uniforms;
3637
+ this.flatTargets.push({ program, uniforms });
3638
+ const camera = this.lastPassCamera;
3639
+ const env = this.lastPassEnv;
3640
+ if (camera !== null && env !== null) {
3641
+ gl.useProgram(program);
3642
+ this.writeMeshPassState(uniforms, camera, env);
3643
+ this.writeMaterialState(uniforms, this.currentMaterial);
3644
+ this.useFlatProgram();
3645
+ }
3369
3646
  }
3370
3647
  /** Push placement and colour. Only the live prefix. */
3371
3648
  uploadInstanced(batch, data) {
@@ -3418,18 +3695,29 @@ export class WebGL2Renderer {
3418
3695
  const culling = !blend && batch.cull;
3419
3696
  if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion))
3420
3697
  return;
3698
+ /* A batch playing a bone animation takes the animated variant; a disposed clip draws nothing. */
3699
+ const clip = batch.animation;
3700
+ if (clip !== null && clip.disposed)
3701
+ return;
3421
3702
  this.drawBudget.ask();
3422
3703
  this.ensureInstancedProgram();
3704
+ if (clip !== null)
3705
+ this.ensureAnimatedProgram();
3423
3706
  /* A material with a model takes that model's instanced program; see `modelProgram`. */
3424
3707
  const modelKind = this.currentMaterial?.model?.kind ?? null;
3425
- const modelled = modelKind === null ? null : this.modelProgram('instanced', modelKind);
3426
- const program = modelled?.program ?? this.flatInstancedProgram;
3427
- const u = modelled?.uniforms ?? this.flatInstancedUniforms;
3708
+ const modelled = modelKind === null
3709
+ ? null
3710
+ : this.modelProgram(clip === null ? 'instanced' : 'animated', modelKind);
3711
+ const program = modelled?.program ?? (clip === null ? this.flatInstancedProgram : this.flatAnimatedProgram);
3712
+ const u = modelled?.uniforms ??
3713
+ (clip === null ? this.flatInstancedUniforms : this.flatAnimatedUniforms);
3428
3714
  if (program === null || u === null)
3429
3715
  return;
3430
3716
  const { gl } = this;
3431
3717
  gl.useProgram(program);
3432
3718
  gl.uniform1i(u['uHasTangents'] ?? null, mesh.hasTangents ? 1 : 0);
3719
+ if (clip !== null)
3720
+ this.bindAnimation(u, batch);
3433
3721
  const lit = options.lit ?? true;
3434
3722
  /* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
3435
3723
  const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
@@ -3496,10 +3784,19 @@ export class WebGL2Renderer {
3496
3784
  gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE);
3497
3785
  if (ditheredBlend)
3498
3786
  gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, true));
3499
- if (culling && cullsInstances(count, mesh.indexCount))
3787
+ const keeps = !blend && this.reflectionKeeps();
3788
+ if (keeps)
3789
+ this.reflectionTargets?.on();
3790
+ /* An animated batch reads its clocks by instance index, which the culled copy would reorder:
3791
+ it is culled whole and never instance by instance. */
3792
+ if (culling && clip === null && cullsInstances(count, mesh.indexCount)) {
3500
3793
  batch.drawCulled(gl, data, this.frustum);
3501
- else
3794
+ }
3795
+ else {
3502
3796
  batch.draw(gl, count);
3797
+ }
3798
+ if (keeps)
3799
+ this.reflectionTargets?.off();
3503
3800
  if (coverage)
3504
3801
  gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
3505
3802
  if (ditheredBlend)
@@ -3561,7 +3858,9 @@ export class WebGL2Renderer {
3561
3858
  const glass = this.glassScratch;
3562
3859
  /* Strength, path thickness, transmission, frost: one vector, for the budget the shader states. */
3563
3860
  gl.uniform4f(u['uSeeThrough'] ?? null, Math.max(refraction, 0), options.thicknessM ?? 0, glass.transmission, glass.frost);
3564
- gl.uniform3fv(u['uRefractTint'] ?? null, options.refractTint ?? WHITE_TINT);
3861
+ /* w: this framebuffer's first row is the bottom, so up the screen is up the rows. */
3862
+ const tint = options.refractTint ?? WHITE_TINT;
3863
+ gl.uniform4f(u['uRefractTint'] ?? null, tint[0] ?? 1, tint[1] ?? 1, tint[2] ?? 1, 1);
3565
3864
  gl.uniform3fv(u['uGlassTint'] ?? null, glass.tint);
3566
3865
  return true;
3567
3866
  }
@@ -3570,7 +3869,8 @@ export class WebGL2Renderer {
3570
3869
  const { gl } = this;
3571
3870
  gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
3572
3871
  gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
3573
- gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
3872
+ /* The overlay's atlas, where one is held: it shares the unit. See `setSurfaceOverlay`. */
3873
+ gl.bindTexture(gl.TEXTURE_2D, this.overlayTexture ?? this.emptyTexture2D);
3574
3874
  }
3575
3875
  disposeMesh(mesh) {
3576
3876
  /*
@@ -3781,7 +4081,7 @@ export class WebGL2Renderer {
3781
4081
  */
3782
4082
  gradeCode() {
3783
4083
  return this.sceneTarget === null || !this.quality.hdrScene
3784
- ? OUTPUT_TRANSFORM_CODE[this.quality.outputTransform]
4084
+ ? forwardTransformCode(this.quality.outputTransform)
3785
4085
  : 0;
3786
4086
  }
3787
4087
  gradeExposure() {
@@ -4603,6 +4903,69 @@ export class WebGL2Renderer {
4603
4903
  return;
4604
4904
  casters(this.casterSink);
4605
4905
  }
4906
+ /**
4907
+ * Draws that do not change between frames, enumerated once and kept: what a stage, a set or any
4908
+ * world that stands still draws, recorded so a frame replays it rather than issuing it.
4909
+ *
4910
+ * **The same enumeration `drawSceneCasters` takes**, called once, here: rigid meshes and instanced
4911
+ * batches, each with its material. A skinned mesh or a scatter batch is refused by name, since
4912
+ * both change every frame. What it captures is what the enumeration said at the time — the
4913
+ * matrices and the instance counts are copied, so a caller's scratch is free to move on — while
4914
+ * the meshes, batches, textures and materials stay the caller's: a list must be disposed, or made
4915
+ * again, before anything it holds is, and made again when a material it holds is changed.
4916
+ *
4917
+ * **What it is for is the frame's processor time.** WebGPU records the list into a render bundle
4918
+ * per view and replays it with one call, where a draw issued a frame at a time costs its encoding
4919
+ * every frame and again for every view that draws it. This backend has no bundles and replays the
4920
+ * entries through the scene-caster path every frame, at the cost a frame drawing them itself pays;
4921
+ * the picture is the same.
4922
+ *
4923
+ * What a list gives up against drawing each frame is culling: every entry is drawn in every view,
4924
+ * because a bundle cannot leave one out. A world split into lists by region culls by list.
4925
+ */
4926
+ createStaticDraws(casters) {
4927
+ return captureStaticDraws(casters);
4928
+ }
4929
+ /**
4930
+ * Draw a list into the open mesh pass, under the camera, the lights and the fog `bindMeshPass`
4931
+ * set — the frame's, a mirror's, a capture's. Opaque draws, through each entry's material.
4932
+ *
4933
+ * **Its draws are rigid and wear no overlay**, so a skin palette, morph weights, cloth or a surface
4934
+ * overlay set before it is put back to none, as `bindMeshPass` puts an overlay back; and no
4935
+ * material is left set after it, which is what `setMaterial(null)` leaves. The state a pass set —
4936
+ * the grain, the relief, the fog, the grade — is the list's to draw with, as it is any draw's, and
4937
+ * a list drawn under a different one is recorded again for it on WebGPU.
4938
+ */
4939
+ drawStaticDraws(draws) {
4940
+ if (this.contextLost)
4941
+ return;
4942
+ const list = draws;
4943
+ if (list.disposed) {
4944
+ this.warnStaticDisposed();
4945
+ return;
4946
+ }
4947
+ this.setSkinPalette(null);
4948
+ this.setMorphWeights(null);
4949
+ this.setCloth(null);
4950
+ this.setSurfaceOverlay(null);
4951
+ this.setMaterial(null);
4952
+ this.sceneCasterMaterial = undefined;
4953
+ replayStaticDraws(list, this.sceneCasterSink);
4954
+ this.setMaterial(null);
4955
+ this.sceneCasterMaterial = undefined;
4956
+ }
4957
+ /** Release a list. Its meshes, batches and materials are the caller's and are not released. */
4958
+ disposeStaticDraws(draws) {
4959
+ draws.disposed = true;
4960
+ }
4961
+ /** A disposed list drawn, said once: the frame loop does not throw. */
4962
+ warnStaticDisposed() {
4963
+ if (this.warnedStaticDisposed)
4964
+ return;
4965
+ this.warnedStaticDisposed = true;
4966
+ console.warn('drawStaticDraws: this list was disposed, and draws nothing.');
4967
+ }
4968
+ warnedStaticDisposed = false;
4606
4969
  /**
4607
4970
  * Draw the pass's glass twice: where the nearest pane is, and what the panes let through.
4608
4971
  *
@@ -4784,7 +5147,7 @@ export class WebGL2Renderer {
4784
5147
  * throws a sentence with the device's numbers in it rather than a bare driver string.
4785
5148
  */
4786
5149
  /** The lit fragment source this renderer's profile compiles, at a light budget. */
4787
- flatFragAt(budget) {
5150
+ flatFragAt(budget, overlay = this.litOn.SURFACE_OVERLAY) {
4788
5151
  return flatFrag({
4789
5152
  pointShadows: this.quality.pointShadows,
4790
5153
  directionalShadows: this.quality.directionalShadows,
@@ -4797,6 +5160,8 @@ export class WebGL2Renderer {
4797
5160
  surfaceEffects: this.litOn.SURFACE_EFFECTS,
4798
5161
  driftLight: this.litOn.DRIFT_LIGHT,
4799
5162
  physicalSpecular: this.litOn.PHYSICAL_SPECULAR,
5163
+ surfaceOverlay: overlay,
5164
+ reflectionMaps: this.litOn.REFLECTION_MAPS,
4800
5165
  maxLights: budget.maxLights,
4801
5166
  maxAreaLights: budget.maxAreaLights,
4802
5167
  });
@@ -4813,10 +5178,10 @@ export class WebGL2Renderer {
4813
5178
  const { gl } = this;
4814
5179
  Object.assign(this.litOn, this.litWanted);
4815
5180
  this.flatFragSource = this.flatFragAt(this.lightBudget);
4816
- const recompile = (old, skinned, morphed, instanced, label, eight = false) => {
5181
+ const recompile = (old, skinned, morphed, instanced, label, eight = false, animated = false) => {
4817
5182
  if (old === null)
4818
5183
  return null;
4819
- const vertex = flatVert({ skinned, morphed, instanced });
5184
+ const vertex = flatVert({ skinned, morphed, instanced, animated });
4820
5185
  const program = compileProgram(gl, eight ? skinEight(vertex) : vertex, this.flatFragSource, label);
4821
5186
  gl.deleteProgram(old);
4822
5187
  return { program, uniforms: uniformLocations(gl, program, label) };
@@ -4844,6 +5209,9 @@ export class WebGL2Renderer {
4844
5209
  const instanced = recompile(this.flatInstancedProgram, false, false, true, 'flat.instanced');
4845
5210
  this.flatInstancedProgram = instanced?.program ?? null;
4846
5211
  this.flatInstancedUniforms = instanced?.uniforms ?? null;
5212
+ const animated = recompile(this.flatAnimatedProgram, false, false, true, 'flat.animated', false, true);
5213
+ this.flatAnimatedProgram = animated?.program ?? null;
5214
+ this.flatAnimatedUniforms = animated?.uniforms ?? null;
4847
5215
  for (const [key, entry] of this.clothFlat) {
4848
5216
  const eight = key.startsWith('8');
4849
5217
  const withMorph = key.endsWith('m');
@@ -4861,7 +5229,7 @@ export class WebGL2Renderer {
4861
5229
  this.modelFlat.set(key, { program, uniforms: uniformLocations(gl, program, label) });
4862
5230
  }
4863
5231
  this.flatTargets.length = 0;
4864
- for (const target of [plain, skinned, morphed, both, skinned8, both8, instanced]) {
5232
+ for (const target of [plain, skinned, morphed, both, skinned8, both8, instanced, animated]) {
4865
5233
  if (target !== null)
4866
5234
  this.flatTargets.push(target);
4867
5235
  }
@@ -5468,10 +5836,20 @@ export class WebGL2Renderer {
5468
5836
  * Set before `beginFrame` and held until changed, like `setSpeedRush`, which it sits
5469
5837
  * beside for the same reason: both describe how the finished image is presented rather
5470
5838
  * than anything in the world.
5839
+ *
5840
+ * `maxShare` is the longest smear, as a share of the frame (0.03 unless given, at most half): a
5841
+ * fast turn softens the frame rather than erasing it. **Each drawn object's own motion is
5842
+ * followed only under a reconstruction on WebGPU**, which draws the motion target it reads; here,
5843
+ * and on WebGPU without one, the camera's motion alone smears the frame, and a draw that names
5844
+ * where it was last frame is told so once.
5471
5845
  */
5472
- setCameraMotionBlur(scale) {
5846
+ setCameraMotionBlur(scale, maxShare = MOTION_BLUR_MAX_UV) {
5473
5847
  this.motionBlurScale = Math.min(Math.max(scale, 0), 1);
5848
+ this.motionBlurMax = clampMotionShare(maxShare);
5474
5849
  }
5850
+ /** The longest smear, as a share of the frame. See `setCameraMotionBlur`. */
5851
+ motionBlurMax = MOTION_BLUR_MAX_UV;
5852
+ warnedObjectBlur = false;
5475
5853
  /**
5476
5854
  * The camera has cut: this frame is a new shot, not the next moment of the last one.
5477
5855
  *
@@ -5671,6 +6049,42 @@ export class WebGL2Renderer {
5671
6049
  ? Math.max(exposure, 1e-3)
5672
6050
  : this.quality.outputExposure;
5673
6051
  }
6052
+ /**
6053
+ * The `filmic` curve this and every later frame is graded through, until it is set again: a
6054
+ * grading volume's five numbers, solved once here into the constants the resolve reads. Ignored
6055
+ * unless `outputTransform` is `filmic`. A number out of range is clamped into it and said once,
6056
+ * since a frame loop must not throw. See `filmicCurve.ts`.
6057
+ */
6058
+ setFilmicCurve(curve) {
6059
+ if (resolveFilmicCurve(curve, this.filmConstants) && !this.warnedFilmClamped) {
6060
+ this.warnedFilmClamped = true;
6061
+ console.warn(FILMIC_CLAMPED);
6062
+ }
6063
+ }
6064
+ warnedFilmClamped = false;
6065
+ /**
6066
+ * The range the frame goes out in: `'high'` where the profile asked for `highDynamicRange`, the
6067
+ * frame holds light past white and the display shows it — on WebGPU — and `'standard'` otherwise.
6068
+ * **Always standard here**: this backend draws into a canvas of eight bits a channel, which has
6069
+ * nothing above white to show. Typed as the question rather than this backend's answer, because
6070
+ * the shared surface is derived from this class.
6071
+ */
6072
+ displayRange = 'standard';
6073
+ /** Why it is that range, in words, for a game choosing its look and for a bug report. */
6074
+ get displayRangeReason() {
6075
+ return this.quality.highDynamicRange
6076
+ ? 'WebGL2 draws into an eight-bit canvas, which has no range above white'
6077
+ : 'highDynamicRange was not asked for';
6078
+ }
6079
+ /**
6080
+ * How bright paper white and the display's peak are, in one unit, held until changed: their
6081
+ * ratio is how far above white the highlights may run where `displayRange` is `'high'`, and
6082
+ * nothing changes where it is standard, which is always here.
6083
+ */
6084
+ setDisplayLuminance(paperWhite, peak) {
6085
+ void paperWhite;
6086
+ void peak;
6087
+ }
5674
6088
  /**
5675
6089
  * Composite a flat colour over the finished frame — for a cut dipping to white or to black.
5676
6090
  *
@@ -5757,6 +6171,20 @@ export class WebGL2Renderer {
5757
6171
  }
5758
6172
  this.sceneTarget.setVignette(strength);
5759
6173
  }
6174
+ /**
6175
+ * The lens's colour fringe, held until changed: `intensity` is a percentage, 0 is none, and red
6176
+ * and green are pulled toward the centre by their wavelength's distance from blue, growing from
6177
+ * `start` — a share of the half-frame, 0 the centre — to the whole of it at the edge. Applied to
6178
+ * scene light at the composite's first read. Needs `screenEffects`, and says so once rather than
6179
+ * doing nothing. See `shaders/fringe.ts`.
6180
+ */
6181
+ setChromaticAberration(intensity, start = 0) {
6182
+ if (this.sceneTarget === null) {
6183
+ this.warnLookWithoutComposite(intensity);
6184
+ return;
6185
+ }
6186
+ this.sceneTarget.setChromaticAberration(intensity, start);
6187
+ }
5760
6188
  /**
5761
6189
  * Film grain, held until changed: an amplitude in display values (0 is none, 0.03 is a fine
5762
6190
  * grain) and this frame's seed. **The seed is the caller's**, because the engine takes time from
@@ -5937,6 +6365,8 @@ export class WebGL2Renderer {
5937
6365
  beginFrame(clearColor) {
5938
6366
  if (this.contextLost)
5939
6367
  return;
6368
+ /* One frame of the jitter's sequence however many times the frame binds its camera. */
6369
+ this.temporalHistory.nextFrame();
5940
6370
  this.skinScattered = false;
5941
6371
  this.skinScatter?.reset();
5942
6372
  if (this.litAsked) {
@@ -6003,6 +6433,8 @@ export class WebGL2Renderer {
6003
6433
  }
6004
6434
  }
6005
6435
  this.sceneTarget?.begin(this.canvas.width, this.canvas.height);
6436
+ /* The materials' maps on the scene's framebuffer, cleared and disabled: `reflectionKeeps`. */
6437
+ this.reflectionTargets?.begin(this.canvas.width, this.canvas.height);
6006
6438
  gl.clearColor(clearColor[0], clearColor[1], clearColor[2], 1);
6007
6439
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
6008
6440
  }
@@ -6052,7 +6484,7 @@ export class WebGL2Renderer {
6052
6484
  motion = {
6053
6485
  reprojection: this.reprojection,
6054
6486
  strength: blur,
6055
- max: MOTION_BLUR_MAX_UV,
6487
+ max: this.motionBlurMax,
6056
6488
  };
6057
6489
  }
6058
6490
  }
@@ -6132,7 +6564,11 @@ export class WebGL2Renderer {
6132
6564
  : undefined;
6133
6565
  /* The grade travels to the resolve only where the mesh pass gave it up. See `bindMeshPass`. */
6134
6566
  const lateGrade = this.quality.hdrScene
6135
- ? { transform: OUTPUT_TRANSFORM_CODE[this.quality.outputTransform], exposure: this.exposure }
6567
+ ? {
6568
+ transform: OUTPUT_TRANSFORM_CODE[this.quality.outputTransform] ?? 0,
6569
+ exposure: this.exposure,
6570
+ film: this.filmConstants,
6571
+ }
6136
6572
  : { transform: 0, exposure: 1 };
6137
6573
  /*
6138
6574
  * Depth of field, on the same ceiling-and-dial split as bloom above: `depthOfField` decides
@@ -6194,6 +6630,30 @@ export class WebGL2Renderer {
6194
6630
  * drawn over a reflective floor rather than under it, and before the post chain so bloom and
6195
6631
  * the grade see a reflection exactly as they would see any other part of the picture.
6196
6632
  */
6633
+ /*
6634
+ * **The frame's materials' reflections first**, on the same terms: every pixel the opaque lit
6635
+ * draws left a reflection for, marched and swapped for the probe's share. The maps leave the
6636
+ * scene's framebuffer before the resolve draws into it while sampling them.
6637
+ */
6638
+ const reflectionTargets = this.reflectionTargets;
6639
+ const probeMap = reflectionTargets?.probeTexture ?? null;
6640
+ const tintMap = reflectionTargets?.tintTexture ?? null;
6641
+ if (reflectionTargets !== null &&
6642
+ this.frameReflections !== null &&
6643
+ probeMap !== null &&
6644
+ tintMap !== null &&
6645
+ this.frameViewProj !== null &&
6646
+ this.sceneTarget !== null) {
6647
+ const scene = this.sceneTarget.colorAttachment();
6648
+ const depth = this.sceneTarget.snapshotDepth(true);
6649
+ if (scene !== null && depth !== null) {
6650
+ this.ssr ??= new SsrPass(this.gl, this.sceneTarget.keepsRange);
6651
+ this.ssr.traceMaterials(scene, depth, probeMap, tintMap, this.frameViewProj, this.frameEye, this.canvas.width, this.canvas.height, REFLECTION_EDGE_FADE, this.frameReflections);
6652
+ this.sceneTarget.bind();
6653
+ reflectionTargets.detach();
6654
+ this.ssr.resolveMaterials(probeMap, tintMap, this.canvas.width, this.canvas.height, this.frameReflections);
6655
+ }
6656
+ }
6197
6657
  if (this.reflectionQueue.length > 0 && this.frameViewProj !== null) {
6198
6658
  const scene = this.sceneTarget?.colorAttachment() ?? null;
6199
6659
  const depth = this.sceneTarget?.snapshotDepth(true) ?? null;
@@ -6531,9 +6991,17 @@ export class WebGL2Renderer {
6531
6991
  this.materialHasMaps = false;
6532
6992
  this.currentMaterial = null;
6533
6993
  this.currentSurfaceTexture = null;
6994
+ /* Nor an overlay: the atlas's unit is given the empty texture with the refraction copy's. */
6995
+ this.overlayFloats.fill(0);
6996
+ this.overlayMaps = null;
6997
+ this.overlayTexture = null;
6534
6998
  /* Nor an ambient of its own: the frame's, until a draw asks. See `setAmbientSH`. */
6535
6999
  this.ambientSH.fill(0);
6536
7000
  /* No reflection and an environment at its own brightness; the emission the world asks for. */
7001
+ this.passReflectivity = 0;
7002
+ this.passEnvironmentGain = 1;
7003
+ this.materialReflectivity = null;
7004
+ this.materialEnvironmentGain = null;
6537
7005
  this.environmentDials[1] = 0;
6538
7006
  this.environmentDials[2] = 1;
6539
7007
  this.emission[0] = env.emissiveGain;
@@ -6656,12 +7124,14 @@ export class WebGL2Renderer {
6656
7124
  * unless a draw sets the strength above zero.
6657
7125
  */
6658
7126
  gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
6659
- gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
7127
+ gl.bindTexture(gl.TEXTURE_2D, this.overlayTexture ?? this.emptyTexture2D);
6660
7128
  gl.uniform1i(u['uRefractScene'] ?? null, REFRACT_SCENE_TEXTURE_UNIT);
7129
+ /* The draws' overlay, none at a pass's start, and its atlas above: see `setSurfaceOverlay`. */
7130
+ gl.uniform4fv(u['uOverlay'] ?? null, this.overlayFloats);
6661
7131
  /* Neither refracting nor glass, which is every draw that does not say otherwise. See
6662
7132
  `bindSeeThrough` for the four numbers in the one vector. */
6663
7133
  gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
6664
- gl.uniform3fv(u['uRefractTint'] ?? null, WHITE_TINT);
7134
+ gl.uniform4f(u['uRefractTint'] ?? null, 1, 1, 1, 1);
6665
7135
  gl.uniform3fv(u['uGlassTint'] ?? null, WHITE_TINT);
6666
7136
  gl.uniform3fv(u['uDirectionalDir'] ?? null, env.directionalDir);
6667
7137
  gl.uniform3fv(u['uDirectionalColor'] ?? null, env.directionalColor);
@@ -6775,7 +7245,10 @@ export class WebGL2Renderer {
6775
7245
  gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
6776
7246
  gl.uniform1f(u['uShadowMaxSlope'] ?? null, this.quality.directionalShadowMaxSlope);
6777
7247
  gl.uniform1i(u['uShadowFilterTaps'] ?? null, this.quality.shadowFilterTaps);
6778
- gl.uniform3fv(u['uHighlightMin'] ?? null, env.highlightMin);
7248
+ /* The near corner, and in its w the frame the cutout dither moves with. See `uHighlightMin`.
7249
+ An environment written by hand may leave the box out, as the far corner says below. */
7250
+ const min = env.highlightMin;
7251
+ gl.uniform4f(u['uHighlightMin'] ?? null, min?.[0] ?? 0, min?.[1] ?? 0, min?.[2] ?? 0, this.cutoutFrame);
6779
7252
  /* The far corner, and the gain in its w. See `uHighlightMax`. */
6780
7253
  /* An environment written by hand may leave the box out; then there is none, as before. */
6781
7254
  const max = env.highlightMax ?? NO_HIGHLIGHT;
@@ -7036,9 +7509,16 @@ export class WebGL2Renderer {
7036
7509
  * here, so there is no motion target for this to be written into, and a mover passed here is
7037
7510
  * neither read nor advanced.
7038
7511
  */
7039
- _previousModel = null) {
7512
+ previousModel = null) {
7040
7513
  if (this.contextLost)
7041
7514
  return;
7515
+ /* A mover's own motion is not blurred here: see `setCameraMotionBlur`. Said once. */
7516
+ if (previousModel !== null &&
7517
+ !this.warnedObjectBlur &&
7518
+ this.quality.cameraMotionBlur * this.motionBlurScale > 0) {
7519
+ this.warnedObjectBlur = true;
7520
+ console.warn(OBJECT_BLUR_UNKEPT);
7521
+ }
7042
7522
  /* Geometry that has not all arrived is not drawn, nor a mesh with none. See `Mesh.complete`
7043
7523
  and `Mesh.indexCount`. */
7044
7524
  if (!mesh.complete || mesh.indexCount === 0)
@@ -7071,7 +7551,13 @@ export class WebGL2Renderer {
7071
7551
  * light and its colour, each into the blur's own target.
7072
7552
  */
7073
7553
  const splits = this.skinSplits();
7554
+ /* An opaque lit draw into the frame writes its material's reflection too: see `reflectionKeeps`. */
7555
+ const keeps = this.reflectionKeeps();
7556
+ if (keeps)
7557
+ this.reflectionTargets?.on();
7074
7558
  this.drawMeshHalf(mesh, model, depthLayer, tint, splits ? 'scene' : 'whole');
7559
+ if (keeps)
7560
+ this.reflectionTargets?.off();
7075
7561
  if (splits && this.skinScatter !== null) {
7076
7562
  this.skinScatter.begin();
7077
7563
  this.drawMeshHalf(mesh, model, depthLayer, tint, 'diffuse');
@@ -7085,6 +7571,18 @@ export class WebGL2Renderer {
7085
7571
  * skin yet, this is the frame's own picture — not a mirror's or a probe's, nor an overlay after
7086
7572
  * the frame — and the blur's target can be had.
7087
7573
  */
7574
+ /**
7575
+ * Whether a draw now is the frame's own, opaque and lit, and so writes the two maps beside its
7576
+ * colour: not a mirror's, a probe's or a capture's, whose framebuffers have no maps, and not after
7577
+ * the frame. See `reflectionSurfaceTargets.ts`.
7578
+ */
7579
+ reflectionKeeps() {
7580
+ return (this.reflectionTargets !== null &&
7581
+ !this.reflectionPassActive &&
7582
+ !this.probePassActive &&
7583
+ !this.capturePassActive &&
7584
+ !this.framePresented);
7585
+ }
7088
7586
  skinSplits() {
7089
7587
  const skin = this.skinScatter;
7090
7588
  const scene = this.sceneTarget;
@@ -7111,12 +7609,18 @@ export class WebGL2Renderer {
7111
7609
  const projection = this.frameProjection;
7112
7610
  if (skin === null || scene === null || this.skinScattered)
7113
7611
  return;
7612
+ /*
7613
+ * **Not the frame's picture, so not the frame's moment**: a blended draw in a mirror, a probe
7614
+ * bake or a scene capture lands over none of the frame's skin. Latching there switched the
7615
+ * spread off for the frame, so a capture with a glowing sign in it left every face drawn after
7616
+ * it flat. `scripts/capture-check.mjs`.
7617
+ */
7618
+ if (this.reflectionPassActive || this.probePassActive || this.capturePassActive)
7619
+ return;
7114
7620
  /* Latched whether or not skin was drawn: a skin drawn after this point is the whole surface. */
7115
7621
  this.skinScattered = true;
7116
7622
  if (!skin.pending)
7117
7623
  return;
7118
- if (this.reflectionPassActive || this.probePassActive || this.capturePassActive)
7119
- return;
7120
7624
  if (projection === null)
7121
7625
  return;
7122
7626
  const depth = scene.snapshotDepth(true);
@@ -8069,6 +8573,12 @@ function litVertexOf(variant) {
8069
8573
  }
8070
8574
  const skinned = variant.startsWith('skinned') || variant.startsWith('both');
8071
8575
  const morphed = variant === 'morphed' || variant.startsWith('both');
8072
- const vertex = flatVert({ skinned, morphed, instanced: variant === 'instanced' });
8576
+ const animated = variant === 'animated';
8577
+ const vertex = flatVert({
8578
+ skinned,
8579
+ morphed,
8580
+ instanced: variant === 'instanced' || animated,
8581
+ animated,
8582
+ });
8073
8583
  return variant.endsWith('8') ? skinEight(vertex) : vertex;
8074
8584
  }