Skip to content

Commit

Permalink
Implement skeleton animation
Browse files Browse the repository at this point in the history
  • Loading branch information
Looooong committed Jun 19, 2021
1 parent 97d8e4e commit b018cad
Show file tree
Hide file tree
Showing 9 changed files with 428 additions and 6 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ members = ["crates/*", "examples/ios", "tools/ci"]

[features]
default = [
"bevy_animation_rig",
"bevy_audio",
"bevy_dynamic_plugin",
"bevy_gilrs",
Expand All @@ -41,6 +42,7 @@ dynamic = ["bevy_dylib"]
render = ["bevy_internal/bevy_pbr", "bevy_internal/bevy_render", "bevy_internal/bevy_sprite", "bevy_internal/bevy_text", "bevy_internal/bevy_ui"]

# Optional bevy crates
bevy_animation_rig = ["bevy_internal/bevy_animation_rig"]
bevy_audio = ["bevy_internal/bevy_audio"]
bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"]
bevy_gilrs = ["bevy_internal/bevy_gilrs"]
Expand Down
24 changes: 24 additions & 0 deletions crates/bevy_animation_rig/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "bevy_animation_rig"
version = "0.5.0"
edition = "2018"
authors = [
"Bevy Contributors <bevyengine@gmail.com>",
"Carter Anderson <mcanders1@gmail.com>",
]
description = "Bevy Engine Animation Rigging System"
homepage = "https://bevyengine.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT"
keywords = ["bevy", "animation", "rig", "skeleton"]

[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.5.0" }
bevy_asset = { path = "../bevy_asset", version = "0.5.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" }
bevy_math = { path = "../bevy_math", version = "0.5.0" }
bevy_pbr = { path = "../bevy_pbr", version = "0.5.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] }
bevy_render = { path = "../bevy_render", version = "0.5.0" }
bevy_transform = { path = "../bevy_transform", version = "0.5.0" }
39 changes: 39 additions & 0 deletions crates/bevy_animation_rig/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use bevy_app::{AppBuilder, CoreStage, Plugin, StartupStage};
use bevy_asset::AddAsset;
use bevy_ecs::{
schedule::{ParallelSystemDescriptorCoercion, SystemLabel},
system::IntoSystem,
};

mod skinned_mesh;
use bevy_transform::TransformSystem;
pub use skinned_mesh::*;

#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
pub enum AnimationRigSystem {
SkinnedMeshSetup,
SkinnedMeshUpdate,
}

#[derive(Default)]
pub struct AnimationRigPlugin;

impl Plugin for AnimationRigPlugin {
fn build(&self, app: &mut AppBuilder) {
app.register_type::<SkinnedMesh>()
.add_asset::<SkinnedMeshInverseBindposes>()
.add_startup_system_to_stage(
StartupStage::PreStartup,
skinned_mesh_setup
.system()
.label(AnimationRigSystem::SkinnedMeshSetup),
)
.add_system_to_stage(
CoreStage::PostUpdate,
skinned_mesh_update
.system()
.label(AnimationRigSystem::SkinnedMeshUpdate)
.after(TransformSystem::TransformPropagate),
);
}
}
215 changes: 215 additions & 0 deletions crates/bevy_animation_rig/src/skinned_mesh.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
use bevy_asset::{Assets, Handle, HandleUntyped};
use bevy_ecs::{
entity::{Entity, EntityMap, MapEntities, MapEntitiesError},
reflect::{ReflectComponent, ReflectMapEntities},
system::{Query, Res, ResMut},
};
use bevy_math::Mat4;
use bevy_pbr::render_graph;
use bevy_reflect::{Reflect, TypeUuid};
use bevy_render::{
pipeline::PipelineDescriptor,
render_graph::{base::node, RenderGraph, RenderResourcesNode},
renderer::{
RenderResource, RenderResourceHints, RenderResourceIterator, RenderResourceType,
RenderResources,
},
shader::{Shader, ShaderStage},
texture::Texture,
};
use bevy_transform::components::GlobalTransform;

/// Specify RenderPipelines with this handle to render the skinned mesh.
pub const SKINNED_MESH_PIPELINE_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(PipelineDescriptor::TYPE_UUID, 0x14db1922328e7fcc);

/// Used to update and bind joint transforms to the skinned mesh render pipeline specified with [`SKINNED_MESH_PIPELINE_HANDLE`].
///
/// The length of `joint_entities` and `joint_transforms` should equal to the number of matrices inside [`SkinnedMeshInverseBindposes`].
///
/// The content of `joint_transforms` can be modified manually if [`skinned_mesh_update`] system is disabled.
///
/// # Example
/// ```
/// use bevy_animation_rig::{SkinnedMesh, SKINNED_MESH_PIPELINE_HANDLE};
/// use bevy_ecs::{entity::Entity, system::Commands};
/// use bevy_pbr::prelude::PbrBundle;
/// use bevy_render::pipeline::{RenderPipeline, RenderPipelines};
///
/// fn example_system(mut commands: Commands) {
/// commands.spawn_bundle(PbrBundle {
/// render_pipelines: RenderPipelines::from_pipelines(
/// vec![RenderPipeline::new(SKINNED_MESH_PIPELINE_HANDLE.typed())]
/// ),
/// ..Default::default()
/// }).insert(SkinnedMesh::new(
/// // Refer to [`SkinnedMeshInverseBindposes`] example on how to create inverse bindposes data.
/// Default::default(),
/// // Specify joint entities here.
/// vec![Entity::new(0)]
/// ));
/// }
/// ```
#[derive(Debug, Default, Clone, Reflect)]
#[reflect(Component, MapEntities)]
pub struct SkinnedMesh {
pub inverse_bindposes: Handle<SkinnedMeshInverseBindposes>,
pub joint_entities: Vec<Entity>,
pub joint_transforms: Vec<Mat4>,
}

impl SkinnedMesh {
pub fn new(
inverse_bindposes: Handle<SkinnedMeshInverseBindposes>,
joint_entities: Vec<Entity>,
) -> Self {
let entities_count = joint_entities.len();

Self {
inverse_bindposes,
joint_entities,
joint_transforms: vec![Mat4::IDENTITY; entities_count],
}
}
}

impl MapEntities for SkinnedMesh {
fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> {
for entity in &mut self.joint_entities {
*entity = entity_map.get(*entity)?;
}

Ok(())
}
}

impl RenderResource for SkinnedMesh {
fn resource_type(&self) -> Option<RenderResourceType> {
Some(RenderResourceType::Buffer)
}

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

for (index, transform) in self.joint_transforms.iter().enumerate() {
transform.write_buffer_bytes(
&mut buffer[index * transform_size..(index + 1) * transform_size],
);
}
}

fn buffer_byte_len(&self) -> Option<usize> {
Some(self.joint_transforms.len() * std::mem::size_of::<[f32; 16]>())
}

fn texture(&self) -> Option<&Handle<Texture>> {
None
}
}

impl RenderResources for SkinnedMesh {
fn render_resources_len(&self) -> usize {
1
}

fn get_render_resource(&self, index: usize) -> Option<&dyn RenderResource> {
(index == 0).then(|| -> &dyn RenderResource { self })
}

fn get_render_resource_name(&self, index: usize) -> Option<&str> {
(index == 0).then(|| "JointTransforms")
}

// Used to tell GLSL to use storage buffer instead of uniform buffer
fn get_render_resource_hints(&self, _index: usize) -> Option<RenderResourceHints> {
Some(RenderResourceHints::BUFFER)
}

fn iter(&self) -> RenderResourceIterator {
RenderResourceIterator::new(self)
}
}

/// Store joint inverse bindpose matrices. It can be shared between SkinnedMesh instances using assets.
///
/// The matrices can be loaded automatically from glTF or can be defined manually.
///
/// # Example
/// ```
/// use bevy_asset::Assets;
/// use bevy_animation_rig::{SkinnedMesh, SkinnedMeshInverseBindposes, SKINNED_MESH_PIPELINE_HANDLE};
/// use bevy_ecs::{entity::Entity, system::{Commands, ResMut}};
/// use bevy_math::Mat4;
/// use bevy_pbr::prelude::PbrBundle;
/// use bevy_render::pipeline::{RenderPipeline, RenderPipelines};
///
/// fn example_system(mut commands: Commands, mut skinned_mesh_inverse_bindposes_assets: ResMut<Assets<SkinnedMeshInverseBindposes>>) {
/// // A skeleton with only 2 joints
/// let skinned_mesh_inverse_bindposes = skinned_mesh_inverse_bindposes_assets.add(SkinnedMeshInverseBindposes(vec![
/// Mat4::IDENTITY,
/// Mat4::IDENTITY,
/// ]));
///
/// // The inverse bindposes then can be shared between multiple skinned mesh instances
/// for _ in 0..3 {
/// commands.spawn_bundle(PbrBundle {
/// render_pipelines: RenderPipelines::from_pipelines(
/// vec![RenderPipeline::new(SKINNED_MESH_PIPELINE_HANDLE.typed())]
/// ),
/// ..Default::default()
/// }).insert(SkinnedMesh::new(
/// skinned_mesh_inverse_bindposes.clone(),
/// // Remember to assign joint entity here!
/// vec![Entity::new(0); 2],
/// ));
/// }
/// }
/// ```
#[derive(Debug, TypeUuid)]
#[uuid = "b9f155a9-54ec-4026-988f-e0a03e99a76f"]
pub struct SkinnedMeshInverseBindposes(pub Vec<Mat4>);

pub fn skinned_mesh_setup(
mut pipelines: ResMut<Assets<PipelineDescriptor>>,
mut shaders: ResMut<Assets<Shader>>,
mut render_graph: ResMut<RenderGraph>,
) {
let mut skinned_mesh_pipeline = pipelines
.get(render_graph::PBR_PIPELINE_HANDLE)
.unwrap()
.clone();
skinned_mesh_pipeline.name = Some("Skinned Mesh Pipeline".into());
skinned_mesh_pipeline.shader_stages.vertex = shaders.add(Shader::from_glsl(
ShaderStage::Vertex,
include_str!("skinned_mesh.vert"),
));
pipelines.set_untracked(SKINNED_MESH_PIPELINE_HANDLE, skinned_mesh_pipeline);

render_graph.add_system_node(
"JointTransforms",
RenderResourcesNode::<SkinnedMesh>::new(false),
);
render_graph
.add_node_edge("JointTransforms", node::MAIN_PASS)
.unwrap();
}

pub fn skinned_mesh_update(
skinned_mesh_inverse_bindposes_assets: Res<Assets<SkinnedMeshInverseBindposes>>,
global_transform_query: Query<&GlobalTransform>,
mut skinned_mesh_query: Query<&mut SkinnedMesh>,
) {
for mut skinned_mesh in skinned_mesh_query.iter_mut() {
let skinned_mesh_inverse_bindposes = skinned_mesh_inverse_bindposes_assets
.get(skinned_mesh.inverse_bindposes.clone())
.unwrap();

for i in 0..skinned_mesh.joint_entities.len() {
let global_transform = global_transform_query
.get(skinned_mesh.joint_entities[i])
.unwrap();
skinned_mesh.joint_transforms[i] =
global_transform.compute_matrix() * skinned_mesh_inverse_bindposes.0[i];
}
}
}
44 changes: 44 additions & 0 deletions crates/bevy_animation_rig/src/skinned_mesh.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#version 450

layout(location = 0) in vec3 Vertex_Position;
layout(location = 1) in vec3 Vertex_Normal;
layout(location = 2) in vec2 Vertex_Uv;
layout(location = 3) in vec4 Vertex_JointWeight;
layout(location = 4) in uvec4 Vertex_JointIndex;

#ifdef STANDARDMATERIAL_NORMAL_MAP
layout(location = 5) in vec4 Vertex_Tangent;
#endif

layout(location = 0) out vec3 v_WorldPosition;
layout(location = 1) out vec3 v_WorldNormal;
layout(location = 2) out vec2 v_Uv;

layout(set = 0, binding = 0) uniform CameraViewProj {
mat4 ViewProj;
};

#ifdef STANDARDMATERIAL_NORMAL_MAP
layout(location = 3) out vec4 v_WorldTangent;
#endif

layout(set = 2, binding = 1) buffer JointTransforms {
mat4[] Joints;
};

void main() {
mat4 Model =
Vertex_JointWeight.x * Joints[Vertex_JointIndex.x] +
Vertex_JointWeight.y * Joints[Vertex_JointIndex.y] +
Vertex_JointWeight.z * Joints[Vertex_JointIndex.z] +
Vertex_JointWeight.w * Joints[Vertex_JointIndex.w];

vec4 world_position = Model * vec4(Vertex_Position, 1.0);
v_WorldPosition = world_position.xyz;
v_WorldNormal = mat3(Model) * Vertex_Normal;
v_Uv = Vertex_Uv;
#ifdef STANDARDMATERIAL_NORMAL_MAP
v_WorldTangent = vec4(mat3(Model) * Vertex_Tangent.xyz, Vertex_Tangent.w);
#endif
gl_Position = ViewProj * world_position;
}
1 change: 1 addition & 0 deletions crates/bevy_gltf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ keywords = ["bevy"]

[dependencies]
# bevy
bevy_animation_rig = { path = "../bevy_animation_rig", version = "0.5.0" }
bevy_app = { path = "../bevy_app", version = "0.5.0" }
bevy_asset = { path = "../bevy_asset", version = "0.5.0" }
bevy_core = { path = "../bevy_core", version = "0.5.0" }
Expand Down
Loading

0 comments on commit b018cad

Please sign in to comment.