babylonjs-editor-tools 5.5.1 → 5.5.2

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 (44) hide show
  1. package/build/index.node.js +407 -121
  2. package/build/src/decorators/apply.js +2 -2
  3. package/build/src/decorators/apply.js.map +1 -1
  4. package/build/src/decorators/scene.js +16 -16
  5. package/build/src/decorators/scene.js.map +1 -1
  6. package/build/src/decorators/sound.js +2 -2
  7. package/build/src/decorators/sound.js.map +1 -1
  8. package/build/src/index.js +1 -0
  9. package/build/src/index.js.map +1 -1
  10. package/build/src/loading/loader.js +21 -7
  11. package/build/src/loading/loader.js.map +1 -1
  12. package/build/src/rendering/order.js +110 -0
  13. package/build/src/rendering/order.js.map +1 -0
  14. package/build/src/rendering/ssao.js +1 -1
  15. package/build/src/rendering/ssao.js.map +1 -1
  16. package/build/src/rendering/tools.js +3 -0
  17. package/build/src/rendering/tools.js.map +1 -1
  18. package/build/src/rendering/volumetric/pipeline.js +403 -26
  19. package/build/src/rendering/volumetric/pipeline.js.map +1 -1
  20. package/build/src/rendering/volumetric/selector.js +2 -1
  21. package/build/src/rendering/volumetric/selector.js.map +1 -1
  22. package/build/src/rendering/volumetric/shaders-wgsl.js +197 -60
  23. package/build/src/rendering/volumetric/shaders-wgsl.js.map +1 -1
  24. package/build/src/rendering/volumetric/shaders.js +179 -5
  25. package/build/src/rendering/volumetric/shaders.js.map +1 -1
  26. package/build/src/rendering/volumetric/types.js +27 -1
  27. package/build/src/rendering/volumetric/types.js.map +1 -1
  28. package/build/src/rendering/volumetric-lighting.js.map +1 -1
  29. package/build/src/tools/scene.js +18 -5
  30. package/build/src/tools/scene.js.map +1 -1
  31. package/declaration/src/decorators/apply.d.ts +8 -4
  32. package/declaration/src/decorators/scene.d.ts +18 -9
  33. package/declaration/src/decorators/sound.d.ts +10 -1
  34. package/declaration/src/index.d.ts +1 -0
  35. package/declaration/src/loading/loader.d.ts +1 -1
  36. package/declaration/src/rendering/order.d.ts +31 -0
  37. package/declaration/src/rendering/volumetric/pipeline.d.ts +83 -1
  38. package/declaration/src/rendering/volumetric/selector.d.ts +6 -2
  39. package/declaration/src/rendering/volumetric/shaders-wgsl.d.ts +5 -1
  40. package/declaration/src/rendering/volumetric/shaders.d.ts +16 -2
  41. package/declaration/src/rendering/volumetric/types.d.ts +46 -1
  42. package/declaration/src/rendering/volumetric-lighting.d.ts +1 -1
  43. package/declaration/src/tools/scene.d.ts +2 -1
  44. package/package.json +1 -1
@@ -3,22 +3,47 @@ import { Color3 } from "@babylonjs/core/Maths/math.color";
3
3
  import { Constants } from "@babylonjs/core/Engines/constants";
4
4
  import { Vector3, Matrix } from "@babylonjs/core/Maths/math.vector";
5
5
  import { Texture } from "@babylonjs/core/Materials/Textures/texture";
6
+ import { DepthRenderer } from "@babylonjs/core/Rendering/depthRenderer";
6
7
  import { PostProcess } from "@babylonjs/core/PostProcesses/postProcess";
7
8
  import { ShaderLanguage } from "@babylonjs/core/Materials/shaderLanguage";
8
9
  import { PostProcessRenderEffect } from "@babylonjs/core/PostProcesses/RenderPipeline/postProcessRenderEffect";
9
10
  import { PostProcessRenderPipeline } from "@babylonjs/core/PostProcesses/RenderPipeline/postProcessRenderPipeline";
10
11
  import { isDirectionalLight, isSpotLight } from "../../tools/guards";
11
- import { VolumetricDitherMode, VolumetricFogMode, getDefaultVolumetricLightingConfiguration, getVolumetricLightSteps, maxVolumetricArrayLights, maxVolumetricShadowSlots, normalizeVolumetricLightingConfiguration, } from "./types";
12
+ import { VolumetricDebugMode, VolumetricDitherMode, VolumetricFogMode, getDefaultVolumetricLightingConfiguration, getVolumetricSamplingSteps, maxVolumetricArrayLights, maxVolumetricShadowSlots, normalizeVolumetricLightingConfiguration, volumetricTemporalSamplesDivider, } from "./types";
12
13
  import { VolumetricShadowKind, VolumetricTransmittanceMode, computeVolumetricBudget, getVolumetricLightRange, getVolumetricLightWorldDirection, getVolumetricLightWorldPosition, selectVolumetricLights, } from "./selector";
13
14
  import { VolumetricDepthSource, createVolumetricLightingPrePassConfiguration, getVolumetricGeometryBufferDepthTexture, getVolumetricGeometryBufferRenderer, getVolumetricPrePassDepthTexture, getVolumetricPrePassRenderer, resolveVolumetricDepthSource, } from "./depth";
14
- import { registerVolumetricLightingShaders, volumetricLightingBlurShaderName, volumetricLightingComposeShaderName, volumetricLightingLinearDepthShaderName, volumetricLightingScatteringShaderName, } from "./shaders";
15
+ import { registerVolumetricLightingShaders, volumetricLightingBlurShaderName, volumetricLightingComposeShaderName, volumetricLightingLinearDepthShaderName, volumetricLightingScatteringShaderName, volumetricLightingTemporalShaderName, } from "./shaders";
15
16
  const leftHandedForward = new Vector3(0, 0, 1);
16
17
  const rightHandedForward = new Vector3(0, 0, -1);
18
+ /**
19
+ * Defines the number of meshes whose depth shader is prepared each frame while the pipeline has nothing to draw.
20
+ */
21
+ const depthWarmupMeshesPerFrame = 32;
22
+ /**
23
+ * Defines the prefix of the key the depth renderer created by a pipeline is registered with in the scene.
24
+ */
25
+ const depthRendererKeyPrefix = "volumetricLighting_";
17
26
  /**
18
27
  * Defines the keys of the configuration that change the shape of the chain of post-processes and, as a
19
28
  * result, require the render effects of the pipeline to be rebuilt instead of simply rebound.
20
29
  */
21
- const structuralConfigurationKeys = ["resolutionScale", "blurPasses"];
30
+ const structuralConfigurationKeys = ["resolutionScale", "blurPasses", "temporalAccumulation"];
31
+ /**
32
+ * Defines the width, in standard deviations of the neighborhood of a pixel, of the range of values the history of
33
+ * the temporal accumulation is clipped to.
34
+ */
35
+ const temporalClippingWidth = 1;
36
+ /**
37
+ * Defines how much of the change of the medium along a view ray, caused by the motion of the camera, the history of
38
+ * the temporal accumulation may lag behind. The faster the camera moves compared to the distance of what a pixel
39
+ * shows, the more the current frame weighs, whatever the factor of the configuration.
40
+ */
41
+ const temporalMotionTolerance = 0.05;
42
+ /**
43
+ * Defines the conjugate of the golden ratio, the step of the low discrepancy sequence the samples of each pixel walk
44
+ * from one frame to the next with the temporal accumulation.
45
+ */
46
+ const goldenRatioConjugate = 0.6180339887498949;
22
47
  /**
23
48
  * Defines the number of samples of the optical depth integrated per pixel when the medium has no closed form.
24
49
  */
@@ -35,6 +60,7 @@ const scatteringUniforms = [
35
60
  "volCameraMinMaxZ",
36
61
  "volDepthRatio",
37
62
  "volFrameIndex",
63
+ "volTemporalOffset",
38
64
  "volFogInfos",
39
65
  "volFogColor",
40
66
  "volLinearFogEps",
@@ -42,6 +68,7 @@ const scatteringUniforms = [
42
68
  "volScreenShadowParams",
43
69
  "volAmbient",
44
70
  "volParams",
71
+ "volPassParams",
45
72
  "volLightData",
46
73
  "volLightDiffuse",
47
74
  "volLightDirection",
@@ -63,6 +90,16 @@ const scatteringSamplers = ["volCsmSampler"];
63
90
  for (let i = 0; i < maxVolumetricShadowSlots; ++i) {
64
91
  scatteringSamplers.push(`volShadowSampler${i}`);
65
92
  }
93
+ const temporalUniforms = [
94
+ "volCameraMinMaxZ",
95
+ "volDepthRatio",
96
+ "volInverseViewProjection",
97
+ "volCameraPosition",
98
+ "volCameraForward",
99
+ "volPreviousViewProjection",
100
+ "volTemporalParams",
101
+ "volTemporalMotion",
102
+ ];
66
103
  /**
67
104
  * Defines a rendering pipeline computing single-scattering volumetric lighting: the light shafts are
68
105
  * integrated through a participating medium the pipeline describes on its own, and are occluded either by
@@ -79,10 +116,12 @@ for (let i = 0; i < maxVolumetricShadowSlots; ++i) {
79
116
  * light is marched along the whole view ray, but a point or a spot light is only integrated over the part
80
117
  * of the view ray crossing its volume, so a light costs nothing to the pixels whose view ray misses it.
81
118
  *
82
- * The pipeline is composed of four passes:
119
+ * The pipeline is composed of the following passes:
83
120
  * - a pass keeping the color of the scene untouched and extracting its linear depth, which every later pass
84
121
  * reads instead of the depth map,
85
122
  * - the raymarching pass, rendered at a fraction of the resolution of the canvas,
123
+ * - when the temporal accumulation is enabled, a pass blending the result of the raymarching with the result of
124
+ * the previous frames, which lets the raymarching compute a fraction of its samples each frame,
86
125
  * - a separable depth aware blur denoising the result of the raymarching,
87
126
  * - a composition pass upsampling the result and adding it to the color of the scene.
88
127
  */
@@ -95,6 +134,10 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
95
134
  * Defines the name of the render effect raymarching the participating medium.
96
135
  */
97
136
  static ScatteringEffectName = "VolumetricLightingScatteringEffect";
137
+ /**
138
+ * Defines the name of the render effect accumulating the result of the raymarching over the frames.
139
+ */
140
+ static TemporalEffectName = "VolumetricLightingTemporalEffect";
98
141
  /**
99
142
  * Defines the name of the render effect denoising the result of the raymarching.
100
143
  */
@@ -165,30 +208,51 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
165
208
  _textureType;
166
209
  _shaderLanguage;
167
210
  _ldrEncode;
211
+ _linearDepthType;
168
212
  _linearDepthPacked;
169
213
  _msaaSamples = 1;
170
214
  _msaaSamplesAssigned = false;
171
215
  _depthSource = VolumetricDepthSource.DepthRenderer;
172
216
  _depthRenderer = null;
173
217
  _ownsDepthRenderer = false;
218
+ _depthMapRenderedWithCamera = false;
174
219
  _depthMode = 0;
175
220
  _prePassConfiguration = createVolumetricLightingPrePassConfiguration();
176
221
  _linearDepthPostProcess = null;
177
222
  _scatteringPostProcess = null;
223
+ _temporalPostProcess = null;
178
224
  _blurPostProcesses = [];
179
225
  _composePostProcess = null;
226
+ _temporalSamplesDivider = volumetricTemporalSamplesDivider;
227
+ _temporalClippingWidth = temporalClippingWidth;
228
+ _temporalMotionTolerance = temporalMotionTolerance;
229
+ _previousCameraPosition = new Vector3();
230
+ _historyTextures = [null, null];
231
+ _historyFrames = [-1, -1];
232
+ _historyEmpty = [false, false];
233
+ _historyIndex = 0;
234
+ _temporalFrame = 0;
235
+ _previousViewProjection = Matrix.Identity();
236
+ _previousViewProjectionRelative = Matrix.Identity();
237
+ _cameraTranslation = Matrix.Identity();
180
238
  _selection = null;
181
239
  _budget;
182
240
  _linearDepthDefines = "";
183
241
  _scatteringDefines = "";
242
+ _temporalDefines = "";
184
243
  _blurDefines = "";
185
244
  _composeDefines = "";
186
245
  _frameCount = 0;
187
246
  _lightsDirty = true;
188
247
  _disposed = false;
248
+ _idle = false;
249
+ _depthWarmup = new Set();
189
250
  _beforeCameraRenderObserver = null;
251
+ _afterCameraRenderObserver = null;
190
252
  _lightAddedObserver = null;
191
253
  _lightRemovedObserver = null;
254
+ _meshAddedObserver = null;
255
+ _meshRemovedObserver = null;
192
256
  _inverseViewProjection = Matrix.Identity();
193
257
  _temporaryVector = new Vector3();
194
258
  _temporaryColor = new Color3();
@@ -243,9 +307,15 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
243
307
  const supportsHalfFloat = caps.textureHalfFloatRender && caps.textureHalfFloatLinearFiltering;
244
308
  this._textureType = supportsHalfFloat ? Constants.TEXTURETYPE_HALF_FLOAT : Constants.TEXTURETYPE_UNSIGNED_BYTE;
245
309
  this._ldrEncode = !supportsHalfFloat;
246
- // The linear depth is read with texel fetches only, so a 32 bits float texture needs no filtering
247
- // support. Without float render targets it is packed in an 8 bits RGBA texture instead.
248
- this._linearDepthPacked = !caps.textureFloatRender;
310
+ // The linear depth is read with texel fetches only, so a float texture needs no filtering support. A 16 bits
311
+ // float holds it with a relative precision of 0.05%, which no pass tells apart from a 32 bits one, for half
312
+ // the memory traffic. Without float render targets it is packed in an 8 bits RGBA texture instead.
313
+ this._linearDepthType = caps.textureHalfFloatRender
314
+ ? Constants.TEXTURETYPE_HALF_FLOAT
315
+ : caps.textureFloatRender
316
+ ? Constants.TEXTURETYPE_FLOAT
317
+ : Constants.TEXTURETYPE_UNSIGNED_BYTE;
318
+ this._linearDepthPacked = this._linearDepthType === Constants.TEXTURETYPE_UNSIGNED_BYTE;
249
319
  this._budget = computeVolumetricBudget(engine, this._configuration);
250
320
  this._depthSource = resolveVolumetricDepthSource(scene);
251
321
  this._setupDepthSource();
@@ -256,10 +326,23 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
256
326
  if (renderedCamera === this._camera) {
257
327
  this._updateDepthSource();
258
328
  this._updateSelection();
329
+ this._warmUpDepthRenderer();
330
+ }
331
+ });
332
+ // The depth map was rendered for this frame by then. @see _setDepthMapRenderedWithCamera
333
+ this._afterCameraRenderObserver = scene.onAfterCameraRenderObservable.add((renderedCamera) => {
334
+ if (renderedCamera === this._camera) {
335
+ this._setDepthMapRenderedWithCamera(false);
259
336
  }
260
337
  });
261
338
  this._lightAddedObserver = scene.onNewLightAddedObservable.add(() => this.markLightsDirty());
262
339
  this._lightRemovedObserver = scene.onLightRemovedObservable.add(() => this.markLightsDirty());
340
+ this._meshAddedObserver = scene.onNewMeshAddedObservable.add((mesh) => {
341
+ if (this._ownsDepthRenderer) {
342
+ this._depthWarmup.add(mesh);
343
+ }
344
+ });
345
+ this._meshRemovedObserver = scene.onMeshRemovedObservable.add((mesh) => this._depthWarmup.delete(mesh));
263
346
  }
264
347
  /**
265
348
  * Gets wether or not the pipeline was disposed. Babylon.js disposes a rendering pipeline on its own when
@@ -287,6 +370,13 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
287
370
  get configuration() {
288
371
  return this._configurationProxy;
289
372
  }
373
+ /**
374
+ * Gets wether or not the pipeline had nothing to draw at its last update: no light reaches the view of the camera
375
+ * and the medium neither glows nor attenuates the scene. Its passes then cost next to nothing.
376
+ */
377
+ get isIdle() {
378
+ return this._idle;
379
+ }
290
380
  /**
291
381
  * Gets where the depth of the scene is currently read from. @see VolumetricDepthSource
292
382
  */
@@ -369,11 +459,17 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
369
459
  }
370
460
  this._disposed = true;
371
461
  this._scene.onBeforeCameraRenderObservable.remove(this._beforeCameraRenderObserver);
462
+ this._scene.onAfterCameraRenderObservable.remove(this._afterCameraRenderObserver);
372
463
  this._scene.onNewLightAddedObservable.remove(this._lightAddedObserver);
373
464
  this._scene.onLightRemovedObservable.remove(this._lightRemovedObserver);
465
+ this._scene.onNewMeshAddedObservable.remove(this._meshAddedObserver);
466
+ this._scene.onMeshRemovedObservable.remove(this._meshRemovedObserver);
374
467
  this._beforeCameraRenderObserver = null;
468
+ this._afterCameraRenderObserver = null;
375
469
  this._lightAddedObserver = null;
376
470
  this._lightRemovedObserver = null;
471
+ this._meshAddedObserver = null;
472
+ this._meshRemovedObserver = null;
377
473
  const cameras = this._cameras.slice();
378
474
  this._scene.postProcessRenderPipelineManager.detachCamerasFromRenderPipeline(this.name, cameras);
379
475
  this._disposePostProcesses(cameras);
@@ -421,11 +517,14 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
421
517
  * Releases what the current source of the depth of the scene owns.
422
518
  */
423
519
  _releaseDepthSource() {
424
- if (this._ownsDepthRenderer) {
425
- this._scene.disableDepthRenderer(this._camera);
520
+ if (this._ownsDepthRenderer && this._depthRenderer) {
521
+ this._setDepthMapRenderedWithCamera(false);
522
+ // Also removes it from the depth renderers of the scene.
523
+ this._depthRenderer.dispose();
426
524
  }
427
525
  this._depthRenderer = null;
428
526
  this._ownsDepthRenderer = false;
527
+ this._depthWarmup.clear();
429
528
  if (this._linearDepthPostProcess) {
430
529
  this._linearDepthPostProcess._prePassEffectConfiguration = undefined;
431
530
  }
@@ -444,6 +543,13 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
444
543
  if (depthSource === VolumetricDepthSource.GeometryBuffer) {
445
544
  this._ensureGeometryBufferDepth();
446
545
  }
546
+ // Another effect enabled the depth renderer of the camera, whose depth map is shared rather than rendering
547
+ // the scene twice, or disabled the one the pipeline was sharing.
548
+ if (depthSource === VolumetricDepthSource.DepthRenderer && this._hasDepthRendererOfCameraChanged()) {
549
+ this._releaseDepthSource();
550
+ this._setupDepthSource();
551
+ this._lightsDirty = true;
552
+ }
447
553
  return;
448
554
  }
449
555
  this._releaseDepthSource();
@@ -505,9 +611,30 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
505
611
  * depth pass, and detects the format it stores so the shader can be compiled for it.
506
612
  */
507
613
  _setupDepthRenderer() {
508
- const existing = this._scene._depthRenderer?.[this._camera.uniqueId];
614
+ const scene = this._scene;
615
+ const existing = scene._depthRenderer?.[this._camera.uniqueId];
509
616
  this._ownsDepthRenderer = !existing;
510
- this._depthRenderer = existing ?? this._scene.enableDepthRenderer(this._camera, false, false, Constants.TEXTURE_NEAREST_SAMPLINGMODE, false);
617
+ if (existing) {
618
+ this._depthRenderer = existing;
619
+ }
620
+ else {
621
+ // The depth map "scene.enableDepthRenderer" would create, registered under a key of its own like the depth
622
+ // reducer of Babylon.js does: the scene renders it and waits for its shaders, but never hands it to another
623
+ // effect asking for the depth renderer of the camera, as the pipeline turns it off when it has nothing to draw.
624
+ const caps = this._scene.getEngine().getCaps();
625
+ const type = caps.textureHalfFloatRender
626
+ ? Constants.TEXTURETYPE_HALF_FLOAT
627
+ : caps.textureFloatRender
628
+ ? Constants.TEXTURETYPE_FLOAT
629
+ : Constants.TEXTURETYPE_UNSIGNED_BYTE;
630
+ this._depthRenderer = new DepthRenderer(this._scene, type, this._camera, false, Constants.TEXTURE_NEAREST_SAMPLINGMODE, false, "VolumetricLightingDepth");
631
+ this._depthRenderer.enabled = !this._idle;
632
+ scene._depthRenderer ??= {};
633
+ scene._depthRenderer[`${depthRendererKeyPrefix}${this._scene.getUniqueId()}`] = this._depthRenderer;
634
+ // The pipeline is usually created once the scene is ready, and may have nothing to draw from its first
635
+ // frame: the depth shaders of the meshes are then prepared while the depth renderer is off.
636
+ this._scene.meshes.forEach((mesh) => this._depthWarmup.add(mesh));
637
+ }
511
638
  if (this._depthRenderer.isPacked) {
512
639
  this._depthMode = 2;
513
640
  }
@@ -519,6 +646,36 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
519
646
  this._depthMode = 0;
520
647
  }
521
648
  }
649
+ /**
650
+ * Returns wether or not the depth renderer of the camera the scene hands to the effects asking for it changed
651
+ * since the pipeline chose its own: created by another effect, or disabled by the one that created it.
652
+ */
653
+ _hasDepthRendererOfCameraChanged() {
654
+ const registered = this._scene._depthRenderer?.[this._camera.uniqueId];
655
+ return this._ownsDepthRenderer ? !!registered : registered !== this._depthRenderer;
656
+ }
657
+ /**
658
+ * Adds the depth map the pipeline owns to the render targets of the camera, or removes it. The scene collects
659
+ * its own render targets before the pipeline decides wether it has anything to draw this frame, but renders
660
+ * the ones of the camera after: the frame the pipeline has something to draw again, only the latter render the
661
+ * depth map in time. They are only used for that frame, rendering it with the ones of the scene costs less
662
+ * time to the CPU.
663
+ */
664
+ _setDepthMapRenderedWithCamera(value) {
665
+ if (value === this._depthMapRenderedWithCamera || !this._depthRenderer) {
666
+ return;
667
+ }
668
+ this._depthMapRenderedWithCamera = value;
669
+ const renderTargets = this._camera.customRenderTargets;
670
+ const depthMap = this._depthRenderer.getDepthMap();
671
+ const index = renderTargets.indexOf(depthMap);
672
+ if (value && index === -1) {
673
+ renderTargets.push(depthMap);
674
+ }
675
+ else if (!value && index !== -1) {
676
+ renderTargets.splice(index, 1);
677
+ }
678
+ }
522
679
  /**
523
680
  * Binds the two values needed to turn the content of the depth map back into a distance along the view
524
681
  * axis. The depth renderer stores "(clipZ + minZ) / (minZ + maxZ)", where "clipZ" is affine in the view
@@ -557,8 +714,26 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
557
714
  const sign = engine.useReverseDepthBuffer ? -1 : 1;
558
715
  effect.setFloat2("volDepthUnpack", (sign * (minZ + maxZ)) / scaleZ, (-sign * minZ - offsetZ) / scaleZ);
559
716
  }
717
+ /**
718
+ * Returns the number of samples the raymarching computes each frame, fewer with the temporal accumulation.
719
+ */
720
+ _getSamplingSteps() {
721
+ return getVolumetricSamplingSteps(this._configuration, this._temporalSamplesDivider);
722
+ }
723
+ /**
724
+ * Returns the dithering mode the raymarching is compiled with. The temporal accumulation needs the samples of
725
+ * neighboring pixels to differ: it tells its own noise from the changes of the scene with it.
726
+ */
727
+ _getDitherMode() {
728
+ const configuration = this._configuration;
729
+ if (configuration.temporalAccumulation && configuration.ditherMode === VolumetricDitherMode.None) {
730
+ return VolumetricDitherMode.InterleavedGradientNoise;
731
+ }
732
+ return configuration.ditherMode;
733
+ }
560
734
  _getEnvironment() {
561
735
  const configuration = this._configuration;
736
+ const sampling = this._getSamplingSteps();
562
737
  // The medium of the pipeline has a closed form transmittance, except when the height falloff is
563
738
  // combined with a fog mode whose density already varies along the ray.
564
739
  let transmittance = VolumetricTransmittanceMode.Analytic;
@@ -574,7 +749,8 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
574
749
  transmittance,
575
750
  linearDepthPacked: this._linearDepthPacked,
576
751
  arrayLightCapacity: this._budget.maxArrayLights,
577
- lightSteps: getVolumetricLightSteps(configuration),
752
+ steps: sampling.steps,
753
+ lightSteps: sampling.lightSteps,
578
754
  };
579
755
  }
580
756
  /**
@@ -603,11 +779,11 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
603
779
  const csm = selection?.csm ?? null;
604
780
  const globalSlotCount = shadowed.filter((entry) => !entry.shadow.local).length;
605
781
  let defines = this._buildLinearDepthReadDefines(environment);
606
- defines += `#define VOL_STEPS ${configuration.steps}\n`;
782
+ defines += `#define VOL_STEPS ${environment.steps}\n`;
607
783
  defines += `#define VOL_LIGHT_STEPS ${environment.lightSteps}\n`;
608
- defines += `#define VOL_LOCAL_MAX_STEPS ${Math.max(environment.lightSteps, configuration.steps)}\n`;
784
+ defines += `#define VOL_LOCAL_MAX_STEPS ${Math.max(environment.lightSteps, environment.steps)}\n`;
609
785
  defines += `#define VOL_DISTRIBUTION ${configuration.stepDistribution}\n`;
610
- defines += `#define VOL_DITHER ${configuration.ditherMode}\n`;
786
+ defines += `#define VOL_DITHER ${this._getDitherMode()}\n`;
611
787
  defines += `#define VOL_PCF_TAPS ${configuration.pcfTaps}\n`;
612
788
  defines += `#define VOL_FOG_MODE ${environment.fogMode}\n`;
613
789
  defines += `#define VOL_TRANSMITTANCE ${environment.transmittance}\n`;
@@ -624,7 +800,11 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
624
800
  defines += `#define VOL_LOCAL_ARRAY_LIGHTS ${environment.arrayLightCapacity > directionalCount ? 1 : 0}\n`;
625
801
  defines += `#define VOL_SHADOW_SLOT_COUNT ${shadowed.length}\n`;
626
802
  defines += `#define VOL_GLOBAL_LIGHT_COUNT ${directionalCount + globalSlotCount + (csm ? 1 : 0)}\n`;
627
- if (configuration.ditherMode !== VolumetricDitherMode.None && configuration.temporalJitter) {
803
+ // The accumulation moves the samples of each pixel on its own, from one frame to the next.
804
+ if (configuration.temporalAccumulation) {
805
+ defines += "#define VOL_TEMPORAL_ACCUMULATION\n";
806
+ }
807
+ else if (configuration.ditherMode !== VolumetricDitherMode.None && configuration.temporalJitter) {
628
808
  defines += "#define VOL_TEMPORAL_JITTER\n";
629
809
  }
630
810
  if (configuration.heightFogEnabled) {
@@ -664,6 +844,9 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
664
844
  }
665
845
  return defines;
666
846
  }
847
+ _buildTemporalDefines(environment) {
848
+ return this._buildLinearDepthReadDefines(environment);
849
+ }
667
850
  _buildBlurDefines(environment) {
668
851
  return `${this._buildLinearDepthReadDefines(environment)}#define VOL_BLUR_RADIUS ${this._configuration.blurRadius}\n`;
669
852
  }
@@ -677,7 +860,7 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
677
860
  }
678
861
  _createPostProcess(options) {
679
862
  const postProcess = new PostProcess(options.name, options.shaderName, options.uniforms, options.samplers, options.ratio, null, options.samplingMode, this._scene.getEngine(), false, options.defines, options.textureType, undefined, undefined, false, options.textureFormat, this._shaderLanguage);
680
- postProcess.autoClear = false;
863
+ postProcess.autoClear = options.autoClear ?? false;
681
864
  postProcess.alphaMode = Constants.ALPHA_DISABLE;
682
865
  return postProcess;
683
866
  }
@@ -687,27 +870,36 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
687
870
  const environment = this._getEnvironment();
688
871
  this._linearDepthDefines = this._buildLinearDepthDefines(environment);
689
872
  this._scatteringDefines = this._buildScatteringDefines(this._selection, environment);
873
+ this._temporalDefines = this._buildTemporalDefines(environment);
690
874
  this._blurDefines = this._buildBlurDefines(environment);
691
875
  this._composeDefines = this._buildComposeDefines(environment);
692
876
  // First pass of the chain: its input is the color of the scene, kept untouched for the composition, and
693
877
  // it writes the linear depth of the scene. The ratio of a post-process sizes the texture it READS, the
694
878
  // size of what it writes is set by the pass that follows.
879
+ //
880
+ // When this pass is the first post-process of the camera, its input is the texture the scene is drawn into,
881
+ // and Babylon.js only clears it, color, depth and stencil, through the "autoClear" of that post-process:
882
+ // "scene.autoClear" clears the default framebuffer. Without it, every frame is drawn over the previous
883
+ // ones, a surface further away than what a texel showed before fails the depth test, and the texel keeps
884
+ // its old color, the bright frames of a muzzle flash included. The prepass renderer, when it draws the
885
+ // scene instead, turns this off while it is in use and clears its own render target.
695
886
  this._linearDepthPostProcess = this._createPostProcess({
696
887
  name: "VolumetricLightingLinearDepth",
697
888
  shaderName: volumetricLightingLinearDepthShaderName,
698
- uniforms: ["volCameraMinMaxZ", "volDepthUnpack"],
889
+ uniforms: ["volCameraMinMaxZ", "volDepthUnpack", "volPassParams"],
699
890
  samplers: ["depthSampler"],
700
891
  ratio: 1.0,
701
892
  samplingMode: Texture.BILINEAR_SAMPLINGMODE,
702
893
  textureType: this._textureType,
703
894
  defines: this._linearDepthDefines,
895
+ autoClear: true,
704
896
  });
705
897
  this._linearDepthPostProcess.onApplyObservable.add((effect) => this._bindLinearDepth(effect));
706
898
  this._applyPrePassConfiguration();
707
899
  // The input of the raymarching pass is the linear depth, kept at the full resolution so the occlusion by
708
900
  // the depth buffer sees the geometry as the scene draws it: the pass itself still rasterizes at the
709
901
  // reduced resolution, which is set by the ratio of the pass that follows. The linear depth is only ever
710
- // read with texel fetches, and a 32 bits float texture can't be filtered on every engine anyway.
902
+ // read with texel fetches, and a float texture can't be filtered on every engine anyway.
711
903
  this._scatteringPostProcess = this._createPostProcess({
712
904
  name: "VolumetricLightingScattering",
713
905
  shaderName: volumetricLightingScatteringShaderName,
@@ -715,17 +907,34 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
715
907
  samplers: scatteringSamplers,
716
908
  ratio: 1.0,
717
909
  samplingMode: Texture.NEAREST_SAMPLINGMODE,
718
- textureType: this._linearDepthPacked ? Constants.TEXTURETYPE_UNSIGNED_BYTE : Constants.TEXTURETYPE_FLOAT,
910
+ textureType: this._linearDepthType,
719
911
  textureFormat: this._linearDepthPacked ? Constants.TEXTUREFORMAT_RGBA : Constants.TEXTUREFORMAT_R,
720
912
  defines: this._scatteringDefines,
721
913
  });
722
914
  this._scatteringPostProcess.onApplyObservable.add((effect) => this._bindScattering(effect));
915
+ // Reads the result of the raymarching at the resolution it was computed, and writes into the textures holding
916
+ // the history, which the pass after it reads in turn: the ratio of that pass is what sizes them.
917
+ this._temporalPostProcess = null;
918
+ if (configuration.temporalAccumulation) {
919
+ this._temporalPostProcess = this._createPostProcess({
920
+ name: "VolumetricLightingTemporal",
921
+ shaderName: volumetricLightingTemporalShaderName,
922
+ uniforms: temporalUniforms,
923
+ samplers: ["volHistorySampler", "volLinearDepthSampler"],
924
+ ratio: configuration.resolutionScale,
925
+ samplingMode: Texture.BILINEAR_SAMPLINGMODE,
926
+ textureType: this._textureType,
927
+ defines: this._temporalDefines,
928
+ });
929
+ this._temporalPostProcess.onActivateObservable.add(() => this._prepareHistory());
930
+ this._temporalPostProcess.onApplyObservable.add((effect) => this._bindTemporal(effect));
931
+ }
723
932
  this._blurPostProcesses = [];
724
933
  for (let i = 0; i < configuration.blurPasses; ++i) {
725
934
  const blurPostProcess = this._createPostProcess({
726
935
  name: `VolumetricLightingBlur${i}`,
727
936
  shaderName: volumetricLightingBlurShaderName,
728
- uniforms: ["volCameraMinMaxZ", "volDepthRatio", "volBlurDirection", "volBlurParams"],
937
+ uniforms: ["volCameraMinMaxZ", "volDepthRatio", "volBlurDirection", "volBlurParams", "volPassParams"],
729
938
  samplers: ["volLinearDepthSampler"],
730
939
  ratio: configuration.resolutionScale,
731
940
  samplingMode: Texture.BILINEAR_SAMPLINGMODE,
@@ -740,7 +949,7 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
740
949
  this._composePostProcess = this._createPostProcess({
741
950
  name: "VolumetricLightingCompose",
742
951
  shaderName: volumetricLightingComposeShaderName,
743
- uniforms: ["volCameraMinMaxZ", "volDepthRatio", "volComposeParams"],
952
+ uniforms: ["volCameraMinMaxZ", "volDepthRatio", "volComposeParams", "volPassParams"],
744
953
  samplers: ["volSceneSampler", "volLinearDepthSampler"],
745
954
  ratio: configuration.resolutionScale,
746
955
  samplingMode: Texture.BILINEAR_SAMPLINGMODE,
@@ -750,6 +959,9 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
750
959
  this._composePostProcess.onApplyObservable.add((effect) => this._bindCompose(effect));
751
960
  this.addEffect(new PostProcessRenderEffect(engine, VolumetricLightingRenderingPipeline.SceneColorEffectName, () => this._linearDepthPostProcess, true));
752
961
  this.addEffect(new PostProcessRenderEffect(engine, VolumetricLightingRenderingPipeline.ScatteringEffectName, () => this._scatteringPostProcess, true));
962
+ if (this._temporalPostProcess) {
963
+ this.addEffect(new PostProcessRenderEffect(engine, VolumetricLightingRenderingPipeline.TemporalEffectName, () => this._temporalPostProcess, true));
964
+ }
753
965
  if (this._blurPostProcesses.length) {
754
966
  this.addEffect(new PostProcessRenderEffect(engine, VolumetricLightingRenderingPipeline.BlurEffectName, () => this._blurPostProcesses, true));
755
967
  }
@@ -758,7 +970,13 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
758
970
  this._applyMsaaSamples();
759
971
  }
760
972
  _disposePostProcesses(cameras) {
761
- const postProcesses = [this._linearDepthPostProcess, this._scatteringPostProcess, ...this._blurPostProcesses, this._composePostProcess];
973
+ const postProcesses = [
974
+ this._linearDepthPostProcess,
975
+ this._scatteringPostProcess,
976
+ this._temporalPostProcess,
977
+ ...this._blurPostProcesses,
978
+ this._composePostProcess,
979
+ ];
762
980
  postProcesses.forEach((postProcess) => {
763
981
  if (!postProcess) {
764
982
  return;
@@ -768,8 +986,53 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
768
986
  });
769
987
  this._linearDepthPostProcess = null;
770
988
  this._scatteringPostProcess = null;
989
+ this._temporalPostProcess = null;
771
990
  this._blurPostProcesses = [];
772
991
  this._composePostProcess = null;
992
+ this._disposeHistory();
993
+ }
994
+ /**
995
+ * Releases the textures holding the history of the temporal accumulation. The next frame starts over from the
996
+ * result of the raymarching alone.
997
+ */
998
+ _disposeHistory() {
999
+ this._historyTextures.forEach((texture) => texture?.dispose());
1000
+ this._historyTextures = [null, null];
1001
+ this._historyFrames = [-1, -1];
1002
+ }
1003
+ /**
1004
+ * Swaps the two textures holding the history of the temporal accumulation, as the pass is activated: the one
1005
+ * written last frame is read back, the other one receives the result of this frame and is what the pass after
1006
+ * it reads, the same way the TAA rendering pipeline of Babylon.js does.
1007
+ */
1008
+ _prepareHistory() {
1009
+ const temporal = this._temporalPostProcess;
1010
+ const next = this._blurPostProcesses[0] ?? this._composePostProcess;
1011
+ if (!temporal || !next) {
1012
+ return;
1013
+ }
1014
+ ++this._temporalFrame;
1015
+ // The size of the texture the pass reads, the result of the raymarching, updated just before this is called.
1016
+ const width = temporal.width;
1017
+ const height = temporal.height;
1018
+ const texture = this._historyTextures[0];
1019
+ if (!texture || texture.width !== width || texture.height !== height) {
1020
+ this._disposeHistory();
1021
+ const engine = this._scene.getEngine();
1022
+ for (let i = 0; i < 2; ++i) {
1023
+ this._historyTextures[i] = engine.createRenderTargetTexture({ width, height }, {
1024
+ generateMipMaps: false,
1025
+ generateDepthBuffer: false,
1026
+ generateStencilBuffer: false,
1027
+ type: this._textureType,
1028
+ format: Constants.TEXTUREFORMAT_RGBA,
1029
+ samplingMode: Constants.TEXTURE_BILINEAR_SAMPLINGMODE,
1030
+ label: `VolumetricLightingHistory${i}`,
1031
+ });
1032
+ }
1033
+ }
1034
+ this._historyIndex ^= 1;
1035
+ next.inputTexture = this._historyTextures[this._historyIndex];
773
1036
  }
774
1037
  /**
775
1038
  * Rebuilds the chain of post-processes. Changing the resolution of the scattering buffer or the number
@@ -822,6 +1085,7 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
822
1085
  const environment = this._getEnvironment();
823
1086
  const selection = selectVolumetricLights(this._scene, this._camera, this._configuration, this._budget, this._selection);
824
1087
  this._selection = selection;
1088
+ this._updateIdleState(selection);
825
1089
  // Each pass is only recompiled when its own defines changed: recompiling a pass makes it skip its
826
1090
  // draws until the new program is linked.
827
1091
  const scatteringDefines = this._buildScatteringDefines(selection, environment);
@@ -829,6 +1093,11 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
829
1093
  this._scatteringDefines = scatteringDefines;
830
1094
  this._scatteringPostProcess.updateEffect(scatteringDefines);
831
1095
  }
1096
+ const temporalDefines = this._buildTemporalDefines(environment);
1097
+ if (temporalDefines !== this._temporalDefines) {
1098
+ this._temporalDefines = temporalDefines;
1099
+ this._temporalPostProcess?.updateEffect(temporalDefines);
1100
+ }
832
1101
  const linearDepthDefines = this._buildLinearDepthDefines(environment);
833
1102
  if (linearDepthDefines !== this._linearDepthDefines) {
834
1103
  this._linearDepthDefines = linearDepthDefines;
@@ -845,6 +1114,67 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
845
1114
  this._composePostProcess?.updateEffect(composeDefines);
846
1115
  }
847
1116
  }
1117
+ /**
1118
+ * Detects when the pipeline has nothing to draw: no light reaches the view of the camera and the medium neither
1119
+ * glows nor attenuates the scene. Every pass then exits right away, the composition outputs the color of the
1120
+ * scene exactly as it would under light shafts of zero intensity, and the depth renderer the pipeline owns stops
1121
+ * rendering the scene a second time. This is driven by uniforms rather than by detaching the passes, so a light
1122
+ * blinking on and off, a muzzle flash typically, never rebuilds anything.
1123
+ */
1124
+ _updateIdleState(selection) {
1125
+ const configuration = this._configuration;
1126
+ const ambient = configuration.ambientColor;
1127
+ const hasLights = selection.shadowed.length > 0 || selection.unshadowed.length > 0 || !!selection.csm;
1128
+ const hasAmbient = configuration.ambientIntensity > 0 && (ambient[0] > 0 || ambient[1] > 0 || ambient[2] > 0);
1129
+ const idle = !hasLights && !hasAmbient && configuration.extinctionAmount <= 0 && configuration.debugMode === VolumetricDebugMode.None;
1130
+ if (idle === this._idle) {
1131
+ return;
1132
+ }
1133
+ this._idle = idle;
1134
+ if (this._ownsDepthRenderer && this._depthRenderer) {
1135
+ this._depthRenderer.enabled = !idle;
1136
+ // The scene already collected its render targets this frame. @see _setDepthMapRenderedWithCamera
1137
+ this._setDepthMapRenderedWithCamera(!idle);
1138
+ }
1139
+ }
1140
+ /**
1141
+ * Prepares the depth shaders of the meshes while the depth renderer the pipeline owns is off, a few meshes per
1142
+ * frame. Without this, the first frame with something to draw after the scene got new meshes, or after the
1143
+ * pipeline was created without anything to draw, would miss every mesh whose depth shader is still compiling.
1144
+ * The checks are the ones the scene runs for its depth renderers when it waits to be ready.
1145
+ */
1146
+ _warmUpDepthRenderer() {
1147
+ const depthRenderer = this._depthRenderer;
1148
+ if (!this._idle || !this._ownsDepthRenderer || !depthRenderer || !this._depthWarmup.size) {
1149
+ return;
1150
+ }
1151
+ const instancedArrays = this._scene.getEngine().getCaps().instancedArrays;
1152
+ const compiling = [];
1153
+ let budget = depthWarmupMeshesPerFrame;
1154
+ for (const mesh of this._depthWarmup) {
1155
+ if (budget-- <= 0) {
1156
+ break;
1157
+ }
1158
+ this._depthWarmup.delete(mesh);
1159
+ if (mesh.isDisposed() || !mesh.subMeshes?.length) {
1160
+ continue;
1161
+ }
1162
+ const className = mesh.getClassName();
1163
+ const instanced = mesh.hasThinInstances || className === "InstancedMesh" || className === "InstancedLinesMesh" || (instancedArrays && (mesh.instances?.length ?? 0) > 0);
1164
+ let ready = true;
1165
+ mesh.subMeshes.forEach((subMesh) => {
1166
+ const material = subMesh.getMaterial();
1167
+ if (material && !material.disableDepthWrite && subMesh.verticesCount > 0 && !depthRenderer.isReady(subMesh, instanced)) {
1168
+ ready = false;
1169
+ }
1170
+ });
1171
+ if (!ready) {
1172
+ compiling.push(mesh);
1173
+ }
1174
+ }
1175
+ // Checked again once the others were: their shaders compile in the meantime.
1176
+ compiling.forEach((mesh) => this._depthWarmup.add(mesh));
1177
+ }
848
1178
  /**
849
1179
  * Fills the four vectors describing a light in the raymarching shader. The packing follows what
850
1180
  * "Light.transferToEffect" writes for the surfaces of the scene so the shafts agree with them, except for
@@ -902,6 +1232,7 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
902
1232
  falloff[offset + 3] = config.shadowDarkness;
903
1233
  }
904
1234
  _bindLinearDepth(effect) {
1235
+ effect.setFloat4("volPassParams", this._idle ? 1 : 0, 0, 0, 0);
905
1236
  let depthTexture = null;
906
1237
  switch (this._depthSource) {
907
1238
  case VolumetricDepthSource.PrePass: {
@@ -932,6 +1263,7 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
932
1263
  if (!selection) {
933
1264
  return;
934
1265
  }
1266
+ effect.setFloat4("volPassParams", this._idle ? 1 : 0, 0, 0, 0);
935
1267
  this._inverseViewProjection.copyFrom(scene.getTransformMatrix());
936
1268
  this._inverseViewProjection.invert();
937
1269
  effect.setMatrix("volInverseViewProjection", this._inverseViewProjection);
@@ -940,6 +1272,7 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
940
1272
  effect.setVector3("volCameraForward", this._temporaryVector.normalize());
941
1273
  effect.setFloat2("volCameraMinMaxZ", camera.minZ, camera.maxZ);
942
1274
  effect.setFloat("volFrameIndex", scene.getRenderId() % 64);
1275
+ effect.setFloat("volTemporalOffset", (this._frameCount * goldenRatioConjugate) % 1);
943
1276
  this._bindDepthRatio(effect);
944
1277
  effect.setFloat4("volFogInfos", configuration.fogMode, configuration.fogStart, configuration.fogEnd, configuration.fogDensity);
945
1278
  this._temporaryColor.set(configuration.fogColor[0], configuration.fogColor[1], configuration.fogColor[2]);
@@ -955,7 +1288,9 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
955
1288
  // The directional lights come first in the arrays, the shader walks the point and spot lights after them.
956
1289
  const unshadowedCount = selection.unshadowed.length;
957
1290
  const localCount = unshadowedCount - selection.directionalCount;
958
- effect.setFloat4("volParams", configuration.maxDistance, configuration.ditherStrength, configuration.lightExtinctionClamp, localCount);
1291
+ // The accumulation averages the samples over their whole segments, it needs them spread over all of it.
1292
+ const ditherStrength = configuration.temporalAccumulation ? 1 : configuration.ditherStrength;
1293
+ effect.setFloat4("volParams", configuration.maxDistance, ditherStrength, configuration.lightExtinctionClamp, localCount);
959
1294
  // Unshadowed lights.
960
1295
  for (let i = 0; i < unshadowedCount; ++i) {
961
1296
  this._writeLight(selection.unshadowed[i], i, this._lightData, this._lightDiffuse, this._lightDirection, this._lightFalloff);
@@ -1031,19 +1366,60 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
1031
1366
  */
1032
1367
  _bindDepthRatio(effect) {
1033
1368
  // The ratio of a post-process sizes its input texture: the raymarching pass reads the linear depth, and
1034
- // the pass that follows it reads the scattering buffer. Both are activated by the time a pass applies.
1369
+ // the pass that follows it reads the scattering buffer. Both are activated by the time a pass applies. The
1370
+ // pass after the temporal accumulation reads a texture of the pipeline instead, and keeps no size of its own.
1035
1371
  const depth = this._scatteringPostProcess;
1036
- const scattering = this._blurPostProcesses[0] ?? this._composePostProcess;
1372
+ const scattering = this._temporalPostProcess ?? this._blurPostProcesses[0] ?? this._composePostProcess;
1037
1373
  const depthWidth = depth && depth.width > 0 ? depth.width : 1;
1038
1374
  const depthHeight = depth && depth.height > 0 ? depth.height : 1;
1039
1375
  const scatteringWidth = scattering && scattering.width > 0 ? scattering.width : depthWidth;
1040
1376
  const scatteringHeight = scattering && scattering.height > 0 ? scattering.height : depthHeight;
1041
1377
  effect.setFloat2("volDepthRatio", depthWidth / scatteringWidth, depthHeight / scatteringHeight);
1042
1378
  }
1379
+ _bindTemporal(effect) {
1380
+ const scene = this._scene;
1381
+ const camera = this._camera;
1382
+ const configuration = this._configuration;
1383
+ if (!this._scatteringPostProcess) {
1384
+ return;
1385
+ }
1386
+ const historyIndex = this._historyIndex ^ 1;
1387
+ const history = this._historyTextures[historyIndex];
1388
+ effect._bindTexture("volHistorySampler", history?.texture ?? scene.getEngine().emptyTexture);
1389
+ // The input of the raymarching pass is the linear depth.
1390
+ effect.setTextureFromPostProcess("volLinearDepthSampler", this._scatteringPostProcess);
1391
+ effect.setFloat2("volCameraMinMaxZ", camera.minZ, camera.maxZ);
1392
+ this._bindDepthRatio(effect);
1393
+ this._inverseViewProjection.copyFrom(scene.getTransformMatrix());
1394
+ this._inverseViewProjection.invert();
1395
+ effect.setMatrix("volInverseViewProjection", this._inverseViewProjection);
1396
+ effect.setVector3("volCameraPosition", camera.globalPosition);
1397
+ camera.getDirectionToRef(scene.useRightHandedSystem ? rightHandedForward : leftHandedForward, this._temporaryVector);
1398
+ effect.setVector3("volCameraForward", this._temporaryVector.normalize());
1399
+ // The history is reprojected with the matrix of the previous frame, applied to positions relative to the
1400
+ // current camera like the raymarching does, which keeps the precision whatever the distance to the origin.
1401
+ const position = camera.globalPosition;
1402
+ Matrix.TranslationToRef(position.x, position.y, position.z, this._cameraTranslation);
1403
+ this._cameraTranslation.multiplyToRef(this._previousViewProjection, this._previousViewProjectionRelative);
1404
+ effect.setMatrix("volPreviousViewProjection", this._previousViewProjectionRelative);
1405
+ // The history is only valid when it was written the frame just before: a resize or a rebuild of the chain
1406
+ // recreates the textures, and a pass waiting for its shader to compile skips its frames. A frame the pipeline
1407
+ // had nothing to draw leaves no light at all to accumulate, which the shader rebuilds instead of reading it.
1408
+ const historyValid = !this._idle && !!history && this._historyFrames[historyIndex] === this._temporalFrame - 1;
1409
+ const historyState = historyValid ? (this._historyEmpty[historyIndex] ? 2 : 1) : 0;
1410
+ effect.setFloat4("volTemporalParams", configuration.temporalAccumulationFactor, this._temporalClippingWidth, configuration.maxDistance, historyState);
1411
+ const translation = historyValid ? Vector3.Distance(position, this._previousCameraPosition) : 0;
1412
+ effect.setFloat2("volTemporalMotion", translation, this._temporalMotionTolerance);
1413
+ this._historyFrames[this._historyIndex] = this._temporalFrame;
1414
+ this._historyEmpty[this._historyIndex] = this._idle;
1415
+ this._previousViewProjection.copyFrom(scene.getTransformMatrix());
1416
+ this._previousCameraPosition.copyFrom(position);
1417
+ }
1043
1418
  _bindBlur(effect, index) {
1044
1419
  if (!this._scatteringPostProcess) {
1045
1420
  return;
1046
1421
  }
1422
+ effect.setFloat4("volPassParams", this._idle ? 1 : 0, 0, 0, 0);
1047
1423
  // The input of the raymarching pass is the linear depth.
1048
1424
  effect.setTextureFromPostProcess("volLinearDepthSampler", this._scatteringPostProcess);
1049
1425
  effect.setFloat2("volCameraMinMaxZ", this._camera.minZ, this._camera.maxZ);
@@ -1060,6 +1436,7 @@ export class VolumetricLightingRenderingPipeline extends PostProcessRenderPipeli
1060
1436
  // input texture of the raymarching pass is the linear depth of the scene.
1061
1437
  effect.setTextureFromPostProcess("volSceneSampler", this._linearDepthPostProcess);
1062
1438
  effect.setTextureFromPostProcess("volLinearDepthSampler", this._scatteringPostProcess);
1439
+ effect.setFloat4("volPassParams", this._idle ? 1 : 0, 0, 0, 0);
1063
1440
  effect.setFloat2("volCameraMinMaxZ", this._camera.minZ, this._camera.maxZ);
1064
1441
  this._bindDepthRatio(effect);
1065
1442
  // The dithering of the output is only useful when the chain ends up in an 8 bits buffer.