Skip to content

Commit b018cad

Browse files
committed
Implement skeleton animation
1 parent 97d8e4e commit b018cad

File tree

9 files changed

+428
-6
lines changed

9 files changed

+428
-6
lines changed

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ members = ["crates/*", "examples/ios", "tools/ci"]
2121

2222
[features]
2323
default = [
24+
"bevy_animation_rig",
2425
"bevy_audio",
2526
"bevy_dynamic_plugin",
2627
"bevy_gilrs",
@@ -41,6 +42,7 @@ dynamic = ["bevy_dylib"]
4142
render = ["bevy_internal/bevy_pbr", "bevy_internal/bevy_render", "bevy_internal/bevy_sprite", "bevy_internal/bevy_text", "bevy_internal/bevy_ui"]
4243

4344
# Optional bevy crates
45+
bevy_animation_rig = ["bevy_internal/bevy_animation_rig"]
4446
bevy_audio = ["bevy_internal/bevy_audio"]
4547
bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"]
4648
bevy_gilrs = ["bevy_internal/bevy_gilrs"]

crates/bevy_animation_rig/Cargo.toml

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "bevy_animation_rig"
3+
version = "0.5.0"
4+
edition = "2018"
5+
authors = [
6+
"Bevy Contributors <[email protected]>",
7+
"Carter Anderson <[email protected]>",
8+
]
9+
description = "Bevy Engine Animation Rigging System"
10+
homepage = "https://bevyengine.org"
11+
repository = "https://github.com/bevyengine/bevy"
12+
license = "MIT"
13+
keywords = ["bevy", "animation", "rig", "skeleton"]
14+
15+
[dependencies]
16+
# bevy
17+
bevy_app = { path = "../bevy_app", version = "0.5.0" }
18+
bevy_asset = { path = "../bevy_asset", version = "0.5.0" }
19+
bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" }
20+
bevy_math = { path = "../bevy_math", version = "0.5.0" }
21+
bevy_pbr = { path = "../bevy_pbr", version = "0.5.0" }
22+
bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] }
23+
bevy_render = { path = "../bevy_render", version = "0.5.0" }
24+
bevy_transform = { path = "../bevy_transform", version = "0.5.0" }

crates/bevy_animation_rig/src/lib.rs

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use bevy_app::{AppBuilder, CoreStage, Plugin, StartupStage};
2+
use bevy_asset::AddAsset;
3+
use bevy_ecs::{
4+
schedule::{ParallelSystemDescriptorCoercion, SystemLabel},
5+
system::IntoSystem,
6+
};
7+
8+
mod skinned_mesh;
9+
use bevy_transform::TransformSystem;
10+
pub use skinned_mesh::*;
11+
12+
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
13+
pub enum AnimationRigSystem {
14+
SkinnedMeshSetup,
15+
SkinnedMeshUpdate,
16+
}
17+
18+
#[derive(Default)]
19+
pub struct AnimationRigPlugin;
20+
21+
impl Plugin for AnimationRigPlugin {
22+
fn build(&self, app: &mut AppBuilder) {
23+
app.register_type::<SkinnedMesh>()
24+
.add_asset::<SkinnedMeshInverseBindposes>()
25+
.add_startup_system_to_stage(
26+
StartupStage::PreStartup,
27+
skinned_mesh_setup
28+
.system()
29+
.label(AnimationRigSystem::SkinnedMeshSetup),
30+
)
31+
.add_system_to_stage(
32+
CoreStage::PostUpdate,
33+
skinned_mesh_update
34+
.system()
35+
.label(AnimationRigSystem::SkinnedMeshUpdate)
36+
.after(TransformSystem::TransformPropagate),
37+
);
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
use bevy_asset::{Assets, Handle, HandleUntyped};
2+
use bevy_ecs::{
3+
entity::{Entity, EntityMap, MapEntities, MapEntitiesError},
4+
reflect::{ReflectComponent, ReflectMapEntities},
5+
system::{Query, Res, ResMut},
6+
};
7+
use bevy_math::Mat4;
8+
use bevy_pbr::render_graph;
9+
use bevy_reflect::{Reflect, TypeUuid};
10+
use bevy_render::{
11+
pipeline::PipelineDescriptor,
12+
render_graph::{base::node, RenderGraph, RenderResourcesNode},
13+
renderer::{
14+
RenderResource, RenderResourceHints, RenderResourceIterator, RenderResourceType,
15+
RenderResources,
16+
},
17+
shader::{Shader, ShaderStage},
18+
texture::Texture,
19+
};
20+
use bevy_transform::components::GlobalTransform;
21+
22+
/// Specify RenderPipelines with this handle to render the skinned mesh.
23+
pub const SKINNED_MESH_PIPELINE_HANDLE: HandleUntyped =
24+
HandleUntyped::weak_from_u64(PipelineDescriptor::TYPE_UUID, 0x14db1922328e7fcc);
25+
26+
/// Used to update and bind joint transforms to the skinned mesh render pipeline specified with [`SKINNED_MESH_PIPELINE_HANDLE`].
27+
///
28+
/// The length of `joint_entities` and `joint_transforms` should equal to the number of matrices inside [`SkinnedMeshInverseBindposes`].
29+
///
30+
/// The content of `joint_transforms` can be modified manually if [`skinned_mesh_update`] system is disabled.
31+
///
32+
/// # Example
33+
/// ```
34+
/// use bevy_animation_rig::{SkinnedMesh, SKINNED_MESH_PIPELINE_HANDLE};
35+
/// use bevy_ecs::{entity::Entity, system::Commands};
36+
/// use bevy_pbr::prelude::PbrBundle;
37+
/// use bevy_render::pipeline::{RenderPipeline, RenderPipelines};
38+
///
39+
/// fn example_system(mut commands: Commands) {
40+
/// commands.spawn_bundle(PbrBundle {
41+
/// render_pipelines: RenderPipelines::from_pipelines(
42+
/// vec![RenderPipeline::new(SKINNED_MESH_PIPELINE_HANDLE.typed())]
43+
/// ),
44+
/// ..Default::default()
45+
/// }).insert(SkinnedMesh::new(
46+
/// // Refer to [`SkinnedMeshInverseBindposes`] example on how to create inverse bindposes data.
47+
/// Default::default(),
48+
/// // Specify joint entities here.
49+
/// vec![Entity::new(0)]
50+
/// ));
51+
/// }
52+
/// ```
53+
#[derive(Debug, Default, Clone, Reflect)]
54+
#[reflect(Component, MapEntities)]
55+
pub struct SkinnedMesh {
56+
pub inverse_bindposes: Handle<SkinnedMeshInverseBindposes>,
57+
pub joint_entities: Vec<Entity>,
58+
pub joint_transforms: Vec<Mat4>,
59+
}
60+
61+
impl SkinnedMesh {
62+
pub fn new(
63+
inverse_bindposes: Handle<SkinnedMeshInverseBindposes>,
64+
joint_entities: Vec<Entity>,
65+
) -> Self {
66+
let entities_count = joint_entities.len();
67+
68+
Self {
69+
inverse_bindposes,
70+
joint_entities,
71+
joint_transforms: vec![Mat4::IDENTITY; entities_count],
72+
}
73+
}
74+
}
75+
76+
impl MapEntities for SkinnedMesh {
77+
fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> {
78+
for entity in &mut self.joint_entities {
79+
*entity = entity_map.get(*entity)?;
80+
}
81+
82+
Ok(())
83+
}
84+
}
85+
86+
impl RenderResource for SkinnedMesh {
87+
fn resource_type(&self) -> Option<RenderResourceType> {
88+
Some(RenderResourceType::Buffer)
89+
}
90+
91+
fn write_buffer_bytes(&self, buffer: &mut [u8]) {
92+
let transform_size = std::mem::size_of::<[f32; 16]>();
93+
94+
for (index, transform) in self.joint_transforms.iter().enumerate() {
95+
transform.write_buffer_bytes(
96+
&mut buffer[index * transform_size..(index + 1) * transform_size],
97+
);
98+
}
99+
}
100+
101+
fn buffer_byte_len(&self) -> Option<usize> {
102+
Some(self.joint_transforms.len() * std::mem::size_of::<[f32; 16]>())
103+
}
104+
105+
fn texture(&self) -> Option<&Handle<Texture>> {
106+
None
107+
}
108+
}
109+
110+
impl RenderResources for SkinnedMesh {
111+
fn render_resources_len(&self) -> usize {
112+
1
113+
}
114+
115+
fn get_render_resource(&self, index: usize) -> Option<&dyn RenderResource> {
116+
(index == 0).then(|| -> &dyn RenderResource { self })
117+
}
118+
119+
fn get_render_resource_name(&self, index: usize) -> Option<&str> {
120+
(index == 0).then(|| "JointTransforms")
121+
}
122+
123+
// Used to tell GLSL to use storage buffer instead of uniform buffer
124+
fn get_render_resource_hints(&self, _index: usize) -> Option<RenderResourceHints> {
125+
Some(RenderResourceHints::BUFFER)
126+
}
127+
128+
fn iter(&self) -> RenderResourceIterator {
129+
RenderResourceIterator::new(self)
130+
}
131+
}
132+
133+
/// Store joint inverse bindpose matrices. It can be shared between SkinnedMesh instances using assets.
134+
///
135+
/// The matrices can be loaded automatically from glTF or can be defined manually.
136+
///
137+
/// # Example
138+
/// ```
139+
/// use bevy_asset::Assets;
140+
/// use bevy_animation_rig::{SkinnedMesh, SkinnedMeshInverseBindposes, SKINNED_MESH_PIPELINE_HANDLE};
141+
/// use bevy_ecs::{entity::Entity, system::{Commands, ResMut}};
142+
/// use bevy_math::Mat4;
143+
/// use bevy_pbr::prelude::PbrBundle;
144+
/// use bevy_render::pipeline::{RenderPipeline, RenderPipelines};
145+
///
146+
/// fn example_system(mut commands: Commands, mut skinned_mesh_inverse_bindposes_assets: ResMut<Assets<SkinnedMeshInverseBindposes>>) {
147+
/// // A skeleton with only 2 joints
148+
/// let skinned_mesh_inverse_bindposes = skinned_mesh_inverse_bindposes_assets.add(SkinnedMeshInverseBindposes(vec![
149+
/// Mat4::IDENTITY,
150+
/// Mat4::IDENTITY,
151+
/// ]));
152+
///
153+
/// // The inverse bindposes then can be shared between multiple skinned mesh instances
154+
/// for _ in 0..3 {
155+
/// commands.spawn_bundle(PbrBundle {
156+
/// render_pipelines: RenderPipelines::from_pipelines(
157+
/// vec![RenderPipeline::new(SKINNED_MESH_PIPELINE_HANDLE.typed())]
158+
/// ),
159+
/// ..Default::default()
160+
/// }).insert(SkinnedMesh::new(
161+
/// skinned_mesh_inverse_bindposes.clone(),
162+
/// // Remember to assign joint entity here!
163+
/// vec![Entity::new(0); 2],
164+
/// ));
165+
/// }
166+
/// }
167+
/// ```
168+
#[derive(Debug, TypeUuid)]
169+
#[uuid = "b9f155a9-54ec-4026-988f-e0a03e99a76f"]
170+
pub struct SkinnedMeshInverseBindposes(pub Vec<Mat4>);
171+
172+
pub fn skinned_mesh_setup(
173+
mut pipelines: ResMut<Assets<PipelineDescriptor>>,
174+
mut shaders: ResMut<Assets<Shader>>,
175+
mut render_graph: ResMut<RenderGraph>,
176+
) {
177+
let mut skinned_mesh_pipeline = pipelines
178+
.get(render_graph::PBR_PIPELINE_HANDLE)
179+
.unwrap()
180+
.clone();
181+
skinned_mesh_pipeline.name = Some("Skinned Mesh Pipeline".into());
182+
skinned_mesh_pipeline.shader_stages.vertex = shaders.add(Shader::from_glsl(
183+
ShaderStage::Vertex,
184+
include_str!("skinned_mesh.vert"),
185+
));
186+
pipelines.set_untracked(SKINNED_MESH_PIPELINE_HANDLE, skinned_mesh_pipeline);
187+
188+
render_graph.add_system_node(
189+
"JointTransforms",
190+
RenderResourcesNode::<SkinnedMesh>::new(false),
191+
);
192+
render_graph
193+
.add_node_edge("JointTransforms", node::MAIN_PASS)
194+
.unwrap();
195+
}
196+
197+
pub fn skinned_mesh_update(
198+
skinned_mesh_inverse_bindposes_assets: Res<Assets<SkinnedMeshInverseBindposes>>,
199+
global_transform_query: Query<&GlobalTransform>,
200+
mut skinned_mesh_query: Query<&mut SkinnedMesh>,
201+
) {
202+
for mut skinned_mesh in skinned_mesh_query.iter_mut() {
203+
let skinned_mesh_inverse_bindposes = skinned_mesh_inverse_bindposes_assets
204+
.get(skinned_mesh.inverse_bindposes.clone())
205+
.unwrap();
206+
207+
for i in 0..skinned_mesh.joint_entities.len() {
208+
let global_transform = global_transform_query
209+
.get(skinned_mesh.joint_entities[i])
210+
.unwrap();
211+
skinned_mesh.joint_transforms[i] =
212+
global_transform.compute_matrix() * skinned_mesh_inverse_bindposes.0[i];
213+
}
214+
}
215+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#version 450
2+
3+
layout(location = 0) in vec3 Vertex_Position;
4+
layout(location = 1) in vec3 Vertex_Normal;
5+
layout(location = 2) in vec2 Vertex_Uv;
6+
layout(location = 3) in vec4 Vertex_JointWeight;
7+
layout(location = 4) in uvec4 Vertex_JointIndex;
8+
9+
#ifdef STANDARDMATERIAL_NORMAL_MAP
10+
layout(location = 5) in vec4 Vertex_Tangent;
11+
#endif
12+
13+
layout(location = 0) out vec3 v_WorldPosition;
14+
layout(location = 1) out vec3 v_WorldNormal;
15+
layout(location = 2) out vec2 v_Uv;
16+
17+
layout(set = 0, binding = 0) uniform CameraViewProj {
18+
mat4 ViewProj;
19+
};
20+
21+
#ifdef STANDARDMATERIAL_NORMAL_MAP
22+
layout(location = 3) out vec4 v_WorldTangent;
23+
#endif
24+
25+
layout(set = 2, binding = 1) buffer JointTransforms {
26+
mat4[] Joints;
27+
};
28+
29+
void main() {
30+
mat4 Model =
31+
Vertex_JointWeight.x * Joints[Vertex_JointIndex.x] +
32+
Vertex_JointWeight.y * Joints[Vertex_JointIndex.y] +
33+
Vertex_JointWeight.z * Joints[Vertex_JointIndex.z] +
34+
Vertex_JointWeight.w * Joints[Vertex_JointIndex.w];
35+
36+
vec4 world_position = Model * vec4(Vertex_Position, 1.0);
37+
v_WorldPosition = world_position.xyz;
38+
v_WorldNormal = mat3(Model) * Vertex_Normal;
39+
v_Uv = Vertex_Uv;
40+
#ifdef STANDARDMATERIAL_NORMAL_MAP
41+
v_WorldTangent = vec4(mat3(Model) * Vertex_Tangent.xyz, Vertex_Tangent.w);
42+
#endif
43+
gl_Position = ViewProj * world_position;
44+
}

crates/bevy_gltf/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ keywords = ["bevy"]
1414

1515
[dependencies]
1616
# bevy
17+
bevy_animation_rig = { path = "../bevy_animation_rig", version = "0.5.0" }
1718
bevy_app = { path = "../bevy_app", version = "0.5.0" }
1819
bevy_asset = { path = "../bevy_asset", version = "0.5.0" }
1920
bevy_core = { path = "../bevy_core", version = "0.5.0" }

0 commit comments

Comments
 (0)