Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement mesh skinning #2359

Closed
wants to merge 1 commit into from
Closed

Conversation

Looooong
Copy link
Contributor

Objective

Solution

  • Modify bevy_gltf to extract skins information as well as the joint data in the mesh.
  • Implement SkinnedMesh that stores:
    • A handle to SkinnedMeshInverseBindposes.
    • Entities list that addresses the joint entities.
    • Joint transform matrices (glTF reference).
  • Implement SkinnedMeshInverseBindposes to store inverse bindposes data which can be shared between multiple SkinnedMesh component.
  • Implement a system to update SkinnedMesh's joint transform matrices from SkinnedMeshInverseBindposes and joint entities' GlobalTransform.
  • These joint transform matrices can be binded directly to shader storage buffer by implementing RenderResource and RenderResources trait for SkinnedMesh, then by adding RenderResourceNode<SkinnedMesh> to render graph.
  • Create a modified version of PBR vertex shader that combines joint weights with joint matrices (glTF reference).

Compare to the counterpart in #1429

  • The code is smaller and simpler.
  • This feature can work independently from the animation crate.
  • By using RenderResourcesNode instead of AssetRenderResourcesNode, it doesn't need to create custom material asset for each skinned mesh instance that shares the same mesh and skin.

@github-actions github-actions bot added the S-Needs-Triage This issue needs to be labelled label Jun 19, 2021
@alice-i-cecile alice-i-cecile added A-Animation Make things move and change over time C-Enhancement A new feature and removed S-Needs-Triage This issue needs to be labelled labels Jun 19, 2021
@alice-i-cecile alice-i-cecile self-requested a review June 19, 2021 03:57
@alice-i-cecile
Copy link
Member

That's much simpler than I expected! Could we get an example or two of how this works in practice?

I'm interested in how this handles both animations from the gltf as well as creating new animations from code.

@@ -0,0 +1,24 @@
[package]
name = "bevy_animation_rig"
Copy link
Member

Choose a reason for hiding this comment

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

IMO this should be the start of a general bevy_animation crate instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The original bevy_animation is meant to animate the component properties. So I created this crate to separate the "skinning" from the "animation".

@@ -263,6 +311,24 @@ async fn load_gltf<'a, 'b>(
if let Some(Err(err)) = err {
return Err(err);
}

for (&entity, &skin_index) in &entity_to_skin_index_map {
Copy link
Member

Choose a reason for hiding this comment

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

So it looks like this grabs animation data from the .gltf file, creating one entity for each joint?

Copy link
Contributor Author

@Looooong Looooong Jun 19, 2021

Choose a reason for hiding this comment

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

Technically, it's only skin data, without any animation clip. Entity joints are already created since they are part of the glTF nodes. This code just map the created entity to the joint defined in glTF.

@@ -142,6 +146,23 @@ async fn load_gltf<'a, 'b>(
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, vertex_attribute);
}

if let Some(vertex_attribute) = reader.read_joints(0).map(|v| {
Copy link
Member

Choose a reason for hiding this comment

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

The new code in this function doesn't seem to be feature gated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This code is in bevy_gltf crate, and it depends on the bevy_animation_rig. Making bevy_animation_rig optional in this crate is kinda hard as bevy_gltf is also optional for bevy. I don't know how to deal with this. Do you have any suggestion?

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

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

Very simple! I have a few nits, but mostly I want to see end-user examples of how this is used before I can comment further.

@Looooong
Copy link
Contributor Author

@alice-i-cecile Sure, I will sort these things out.

Copy link
Contributor

@NathanSWard NathanSWard left a comment

Choose a reason for hiding this comment

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

Here a two very small thing.
I'll give this PR a more formal review tomorrow/this weekend :)

crates/bevy_animation_rig/src/skinned_mesh.rs Outdated Show resolved Hide resolved
crates/bevy_animation_rig/src/skinned_mesh.rs Outdated Show resolved Hide resolved
@Looooong Looooong marked this pull request as draft June 19, 2021 05:57
@Looooong Looooong force-pushed the skeleton-animation branch 2 times, most recently from b018cad to 280c755 Compare June 19, 2021 06:36
crates/bevy_animation_rig/src/lib.rs Outdated Show resolved Hide resolved
crates/bevy_animation_rig/src/skinned_mesh.rs Outdated Show resolved Hide resolved
crates/bevy_animation_rig/src/skinned_mesh.rs Outdated Show resolved Hide resolved
}

fn write_buffer_bytes(&self, buffer: &mut [u8]) {
let transform_size = std::mem::size_of::<[f32; 16]>();
Copy link
Contributor

Choose a reason for hiding this comment

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

If we're checking for the transform size, why are use using [f32; 16] instead of Mat4?
(I might not be understanding something so please correct me if I'm wrong haha :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I got this std::mem::size_of::<[f32; 16]>() from RenderResource implementation for Mat4. I'm not sure why as well. Maybe Mat4 may have internal states so we don't rely on the size of Mat4?

crates/bevy_animation_rig/src/skinned_mesh.rs Outdated Show resolved Hide resolved
crates/bevy_animation_rig/src/skinned_mesh.rs Outdated Show resolved Hide resolved
@Looooong Looooong force-pushed the skeleton-animation branch 5 times, most recently from 8d9371b to ca83297 Compare June 19, 2021 11:11
@Looooong
Copy link
Contributor Author

@alice-i-cecile I have added animation/custom_skinned_mesh example and animation/gltf_skinned_mesh example. You can check them out.

@Looooong Looooong force-pushed the skeleton-animation branch 3 times, most recently from 38ab695 to 28c6b74 Compare June 19, 2021 12:16
@Looooong Looooong changed the title Implement skeleton animation Implement mesh skinning Jun 19, 2021
@NathanSWard
Copy link
Contributor

@Looooong
I'm curious if #2215 is related to the reflection bug we saw.


/// Manually implement [`bevy_reflect::Reflect`] for [`SkinnedMeshJoint`] to work around an issue,
/// where spawning a scene with a component containings a vector of structs would result in runtime panic.
unsafe impl Reflect for SkinnedMeshJoint {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm curious, is deriving Reflect on the SkinnedMeshJoint enough?
(Instead of manually implementing it?)

Copy link
Contributor Author

@Looooong Looooong Jun 20, 2021

Choose a reason for hiding this comment

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

It's not just normal manual implementation but more like a monkey-patch. I patched the clone_value() method to work around this issue.

@Looooong
Copy link
Contributor Author

@Looooong
I'm curious if #2215 is related to the reflection bug we saw.

Yes, it is.

@mockersf mockersf added the S-Pre-Relicense This PR was made before Bevy added the Apache license. Cannot be merged or used for other work label Jul 16, 2021
@tasgon
Copy link

tasgon commented Oct 30, 2021

Hi, just was wondering, are there any issues still blocking this?

@alice-i-cecile
Copy link
Member

@Looooong could you comment in #2373? There's interest in reviving this and it would be a shame to have to start from scratch.

@Looooong
Copy link
Contributor Author

Looooong commented Jan 7, 2022

@alice-i-cecile I have left the comment. I haven't touch this for a while. Is there anything else I need to do to get this shipped?

@mockersf mockersf removed the S-Pre-Relicense This PR was made before Bevy added the Apache license. Cannot be merged or used for other work label Jan 7, 2022
@alice-i-cecile
Copy link
Member

Is there anything else I need to do to get this shipped?

It's looking quite good! Code quality is solid, and I'd be happy to ship this as an initial building block. This PR is currently just waiting on editorial attention: animation wasn't a focus for the 0.6 release. I would expect it to receive attention during the 0.7 cycle; animation seems to be one of the bigger stumbling blocks now that rendering is cleaned up.

@cart
Copy link
Member

cart commented Jan 7, 2022

Some quick notes (I'll do a review soon after 0.6 releases):

  • This would need to be updated to the new renderer
  • I think skeletal animation is "core" enough that it should be a part of our built-in mesh shader (which lives in the new bevy_pbr at the moment), to ensure that user-defined custom shaders can be animated easily.

@nicopap
Copy link
Contributor

nicopap commented Jan 18, 2022

As someone who has been heavily reliant on this PR for my own prototypes (really thank you for your work!) I have a some comments regarding the API:

  • There should be an example for picking gltf node entities for the joints. I've spent way too much time on figuring this out. Here is a gist of what I came up with.
  • Spawning the joint nodes as entities in the world with a Transform and GlobalTransform is perfect! Please keep it this way.
  • The NickJBenson PR on this fork adds an example file that implements a clean API to manage multiple animations and transitions between them. It might be worth picking it up.

@Looooong
Copy link
Contributor Author

@cart

  • I think skeletal animation is "core" enough that it should be a part of our built-in mesh shader (which lives in the new bevy_pbr at the moment), to ensure that user-defined custom shaders can be animated easily.

Now that you mention about it, this is a really good idea that I have never considered before. I will move this feature into bevy_pbr crate.

However, there are a few things I need some advices on:

  • Before this PR, I only had experience writing shader in ShaderLab in Unity. I'm fairly new at writing OpenGL code. I need directions and suggestions on best practices in writing OpenGL code, so that other people can write custom shader with ease.
  • Attempted to push invalid value of type DynamicStruct, expected Handle<T> #2215 is the reason I need to implement Reflect manually to workaround the problem. Is this bug fixed? If so, then I can remove this monkey-patch code.

@robclu
Copy link

robclu commented Feb 2, 2022

Hi, just wanted to check what still needs to be done for this? I need skinning for something that I'm working out so would like to help if possible.

@james7132
Copy link
Member

Closing this in favor for #4238, which is compatible with the new renderer. @Looooong is credited in that PR as it was started by merging this PR into main.

For those who left a review here, please give a once-over for that PR.

@nicopap: re: the @nickjbenson PR to this one could be a good start of an actual animation crate. I'd suggest that they open a PR with it after #4238 gets merged.

@james7132 james7132 closed this Mar 21, 2022
bors bot pushed a commit that referenced this pull request Mar 29, 2022
# Objective
Load skeletal weights and indices from GLTF files. Animate meshes.

## Solution
 - Load skeletal weights and indices from GLTF files.
 - Added `SkinnedMesh` component and ` SkinnedMeshInverseBindPose` asset
 - Added `extract_skinned_meshes` to extract joint matrices.
 - Added queue phase systems for enqueuing the buffer writes.

Some notes:

 -  This ports part of # #2359 to the current main.
 -  This generates new `BufferVec`s and bind groups every frame. The expectation here is that the number of `Query::get` calls during extract is probably going to be the stronger bottleneck, with up to 256 calls per skinned mesh. Until that is optimized, caching buffers and bind groups is probably a non-concern.
 - Unfortunately, due to the uniform size requirements, this means a 16KB buffer is allocated for every skinned mesh every frame. There's probably a few ways to get around this, but most of them require either compute shaders or storage buffers, which are both incompatible with WebGL2.

Co-authored-by: james7132 <contact@jamessliu.com>
Co-authored-by: François <mockersf@gmail.com>
Co-authored-by: James Liu <contact@jamessliu.com>
aevyrie pushed a commit to aevyrie/bevy that referenced this pull request Jun 7, 2022
# Objective
Load skeletal weights and indices from GLTF files. Animate meshes.

## Solution
 - Load skeletal weights and indices from GLTF files.
 - Added `SkinnedMesh` component and ` SkinnedMeshInverseBindPose` asset
 - Added `extract_skinned_meshes` to extract joint matrices.
 - Added queue phase systems for enqueuing the buffer writes.

Some notes:

 -  This ports part of # bevyengine#2359 to the current main.
 -  This generates new `BufferVec`s and bind groups every frame. The expectation here is that the number of `Query::get` calls during extract is probably going to be the stronger bottleneck, with up to 256 calls per skinned mesh. Until that is optimized, caching buffers and bind groups is probably a non-concern.
 - Unfortunately, due to the uniform size requirements, this means a 16KB buffer is allocated for every skinned mesh every frame. There's probably a few ways to get around this, but most of them require either compute shaders or storage buffers, which are both incompatible with WebGL2.

Co-authored-by: james7132 <contact@jamessliu.com>
Co-authored-by: François <mockersf@gmail.com>
Co-authored-by: James Liu <contact@jamessliu.com>
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
# Objective
Load skeletal weights and indices from GLTF files. Animate meshes.

## Solution
 - Load skeletal weights and indices from GLTF files.
 - Added `SkinnedMesh` component and ` SkinnedMeshInverseBindPose` asset
 - Added `extract_skinned_meshes` to extract joint matrices.
 - Added queue phase systems for enqueuing the buffer writes.

Some notes:

 -  This ports part of # bevyengine#2359 to the current main.
 -  This generates new `BufferVec`s and bind groups every frame. The expectation here is that the number of `Query::get` calls during extract is probably going to be the stronger bottleneck, with up to 256 calls per skinned mesh. Until that is optimized, caching buffers and bind groups is probably a non-concern.
 - Unfortunately, due to the uniform size requirements, this means a 16KB buffer is allocated for every skinned mesh every frame. There's probably a few ways to get around this, but most of them require either compute shaders or storage buffers, which are both incompatible with WebGL2.

Co-authored-by: james7132 <contact@jamessliu.com>
Co-authored-by: François <mockersf@gmail.com>
Co-authored-by: James Liu <contact@jamessliu.com>
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 C-Enhancement A new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Skeletal Animation
9 participants