Skip to content

Conversation

@pcwalton
Copy link
Contributor

@pcwalton pcwalton commented Nov 9, 2025

Currently, Bevy automatically computes AABBs for all meshes, even those that have skins or morph targets. This is incorrect, as each skin and/or morph target can deform the mesh arbitrarily. This is not a theoretical problem, as Maya relies on rest poses to position and rotate meshes that are children of skins.

Right now, the only way to disable this Bevy feature is to add the NoFrustumCulling component, or to overwrite the generated Aabb with a different one. Neither are a particularly good experience:

  1. Adding NoFrustumCulling is impossible when loading a glTF scene, and it's also not obvious that NoFrustumCulling is needed to correctly render skinned meshes sometimes.

  2. Overwriting the Aabb is cumbersome.

This commit changes Bevy's behavior to not generate Aabb components for skinned meshes, fixing the issue. Note that, to keep this patch small, morph targets aren't handled yet. The documentation has been updated to inform developers that they may wish to add Aabb components manually to skinned meshes in order to opt in to frustum culling.

Fixes #4971.

Currently, Bevy automatically computes AABBs for all meshes, even those
that have skins or morph targets. This is incorrect, as each skin and/or
morph target can deform the mesh arbitrarily. This is not a theoretical
problem, as Maya relies on rest poses to position and rotate meshes that
are children of skins.

Right now, the only way to disable this Bevy feature is to add the
`NoFrustumCulling` component, or to overwrite the generated `Aabb` with
a different one. Neither are a particularly good experience:

1. Adding `NoFrustumCulling` is impossible when loading a glTF scene,
   and it's also not obvious that `NoFrustumCulling` is needed to
   correctly render skinned meshes sometimes.

2. Overwriting the `Aabb` is cumbersome.

This commit changes Bevy's behavior to not generate `Aabb` components
for skinned meshes, fixing the issue. Note that, to keep this patch
small, morph targets aren't handled yet. The documentation has been
updated to inform developers that they may wish to add `Aabb` components
manually to skinned meshes in order to opt in to frustum culling.
@janhohenheim
Copy link
Member

janhohenheim commented Nov 9, 2025

Some open questions:

  • What are the performance implications of this for the average user, now that their animated meshes suddenly no longer get frustum culled?
  • What do other engines do? I think you said Unity does this approach here. What about Unreal and Godot?
  • Would it make sense to include some kind of utility to calculate the max bounds of the AABBs of an animation so users don't need to go manually check their DCC?
  • Why is this better than upstreaming https://github.com/greeble-dev/bevy_mod_skinned_aabb? I assume performance? If so, does the performance argument also hold for the average user that "just wants to play an animation" and has no idea they're "supposed" to add an AABB manually?

I'm not against this PR, don't get me wrong. Our default behavior is blatantly wrong and needs to go. I remember how confused I was when I first ran into disappearing meshes due to it.
Just have some reservations about the average end-user experience before I click accept :)

Also requesting reviews from Greeble, as they wrote the crate linked above, and James, who I remember wanted to change the skinned mesh AABB behavior at some point too and has some thoughts on what to replace it with

@janhohenheim janhohenheim added A-Animation Make things move and change over time D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Nov 9, 2025
@pcwalton
Copy link
Contributor Author

pcwalton commented Nov 9, 2025

Ok, so looking at what Unity does more closely, it seems that Unity can do something similar to bevy_mod_skinned_aabb but it’s off by default “for performance reasons”. By default, what Unity does is to compute the maximum AABB given all the animations in the FBX and statically caches that, with the ability to override manually if needed. We could do this with glTF by going through the animations, but that wouldn’t help me, as we store various animations for skinned meshes outside the glTF that defines the mesh.

My inclination is:

  • What Bevy currently does is just broken and we need to change something. It is never correct to use the rest pose of a skinned mesh for culling. Almost anything would be better than that.
  • I’m hesitant to enable bevy_mod_skinned_aabb by default, as I think most games want to use static AABBs defined by artists and don’t want to pay that CPU cost.
  • Perhaps we should do what Unity does and compute AABBs based on the maximum bounds of all animations attached to a mesh in a glTF.
  • We should have a mode on the glTF loader that disables computing AABBs, even if we do what Unity does and compute a maximal AABB based on all the animations attached to the skin, because of situations like mine in which we can play animations on our skinned meshes that come from outside the glTF that defines the mesh.

I don’t have time to implement the Unity thing, but I can scale this patch back to be an opt in “don’t compute AABBs for skinned meshes” option on the glTF loader if we feel that’d be the right thing.

@janhohenheim
Copy link
Member

Good assessment. I'm not sure if it's better to

  • change the default
  • have an opt-in
  • remove the old behavior entirely.

I'll leave that decision to the maintainers. PR looks good other than that decision :)

@janhohenheim janhohenheim added the X-Contentious There are nontrivial implications that should be thought through label Nov 9, 2025
Copy link
Member

@tychedelia tychedelia left a comment

Choose a reason for hiding this comment

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

I'm agnostic about the follow up but this is fine as unbreaking the status quo.

@pcwalton
Copy link
Contributor Author

pcwalton commented Nov 9, 2025

From a glance, Godot seems to do what bevy_mod_skinned_aabb does. I was unable to determine what Unreal does from a quick search.

I'm persuaded that this patch should be pared down to be an opt-in (or opt-out?) option on the glTF loader that disables automatic AABB generation for skinned meshes and inserts NoFrustumCulling on those meshes. This would handle the vast majority of cases, I think, as I believe that inserting SkinnedMesh components manually (as opposed to loading them from a glTF file) is rare. As follow-up patches, we can then (a) create AABBs that are the union of all animations and (b) upstream bevy_mod_skinned_aabb. Once those patches land, we'll effectively have three modes: (1) manual artist-defined AABBs (what I'll use); (2) automatic tight AABB generation at the cost of CPU time; (3) automatic approximate AABB generation on import (only correct if all the animations that will ever be played on the skin are present in the glTF and if the bone transforms are never changed manually).

This patch should wait to land until I can make that change.

@janhohenheim janhohenheim added the S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged label Nov 9, 2025
@janhohenheim janhohenheim added this to the 0.18 milestone Nov 9, 2025
@NicoZweifel
Copy link
Contributor

I checked the UE source and it uses a PhysicsAsset.

If no PhysicsAsset is attached or the opt-out is enabled (bComponentUseFixedSkelBounds) the aabb is static and has the same frustum culling issues as described above, there are a bunch of other conditions (e.g. uniform scaling) but the default from what I understand boils down to a physics based proxy (i.e. ragdoll) and a dynamic aabb that gets calculated every frame.

@greeble-dev
Copy link
Contributor

greeble-dev commented Nov 9, 2025

Gonna add a few notes although they're kinda tangential to this PR. I think that this PR is the practical choice right now - either the current version or the pared down alternative.

I was unable to determine what Unreal does from a quick search.

(EDIT: Oops, I didn't see NicoZweifel's comment before posting this, but I'll leave my notes for reference)

Unreal defaults to creating collision shapes for joints during mesh import. These are used to calculate the culling bounds, even if they're not instantiated in the physics system. So it's like bevy_mod_skinned_aabb, but inefficient and fragile.

Perhaps we should do what Unity does and compute AABBs based on the maximum bounds of all animations attached to a mesh in a glTF.

My hot take on this is that the juice isn't worth the squeeze - as you mentioned there's a bunch of reasons it's awkward and unreliable.

Side note in case it's useful: for most humanoid character rigs - i.e. rigs where joints below the pelvis usually only rotate - there's a simple approximation. For each leaf joint, sum its length and the lengths of all its ancestors, then take the maximum of this value across all leaf joints, and attach an AABB/sphere of that size to the pelvis. In practice this is a bit bigger than an AABB calculated from animations, but way simpler.

As follow-up patches, we can ... upstream bevy_mod_skinned_aabb.

I would be mildly negative on upstreaming bevy_mod_skinned_aabb in its current form. It's a bit too hacky and unreliable (e.g. breaks if meshes are not RenderAssetUsages::MAIN_WORLD), and less efficient than it could be. But I would be open to upstreaming if a maintainer says they understand the limitations, and it's viewed as an experimental option that's only a first step towards a better solution.

I would also like to do an in-engine version that's reliable and more performant. But it's kinda awkward to do right now. The main issues are:

  1. The ideal place to calculate the bounds is in extract_skins. But that's after the CPU culling path has run, and I'm reluctant to make it dependent on GPU culling.
  2. There are other options, like adding it to the CPU culling. But they cost performance/reliability and would be more controversial.

This situation could change. Maybe the render world gets removed, maybe the culling system gets redesigned, maybe WebGL is dropped and GPU culling is the default. And after the dust has settled my job could be easier. But for now I'm waiting to see what happens.

I can expand on these reasons if any render folks want to chew it over.

I’m hesitant to enable bevy_mod_skinned_aabb by default, as I think most games want to use static AABBs defined by artists and don’t want to pay that CPU cost.

I understand the hesitation, but I would advocate for it to be the default - so static AABBs would be an optional optimisation for sophisticated users. I think per-joint bounds are cheaper than many people expect, and the value of making (non-morph) skinned meshes reliable is very high. There's also some hybrid approaches that trade off reliability for performance, like approximating faces and hands with a single bound.

If upstreaming moves forward then I'll provide more details and concrete performance numbers.

@janhohenheim
Copy link
Member

@greeble-dev regarding upstreaming: certainly these limitations are better than the status quo? Especially since e.g. not using the right world simply defaults to not generating an AABB, which is not worse than what we have now.
Right now from a user perspective, some objects just magically disappear sometimes with no indication of why, it's not even clear that animations are to blame. Hard to be more brittle than that.

@greeble-dev
Copy link
Contributor

@greeble-dev regarding upstreaming: certainly these limitations are better than the status quo?

I would agree, but I think there's a decent chance that maintainers might disagree, or the PR would stall over performance doubts. Particularly if this PR lands and things (kinda sorta) work by default. So for now the cost/benefit isn't there for me. But I can change my mind if there's an indication that maintainers will be ok with an unreliable/inefficient version.

@alice-i-cecile alice-i-cecile removed the S-Needs-Review Needs reviewer attention (from anyone!) to move forward label Nov 9, 2025
@alice-i-cecile
Copy link
Member

I'm fine WRT almost any solution here, and would prefer to merge a fix. Off-screen performance issues are less bad than meshes suddenly vanishing.

I understand the hesitation, but I would advocate for it to be the default - so static AABBs would be an optional optimisation for sophisticated users.

This is my preferred long-term solution, but I won't block on it in this PR and would prefer to land a simple fix first.

@greeble-dev
Copy link
Contributor

So @janhohenheim's comment kinda guilted me into taking another look at upstreaming options for bevy_mod_skinned_aabb, and I think I have a compromise with a reasonable chance of landing. It's a bit simpler and more reliable, but less accurate. I expect to file a PR later this week - sneak preview here: 11b3a8d.

I don't think my PR will necessarily conflict with this PR or other alternatives. I would advocate for my PR to be the default behaviour, but that's not a requirement, and either way the alternatives should still be available as an option.

@pcwalton
Copy link
Contributor Author

@greeble-dev I'm quite happy to close this in favor of your solution, and review your solution, if you submit the PR. Honestly, I just want some solution here--I have no strong opinions as to what in particular we do.

What I'm working on is a band-aid solution that simply takes the rest pose into account when computing the AABB at import time and nothing else. This is still wrong, but better than what we do, and should be uncontroversial as more or less a strict improvement on what we currently do. However, if you're going to submit a better solution I'm happy to hold off.

@janhohenheim
Copy link
Member

@greeble-dev oh no, I certainly didn't want to guilt you ❤️

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it labels Nov 10, 2025
@alice-i-cecile alice-i-cecile added the S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged label Nov 10, 2025
@alice-i-cecile
Copy link
Member

@pcwalton, please bother me when you're happy with a bandaid solution :) I'll be happy to take a final look and likely merge it in.

@greeble-dev
Copy link
Contributor

greeble-dev commented Nov 14, 2025

I've filed my PR: #21837. It's only a draft and doesn't require a detailed review right now, but does need feedback like "is this the right approach?" and "should it be enabled by default?".

My PR supercedes this PR in its current state. But if @pcwalton adds the rest pose option then I'd expect to incorporate that into the options I've added.

/// Controls the bounds related components that are assigned to skinned mesh
/// entities. These components are used by systems like frustum culling.
pub enum GltfSkinnedMeshBoundsPolicy {
    /// Skinned meshes are assigned an `Aabb` component calculated from the bind
    /// pose `Mesh`.
    BindPose,
    /// Skinned meshes are assigned a `SkinnedMeshBounds` component, which will
    /// be used by the `bevy_camera` plugin to update the `Aabb` component
    /// based on joint positions.
    Dynamic,
    /// Same as `BindPose`, but also assign a `NoFrustumCulling` component. That
    /// component tells the `bevy_camera` plugin to avoid frustum culling the
    /// skinned mesh.
    NoFrustumCulling,
}

BindPose is the behaviour on main, and NoFrustumCulling is roughly what this PR currently does.

pcwalton added a commit to pcwalton/bevy that referenced this pull request Nov 15, 2025
meshes in glTF files.

In glTF, the joints of a skinned mesh aren't required to have identity
transforms for the rest pose. In particular, Maya likes to place skinned
meshes at the origin and then uses joint transforms to move them into
place. At present, the Bevy glTF loader naively assumes that the minimum
and maximum values of the `POSITION` accessor suffice to determine the
bounding box of a mesh, but for skinned meshes with non-identity joint
transforms this is not true. This could cause Bevy to apply incorrect
`Aabb` components to skinned meshes, which would in turn cause those
meshes to be incorrectly frustum culled and disappear.

This PR fixes the issue by constructing the AABBs manually for skinned
meshes in glTF files. When computing these AABBs, this patch takes the
rest pose into account, fixing the issue. For non-skinned meshes, this
patch makes Bevy continue to use the precomputed minimum and maximum
values of the `POSITION` accessor, as this is safe.

Note that this patch *doesn't* fix all possible causes of incorrect
AABBs. In particular, animation of skins and morph targets can still
cause meshes to extend outside their AABBs and be incorrectly culled.
The [`bevy_mod_skinned_aabb`] plugin can compute per-joint AABBs that
remain correct in the presence of animation, at some CPU cost.
Alternately, developers may wish to manually extend AABBs for skinned
meshes as necessary to include all possible animations by modifying the
automatically-generated `Aabb` component, or even remove the `Aabb`
component altogether.

Additionally, this patch doesn't handle the case in which a mesh and
joints are manually constructed outside of glTF. In this case, the
`bevy_camera::visibility::calculate_bounds` system will generate an
incorrect AABB for the mesh. I intentionally left that out of this
patch, because regenerating AABBs on CPU whenever a joint is updated
would be slow; [`bevy_mod_skinned_aabb`] would be a better approach.
Besides, constructing skinned meshes programmatically is rare, and glTF
is much more commonly used in practice.

For comparison, Unreal and Godot use a technique similar to
[`bevy_mod_skinned_aabb`] to generate AABBs. Unity can either use that
technique or, by default, simply widens the AABB to encompass not only
the rest pose but also all animations in the imported FBX file. We could
implement that if desired in a follow-up.

This PR obsoletes bevyengine#21787, which removed `Aabb` components entirely for
skinned meshes. The current patch is a more aggressive approach that,
while not foolproof, strictly improves the situation in common cases
while maintaining automatic frustum culling support for skinned meshes
in general.

[`bevy_mod_skinned_aabb`]: https://github.com/greeble-dev/bevy_mod_skinned_aabb
@pcwalton
Copy link
Contributor Author

Closed in favor of #21845.

@pcwalton pcwalton closed this Nov 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Animation Make things move and change over time D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged X-Contentious There are nontrivial implications that should be thought through

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Animated meshes disapear if their original position is not within the field of view

7 participants