Skip to content

Conversation

MiiBond
Copy link
Contributor

@MiiBond MiiBond commented Oct 10, 2025

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?

@bjsplat
Copy link
Collaborator

bjsplat commented Oct 10, 2025

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

@bjsplat
Copy link
Collaborator

bjsplat commented Oct 10, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Oct 10, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Oct 10, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Oct 10, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Oct 10, 2025

@Popov72
Copy link
Contributor

Popov72 commented Oct 13, 2025

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?

In a real-world scenario, I think we would let the user decide when to update the voxelization (meshes can also be moved without using animation, a custom vertex shader can apply deformation, etc.). But for a test PG, I think this is fine.

Let us know when the PR is ready for review!

@MiiBond MiiBond force-pushed the mbond/single-pass-voxelization-via-compute branch from 2111655 to 4775e9e Compare October 17, 2025 21:07
@MiiBond MiiBond marked this pull request as ready for review October 17, 2025 21:11
@bjsplat
Copy link
Collaborator

bjsplat commented Oct 17, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Oct 17, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Oct 17, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Oct 17, 2025

@sebavan sebavan requested a review from Popov72 October 17, 2025 22:26
if (this._engine.isWebGPU) {
// Clear the voxel grid storage texture.
// Need to clear each layer individually.
// Would a compute shader be faster here to clear all layers in one go?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A compute shader will likely be faster, as each layer clear generates a render pass:

image


#ifdef MORPHTARGETS_POSITION
#ifdef USE_VERTEX_PULLING
positionUpdated = positionUpdated + (readVector3FromRawSampler(i, vertexID) - inputPosition) * uniforms.morphTargetInfluences[i];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change ties morphTargetsVertex.fx too closely to iblVoxelGrid.vertex.fx, because inputPosition is defined in the latter file, and we don't yet know how we're going to handle vertex pulling for our standard materials. In the meantime, check out my suggestion in iblVoxelGrid.vertex.fx to work around this issue.


varying vNormalizedPosition : vec3f;
flat varying f_swizzle: i32;
varying vNormal : vec3f;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this declaration, as vNormal is not used.


#include <morphTargetsVertexGlobal>
let inputPosition: vec3f = positionUpdated;
#include <morphTargetsVertex>[0..maxSimultaneousMorphTargets]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of updating morphTargetsVertex.fx, you can modify this declaration as follows:

#include <morphTargetsVertex>("vertexInputs.position),inputPosition)")[0..maxSimultaneousMorphTargets]

This should (not tested!) replace the two occurrences of vertexInputs.position) with inputPosition), which will allow you to achieve the desired result without using inputPosition in morphTargetsVertex.fx.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Popov72 The code, as given, has syntax errors in it (two ')' brackets without matching '('). However, I can't find examples of the expected syntax for this. Are there existing places in the code where this type of substitution happens?

Copy link
Contributor

@Popov72 Popov72 Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's on purpose :)

In the #include<XXX>("YYYY") syntax, the YYY part is a simple find and replace mechanism, where YYYY has this format: find1,replace1,find2,replace2,etc.

So, what I did is to replace vertexInputs.position) with inputPosition). I put the closing parenthesis in the string, else it does a replacement for an additional occurrence (vertexInputs.position{X}) that we don't want to update.

We use this syntax a lot in the PBR/OpenPBR/standard vertex shaders:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants