godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
91.6k stars 21.28k forks source link

Compressed animation blend shape tracks don't preserve weights of zero #99794

Open greeble-dev opened 6 days ago

greeble-dev commented 6 days ago

Tested versions

Reproducible in Godot v4.4.dev [bbc54692c].

System information

Windows 10.0.19045. Compiled with MSVC.

Issue description

When rendering a mesh with blend shapes, the vertex shader will skip any shapes with a weight close to zero. However, compressed animation blend shape tracks output a weight above this threshold even though the original animated weight was zero. This could cause a performance issue if an animation has a lot of zero weight blend shapes. It will also affect the visuals, although that will be hard to see as the weight is very close to zero.

The blend shape compression code is:

// Animation::_compress_key
blend = (blend / float(Compression::BLEND_SHAPE_RANGE)) * 0.5 + 0.5;
values[0] = CLAMP(int32_t(blend * 65535.0), 0, 65535);

// Animation::_uncompress_blend_shape
float bsn = float(p_value.x) / 65535.0;
return (bsn * 2.0 - 1.0) * float(Compression::BLEND_SHAPE_RANGE);

A weight of 0.0 compresses to unorm 32767, which decompresses to -0.00012207. This is slightly bigger than the vertex shader's threshold of 0.0001.

// Skeleton.glsl
for (uint i = 0; i < params.blend_shape_count; i++) {
    float w = blend_shape_weights.data[i];
    if (abs(w) > 0.0001) {

A potential fix is to change the 65535 scale to 65534 (https://github.com/greeble-dev/godot/commit/06502a0fbf778f1000e948261dbb3a20d4551b87). This means zero still compresses to unorm 32767, but that now decompresses to zero. The change would be backwards compatible with assets that were compressed before the change.

I can make a PR with this change if desired.

Note that there's another potential issue with how blend shape tracks and other tracks are doing unorm rounding. I've filed that as https://github.com/godotengine/godot/issues/99796, and will roll both fixes into the same PR unless advised otherwise.

Steps to reproduce

Launch the MRP project. The main scene has two cubes playing an animation with some zero weight blend shape tracks. Left cube = uncompressed animation, right cube = compressed. Initially there should be no visual difference.

Repo option 1 (requires code changes): Apply change https://github.com/greeble-dev/godot/commit/107bbe62e444f8d517a96a8732d016838e52779e to skeleton.glsl, which will force any blend shape that passes the weight threshold to blend with a weight of one instead. This is just a hacky way to show if the vertex shader skipped the shape. Launch the project and note that the cube with compressed animation is now distorted while the uncompressed animation is unchanged.

Repro option 2 (no code changes): Breakpoint Animation::try_blend_shape_track_interpolate, then in the editor select node AnimCompressed/AnimationPlayer to trigger the breakpoint. Note that the compressed path returns a weight of -0.00012207.

To test the potential fix, apply change https://github.com/greeble-dev/godot/commit/06502a0fbf778f1000e948261dbb3a20d4551b87. Launch the editor and select node AnimCompressed/AnimationPlayer to make it update. The cubes should now match, showing that the blend shape is correctly skipped.

Minimal reproduction project (MRP)

bug-blend-shape-repro.zip

greeble-dev commented 3 days ago

I'm working on a PR to fix this along with https://github.com/godotengine/godot/issues/99796.