-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Add morph targets #8158
Add morph targets #8158
Conversation
d657f73
to
64b5322
Compare
Todo when merged: open issue detailing how paused AnimationPlayers overwrite user-set weights. |
Ready for review. @james7132 and @willstott101 might be interested in this. |
fn morph_vertex(vertex: Vertex) -> Vertex { | ||
var vertex = vertex; | ||
let weight_count = layer_count(); | ||
for (var i: u32 = 0u; i < weight_count; i ++) { | ||
let weight = weight_at(i); | ||
if weight == 0.0 { | ||
continue; | ||
} | ||
vertex.position += weight * morph(vertex.index, position_offset, i); | ||
#ifdef VERTEX_NORMALS | ||
vertex.normal += weight * morph(vertex.index, normal_offset, i); | ||
#endif | ||
#ifdef VERTEX_TANGENTS | ||
vertex.tangent += vec4(weight * morph(vertex.index, tangent_offset, i), 0.0); | ||
#endif | ||
} | ||
return vertex; | ||
} | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this is duplicated in prepass.wgsl
because I didn't want to move the struct Vertex
definition to mesh_types.wgsl
(which probably should be the place where Vertex
is defined)
morph_nodes: Query<(&Children, &MorphWeights), (Without<Handle<Mesh>>, Changed<MorphWeights>)>, | ||
mut morph_primitives: Query<&mut MorphWeights, With<Handle<Mesh>>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect splitting this in two different components is preferable. Otherwise it might become a bit awkward for the user to Query MorphWeights for specific mesh (as glTF mesh) or mesh (as bevy mesh)
could you add an example dedicated to morphing, out of the scene viewer? |
@mockersf I can add a standalone example. Additionally, do you have any tips for avoiding code duplication between the example and the scene viewer? |
No, example scene viewer is duplicating code from other examples... If something is useful enough you can put it in bevy_animation |
Just pushed another "proposal commit": I moved morph target names to |
My latest change broke |
I think morph target names are orthogonal to |
I don't follow this logic. Can you explain how it is orthogonal? The We do the same thing for "mesh vertex attribute data" by correlating their "mesh vertex attribute names". This feels conceptually almost identical. |
Hmm. Your reasoning is sensible. After all, the morph target image is stored on the Looks good. |
You can use the old test model here to try your changes. It has several meshes with morph targets, including Morph-Primitive Test: It has more edge cases than |
I just removed the "nested propagated MorphWeights" in favor of duplicating curves within a clip. I think it keeps the mental model simpler by being truer to the current Bevy Mesh api. I also simplified the |
Sure. I can finish work on your design to speed things up. |
I updated the Specifically pay attention to the Manipulating individual morph targets programmatically, ie: not through a loaded glTF animation. Is a common usecase (dark souls character editor is an example). Similarly to how one would programmatically move bone transforms in a skinned mesh to handle IKs and ragdolls. Try the scene_viewer with the It's possible to write correct code. But if the user doesn't know about the subtle difference between a bevy mesh and a glTF mesh, they are going to be surprised when they update a Note that it's fairly poor as a out-of-the-box experience. But it's possible to write a 3rd party plugin that adds a |
Hmmm ok I do see your point. I've also played around with morph targets a bit in Godot (which has the sync behavior). There are definitely arguments in favor of both approaches, but your "hierarchical sync" approach does have the benefits of "less user code for manual morph targets" and "more aligned with GLTF and Godot user expectations for morph target behavior". I guess either of those should outweigh "conceptual purity / Bevy alignment". This is especially true in light of the fact that Godot, Unity, and Unreal all have "mesh surface / primitives" concepts. Which means we will likely also want them, just to accommodate existing workflows. And then we'd have "bevy aligned / conceptual purity" and "behavior aligned with user expectations". In short: you were right and sorry for the back and forth! I'll revert things back to hierarchical sync (while keeping the other changes I made). I'll also do the "MorphWeights component split" to resolve some of the UX concerns with the hierarchy approach. |
Small non-blocking consideration: in order to write proper motion vectors, morph targets will need to be able to calculate their vertex position last frame during the current frame, which will involve knowing last frame's weights. |
…". Store first_mesh on MorphWeights
I pushed the changes mentioned above. I also added |
Objective
bevy_pbr
(closes GLTF Morph Targets #5756) & load them from glTFMorph targets (also known as shape interpolation, shape keys, or blend shapes) allow animating individual vertices with fine grained controls. This is typically used for facial expressions. By specifying multiple poses as vertex offset, and providing a set of weight of each pose, it is possible to define surprisingly realistic transitions between poses. Blending between multiple poses also allow composition. Morph targets are part of the gltf standard and are a feature of Unity and Unreal, and babylone.js, it is only natural to implement them in bevy.
Solution
This implementation of morph targets uses a 3d texture where each pixel is a component of an animated attribute. Each layer is a different target. We use a 2d texture for each target, because the number of attribute×components×animated vertices is expected to always exceed the maximum pixel row size limit of webGL2. It copies fairly closely the way skinning is implemented on the CPU side, while on the GPU side, the shader morph target implementation is a relatively trivial detail.
We add an optional
morph_texture
to theMesh
struct. Themorph_texture
is built through a method that accepts an iterator over attribute buffers.The
MorphWeights
component, user-accessible, controls the blend of poses used by mesh instances (so that multiple copy of the same mesh may have different weights), all the weights are uploaded to a uniform buffer of 256f32
. We limit to 16 poses per mesh, and a total of 256 poses.More literature:
bevy_morph_targets-2023-04-11.mp4
bevy_morph_targets-2023-05-10.mp4
Acknowledgements
storytold
for sponsoring the featuresuperdump
andjames7132
for guidance and help figuring out stuffFuture work
Changelog
Mesh::set_morph_targets
VisitMorphTargets
andVisitMorphAttributes
traits tobevy_render
, this allows defining morph targets (a fairly complex and nested data structure) through iterators (ie: single copy instead of passing around buffers), see documentation of those traits for detailsMorphWeights
component exported bybevy_render
MorphWeights
control mesh's morph target weights, blending between various poses defined as morph targets.MorphWeights
are directly inherited by direct children (single level of hierarchy) of an entity. This allows controlling several mesh primitives through a unique entity as per GLTF spec.MorphTargetNames
component, naming each indices of loaded morph targets.bevy_gltf
bevy_animation
(previously, it was awarn!
log)MorphStressTest.gltf
asset for morph targets testing, taken from the glTF samples repo, CC0.scene_viewer
scene_viewer
from the rest of the code, reducing#[cfg(feature)]
noisemorph_targets.rs
example to show off how to manipulate morph targets, loadingMorpStressTest.gltf
Migration Guide
MeshPipeline
now has a singlemesh_layouts
field rather than separatemesh_layout
andskinned_mesh_layout
fields. You should handle all possible mesh bind group layouts in your implementationMORPH_TARGETS
shader def and mesh pipeline key. A new function is exposed to make this easier:setup_moprh_and_skinning_defs
MeshBindGroup
is nowMeshBindGroups
, cached bind groups are now accessed through theget
method.