Skip to content

Commit c703f67

Browse files
MiiBondMike Bond
andauthored
Single-pass IBL shadow voxelization for WebGPU (#17270)
This changes the voxelization strategy for WebGPU to allow it to be done in a single pass (though, generation of mips still takes additional steps). This is possible using `textureStore` to write to arbitrary texels in a 3D texture. It also uses vertex pulling to correctly determine the provoking vertex for each triangle and retrieve adjacent vertices so that we can calculate the triangle's normal. This is used to swizzle the xyz components to maximize the rasterized area and not miss voxels. Anywho, this makes updating shadows for animated meshes much more plausible. I'm not sure the best way to do this but currently, in my playground, I'm doing this: ``` const frameSkip = 1; let frame = 0; scene.onAfterAnimationsObservable.add(() => { if (enableIblShadows) { if (frame == 0 && scene.animatables.find((anim) => !anim.paused)) { iblShadowsPipeline.updateVoxelization(); iblShadowsPipeline.resetAccumulation(); } if (frame === frameSkip) { frame = 0; } else { frame++; } } }); ``` Is there a better way to query whether there is currently an animation happening? --------- Co-authored-by: Mike Bond <[email protected]>
1 parent 618dae7 commit c703f67

File tree

4 files changed

+191
-79
lines changed

4 files changed

+191
-79
lines changed

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

Lines changed: 75 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,34 @@ import { ShaderLanguage } from "core/Materials/shaderLanguage";
3030
export class _IblShadowsVoxelRenderer {
3131
private _scene: Scene;
3232
private _engine: Engine;
33-
private _voxelGridRT: ProceduralTexture;
33+
34+
// WebGPU, single-pass voxelization.
35+
// See https://playground.babylonjs.com/#XSNYAU#133
36+
private _voxelGrid: RenderTargetTexture;
37+
private _voxelGridRT: RenderTargetTexture;
38+
39+
// WebGL voxelization, including tri-planar voxelization.
40+
private _combinedVoxelGridPT: ProceduralTexture;
3441
private _voxelGridXaxis: RenderTargetTexture;
3542
private _voxelGridYaxis: RenderTargetTexture;
3643
private _voxelGridZaxis: RenderTargetTexture;
3744
private _voxelMrtsXaxis: MultiRenderTarget[] = [];
3845
private _voxelMrtsYaxis: MultiRenderTarget[] = [];
3946
private _voxelMrtsZaxis: MultiRenderTarget[] = [];
40-
private _isVoxelGrid3D: boolean = true;
47+
4148
private _voxelMaterial: ShaderMaterial;
4249
private _voxelSlabDebugMaterial: ShaderMaterial;
50+
private _voxelClearColor: Color4 = new Color4(0, 0, 0, 1);
4351

4452
/**
4553
* Return the voxel grid texture.
4654
* @returns The voxel grid texture.
4755
*/
4856
public getVoxelGrid(): ProceduralTexture | RenderTargetTexture {
49-
if (this._triPlanarVoxelization) {
50-
return this._voxelGridRT;
57+
if (this._engine.isWebGPU) {
58+
return this._voxelGrid;
59+
} else if (this._triPlanarVoxelization) {
60+
return this._combinedVoxelGridPT;
5161
} else {
5262
return this._voxelGridZaxis;
5363
}
@@ -85,6 +95,11 @@ export class _IblShadowsVoxelRenderer {
8595
* Whether to use tri-planar voxelization. More expensive, but can help with artifacts.
8696
*/
8797
public set triPlanarVoxelization(enabled: boolean) {
98+
if (this._engine.isWebGPU) {
99+
// WebGPU only supports tri-planar voxelization.
100+
this._triPlanarVoxelization = true;
101+
return;
102+
}
88103
if (this._triPlanarVoxelization === enabled) {
89104
return;
90105
}
@@ -238,22 +253,14 @@ export class _IblShadowsVoxelRenderer {
238253
reusable: false,
239254
shaderLanguage: isWebGPU ? ShaderLanguage.WGSL : ShaderLanguage.GLSL,
240255
extraInitializations: (useWebGPU: boolean, list: Promise<any>[]) => {
241-
if (this._isVoxelGrid3D) {
242-
if (useWebGPU) {
243-
list.push(import("../../ShadersWGSL/iblVoxelGrid3dDebug.fragment"));
244-
} else {
245-
list.push(import("../../Shaders/iblVoxelGrid3dDebug.fragment"));
246-
}
247-
return;
248-
}
249256
if (useWebGPU) {
250-
list.push(import("../../ShadersWGSL/iblVoxelGrid2dArrayDebug.fragment"));
257+
list.push(import("../../ShadersWGSL/iblVoxelGrid3dDebug.fragment"));
251258
} else {
252-
list.push(import("../../Shaders/iblVoxelGrid2dArrayDebug.fragment"));
259+
list.push(import("../../Shaders/iblVoxelGrid3dDebug.fragment"));
253260
}
254261
},
255262
};
256-
this._voxelDebugPass = new PostProcess(this.debugPassName, this._isVoxelGrid3D ? "iblVoxelGrid3dDebug" : "iblVoxelGrid2dArrayDebug", debugOptions);
263+
this._voxelDebugPass = new PostProcess(this.debugPassName, "iblVoxelGrid3dDebug", debugOptions);
257264
this._voxelDebugPass.onApplyObservable.add((effect) => {
258265
if (this._voxelDebugAxis === 0) {
259266
effect.setTexture("voxelTexture", this._voxelGridXaxis);
@@ -276,13 +283,13 @@ export class _IblShadowsVoxelRenderer {
276283
* @param scene Scene to attach to
277284
* @param iblShadowsRenderPipeline The render pipeline this pass is associated with
278285
* @param resolutionExp Resolution of the voxel grid. The final resolution will be 2^resolutionExp.
279-
* @param triPlanarVoxelization Whether to use tri-planar voxelization. More expensive, but can help with artifacts.
286+
* @param triPlanarVoxelization Whether to use tri-planar voxelization. Only applies to WebGL. Voxelization will take longer but will reduce missing geometry.
280287
* @returns The voxel renderer
281288
*/
282289
constructor(scene: Scene, iblShadowsRenderPipeline: IblShadowsRenderPipeline, resolutionExp: number = 6, triPlanarVoxelization: boolean = true) {
283290
this._scene = scene;
284291
this._engine = scene.getEngine() as Engine;
285-
this._triPlanarVoxelization = triPlanarVoxelization;
292+
this._triPlanarVoxelization = this._engine.isWebGPU || triPlanarVoxelization;
286293
if (!this._engine.getCaps().drawBuffersExtension) {
287294
Logger.Error("Can't do voxel rendering without the draw buffers extension.");
288295
}
@@ -374,14 +381,13 @@ export class _IblShadowsVoxelRenderer {
374381
const size: TextureSize = {
375382
width: this._voxelResolution,
376383
height: this._voxelResolution,
377-
layers: this._isVoxelGrid3D ? undefined : this._voxelResolution,
378-
depth: this._isVoxelGrid3D ? this._voxelResolution : undefined,
384+
depth: this._voxelResolution,
379385
};
380386
const voxelAxisOptions: RenderTargetTextureOptions = {
381387
generateDepthBuffer: false,
382388
generateMipMaps: false,
383389
type: Constants.TEXTURETYPE_UNSIGNED_BYTE,
384-
format: Constants.TEXTUREFORMAT_R,
390+
format: Constants.TEXTUREFORMAT_RGBA,
385391
samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE,
386392
};
387393

@@ -403,24 +409,36 @@ export class _IblShadowsVoxelRenderer {
403409
}
404410
},
405411
};
406-
if (this._triPlanarVoxelization) {
412+
if (this._engine.isWebGPU) {
413+
this._voxelGrid = new RenderTargetTexture("voxelGrid", size, this._scene, {
414+
...voxelCombinedOptions,
415+
format: Constants.TEXTUREFORMAT_RGBA,
416+
creationFlags: Constants.TEXTURE_CREATIONFLAG_STORAGE,
417+
});
418+
this._voxelGridRT = new RenderTargetTexture(
419+
"voxelGridRT",
420+
{ width: Math.min(size.width * 2.0, 2048), height: Math.min(size.height * 2.0, 2048) },
421+
this._scene,
422+
voxelAxisOptions
423+
);
424+
} else if (this._triPlanarVoxelization) {
407425
this._voxelGridXaxis = new RenderTargetTexture("voxelGridXaxis", size, this._scene, voxelAxisOptions);
408426
this._voxelGridYaxis = new RenderTargetTexture("voxelGridYaxis", size, this._scene, voxelAxisOptions);
409427
this._voxelGridZaxis = new RenderTargetTexture("voxelGridZaxis", size, this._scene, voxelAxisOptions);
410428
this._voxelMrtsXaxis = this._createVoxelMRTs("x_axis_", this._voxelGridXaxis, numSlabs);
411429
this._voxelMrtsYaxis = this._createVoxelMRTs("y_axis_", this._voxelGridYaxis, numSlabs);
412430
this._voxelMrtsZaxis = this._createVoxelMRTs("z_axis_", this._voxelGridZaxis, numSlabs);
413431

414-
this._voxelGridRT = new ProceduralTexture("combinedVoxelGrid", size, "iblCombineVoxelGrids", this._scene, voxelCombinedOptions, false);
415-
this._scene.proceduralTextures.splice(this._scene.proceduralTextures.indexOf(this._voxelGridRT), 1);
416-
this._voxelGridRT.setFloat("layer", 0.0);
417-
this._voxelGridRT.setTexture("voxelXaxisSampler", this._voxelGridXaxis);
418-
this._voxelGridRT.setTexture("voxelYaxisSampler", this._voxelGridYaxis);
419-
this._voxelGridRT.setTexture("voxelZaxisSampler", this._voxelGridZaxis);
432+
this._combinedVoxelGridPT = new ProceduralTexture("combinedVoxelGrid", size, "iblCombineVoxelGrids", this._scene, voxelCombinedOptions, false);
433+
this._scene.proceduralTextures.splice(this._scene.proceduralTextures.indexOf(this._combinedVoxelGridPT), 1);
434+
this._combinedVoxelGridPT.setFloat("layer", 0.0);
435+
this._combinedVoxelGridPT.setTexture("voxelXaxisSampler", this._voxelGridXaxis);
436+
this._combinedVoxelGridPT.setTexture("voxelYaxisSampler", this._voxelGridYaxis);
437+
this._combinedVoxelGridPT.setTexture("voxelZaxisSampler", this._voxelGridZaxis);
420438
// We will render this only after voxelization is completed for the 3 axes.
421-
this._voxelGridRT.autoClear = false;
422-
this._voxelGridRT.wrapU = Texture.CLAMP_ADDRESSMODE;
423-
this._voxelGridRT.wrapV = Texture.CLAMP_ADDRESSMODE;
439+
this._combinedVoxelGridPT.autoClear = false;
440+
this._combinedVoxelGridPT.wrapU = Texture.CLAMP_ADDRESSMODE;
441+
this._combinedVoxelGridPT.wrapV = Texture.CLAMP_ADDRESSMODE;
424442
} else {
425443
this._voxelGridZaxis = new RenderTargetTexture("voxelGridZaxis", size, this._scene, voxelCombinedOptions);
426444
this._voxelMrtsZaxis = this._createVoxelMRTs("z_axis_", this._voxelGridZaxis, numSlabs);
@@ -464,7 +482,7 @@ export class _IblShadowsVoxelRenderer {
464482
voxelRT.wrapV = Texture.CLAMP_ADDRESSMODE;
465483
voxelRT.noPrePassRenderer = true;
466484
const mrtArray: MultiRenderTarget[] = [];
467-
const targetTypes = new Array(this._maxDrawBuffers).fill(this._isVoxelGrid3D ? Constants.TEXTURE_3D : Constants.TEXTURE_2D_ARRAY);
485+
const targetTypes = new Array(this._maxDrawBuffers).fill(Constants.TEXTURE_3D);
468486

469487
for (let mrtIndex = 0; mrtIndex < numSlabs; mrtIndex++) {
470488
let layerIndices = new Array(this._maxDrawBuffers).fill(0);
@@ -475,7 +493,7 @@ export class _IblShadowsVoxelRenderer {
475493

476494
const mrt = new MultiRenderTarget(
477495
"mrt_" + name + mrtIndex,
478-
{ width: this._voxelResolution, height: this._voxelResolution, depth: this._isVoxelGrid3D ? this._voxelResolution : undefined },
496+
{ width: this._voxelResolution, height: this._voxelResolution, depth: this._voxelResolution },
479497
this._maxDrawBuffers, // number of draw buffers
480498
this._scene,
481499
{
@@ -516,7 +534,7 @@ export class _IblShadowsVoxelRenderer {
516534
if (this._triPlanarVoxelization) {
517535
this._voxelGridXaxis?.dispose();
518536
this._voxelGridYaxis?.dispose();
519-
this._voxelGridRT?.dispose();
537+
this._combinedVoxelGridPT?.dispose();
520538
}
521539
this._voxelGridZaxis?.dispose();
522540
for (const mip of this._mipArray) {
@@ -533,7 +551,7 @@ export class _IblShadowsVoxelRenderer {
533551
private _createVoxelMaterials(): void {
534552
const isWebGPU = this._engine.isWebGPU;
535553
this._voxelMaterial = new ShaderMaterial("voxelization", this._scene, "iblVoxelGrid", {
536-
uniforms: ["world", "viewMatrix", "invWorldScale", "nearPlane", "farPlane", "stepSize"],
554+
uniforms: ["world", "viewMatrix", "invTransWorld", "invWorldScale", "nearPlane", "farPlane", "stepSize"],
537555
defines: ["MAX_DRAW_BUFFERS " + this._maxDrawBuffers],
538556
shaderLanguage: isWebGPU ? ShaderLanguage.WGSL : ShaderLanguage.GLSL,
539557
extraInitializationsAsync: async () => {
@@ -594,6 +612,7 @@ export class _IblShadowsVoxelRenderer {
594612
this._removeVoxelRTs(this._voxelMrtsXaxis);
595613
this._removeVoxelRTs(this._voxelMrtsYaxis);
596614
this._removeVoxelRTs(this._voxelMrtsZaxis);
615+
this._removeVoxelRTs([this._voxelGridRT]);
597616
}
598617

599618
private _removeVoxelRTs(rts: RenderTargetTexture[]) {
@@ -628,7 +647,10 @@ export class _IblShadowsVoxelRenderer {
628647
this._includedMeshes = includedMeshes;
629648
this._voxelizationInProgress = true;
630649

631-
if (this._triPlanarVoxelization) {
650+
if (this._engine.isWebGPU) {
651+
this._voxelGridRT.renderList = includedMeshes;
652+
this._addRTsForRender([this._voxelGridRT], includedMeshes, 0);
653+
} else if (this._triPlanarVoxelization) {
632654
this._addRTsForRender(this._voxelMrtsXaxis, includedMeshes, 0);
633655
this._addRTsForRender(this._voxelMrtsYaxis, includedMeshes, 1);
634656
this._addRTsForRender(this._voxelMrtsZaxis, includedMeshes, 2);
@@ -656,13 +678,25 @@ export class _IblShadowsVoxelRenderer {
656678
allReady &&= rttReady;
657679
}
658680
if (allReady) {
681+
if (this._engine.isWebGPU) {
682+
// Clear the voxel grid storage texture.
683+
// Need to clear each layer individually.
684+
// Would a compute shader be faster here to clear all layers in one go?
685+
if (this._voxelGrid && this._voxelGrid.renderTarget) {
686+
for (let layer = 0; layer < this._voxelResolution; layer++) {
687+
this._engine.bindFramebuffer(this._voxelGrid.renderTarget, 0, undefined, undefined, true, 0, layer);
688+
this._engine.clear(this._voxelClearColor, true, false, false);
689+
this._engine.unBindFramebuffer(this._voxelGrid.renderTarget, true);
690+
}
691+
}
692+
}
659693
for (const rt of this._renderTargets) {
660694
rt.render();
661695
}
662696
this._stopVoxelization();
663697

664-
if (this._triPlanarVoxelization) {
665-
this._voxelGridRT.render();
698+
if (this._triPlanarVoxelization && !this._engine.isWebGPU) {
699+
this._combinedVoxelGridPT.render();
666700
}
667701
this._generateMipMaps();
668702
// eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then
@@ -710,6 +744,10 @@ export class _IblShadowsVoxelRenderer {
710744
voxelMaterial.setFloat("nearPlane", nearPlane);
711745
voxelMaterial.setFloat("farPlane", farPlane);
712746
voxelMaterial.setFloat("stepSize", stepSize);
747+
if (this._engine.isWebGPU) {
748+
this._voxelMaterial.useVertexPulling = true;
749+
this._voxelMaterial.setTexture("voxel_storage", this.getVoxelGrid());
750+
}
713751
});
714752

715753
// Set this material on every mesh in the scene (for this RT)
Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
1+
var voxel_storage: texture_storage_3d<rgba8unorm, write>;
12
varying vNormalizedPosition: vec3f;
2-
3-
uniform nearPlane: f32;
4-
uniform farPlane: f32;
5-
uniform stepSize: f32;
3+
flat varying f_swizzle: i32;
64

75
@fragment
86
fn main(input: FragmentInputs) -> FragmentOutputs {
7+
var size: vec3f = vec3f(textureDimensions(voxel_storage));
98
var normPos: vec3f = input.vNormalizedPosition.xyz;
10-
if (normPos.z < uniforms.nearPlane || normPos.z > uniforms.farPlane) {
11-
discard;
9+
var outputColor: vec4f = vec4f(0.0, 0.0, 0.0, 1.0);
10+
switch (input.f_swizzle) {
11+
case 0: {
12+
normPos = normPos.zxy;
13+
outputColor = vec4f(1.0, 1.0, 0.0, 1.0);
14+
break;
15+
}
16+
case 1: {
17+
normPos = normPos.yzx;
18+
outputColor = vec4f(1.0, 1.0, 1.0, 1.0);
19+
break;
20+
}
21+
default: {
22+
normPos = normPos.xyz;
23+
outputColor = vec4f(1.0, 1.0, 0.0, 1.0);
24+
break;
25+
}
1226
}
1327

14-
fragmentOutputs.fragData0 = select(vec4f(0.0), vec4f(1.0), normPos.z < uniforms.nearPlane + uniforms.stepSize);
15-
fragmentOutputs.fragData1 = select(vec4f(0.0), vec4f(1.0), normPos.z >= uniforms.nearPlane + uniforms.stepSize && normPos.z < uniforms.nearPlane + 2.0 * uniforms.stepSize);
16-
fragmentOutputs.fragData2 = select(vec4f(0.0), vec4f(1.0), normPos.z >= uniforms.nearPlane + 2.0 * uniforms.stepSize && normPos.z < uniforms.nearPlane + 3.0 * uniforms.stepSize);
17-
fragmentOutputs.fragData3 = select(vec4f(0.0), vec4f(1.0), normPos.z >= uniforms.nearPlane + 3.0 * uniforms.stepSize && normPos.z < uniforms.nearPlane + 4.0 * uniforms.stepSize);
18-
#if MAX_DRAW_BUFFERS > 4
19-
fragmentOutputs.fragData4 = select(vec4f(0.0), vec4f(1.0), normPos.z >= uniforms.nearPlane + 4.0 * uniforms.stepSize && normPos.z < uniforms.nearPlane + 5.0 * uniforms.stepSize);
20-
fragmentOutputs.fragData5 = select(vec4f(0.0), vec4f(1.0), normPos.z >= uniforms.nearPlane + 5.0 * uniforms.stepSize && normPos.z < uniforms.nearPlane + 6.0 * uniforms.stepSize);
21-
fragmentOutputs.fragData6 = select(vec4f(0.0), vec4f(1.0), normPos.z >= uniforms.nearPlane + 6.0 * uniforms.stepSize && normPos.z < uniforms.nearPlane + 7.0 * uniforms.stepSize);
22-
fragmentOutputs.fragData7 = select(vec4f(0.0), vec4f(1.0), normPos.z >= uniforms.nearPlane + 7.0 * uniforms.stepSize && normPos.z < uniforms.nearPlane + 8.0 * uniforms.stepSize);
23-
#endif
28+
textureStore(voxel_storage, vec3<i32>(i32(normPos.x * size.x), i32(normPos.y * size.y), i32(normPos.z * size.z)), outputColor);
29+
fragmentOutputs.color = vec4<f32>(vec3<f32>(normPos), 1.);
2430
}

0 commit comments

Comments
 (0)