bevy_mesh optional morph (#21389)

# Objective

- make morph support optional on bevy_mesh
- unblock #20742
- another step towards #20867

## Solution

- feature

## Testing

- morph targets example works
This commit is contained in:
atlv
2025-10-06 14:53:27 -04:00
committed by GitHub
parent 7205b918f5
commit c23377228c
10 changed files with 104 additions and 68 deletions
+4
View File
@@ -166,6 +166,7 @@ default = [
"default_font",
"hdr",
"ktx2",
"morph",
"multi_threaded",
"png",
"reflect_auto_register",
@@ -441,6 +442,9 @@ bevy_ci_testing = ["bevy_internal/bevy_ci_testing"]
# Enable animation support, and glTF animation loading
animation = ["bevy_internal/animation", "bevy_animation"]
# Enables support for morph target weights in bevy_mesh
morph = ["bevy_internal/morph"]
# Enable using a shared stdlib for cxx on Android
android_shared_stdcxx = ["bevy_internal/android_shared_stdcxx"]
+3 -1
View File
@@ -16,7 +16,9 @@ bevy_asset = { path = "../bevy_asset", version = "0.18.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.18.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.18.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.18.0-dev" }
bevy_mesh = { path = "../bevy_mesh", version = "0.18.0-dev" }
bevy_mesh = { path = "../bevy_mesh", version = "0.18.0-dev", features = [
"morph",
] }
bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev", features = [
"petgraph",
] }
+3
View File
@@ -209,6 +209,9 @@ animation = [
"bevy_gltf?/bevy_animation",
]
# Enables support for morph target weights in bevy_mesh
morph = ["bevy_mesh?/morph", "bevy_render?/morph"]
bevy_shader = ["dep:bevy_shader"]
bevy_image = ["dep:bevy_image", "bevy_color", "bevy_asset"]
bevy_sprite = ["dep:bevy_sprite", "bevy_camera"]
+2 -1
View File
@@ -12,7 +12,7 @@ keywords = ["bevy"]
# bevy
bevy_app = { path = "../bevy_app", version = "0.18.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.18.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.18.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.18.0-dev", optional = true }
bevy_math = { path = "../bevy_math", version = "0.18.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.18.0-dev" }
@@ -43,6 +43,7 @@ serde_json = "1.0.140"
default = []
## Adds serialization support through `serde`.
serialize = ["dep:serde", "wgpu-types/serde"]
morph = ["dep:bevy_image"]
[lints]
workspace = true
+4 -3
View File
@@ -8,6 +8,7 @@ mod conversions;
mod index;
mod mesh;
mod mikktspace;
#[cfg(feature = "morph")]
pub mod morph;
pub mod primitives;
pub mod skinning;
@@ -28,10 +29,10 @@ pub use wgpu_types::VertexFormat;
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[cfg(feature = "morph")]
pub use crate::morph::MorphWeights;
#[doc(hidden)]
pub use crate::{
morph::MorphWeights, primitives::MeshBuilder, primitives::Meshable, Mesh, Mesh2d, Mesh3d,
};
pub use crate::{primitives::MeshBuilder, primitives::Meshable, Mesh, Mesh2d, Mesh3d};
}
bitflags! {
+60 -50
View File
@@ -10,7 +10,10 @@ use super::{
#[cfg(feature = "serialize")]
use crate::SerializedMeshAttributeData;
use alloc::collections::BTreeMap;
use bevy_asset::{Asset, Handle, RenderAssetUsages};
#[cfg(feature = "morph")]
use bevy_asset::Handle;
use bevy_asset::{Asset, RenderAssetUsages};
#[cfg(feature = "morph")]
use bevy_image::Image;
use bevy_math::{primitives::Triangle3d, *};
#[cfg(feature = "serialize")]
@@ -127,7 +130,9 @@ pub struct Mesh {
#[reflect(ignore, clone)]
attributes: BTreeMap<MeshVertexAttributeId, MeshAttributeData>,
indices: Option<Indices>,
#[cfg(feature = "morph")]
morph_targets: Option<Handle<Image>>,
#[cfg(feature = "morph")]
morph_target_names: Option<Vec<String>>,
pub asset_usage: RenderAssetUsages,
/// Whether or not to build a BLAS for use with `bevy_solari` raytracing.
@@ -230,7 +235,9 @@ impl Mesh {
primitive_topology,
attributes: Default::default(),
indices: None,
#[cfg(feature = "morph")]
morph_targets: None,
#[cfg(feature = "morph")]
morph_target_names: None,
asset_usage,
enable_raytracing: true,
@@ -1231,55 +1238,6 @@ impl Mesh {
}
}
/// Whether this mesh has morph targets.
pub fn has_morph_targets(&self) -> bool {
self.morph_targets.is_some()
}
/// Set [morph targets] image for this mesh. This requires a "morph target image". See [`MorphTargetImage`](crate::morph::MorphTargetImage) for info.
///
/// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation
pub fn set_morph_targets(&mut self, morph_targets: Handle<Image>) {
self.morph_targets = Some(morph_targets);
}
pub fn morph_targets(&self) -> Option<&Handle<Image>> {
self.morph_targets.as_ref()
}
/// Consumes the mesh and returns a mesh with the given [morph targets].
///
/// This requires a "morph target image". See [`MorphTargetImage`](crate::morph::MorphTargetImage) for info.
///
/// (Alternatively, you can use [`Mesh::set_morph_targets`] to mutate an existing mesh in-place)
///
/// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation
#[must_use]
pub fn with_morph_targets(mut self, morph_targets: Handle<Image>) -> Self {
self.set_morph_targets(morph_targets);
self
}
/// Sets the names of each morph target. This should correspond to the order of the morph targets in `set_morph_targets`.
pub fn set_morph_target_names(&mut self, names: Vec<String>) {
self.morph_target_names = Some(names);
}
/// Consumes the mesh and returns a mesh with morph target names.
/// Names should correspond to the order of the morph targets in `set_morph_targets`.
///
/// (Alternatively, you can use [`Mesh::set_morph_target_names`] to mutate an existing mesh in-place)
#[must_use]
pub fn with_morph_target_names(mut self, names: Vec<String>) -> Self {
self.set_morph_target_names(names);
self
}
/// Gets a list of all morph target names, if they exist.
pub fn morph_target_names(&self) -> Option<&[String]> {
self.morph_target_names.as_deref()
}
/// Normalize joint weights so they sum to 1.
pub fn normalize_joint_weights(&mut self) {
if let Some(joints) = self.attribute_mut(Self::ATTRIBUTE_JOINT_WEIGHT) {
@@ -1404,6 +1362,58 @@ impl Mesh {
}
}
#[cfg(feature = "morph")]
impl Mesh {
/// Whether this mesh has morph targets.
pub fn has_morph_targets(&self) -> bool {
self.morph_targets.is_some()
}
/// Set [morph targets] image for this mesh. This requires a "morph target image". See [`MorphTargetImage`](crate::morph::MorphTargetImage) for info.
///
/// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation
pub fn set_morph_targets(&mut self, morph_targets: Handle<Image>) {
self.morph_targets = Some(morph_targets);
}
pub fn morph_targets(&self) -> Option<&Handle<Image>> {
self.morph_targets.as_ref()
}
/// Consumes the mesh and returns a mesh with the given [morph targets].
///
/// This requires a "morph target image". See [`MorphTargetImage`](crate::morph::MorphTargetImage) for info.
///
/// (Alternatively, you can use [`Mesh::set_morph_targets`] to mutate an existing mesh in-place)
///
/// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation
#[must_use]
pub fn with_morph_targets(mut self, morph_targets: Handle<Image>) -> Self {
self.set_morph_targets(morph_targets);
self
}
/// Sets the names of each morph target. This should correspond to the order of the morph targets in `set_morph_targets`.
pub fn set_morph_target_names(&mut self, names: Vec<String>) {
self.morph_target_names = Some(names);
}
/// Consumes the mesh and returns a mesh with morph target names.
/// Names should correspond to the order of the morph targets in `set_morph_targets`.
///
/// (Alternatively, you can use [`Mesh::set_morph_target_names`] to mutate an existing mesh in-place)
#[must_use]
pub fn with_morph_target_names(mut self, names: Vec<String>) -> Self {
self.set_morph_target_names(names);
self
}
/// Gets a list of all morph target names, if they exist.
pub fn morph_target_names(&self) -> Option<&[String]> {
self.morph_target_names.as_deref()
}
}
impl core::ops::Mul<Mesh> for Transform {
type Output = Mesh;
+2
View File
@@ -23,6 +23,8 @@ decoupled_naga = ["bevy_shader/decoupled_naga"]
multi_threaded = ["bevy_tasks/multi_threaded"]
morph = ["bevy_mesh/morph"]
shader_format_spirv = ["bevy_shader/shader_format_spirv", "wgpu/spirv"]
# Enable SPIR-V shader passthrough
+3 -2
View File
@@ -79,7 +79,7 @@ pub use extract_param::Extract;
use crate::{
camera::CameraPlugin,
gpu_readback::GpuReadbackPlugin,
mesh::{MeshRenderAssetPlugin, MorphPlugin, RenderMesh},
mesh::{MeshRenderAssetPlugin, RenderMesh},
render_asset::prepare_assets,
render_resource::{init_empty_bind_group_layout, PipelineCache},
renderer::{render_system, RenderAdapterInfo},
@@ -368,7 +368,8 @@ impl Plugin for RenderPlugin {
ViewPlugin,
MeshRenderAssetPlugin,
GlobalsPlugin,
MorphPlugin,
#[cfg(feature = "morph")]
mesh::MorphPlugin,
TexturePlugin,
BatchingPlugin {
debug_flags: self.debug_flags,
+22 -11
View File
@@ -1,12 +1,11 @@
pub mod allocator;
use crate::{
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
render_resource::TextureView,
texture::GpuImage,
RenderApp,
};
use allocator::MeshAllocatorPlugin;
use bevy_app::{App, Plugin, PostUpdate};
use bevy_app::{App, Plugin};
use bevy_asset::{AssetId, RenderAssetUsages};
use bevy_ecs::{
prelude::*,
@@ -15,6 +14,7 @@ use bevy_ecs::{
SystemParamItem,
},
};
#[cfg(feature = "morph")]
use bevy_mesh::morph::{MeshMorphWeights, MorphWeights};
use bevy_mesh::*;
use wgpu::IndexFormat;
@@ -40,10 +40,15 @@ impl Plugin for MeshRenderAssetPlugin {
/// [Inherit weights](inherit_weights) from glTF mesh parent entity to direct
/// bevy mesh child entities (ie: glTF primitive).
#[cfg(feature = "morph")]
pub struct MorphPlugin;
#[cfg(feature = "morph")]
impl Plugin for MorphPlugin {
fn build(&self, app: &mut App) {
app.add_systems(PostUpdate, inherit_weights.in_set(InheritWeightSystems));
app.add_systems(
bevy_app::PostUpdate,
inherit_weights.in_set(InheritWeightSystems),
);
}
}
@@ -51,6 +56,7 @@ impl Plugin for MorphPlugin {
/// should be inherited by children meshes.
///
/// Only direct children are updated, to fulfill the expectations of glTF spec.
#[cfg(feature = "morph")]
pub fn inherit_weights(
morph_nodes: Query<(&Children, &MorphWeights), (Without<Mesh3d>, Changed<MorphWeights>)>,
mut morph_primitives: Query<&mut MeshMorphWeights, With<Mesh3d>>,
@@ -71,7 +77,8 @@ pub struct RenderMesh {
pub vertex_count: u32,
/// Morph targets for the mesh, if present.
pub morph_targets: Option<TextureView>,
#[cfg(feature = "morph")]
pub morph_targets: Option<crate::render_resource::TextureView>,
/// Information about the mesh data buffers, including whether the mesh uses
/// indices or not.
@@ -140,12 +147,13 @@ impl RenderAsset for RenderMesh {
fn prepare_asset(
mesh: Self::SourceAsset,
_: AssetId<Self::SourceAsset>,
(images, mesh_vertex_buffer_layouts): &mut SystemParamItem<Self::Param>,
(_images, mesh_vertex_buffer_layouts): &mut SystemParamItem<Self::Param>,
_: Option<&Self>,
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
#[cfg(feature = "morph")]
let morph_targets = match mesh.morph_targets() {
Some(mt) => {
let Some(target_image) = images.get(mt) else {
let Some(target_image) = _images.get(mt) else {
return Err(PrepareAssetError::RetryNextUpdate(mesh));
};
Some(target_image.texture_view.clone())
@@ -164,17 +172,20 @@ impl RenderAsset for RenderMesh {
let mesh_vertex_buffer_layout =
mesh.get_mesh_vertex_buffer_layout(mesh_vertex_buffer_layouts);
let mut key_bits = BaseMeshPipelineKey::from_primitive_topology(mesh.primitive_topology());
key_bits.set(
BaseMeshPipelineKey::MORPH_TARGETS,
mesh.morph_targets().is_some(),
);
let key_bits = BaseMeshPipelineKey::from_primitive_topology(mesh.primitive_topology());
#[cfg(feature = "morph")]
let key_bits = if mesh.morph_targets().is_some() {
key_bits | BaseMeshPipelineKey::MORPH_TARGETS
} else {
key_bits
};
Ok(RenderMesh {
vertex_count: mesh.count_vertices() as u32,
buffer_info,
key_bits,
layout: mesh_vertex_buffer_layout,
#[cfg(feature = "morph")]
morph_targets,
})
}
+1
View File
@@ -52,6 +52,7 @@ The default feature set enables most of the expected features of a game engine,
|default_font|Include a default font, containing only ASCII characters, at the cost of a 20kB binary size increase|
|hdr|HDR image format support|
|ktx2|KTX2 compressed texture support|
|morph|Enables support for morph target weights in bevy_mesh|
|multi_threaded|Enables multithreaded parallelism in the engine. Disabling it forces all engine tasks to run on a single thread.|
|png|PNG image format support|
|reflect_auto_register|Enable automatic reflect registration|