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.
- package/build/index.node.js +407 -121
- package/build/src/decorators/apply.js +2 -2
- package/build/src/decorators/apply.js.map +1 -1
- package/build/src/decorators/scene.js +16 -16
- package/build/src/decorators/scene.js.map +1 -1
- package/build/src/decorators/sound.js +2 -2
- package/build/src/decorators/sound.js.map +1 -1
- package/build/src/index.js +1 -0
- package/build/src/index.js.map +1 -1
- package/build/src/loading/loader.js +21 -7
- package/build/src/loading/loader.js.map +1 -1
- package/build/src/rendering/order.js +110 -0
- package/build/src/rendering/order.js.map +1 -0
- package/build/src/rendering/ssao.js +1 -1
- package/build/src/rendering/ssao.js.map +1 -1
- package/build/src/rendering/tools.js +3 -0
- package/build/src/rendering/tools.js.map +1 -1
- package/build/src/rendering/volumetric/pipeline.js +403 -26
- package/build/src/rendering/volumetric/pipeline.js.map +1 -1
- package/build/src/rendering/volumetric/selector.js +2 -1
- package/build/src/rendering/volumetric/selector.js.map +1 -1
- package/build/src/rendering/volumetric/shaders-wgsl.js +197 -60
- package/build/src/rendering/volumetric/shaders-wgsl.js.map +1 -1
- package/build/src/rendering/volumetric/shaders.js +179 -5
- package/build/src/rendering/volumetric/shaders.js.map +1 -1
- package/build/src/rendering/volumetric/types.js +27 -1
- package/build/src/rendering/volumetric/types.js.map +1 -1
- package/build/src/rendering/volumetric-lighting.js.map +1 -1
- package/build/src/tools/scene.js +18 -5
- package/build/src/tools/scene.js.map +1 -1
- package/declaration/src/decorators/apply.d.ts +8 -4
- package/declaration/src/decorators/scene.d.ts +18 -9
- package/declaration/src/decorators/sound.d.ts +10 -1
- package/declaration/src/index.d.ts +1 -0
- package/declaration/src/loading/loader.d.ts +1 -1
- package/declaration/src/rendering/order.d.ts +31 -0
- package/declaration/src/rendering/volumetric/pipeline.d.ts +83 -1
- package/declaration/src/rendering/volumetric/selector.d.ts +6 -2
- package/declaration/src/rendering/volumetric/shaders-wgsl.d.ts +5 -1
- package/declaration/src/rendering/volumetric/shaders.d.ts +16 -2
- package/declaration/src/rendering/volumetric/types.d.ts +46 -1
- package/declaration/src/rendering/volumetric-lighting.d.ts +1 -1
- package/declaration/src/tools/scene.d.ts +2 -1
- 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,
|
|
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
|
|
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
|
|
247
|
-
//
|
|
248
|
-
|
|
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.
|
|
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
|
|
614
|
+
const scene = this._scene;
|
|
615
|
+
const existing = scene._depthRenderer?.[this._camera.uniqueId];
|
|
509
616
|
this._ownsDepthRenderer = !existing;
|
|
510
|
-
|
|
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
|
-
|
|
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 ${
|
|
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,
|
|
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 ${
|
|
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
|
-
|
|
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
|
|
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.
|
|
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 = [
|
|
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
|
-
|
|
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.
|