Skip to content

Commit 329b8c5

Browse files
authored
IBL Shadows - Init passes only after voxelization (#16527)
Okay, I've been investigating the issue where the shadow accumulation isn't working initially after loading the [IBL Shadow Demo playground](https://playground.babylonjs.com/#8R5SSE#618). On some loads, the shadows will appear noisy and not accumulate over multiple frames. Note that this issue never happens if you have Spector.js enabled, which is why I had such a hard time reproducing it originally. The actual issue is that the uniform, `accumulationParameters` fails to be set with `WebGL: INVALID_OPERATION: uniform4f: location is not from the associated program` Even though this fails, the uniform value is still cached and Babylon doesn't set the uniform again until the value changes. That is why accumulation appears to be disabled until you move the camera (which causes the uniform to change and update). I have not been able to figure out _why_ the uniform set fails in the first place. It seems to be consistently the 3rd render that fails. Before that, the uniform is set successfully and, as far as I can tell, the shader program isn't updated after that point. And after the failure, the uniform can be set again. The one thing that I was able to change to make the issue seem to go away was to put enough of a delay on the rendering of the accumulation pass. I've added logic to only start rendering the accumulation pass once the first voxelization is complete. This is fine since we can't render shadows until this is done anyway but I don't know why it should fix the problem.
1 parent 26e58e2 commit 329b8c5

File tree

4 files changed

+88
-42
lines changed

4 files changed

+88
-42
lines changed

packages/dev/core/src/Rendering/IBLShadows/iblShadowsAccumulationPass.ts

+27-13
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { ProceduralTexture } from "core/Materials/Textures/Procedurals/procedura
1010
import type { IProceduralTextureCreationOptions } from "core/Materials/Textures/Procedurals/proceduralTexture";
1111
import type { IblShadowsRenderPipeline } from "./iblShadowsRenderPipeline";
1212
import { Observable } from "../../Misc/observable";
13+
import type { EventState } from "../../Misc/observable";
14+
import type { Nullable } from "../../types";
1315

1416
/**
1517
* This should not be instanciated directly, as it is part of a scene component
@@ -207,17 +209,10 @@ export class _IblShadowsAccumulationPass {
207209
// Need to set all the textures first so that the effect gets created with the proper uniforms.
208210
this._setOutputTextureBindings();
209211

210-
let counter = 0;
211-
this._scene.onBeforeRenderObservable.add(() => {
212-
counter = 0;
213-
});
214-
this._scene.onAfterRenderTargetsRenderObservable.add(() => {
215-
if (++counter == 2) {
216-
if (this.enabled && this._outputTexture.isReady()) {
217-
this._setOutputTextureBindings();
218-
this._outputTexture.render();
219-
}
220-
}
212+
this._renderWhenGBufferReady = this._render.bind(this);
213+
// Don't start rendering until the first vozelization is done.
214+
this._renderPipeline.onVoxelizationCompleteObservable.addOnce(() => {
215+
this._scene.geometryBufferRenderer!.getGBuffer().onAfterRenderObservable.add(this._renderWhenGBufferReady);
221216
});
222217

223218
// Create the accumulation texture for the previous frame.
@@ -284,7 +279,7 @@ export class _IblShadowsAccumulationPass {
284279
this._oldPositionCopy.onBeforeGenerationObservable.add(this._updatePositionCopy.bind(this));
285280
}
286281

287-
private _setOutputTextureBindings() {
282+
private _setOutputTextureBindings(): boolean {
288283
const remanence = this._isMoving ? this.remanence : 0.99;
289284
this._accumulationParams.set(remanence, this.reset ? 1.0 : 0.0, this._renderPipeline.voxelGridSize, 0.0);
290285
this._outputTexture.setTexture("spatialBlurSampler", this._renderPipeline._getSpatialBlurTexture());
@@ -294,7 +289,7 @@ export class _IblShadowsAccumulationPass {
294289

295290
const geometryBufferRenderer = this._scene.geometryBufferRenderer;
296291
if (!geometryBufferRenderer) {
297-
return;
292+
return false;
298293
}
299294
const velocityIndex = geometryBufferRenderer.getTextureIndex(GeometryBufferRenderer.VELOCITY_LINEAR_TEXTURE_TYPE);
300295
this._outputTexture.setTexture("motionSampler", geometryBufferRenderer.getGBuffer().textures[velocityIndex]);
@@ -303,6 +298,7 @@ export class _IblShadowsAccumulationPass {
303298

304299
this.reset = false;
305300
this._isMoving = false;
301+
return true;
306302
}
307303

308304
private _updatePositionCopy() {
@@ -315,6 +311,16 @@ export class _IblShadowsAccumulationPass {
315311
this._oldAccumulationCopy.setTexture("textureSampler", this._outputTexture);
316312
}
317313

314+
private _render() {
315+
if (this.enabled && this._outputTexture.isReady() && this._outputTexture.getEffect()?.isReady()) {
316+
if (this._setOutputTextureBindings()) {
317+
this._outputTexture.render();
318+
}
319+
}
320+
}
321+
322+
private _renderWhenGBufferReady: Nullable<(eventData: number, eventState: EventState) => void> = null;
323+
318324
/**
319325
* Called by render pipeline when canvas resized.
320326
* @param scaleFactor The factor by which to scale the canvas size.
@@ -324,6 +330,10 @@ export class _IblShadowsAccumulationPass {
324330
width: Math.max(1.0, Math.floor(this._engine.getRenderWidth() * scaleFactor)),
325331
height: Math.max(1.0, Math.floor(this._engine.getRenderHeight() * scaleFactor)),
326332
};
333+
// Don't resize if the size is the same as the current size.
334+
if (this._outputTexture.getSize().width === newSize.width && this._outputTexture.getSize().height === newSize.height) {
335+
return;
336+
}
327337
this._outputTexture.resize(newSize, false);
328338
this._oldAccumulationCopy.resize(newSize, false);
329339
this._oldPositionCopy.resize({ width: this._engine.getRenderWidth(), height: this._engine.getRenderHeight() }, false);
@@ -355,6 +365,10 @@ export class _IblShadowsAccumulationPass {
355365
* Disposes the associated resources
356366
*/
357367
public dispose() {
368+
if (this._scene.geometryBufferRenderer && this._renderWhenGBufferReady) {
369+
const gBuffer = this._scene.geometryBufferRenderer.getGBuffer();
370+
gBuffer.onAfterRenderObservable.removeCallback(this._renderWhenGBufferReady);
371+
}
358372
this._disposeTextures();
359373
if (this._debugPassPP) {
360374
this._debugPassPP.dispose();

packages/dev/core/src/Rendering/IBLShadows/iblShadowsRenderPipeline.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,7 @@ export class IblShadowsRenderPipeline extends PostProcessRenderPipeline {
11251125
this._dummyTexture3d.dispose();
11261126
this.onNewIblReadyObservable.clear();
11271127
this.onShadowTextureReadyObservable.clear();
1128+
this.onVoxelizationCompleteObservable.clear();
11281129
super.dispose();
11291130
}
11301131
}

packages/dev/core/src/Rendering/IBLShadows/iblShadowsSpatialBlurPass.ts

+27-13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { GeometryBufferRenderer } from "../../Rendering/geometryBufferRenderer";
99
import { ProceduralTexture } from "core/Materials/Textures/Procedurals/proceduralTexture";
1010
import type { IProceduralTextureCreationOptions } from "core/Materials/Textures/Procedurals/proceduralTexture";
1111
import type { IblShadowsRenderPipeline } from "./iblShadowsRenderPipeline";
12+
import type { EventState } from "../../Misc/observable";
13+
import type { Nullable } from "../../types";
1214

1315
/**
1416
* This should not be instanciated directly, as it is part of a scene component
@@ -161,35 +163,39 @@ export class _IblShadowsSpatialBlurPass {
161163
// Need to set all the textures first so that the effect gets created with the proper uniforms.
162164
this._setBindings();
163165

164-
let counter = 0;
165-
this._scene.onBeforeRenderObservable.add(() => {
166-
counter = 0;
167-
});
168-
this._scene.onAfterRenderTargetsRenderObservable.add(() => {
169-
if (++counter == 2) {
170-
if (this.enabled && this._outputTexture.isReady()) {
171-
this._setBindings();
172-
this._outputTexture.render();
173-
}
174-
}
166+
this._renderWhenGBufferReady = this._render.bind(this);
167+
// Don't start rendering until the first vozelization is done.
168+
this._renderPipeline.onVoxelizationCompleteObservable.addOnce(() => {
169+
this._scene.geometryBufferRenderer!.getGBuffer().onAfterRenderObservable.add(this._renderWhenGBufferReady);
175170
});
176171
}
177172

178-
private _setBindings() {
173+
private _setBindings(): boolean {
179174
this._outputTexture.setTexture("voxelTracingSampler", this._renderPipeline._getVoxelTracingTexture());
180175
const iterationCount = 1;
181176
this._blurParameters.set(iterationCount, this._worldScale, 0.0, 0.0);
182177
this._outputTexture.setVector4("blurParameters", this._blurParameters);
183178
const geometryBufferRenderer = this._scene.geometryBufferRenderer;
184179
if (!geometryBufferRenderer) {
185-
return;
180+
return false;
186181
}
187182
const depthIndex = geometryBufferRenderer.getTextureIndex(GeometryBufferRenderer.SCREENSPACE_DEPTH_TEXTURE_TYPE);
188183
this._outputTexture.setTexture("depthSampler", geometryBufferRenderer.getGBuffer().textures[depthIndex]);
189184
const wnormalIndex = geometryBufferRenderer.getTextureIndex(GeometryBufferRenderer.NORMAL_TEXTURE_TYPE);
190185
this._outputTexture.setTexture("worldNormalSampler", geometryBufferRenderer.getGBuffer().textures[wnormalIndex]);
186+
return true;
187+
}
188+
189+
private _render() {
190+
if (this.enabled && this._outputTexture.isReady() && this._outputTexture.getEffect()?.isReady()) {
191+
if (this._setBindings()) {
192+
this._outputTexture.render();
193+
}
194+
}
191195
}
192196

197+
private _renderWhenGBufferReady: Nullable<(eventData: number, eventState: EventState) => void> = null;
198+
193199
/**
194200
* Called by render pipeline when canvas resized.
195201
* @param scaleFactor The factor by which to scale the canvas size.
@@ -199,6 +205,10 @@ export class _IblShadowsSpatialBlurPass {
199205
width: Math.max(1.0, Math.floor(this._engine.getRenderWidth() * scaleFactor)),
200206
height: Math.max(1.0, Math.floor(this._engine.getRenderHeight() * scaleFactor)),
201207
};
208+
// Don't resize if the size is the same as the current size.
209+
if (this._outputTexture.getSize().width === newSize.width && this._outputTexture.getSize().height === newSize.height) {
210+
return;
211+
}
202212
this._outputTexture.resize(newSize, false);
203213
}
204214

@@ -214,6 +224,10 @@ export class _IblShadowsSpatialBlurPass {
214224
* Disposes the associated resources
215225
*/
216226
public dispose() {
227+
if (this._scene.geometryBufferRenderer && this._renderWhenGBufferReady) {
228+
const gBuffer = this._scene.geometryBufferRenderer.getGBuffer();
229+
gBuffer.onAfterRenderObservable.removeCallback(this._renderWhenGBufferReady);
230+
}
217231
this._outputTexture.dispose();
218232
if (this._debugPassPP) {
219233
this._debugPassPP.dispose();

packages/dev/core/src/Rendering/IBLShadows/iblShadowsVoxelTracingPass.ts

+33-16
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import { GeometryBufferRenderer } from "../../Rendering/geometryBufferRenderer";
1111
import { ProceduralTexture } from "core/Materials/Textures/Procedurals/proceduralTexture";
1212
import type { IProceduralTextureCreationOptions } from "core/Materials/Textures/Procedurals/proceduralTexture";
1313
import type { CubeTexture } from "../../Materials/Textures/cubeTexture";
14+
import { Logger } from "../../Misc/logger";
15+
import type { EventState } from "../../Misc/observable";
16+
import type { Nullable } from "../../types";
1417

1518
/**
1619
* Build cdf maps for IBL importance sampling during IBL shadow computation.
@@ -322,18 +325,10 @@ export class _IblShadowsVoxelTracingPass {
322325
this._outputTexture.defines = defines;
323326
// Need to set all the textures first so that the effect gets created with the proper uniforms.
324327
this._setBindings(this._scene.activeCamera!);
325-
326-
let counter = 0;
327-
this._scene.onBeforeRenderObservable.add(() => {
328-
counter = 0;
329-
});
330-
this._scene.onAfterRenderTargetsRenderObservable.add(() => {
331-
if (++counter == 2) {
332-
if (this.enabled && this._outputTexture.isReady()) {
333-
this._setBindings(this._scene.activeCamera!);
334-
this._outputTexture.render();
335-
}
336-
}
328+
this._renderWhenGBufferReady = this._render.bind(this);
329+
// Don't start rendering until the first vozelization is done.
330+
this._renderPipeline.onVoxelizationCompleteObservable.addOnce(() => {
331+
this._scene.geometryBufferRenderer!.getGBuffer().onAfterRenderObservable.add(this._renderWhenGBufferReady);
337332
});
338333
}
339334

@@ -351,7 +346,7 @@ export class _IblShadowsVoxelTracingPass {
351346
return defines;
352347
}
353348

354-
private _setBindings(camera: Camera) {
349+
private _setBindings(camera: Camera): boolean {
355350
this._outputTexture.defines = this._createDefines();
356351
this._outputTexture.setMatrix("viewMtx", camera.getViewMatrix());
357352
this._outputTexture.setMatrix("projMtx", camera.getProjectionMatrix());
@@ -384,23 +379,37 @@ export class _IblShadowsVoxelTracingPass {
384379
this._outputTexture.setTexture("voxelGridSampler", voxelGrid);
385380
this._outputTexture.setTexture("blueNoiseSampler", this._renderPipeline!._getNoiseTexture());
386381
const cdfGenerator = this._scene.iblCdfGenerator;
387-
if (cdfGenerator) {
388-
this._outputTexture.setTexture("icdfSampler", cdfGenerator.getIcdfTexture());
382+
if (!cdfGenerator) {
383+
Logger.Warn("IBLShadowsVoxelTracingPass: Can't bind for render because iblCdfGenerator is not enabled.");
384+
return false;
389385
}
386+
this._outputTexture.setTexture("icdfSampler", cdfGenerator.getIcdfTexture());
390387
if (this._coloredShadows && this._scene.environmentTexture) {
391388
this._outputTexture.setTexture("iblSampler", this._scene.environmentTexture);
392389
}
393390

394391
const geometryBufferRenderer = this._scene.geometryBufferRenderer;
395392
if (!geometryBufferRenderer) {
396-
return;
393+
Logger.Warn("IBLShadowsVoxelTracingPass: Can't bind for render because GeometryBufferRenderer is not enabled.");
394+
return false;
397395
}
398396
const depthIndex = geometryBufferRenderer.getTextureIndex(GeometryBufferRenderer.SCREENSPACE_DEPTH_TEXTURE_TYPE);
399397
this._outputTexture.setTexture("depthSampler", geometryBufferRenderer.getGBuffer().textures[depthIndex]);
400398
const wnormalIndex = geometryBufferRenderer.getTextureIndex(GeometryBufferRenderer.NORMAL_TEXTURE_TYPE);
401399
this._outputTexture.setTexture("worldNormalSampler", geometryBufferRenderer.getGBuffer().textures[wnormalIndex]);
400+
return true;
402401
}
403402

403+
private _render() {
404+
if (this.enabled && this._outputTexture.isReady() && this._outputTexture.getEffect()?.isReady()) {
405+
if (this._setBindings(this._scene.activeCamera!)) {
406+
this._outputTexture.render();
407+
}
408+
}
409+
}
410+
411+
private _renderWhenGBufferReady: Nullable<(eventData: number, eventState: EventState) => void> = null;
412+
404413
/**
405414
* Called by render pipeline when canvas resized.
406415
* @param scaleFactor The factor by which to scale the canvas size.
@@ -410,6 +419,10 @@ export class _IblShadowsVoxelTracingPass {
410419
width: Math.max(1.0, Math.floor(this._engine.getRenderWidth() * scaleFactor)),
411420
height: Math.max(1.0, Math.floor(this._engine.getRenderHeight() * scaleFactor)),
412421
};
422+
// Don't resize if the size is the same as the current size.
423+
if (this._outputTexture.getSize().width === newSize.width && this._outputTexture.getSize().height === newSize.height) {
424+
return;
425+
}
413426
this._outputTexture.resize(newSize, false);
414427
}
415428

@@ -431,6 +444,10 @@ export class _IblShadowsVoxelTracingPass {
431444
* Disposes the associated resources
432445
*/
433446
public dispose() {
447+
if (this._scene.geometryBufferRenderer && this._renderWhenGBufferReady) {
448+
const gBuffer = this._scene.geometryBufferRenderer.getGBuffer();
449+
gBuffer.onAfterRenderObservable.removeCallback(this._renderWhenGBufferReady);
450+
}
434451
this._outputTexture.dispose();
435452
if (this._debugPassPP) {
436453
this._debugPassPP.dispose();

0 commit comments

Comments
 (0)