@driftengine/core 4.8.7 → 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 (163) hide show
  1. package/CHANGELOG.json +61 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +5 -0
  4. package/dist/index.js +7 -0
  5. package/dist/render/atmosphere.d.ts +13 -0
  6. package/dist/render/atmosphere.js +9 -1
  7. package/dist/render/backend/api.d.ts +9 -1
  8. package/dist/render/backend/createRenderer.d.ts +6 -0
  9. package/dist/render/backend/createRenderer.js +4 -1
  10. package/dist/render/backend/webgl2/renderer.d.ts +90 -2
  11. package/dist/render/backend/webgl2/renderer.js +297 -16
  12. package/dist/render/backend/webgpu/device.d.ts +7 -1
  13. package/dist/render/backend/webgpu/device.js +31 -3
  14. package/dist/render/backend/webgpu/flatPass.d.ts +16 -1
  15. package/dist/render/backend/webgpu/flatPass.js +58 -26
  16. package/dist/render/backend/webgpu/postPass.d.ts +51 -9
  17. package/dist/render/backend/webgpu/postPass.js +16 -2
  18. package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
  19. package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
  20. package/dist/render/backend/webgpu/renderer.d.ts +131 -2
  21. package/dist/render/backend/webgpu/renderer.js +586 -70
  22. package/dist/render/displayRange.d.ts +31 -0
  23. package/dist/render/displayRange.js +47 -0
  24. package/dist/render/filmicCurve.d.ts +74 -0
  25. package/dist/render/filmicCurve.js +203 -0
  26. package/dist/render/fog.d.ts +4 -1
  27. package/dist/render/fog.js +12 -2
  28. package/dist/render/frame/resources.d.ts +2 -2
  29. package/dist/render/frame/resources.js +6 -0
  30. package/dist/render/frameReflections.d.ts +40 -0
  31. package/dist/render/frameReflections.js +48 -0
  32. package/dist/render/pass.d.ts +49 -0
  33. package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
  34. package/dist/render/reflectionSurfaceTargets.js +99 -0
  35. package/dist/render/renderQuality.d.ts +39 -8
  36. package/dist/render/renderQuality.js +4 -0
  37. package/dist/render/sceneTarget.d.ts +5 -0
  38. package/dist/render/sceneTarget.js +24 -1
  39. package/dist/render/screenSpaceReflection.d.ts +5 -0
  40. package/dist/render/screenSpaceReflection.js +5 -0
  41. package/dist/render/shaders/arcane.d.ts +1 -1
  42. package/dist/render/shaders/bolt.d.ts +1 -1
  43. package/dist/render/shaders/caustics.d.ts +1 -1
  44. package/dist/render/shaders/film.d.ts +1 -1
  45. package/dist/render/shaders/filmLook.js +2 -1
  46. package/dist/render/shaders/fire.d.ts +1 -1
  47. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  48. package/dist/render/shaders/flat/glassTint.js +2 -0
  49. package/dist/render/shaders/flat/index.d.ts +10 -0
  50. package/dist/render/shaders/flat/index.js +6 -0
  51. package/dist/render/shaders/flat/main.d.ts +1 -1
  52. package/dist/render/shaders/flat/main.js +28 -0
  53. package/dist/render/shaders/flat/overlay.d.ts +16 -0
  54. package/dist/render/shaders/flat/overlay.js +117 -0
  55. package/dist/render/shaders/flat/pointShadow.js +1 -1
  56. package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
  57. package/dist/render/shaders/flat/reflectionSurface.js +61 -0
  58. package/dist/render/shaders/fog.d.ts +1 -1
  59. package/dist/render/shaders/fog.js +9 -2
  60. package/dist/render/shaders/fringe.d.ts +29 -0
  61. package/dist/render/shaders/fringe.js +55 -0
  62. package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
  63. package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
  64. package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
  65. package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
  66. package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
  67. package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
  68. package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
  69. package/dist/render/shaders/generated/film.wgsl.js +1 -1
  70. package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
  71. package/dist/render/shaders/generated/fire.wgsl.js +1 -1
  72. package/dist/render/shaders/generated/flat.wgsl.d.ts +176 -16
  73. package/dist/render/shaders/generated/flat.wgsl.js +214 -54
  74. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  75. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  76. package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
  77. package/dist/render/shaders/generated/particle.wgsl.js +4 -4
  78. package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
  79. package/dist/render/shaders/generated/rush.wgsl.js +56 -26
  80. package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
  81. package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
  82. package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
  83. package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
  84. package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
  85. package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
  86. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +22 -2
  87. package/dist/render/shaders/generated/ssrTrace.wgsl.js +22 -2
  88. package/dist/render/shaders/generated/water.wgsl.d.ts +1 -1
  89. package/dist/render/shaders/generated/water.wgsl.js +1 -1
  90. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
  91. package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
  92. package/dist/render/shaders/line.d.ts +1 -1
  93. package/dist/render/shaders/particle.d.ts +3 -3
  94. package/dist/render/shaders/rush.js +54 -11
  95. package/dist/render/shaders/scatter.d.ts +1 -1
  96. package/dist/render/shaders/smoke.d.ts +1 -1
  97. package/dist/render/shaders/ssrResolve.d.ts +17 -0
  98. package/dist/render/shaders/ssrResolve.js +51 -0
  99. package/dist/render/shaders/ssrTrace.js +26 -0
  100. package/dist/render/shaders/water.d.ts +1 -1
  101. package/dist/render/ssrPass.d.ts +16 -0
  102. package/dist/render/ssrPass.js +106 -1
  103. package/dist/render/surfaceOverlay.d.ts +132 -0
  104. package/dist/render/surfaceOverlay.js +85 -0
  105. package/dist/render/surfaceTexture.d.ts +19 -0
  106. package/dist/render/surfaceTexture.js +69 -0
  107. package/dist/render/temporalAa.d.ts +14 -0
  108. package/dist/render/temporalAa.js +20 -0
  109. package/dist/render/vertexDefaults.d.ts +17 -0
  110. package/dist/render/vertexDefaults.js +24 -0
  111. package/package.json +6 -6
  112. package/src/index.ts +16 -0
  113. package/src/render/atmosphere.ts +22 -1
  114. package/src/render/backend/api.ts +9 -0
  115. package/src/render/backend/createRenderer.ts +18 -1
  116. package/src/render/backend/webgl2/renderer.ts +357 -13
  117. package/src/render/backend/webgpu/device.ts +42 -3
  118. package/src/render/backend/webgpu/flatPass.ts +63 -26
  119. package/src/render/backend/webgpu/postPass.ts +18 -0
  120. package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
  121. package/src/render/backend/webgpu/renderer.ts +735 -83
  122. package/src/render/displayRange.ts +61 -0
  123. package/src/render/filmicCurve.ts +229 -0
  124. package/src/render/fog.ts +19 -3
  125. package/src/render/frame/resources.ts +6 -0
  126. package/src/render/frameReflections.ts +72 -0
  127. package/src/render/pass.ts +53 -1
  128. package/src/render/reflectionSurfaceTargets.ts +105 -0
  129. package/src/render/renderQuality.ts +44 -8
  130. package/src/render/sceneTarget.ts +30 -2
  131. package/src/render/screenSpaceReflection.ts +5 -0
  132. package/src/render/shaders/filmLook.ts +2 -1
  133. package/src/render/shaders/flat/glassTint.ts +5 -1
  134. package/src/render/shaders/flat/index.ts +16 -0
  135. package/src/render/shaders/flat/main.ts +28 -0
  136. package/src/render/shaders/flat/overlay.ts +117 -0
  137. package/src/render/shaders/flat/pointShadow.ts +1 -1
  138. package/src/render/shaders/flat/reflectionSurface.ts +61 -0
  139. package/src/render/shaders/fog.ts +9 -2
  140. package/src/render/shaders/fringe.ts +59 -0
  141. package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
  142. package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
  143. package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
  144. package/src/render/shaders/generated/film.wgsl.ts +1 -1
  145. package/src/render/shaders/generated/fire.wgsl.ts +1 -1
  146. package/src/render/shaders/generated/flat.wgsl.ts +214 -54
  147. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  148. package/src/render/shaders/generated/particle.wgsl.ts +4 -4
  149. package/src/render/shaders/generated/rush.wgsl.ts +56 -26
  150. package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
  151. package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
  152. package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
  153. package/src/render/shaders/generated/ssrTrace.wgsl.ts +22 -2
  154. package/src/render/shaders/generated/water.wgsl.ts +1 -1
  155. package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
  156. package/src/render/shaders/rush.ts +54 -11
  157. package/src/render/shaders/ssrResolve.ts +52 -0
  158. package/src/render/shaders/ssrTrace.ts +26 -0
  159. package/src/render/ssrPass.ts +138 -1
  160. package/src/render/surfaceOverlay.ts +195 -0
  161. package/src/render/surfaceTexture.ts +67 -0
  162. package/src/render/temporalAa.ts +20 -0
  163. package/src/render/vertexDefaults.ts +28 -0
@@ -1,5 +1,6 @@
1
1
  import type { ReadonlyMat4 } from 'gl-matrix';
2
2
  import type { ReflectionQueue } from './screenSpaceReflection.ts';
3
+ import type { ResolvedFrameReflections } from './frameReflections.ts';
3
4
  /**
4
5
  * Screen-space reflection on WebGL2: a scissored trace per surface, then one composite.
5
6
  *
@@ -33,6 +34,9 @@ export declare class SsrPass {
33
34
  private width;
34
35
  private height;
35
36
  /** `inverse(viewProjection) * DEPTH_01_TO_CLIP`, rebuilt once a frame rather than per surface. */
37
+ /** The frame's materials' resolve, compiled the first time a frame traces them. */
38
+ private materialResolve;
39
+ private materialUniforms;
36
40
  private readonly depthToWorld;
37
41
  private readonly scissor;
38
42
  constructor(gl: WebGL2RenderingContext,
@@ -54,6 +58,18 @@ export declare class SsrPass {
54
58
  */
55
59
  traceReflections(queue: ReflectionQueue, scene: WebGLTexture, depth: WebGLTexture, viewProjection: ReadonlyMat4, eye: ArrayLike<number>, width: number, height: number, edgeFade: number): void;
56
60
  /** Composite what the trace found over whatever framebuffer is bound. */
61
+ /**
62
+ * Every pixel the lit stage left a reflection for, marched against the frame's depth by the
63
+ * trace's material mode, into this pass's own target; `resolveMaterials` then swaps the probe's
64
+ * share for it. See `frameReflections.ts`. Ends on the default framebuffer, as the surfaces' does.
65
+ */
66
+ traceMaterials(scene: WebGLTexture, depth: WebGLTexture, probe: WebGLTexture, tint: WebGLTexture, viewProjection: ReadonlyMat4, eye: ArrayLike<number>, width: number, height: number, edgeFade: number, reflections: ResolvedFrameReflections): void;
67
+ /**
68
+ * The probe's share of each pixel swapped for what `traceMaterials` found, blurred by the
69
+ * surface's roughness, added onto whatever framebuffer is bound — the scene's, with the two maps
70
+ * already detached from it. See `SSR_MATERIAL_RESOLVE_FRAG`.
71
+ */
72
+ resolveMaterials(probe: WebGLTexture, tint: WebGLTexture, width: number, height: number, reflections: ResolvedFrameReflections): void;
57
73
  resolveReflections(): void;
58
74
  dispose(): void;
59
75
  }
@@ -3,7 +3,7 @@ import { decalScissor } from './decalProjector.js';
3
3
  import { DEPTH_01_TO_CLIP } from './lightVolumeDraw.js';
4
4
  import { compileProgram, uniformLocations } from './shader.js';
5
5
  import { FULLSCREEN_VERT } from './shaders/fullscreen.js';
6
- import { SSR_RESOLVE_FRAG } from './shaders/ssrResolve.js';
6
+ import { SSR_MATERIAL_RESOLVE_FRAG, SSR_RESOLVE_FRAG } from './shaders/ssrResolve.js';
7
7
  import { SSR_TRACE_FRAG } from './shaders/ssrTrace.js';
8
8
  import { blendKeeping } from './sceneCoverage.js';
9
9
  /**
@@ -32,6 +32,9 @@ export class SsrPass {
32
32
  width = 0;
33
33
  height = 0;
34
34
  /** `inverse(viewProjection) * DEPTH_01_TO_CLIP`, rebuilt once a frame rather than per surface. */
35
+ /** The frame's materials' resolve, compiled the first time a frame traces them. */
36
+ materialResolve = null;
37
+ materialUniforms = {};
35
38
  depthToWorld = new Float32Array(16);
36
39
  scissor = new Int32Array(4);
37
40
  constructor(gl,
@@ -118,6 +121,11 @@ export class SsrPass {
118
121
  gl.activeTexture(gl.TEXTURE1);
119
122
  gl.bindTexture(gl.TEXTURE_2D, scene);
120
123
  gl.uniform1i(u['uSsrScene'] ?? null, 1);
124
+ /* The materials' maps unread in this mode, and pointed at a bound texture all the same: a
125
+ driver may fetch a sampler's descriptor before the branch that skips it. */
126
+ gl.uniform1i(u['uSsrProbeMap'] ?? null, 0);
127
+ gl.uniform1i(u['uSsrTintMap'] ?? null, 0);
128
+ gl.uniform1f(u['uSsrMaterial'] ?? null, 0);
121
129
  gl.uniformMatrix4fv(u['uSsrDepthToWorld'] ?? null, false, this.depthToWorld);
122
130
  /* No Y flip on this backend: `gl.scissor` and the framebuffer both count rows from the bottom,
123
131
  which is the space the raw matrix already emits. See `CLIP_Y_FLIP`. */
@@ -153,6 +161,100 @@ export class SsrPass {
153
161
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
154
162
  }
155
163
  /** Composite what the trace found over whatever framebuffer is bound. */
164
+ /**
165
+ * Every pixel the lit stage left a reflection for, marched against the frame's depth by the
166
+ * trace's material mode, into this pass's own target; `resolveMaterials` then swaps the probe's
167
+ * share for it. See `frameReflections.ts`. Ends on the default framebuffer, as the surfaces' does.
168
+ */
169
+ traceMaterials(scene, depth, probe, tint, viewProjection, eye, width, height, edgeFade, reflections) {
170
+ const { gl } = this;
171
+ const u = this.traceUniforms;
172
+ this.ensureSize(width, height);
173
+ mat4.invert(this.depthToWorld, viewProjection);
174
+ mat4.multiply(this.depthToWorld, this.depthToWorld, DEPTH_01_TO_CLIP);
175
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
176
+ gl.viewport(0, 0, width, height);
177
+ gl.disable(gl.DEPTH_TEST);
178
+ gl.depthMask(false);
179
+ gl.disable(gl.CULL_FACE);
180
+ gl.disable(gl.SCISSOR_TEST);
181
+ gl.disable(gl.BLEND);
182
+ gl.clearBufferfv(gl.COLOR, 0, [0, 0, 0, 0]);
183
+ gl.useProgram(this.trace);
184
+ gl.bindVertexArray(this.vao);
185
+ const textures = [depth, scene, probe, tint];
186
+ const names = ['uSsrDepth', 'uSsrScene', 'uSsrProbeMap', 'uSsrTintMap'];
187
+ for (let unit = 0; unit < 4; unit++) {
188
+ gl.activeTexture(gl.TEXTURE0 + unit);
189
+ gl.bindTexture(gl.TEXTURE_2D, textures[unit] ?? null);
190
+ gl.uniform1i(u[names[unit] ?? ''] ?? null, unit);
191
+ }
192
+ gl.uniformMatrix4fv(u['uSsrDepthToWorld'] ?? null, false, this.depthToWorld);
193
+ gl.uniformMatrix4fv(u['uSsrViewProj'] ?? null, false, viewProjection);
194
+ gl.uniform3f(u['uSsrEye'] ?? null, eye[0] ?? 0, eye[1] ?? 0, eye[2] ?? 0);
195
+ gl.uniform1f(u['uSsrEdgeFade'] ?? null, edgeFade);
196
+ gl.uniform3f(u['uSsrAxis'] ?? null, 0, 0, 0);
197
+ gl.uniform3f(u['uSsrTint'] ?? null, 1, 1, 1);
198
+ gl.uniform1f(u['uSsrStrength'] ?? null, 1);
199
+ gl.uniform2f(u['uSsrFresnel'] ?? null, 0, 0);
200
+ gl.uniform1f(u['uSsrFacingCos'] ?? null, -1);
201
+ gl.uniform1f(u['uSsrReach'] ?? null, reflections.reachM);
202
+ gl.uniform1f(u['uSsrThickness'] ?? null, reflections.thicknessM);
203
+ gl.uniform1f(u['uSsrSteps'] ?? null, reflections.steps);
204
+ gl.uniform1f(u['uSsrMaterial'] ?? null, 1);
205
+ gl.uniform1f(u['uSsrMaxRoughness'] ?? null, reflections.maxRoughness);
206
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
207
+ /* Back to the surfaces' mode, which every other trace in the frame is. */
208
+ gl.uniform1f(u['uSsrMaterial'] ?? null, 0);
209
+ gl.bindVertexArray(null);
210
+ for (let unit = 3; unit >= 0; unit--) {
211
+ gl.activeTexture(gl.TEXTURE0 + unit);
212
+ gl.bindTexture(gl.TEXTURE_2D, null);
213
+ }
214
+ gl.depthMask(true);
215
+ gl.enable(gl.DEPTH_TEST);
216
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
217
+ }
218
+ /**
219
+ * The probe's share of each pixel swapped for what `traceMaterials` found, blurred by the
220
+ * surface's roughness, added onto whatever framebuffer is bound — the scene's, with the two maps
221
+ * already detached from it. See `SSR_MATERIAL_RESOLVE_FRAG`.
222
+ */
223
+ resolveMaterials(probe, tint, width, height, reflections) {
224
+ const { gl } = this;
225
+ if (this.reflection === null)
226
+ return;
227
+ if (this.materialResolve === null) {
228
+ this.materialResolve = compileProgram(gl, FULLSCREEN_VERT, SSR_MATERIAL_RESOLVE_FRAG, 'ssrMaterialResolve');
229
+ this.materialUniforms = uniformLocations(gl, this.materialResolve, 'ssrMaterialResolve');
230
+ }
231
+ const u = this.materialUniforms;
232
+ gl.disable(gl.DEPTH_TEST);
233
+ gl.depthMask(false);
234
+ gl.disable(gl.CULL_FACE);
235
+ gl.enable(gl.BLEND);
236
+ /* Added, a negative addend subtracting, and the scene's alpha kept. */
237
+ blendKeeping(gl, gl.ONE, gl.ONE);
238
+ gl.useProgram(this.materialResolve);
239
+ const textures = [this.reflection, probe, tint];
240
+ const names = ['uSsrReflection', 'uSsrProbeMap', 'uSsrTintMap'];
241
+ for (let unit = 0; unit < 3; unit++) {
242
+ gl.activeTexture(gl.TEXTURE0 + unit);
243
+ gl.bindTexture(gl.TEXTURE_2D, textures[unit] ?? null);
244
+ gl.uniform1i(u[names[unit] ?? ''] ?? null, unit);
245
+ }
246
+ gl.uniform3f(u['uSsrBlur'] ?? null, 1 / Math.max(width, 1), 1 / Math.max(height, 1), reflections.blur * height);
247
+ gl.bindVertexArray(this.vao);
248
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
249
+ gl.bindVertexArray(null);
250
+ for (let unit = 2; unit >= 0; unit--) {
251
+ gl.activeTexture(gl.TEXTURE0 + unit);
252
+ gl.bindTexture(gl.TEXTURE_2D, null);
253
+ }
254
+ gl.disable(gl.BLEND);
255
+ gl.depthMask(true);
256
+ gl.enable(gl.DEPTH_TEST);
257
+ }
156
258
  resolveReflections() {
157
259
  const { gl } = this;
158
260
  if (this.reflection === null)
@@ -189,6 +291,9 @@ export class SsrPass {
189
291
  gl.deleteFramebuffer(this.framebuffer);
190
292
  gl.deleteProgram(this.trace);
191
293
  gl.deleteProgram(this.resolve);
294
+ if (this.materialResolve !== null)
295
+ gl.deleteProgram(this.materialResolve);
296
+ this.materialResolve = null;
192
297
  gl.deleteVertexArray(this.vao);
193
298
  this.reflection = null;
194
299
  this.framebuffer = null;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * What a draw lays over its surface beyond its material: a rim of light, an emissive overlay that
3
+ * dissolves the surface away, and a second normal map blended in by region. Set per draw with
4
+ * `RendererApi.setSurfaceOverlay`, so one character's whole set of materials changes together
5
+ * while another's does not.
6
+ *
7
+ * **Per draw and held, like `setEnvironmentGain`**: set before a character's draws, cleared after,
8
+ * and every draw between them wears it. **Images come from one atlas** (`maps`), each named by the
9
+ * rectangle it occupies — a scale and an offset in the atlas's uv — because the lit stage binds every
10
+ * sampler a device is guaranteed and has none left for a texture of its own: the atlas is bound in
11
+ * the slot the frame's refraction copy takes, which an opaque surface never reads. A draw that both
12
+ * refracts and wears an overlay's images keeps its refraction and loses the images, said once.
13
+ *
14
+ * **Fifteen vectors of numbers**, in the material's uniform block. WebGPU has the room; WebGL2 has
15
+ * room where its device reports more than the lit stage's 254 vectors, and refuses the overlay in
16
+ * words where it does not. See `packSurfaceOverlay` for the layout.
17
+ */
18
+ import type { Vec3 } from '../math/color.ts';
19
+ import type { SurfaceTextureHandle } from './backend/api.ts';
20
+ /** Where an image sits in the atlas: its size and its corner, each in the atlas's uv. */
21
+ export interface OverlayRegion {
22
+ readonly scale: readonly [number, number];
23
+ readonly offset: readonly [number, number];
24
+ }
25
+ /**
26
+ * Light along the silhouette: the edge `(1 - N·V)^falloff`, leaning toward normals that face up,
27
+ * raised to a contrast, times a noise laid in screen space, a slow pulse and a mask, in a colour.
28
+ * Added to what the surface emits, and not gated on the night as emission is: it is a glow the
29
+ * surface wears, not light it gives off.
30
+ */
31
+ export interface SurfaceRim {
32
+ readonly colour: Vec3;
33
+ /** How bright, 0 for none. Multiplied with `alpha`. */
34
+ readonly intensity: number;
35
+ readonly alpha?: number;
36
+ /** The edge's exponent: larger keeps the rim nearer the silhouette. 1.5 unless given. */
37
+ readonly falloff?: number;
38
+ /**
39
+ * How far the rim leans toward normals that face up: the weight is `(N·up · ½ + ½)^upward`, so
40
+ * 0 is no lean. 0.4 unless given.
41
+ */
42
+ readonly upward?: number;
43
+ /** The exponent the whole edge is raised to. 1 unless given. */
44
+ readonly contrast?: number;
45
+ /** A noise image laid in screen space and scrolled, the rim taken times it, clamped to 1. */
46
+ readonly noise?: {
47
+ readonly region: OverlayRegion;
48
+ /** Screens a second, along x and y. */
49
+ readonly scroll?: readonly [number, number];
50
+ /** Repeats across the screen's height. 1 unless given. */
51
+ readonly tiling?: number;
52
+ };
53
+ /** A pulse `½ + ½ sin(rate · t)`, held between `low` and `high`, on `Environment.surfaceTime`. */
54
+ readonly pulse?: {
55
+ readonly rate: number;
56
+ readonly low: number;
57
+ readonly high: number;
58
+ };
59
+ /** A mask in the mesh's uv where the rim is erased: the rim times `1 - weight · mask`. */
60
+ readonly mask?: {
61
+ readonly region: OverlayRegion;
62
+ readonly weight: number;
63
+ };
64
+ }
65
+ /**
66
+ * The surface cut away where a noise in its uv falls under `threshold`, with a glowing band just
67
+ * above it, and a colour laid over the surface through the same noise.
68
+ */
69
+ export interface SurfaceDissolve {
70
+ readonly noise: {
71
+ readonly region: OverlayRegion;
72
+ readonly tiling?: readonly [number, number];
73
+ };
74
+ /** 0 keeps everything; 1 cuts everything. */
75
+ readonly threshold: number;
76
+ /** The band's width, in the noise's own units. 0.05 unless given. */
77
+ readonly edge?: number;
78
+ /** The band's light. */
79
+ readonly edgeColour?: Vec3;
80
+ readonly edgeIntensity?: number;
81
+ /** A colour laid over the surface through the noise, as light, and how much of it. */
82
+ readonly overlay?: {
83
+ readonly colour: Vec3;
84
+ readonly amount: number;
85
+ };
86
+ }
87
+ /**
88
+ * A second normal map blended into the surface's by six region weights, read through two masks:
89
+ * the first mask's red, green and blue carry regions 1 to 3, the second's regions 4 to 6. A face's
90
+ * expressions, or veins brought up all over.
91
+ */
92
+ export interface SurfaceWrinkle {
93
+ readonly normal: OverlayRegion;
94
+ readonly masks: readonly [OverlayRegion, OverlayRegion];
95
+ readonly weights: readonly [number, number, number, number, number, number];
96
+ }
97
+ /**
98
+ * What a draw lays over its surface beyond its material, for `setSurfaceOverlay`: a rim, a dissolve
99
+ * and wrinkles, each optional, and the atlas their images are regions of.
100
+ */
101
+ export interface SurfaceOverlay {
102
+ /**
103
+ * The atlas every image above is a region of. Absent: no image is read whatever regions are
104
+ * named — rims are smooth, nothing dissolves and nothing wrinkles — because the slot then holds a
105
+ * stand-in whose colour would read as a noise and a mask.
106
+ */
107
+ readonly maps?: SurfaceTextureHandle;
108
+ readonly rim?: SurfaceRim;
109
+ readonly dissolve?: SurfaceDissolve;
110
+ readonly wrinkle?: SurfaceWrinkle;
111
+ }
112
+ /** Vectors the overlay takes in the material's block: `uOverlay[OVERLAY_VECTORS]`. */
113
+ export declare const OVERLAY_VECTORS = 15;
114
+ /** Floats `packSurfaceOverlay` fills. */
115
+ export declare const OVERLAY_FLOATS: number;
116
+ /**
117
+ * An overlay as the shader reads it, into `out` (`OVERLAY_FLOATS`); all zeros for `null`, which is
118
+ * no overlay. A region with a zero scale is an image not given, and its term reads as none — a
119
+ * noise of 1, a mask of 0, no wrinkle — and every region is one without `maps`. Fifteen vectors:
120
+ *
121
+ * - 0: rim colour, rim strength (intensity times alpha; 0 is no rim)
122
+ * - 1: falloff, upward lean, contrast, mask weight
123
+ * - 2: the rim's noise region; 3: its scroll along x and y, its tiling, the pulse's rate
124
+ * - 4: the pulse's low and high (1 and 1 where none)
125
+ * - 5: the rim's mask region
126
+ * - 6: the dissolve's noise region (a zero scale is no dissolve); 7: its tiling, threshold, edge
127
+ * - 8: the edge's light and its strength; 9: the laid-over colour and its amount
128
+ * - 10: the wrinkle normal's region; 11 and 12: the two masks'; 13 and 14: the six weights
129
+ */
130
+ export declare function packSurfaceOverlay(overlay: SurfaceOverlay | null, out: Float32Array): void;
131
+ /** Whether an overlay lays anything at all, so a renderer can leave the lit switch off. */
132
+ export declare function overlayLays(overlay: SurfaceOverlay | null): boolean;
@@ -0,0 +1,85 @@
1
+ /** Vectors the overlay takes in the material's block: `uOverlay[OVERLAY_VECTORS]`. */
2
+ export const OVERLAY_VECTORS = 15;
3
+ /** Floats `packSurfaceOverlay` fills. */
4
+ export const OVERLAY_FLOATS = OVERLAY_VECTORS * 4;
5
+ const finite = (value, fallback) => value !== undefined && Number.isFinite(value) ? value : fallback;
6
+ function region(out, at, r) {
7
+ out[at] = finite(r?.scale[0], 0);
8
+ out[at + 1] = finite(r?.scale[1], 0);
9
+ out[at + 2] = finite(r?.offset[0], 0);
10
+ out[at + 3] = finite(r?.offset[1], 0);
11
+ }
12
+ /**
13
+ * An overlay as the shader reads it, into `out` (`OVERLAY_FLOATS`); all zeros for `null`, which is
14
+ * no overlay. A region with a zero scale is an image not given, and its term reads as none — a
15
+ * noise of 1, a mask of 0, no wrinkle — and every region is one without `maps`. Fifteen vectors:
16
+ *
17
+ * - 0: rim colour, rim strength (intensity times alpha; 0 is no rim)
18
+ * - 1: falloff, upward lean, contrast, mask weight
19
+ * - 2: the rim's noise region; 3: its scroll along x and y, its tiling, the pulse's rate
20
+ * - 4: the pulse's low and high (1 and 1 where none)
21
+ * - 5: the rim's mask region
22
+ * - 6: the dissolve's noise region (a zero scale is no dissolve); 7: its tiling, threshold, edge
23
+ * - 8: the edge's light and its strength; 9: the laid-over colour and its amount
24
+ * - 10: the wrinkle normal's region; 11 and 12: the two masks'; 13 and 14: the six weights
25
+ */
26
+ export function packSurfaceOverlay(overlay, out) {
27
+ out.fill(0);
28
+ if (overlay === null)
29
+ return;
30
+ /* No atlas, no region: see `SurfaceOverlay.maps`. */
31
+ const images = overlay.maps !== undefined;
32
+ const rim = overlay.rim;
33
+ if (rim !== undefined) {
34
+ out[0] = finite(rim.colour[0], 0);
35
+ out[1] = finite(rim.colour[1], 0);
36
+ out[2] = finite(rim.colour[2], 0);
37
+ out[3] = Math.max(0, finite(rim.intensity, 0) * finite(rim.alpha, 1));
38
+ out[4] = Math.max(0, finite(rim.falloff, 1.5));
39
+ out[5] = Math.max(0, finite(rim.upward, 0.4));
40
+ out[6] = Math.max(0, finite(rim.contrast, 1));
41
+ out[7] = rim.mask === undefined ? 0 : Math.min(Math.max(finite(rim.mask.weight, 0), 0), 1);
42
+ region(out, 8, images ? rim.noise?.region : null);
43
+ out[12] = finite(rim.noise?.scroll?.[0], 0);
44
+ out[13] = finite(rim.noise?.scroll?.[1], 0);
45
+ out[14] = Math.max(0, finite(rim.noise?.tiling, 1));
46
+ out[15] = finite(rim.pulse?.rate, 0);
47
+ out[16] = rim.pulse === undefined ? 1 : finite(rim.pulse.low, 1);
48
+ out[17] = rim.pulse === undefined ? 1 : finite(rim.pulse.high, 1);
49
+ region(out, 20, images ? rim.mask?.region : null);
50
+ }
51
+ const dissolve = overlay.dissolve;
52
+ if (dissolve !== undefined) {
53
+ region(out, 24, images ? dissolve.noise.region : null);
54
+ out[28] = finite(dissolve.noise.tiling?.[0], 1);
55
+ out[29] = finite(dissolve.noise.tiling?.[1], 1);
56
+ out[30] = Math.min(Math.max(finite(dissolve.threshold, 0), 0), 1);
57
+ out[31] = Math.max(1e-4, finite(dissolve.edge, 0.05));
58
+ const edge = dissolve.edgeColour ?? [0, 0, 0];
59
+ out[32] = finite(edge[0], 0);
60
+ out[33] = finite(edge[1], 0);
61
+ out[34] = finite(edge[2], 0);
62
+ out[35] = Math.max(0, finite(dissolve.edgeIntensity, 1));
63
+ const laid = dissolve.overlay;
64
+ if (laid !== undefined) {
65
+ out[36] = finite(laid.colour[0], 0);
66
+ out[37] = finite(laid.colour[1], 0);
67
+ out[38] = finite(laid.colour[2], 0);
68
+ out[39] = Math.max(0, finite(laid.amount, 0));
69
+ }
70
+ }
71
+ const wrinkle = overlay.wrinkle;
72
+ if (wrinkle !== undefined) {
73
+ region(out, 40, images ? wrinkle.normal : null);
74
+ region(out, 44, images ? wrinkle.masks[0] : null);
75
+ region(out, 48, images ? wrinkle.masks[1] : null);
76
+ for (let i = 0; i < 6; i++) {
77
+ out[52 + i] = Math.min(Math.max(finite(wrinkle.weights[i], 0), 0), 1);
78
+ }
79
+ }
80
+ }
81
+ /** Whether an overlay lays anything at all, so a renderer can leave the lit switch off. */
82
+ export function overlayLays(overlay) {
83
+ return (overlay !== null &&
84
+ (overlay.rim !== undefined || overlay.dissolve !== undefined || overlay.wrinkle !== undefined));
85
+ }
@@ -276,6 +276,13 @@ export declare class SurfaceTexture {
276
276
  private effectsTable;
277
277
  /** The block format a compressed texture holds, or null for one uploaded from images. */
278
278
  private readonly blockFormat;
279
+ /** Level 0's size, which `flatLayer` copies; kept as `update` changes it. */
280
+ private width;
281
+ private height;
282
+ /** The texture's storage format, which `flatLayer`'s copy matches so the copy converts nothing. */
283
+ private readonly storage;
284
+ /** Layer 0 as a plain 2D texture, made by `flatLayer` and dropped when the pixels change. */
285
+ private flat;
279
286
  /**
280
287
  * One image, or an array of images that must share one size (`layerSize` refuses otherwise).
281
288
  * Uploaded unflipped either way; see the constructor body for why. Or blocks, uploaded as they are
@@ -304,5 +311,17 @@ export declare class SurfaceTexture {
304
311
  attachColor(gl: WebGL2RenderingContext): void;
305
312
  /** Bind to a unit for sampling. Engine-internal: the renderer owns unit assignment. */
306
313
  bind(gl: WebGL2RenderingContext, unit: number): void;
314
+ /**
315
+ * Layer 0 as a plain 2D texture: what a `sampler2D` can be handed, which an array cannot. Copied
316
+ * the first time it is asked for, by a framebuffer blit into storage of the same format, and kept
317
+ * until `update` or `dispose`. Engine-internal: a surface overlay's atlas is bound where the
318
+ * lit stage's one 2D image slot is (`surfaceOverlay.ts`).
319
+ *
320
+ * **Null for blocks**, which no framebuffer can read; the caller says so. What it costs is a copy
321
+ * of level 0 in memory beside the array, and the bindings of both framebuffer targets, which are
322
+ * read back before the blit and put back after it — a sync query, paid once per image.
323
+ */
324
+ flatLayer(gl: WebGL2RenderingContext): WebGLTexture | null;
325
+ private dropFlat;
307
326
  dispose(gl: WebGL2RenderingContext): void;
308
327
  }
@@ -25,6 +25,13 @@ export class SurfaceTexture {
25
25
  effectsTable = null;
26
26
  /** The block format a compressed texture holds, or null for one uploaded from images. */
27
27
  blockFormat;
28
+ /** Level 0's size, which `flatLayer` copies; kept as `update` changes it. */
29
+ width = 0;
30
+ height = 0;
31
+ /** The texture's storage format, which `flatLayer`'s copy matches so the copy converts nothing. */
32
+ storage;
33
+ /** Layer 0 as a plain 2D texture, made by `flatLayer` and dropped when the pixels change. */
34
+ flat = null;
28
35
  /**
29
36
  * One image, or an array of images that must share one size (`layerSize` refuses otherwise).
30
37
  * Uploaded unflipped either way; see the constructor body for why. Or blocks, uploaded as they are
@@ -51,6 +58,9 @@ export class SurfaceTexture {
51
58
  this.layers = 1;
52
59
  this.blockFormat = null;
53
60
  this.mipmapped = false;
61
+ this.width = capture.width;
62
+ this.height = capture.height;
63
+ this.storage = capture.float ? gl.RGBA16F : gl.RGBA8;
54
64
  gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, capture.float ? gl.RGBA16F : gl.RGBA8, capture.width, capture.height, 1);
55
65
  }
56
66
  else if (baked !== null) {
@@ -58,6 +68,9 @@ export class SurfaceTexture {
58
68
  this.layers = baked.layers.length;
59
69
  this.blockFormat = null;
60
70
  this.mipmapped = false;
71
+ this.width = baked.width;
72
+ this.height = baked.height;
73
+ this.storage = gl.RGBA8;
61
74
  /* A typed array refuses either flag set, so both are put down here rather than assumed. */
62
75
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
63
76
  gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
@@ -73,6 +86,7 @@ export class SurfaceTexture {
73
86
  `generateMipmap` cannot write a compressed format. */
74
87
  this.layers = blocks.length;
75
88
  this.blockFormat = blocks[0].format;
89
+ this.storage = gl.NONE;
76
90
  this.mipmapped = (options.mipmap ?? true) && plan.levels > 1;
77
91
  uploadCompressedArray(gl, plan.name, blocks, this.mipmapped ? plan.levels : 1);
78
92
  }
@@ -81,6 +95,10 @@ export class SurfaceTexture {
81
95
  this.layers = sources.length;
82
96
  this.blockFormat = null;
83
97
  this.mipmapped = options.mipmap ?? true;
98
+ const { width, height } = layerSize(sources);
99
+ this.width = width;
100
+ this.height = height;
101
+ this.storage = this.srgb ? gl.SRGB8_ALPHA8 : gl.RGBA8;
84
102
  uploadImages(gl, sources, this.srgb);
85
103
  }
86
104
  gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, wrap);
@@ -143,6 +161,9 @@ export class SurfaceTexture {
143
161
  refuseArrayUpdate(this.layers);
144
162
  refuseBlockUpdate(this.blockFormat);
145
163
  const { width, height } = sourceSize(source);
164
+ this.width = width;
165
+ this.height = height;
166
+ this.dropFlat(gl);
146
167
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
147
168
  /* Unflipped, matching the constructor. See it for why, and for what it cost. */
148
169
  gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, this.srgb ? gl.SRGB8_ALPHA8 : gl.RGBA, width, height, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, source);
@@ -162,9 +183,57 @@ export class SurfaceTexture {
162
183
  gl.activeTexture(gl.TEXTURE0 + unit);
163
184
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
164
185
  }
186
+ /**
187
+ * Layer 0 as a plain 2D texture: what a `sampler2D` can be handed, which an array cannot. Copied
188
+ * the first time it is asked for, by a framebuffer blit into storage of the same format, and kept
189
+ * until `update` or `dispose`. Engine-internal: a surface overlay's atlas is bound where the
190
+ * lit stage's one 2D image slot is (`surfaceOverlay.ts`).
191
+ *
192
+ * **Null for blocks**, which no framebuffer can read; the caller says so. What it costs is a copy
193
+ * of level 0 in memory beside the array, and the bindings of both framebuffer targets, which are
194
+ * read back before the blit and put back after it — a sync query, paid once per image.
195
+ */
196
+ flatLayer(gl) {
197
+ if (this.texture === null || this.blockFormat !== null)
198
+ return null;
199
+ if (this.flat !== null)
200
+ return this.flat;
201
+ const flat = gl.createTexture();
202
+ const read = gl.createFramebuffer();
203
+ const draw = gl.createFramebuffer();
204
+ if (flat === null || read === null || draw === null)
205
+ return null;
206
+ gl.bindTexture(gl.TEXTURE_2D, flat);
207
+ gl.texStorage2D(gl.TEXTURE_2D, 1, this.storage, this.width, this.height);
208
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
209
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
210
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
211
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
212
+ gl.bindTexture(gl.TEXTURE_2D, null);
213
+ const heldRead = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING);
214
+ const heldDraw = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING);
215
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, read);
216
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, 0);
217
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, draw);
218
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, flat, 0);
219
+ const [w, h] = [this.width, this.height];
220
+ gl.blitFramebuffer(0, 0, w, h, 0, 0, w, h, gl.COLOR_BUFFER_BIT, gl.NEAREST);
221
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, heldRead);
222
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, heldDraw);
223
+ gl.deleteFramebuffer(read);
224
+ gl.deleteFramebuffer(draw);
225
+ this.flat = flat;
226
+ return flat;
227
+ }
228
+ dropFlat(gl) {
229
+ if (this.flat !== null)
230
+ gl.deleteTexture(this.flat);
231
+ this.flat = null;
232
+ }
165
233
  dispose(gl) {
166
234
  if (this.texture === null)
167
235
  return;
236
+ this.dropFlat(gl);
168
237
  gl.deleteTexture(this.texture);
169
238
  this.texture = null;
170
239
  if (this.effectsTable !== null)
@@ -248,12 +248,26 @@ export declare class TemporalHistory {
248
248
  private picture;
249
249
  private width;
250
250
  private height;
251
+ /** Whether this frame of the renderer's has opened one of the sequence, and what it answered. */
252
+ private opened;
253
+ private answer;
254
+ /**
255
+ * A frame of the renderer's began: the next `openFrame` opens a frame of the sequence. Called at
256
+ * `beginFrame`, by both backends.
257
+ */
258
+ nextFrame(): void;
251
259
  /**
252
260
  * Open a frame at this target size, and answer whether the history may be sampled.
253
261
  *
254
262
  * **The jitter advances whether or not it may be.** Holding the sequence still on the frames a
255
263
  * resize or a cut discarded would sample the same position twice running, which is one sample
256
264
  * and not two.
265
+ *
266
+ * **Once a frame, however many times it is asked.** A renderer asks where the scene's camera
267
+ * arrives, and a frame binds that camera again after a scene capture or anything else that bound
268
+ * another; each asking opened a frame of its own, so the sequence stepped twice in such a frame
269
+ * and the edges of the picture swam. Asked again, it gives the answer it gave. What it gives up:
270
+ * a size changed between two asks in one frame is seen at the next frame, not at once.
257
271
  */
258
272
  openFrame(width: number, height: number): boolean;
259
273
  /**
@@ -355,20 +355,40 @@ export class TemporalHistory {
355
355
  picture = false;
356
356
  width = 0;
357
357
  height = 0;
358
+ /** Whether this frame of the renderer's has opened one of the sequence, and what it answered. */
359
+ opened = false;
360
+ answer = false;
361
+ /**
362
+ * A frame of the renderer's began: the next `openFrame` opens a frame of the sequence. Called at
363
+ * `beginFrame`, by both backends.
364
+ */
365
+ nextFrame() {
366
+ this.opened = false;
367
+ }
358
368
  /**
359
369
  * Open a frame at this target size, and answer whether the history may be sampled.
360
370
  *
361
371
  * **The jitter advances whether or not it may be.** Holding the sequence still on the frames a
362
372
  * resize or a cut discarded would sample the same position twice running, which is one sample
363
373
  * and not two.
374
+ *
375
+ * **Once a frame, however many times it is asked.** A renderer asks where the scene's camera
376
+ * arrives, and a frame binds that camera again after a scene capture or anything else that bound
377
+ * another; each asking opened a frame of its own, so the sequence stepped twice in such a frame
378
+ * and the edges of the picture swam. Asked again, it gives the answer it gave. What it gives up:
379
+ * a size changed between two asks in one frame is seen at the next frame, not at once.
364
380
  */
365
381
  openFrame(width, height) {
382
+ if (this.opened)
383
+ return this.answer;
384
+ this.opened = true;
366
385
  this.frameIndex++;
367
386
  const usable = this.picture && width === this.width && height === this.height;
368
387
  if (!usable)
369
388
  this.picture = false;
370
389
  this.width = width;
371
390
  this.height = height;
391
+ this.answer = usable;
372
392
  return usable;
373
393
  }
374
394
  /**
@@ -38,6 +38,12 @@ export declare const ABSENT_ATTRIBUTE: Readonly<Record<string, readonly number[]
38
38
  * value that produces a picture. `flat.ts` reads `0` as none, `1` as sRGB and `2` as ACES.
39
39
  */
40
40
  export declare const OUTPUT_TRANSFORM_CODE: Readonly<Record<string, number>>;
41
+ /**
42
+ * The code a **forward** pass grades with: the transform's own, except `filmic`, which only the
43
+ * composite carries the constants for, and which a pass grading itself therefore draws as `aces`.
44
+ * Both backends' `gradeCode` read this, so neither can hand a forward stage a code it lacks.
45
+ */
46
+ export declare function forwardTransformCode(transform: string): number;
41
47
  /**
42
48
  * How far the speed rush reaches, and how far a motion-blurred pixel may travel, both in UV.
43
49
  *
@@ -46,3 +52,14 @@ export declare const OUTPUT_TRANSFORM_CODE: Readonly<Record<string, number>>;
46
52
  */
47
53
  export declare const RUSH_REACH_UV = 0.012;
48
54
  export declare const MOTION_BLUR_MAX_UV = 0.03;
55
+ /**
56
+ * A longest smear as `setCameraMotionBlur` takes it: a share of the frame, above zero and at most
57
+ * half, and the default where the number is not one. Shared, so both backends cap one way.
58
+ */
59
+ export declare function clampMotionShare(share: number): number;
60
+ /**
61
+ * Said once where a draw names where it was last frame and the frame blurs by motion, but keeps no
62
+ * motion target to blur that draw by: each object's own motion is drawn only under a
63
+ * reconstruction on WebGPU, and anywhere else the camera's motion alone smears the frame.
64
+ */
65
+ export declare const OBJECT_BLUR_UNKEPT: string;