|
| 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::{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 | +/// The name of skinned mesh node |
| 23 | +pub mod node { |
| 24 | + pub const SKINNED_MESH: &str = "skinned_mesh"; |
| 25 | +} |
| 26 | + |
| 27 | +/// The name of skinned mesh buffer |
| 28 | +pub mod buffer { |
| 29 | + pub const JOINT_TRANSFORMS: &str = "JointTransforms"; |
| 30 | +} |
| 31 | + |
| 32 | +/// Specify RenderPipelines with this handle to render the skinned mesh. |
| 33 | +pub const SKINNED_MESH_PIPELINE_HANDLE: HandleUntyped = |
| 34 | + HandleUntyped::weak_from_u64(PipelineDescriptor::TYPE_UUID, 0x14db1922328e7fcc); |
| 35 | + |
| 36 | +/// Used to update and bind joint transforms to the skinned mesh render pipeline specified with [`SKINNED_MESH_PIPELINE_HANDLE`]. |
| 37 | +/// |
| 38 | +/// The length of `joint_entities` and `joint_transforms` should equal to the number of matrices inside [`SkinnedMeshInverseBindposes`]. |
| 39 | +/// |
| 40 | +/// The content of `joint_transforms` can be modified manually if [`skinned_mesh_update`] system is disabled. |
| 41 | +/// |
| 42 | +/// # Example |
| 43 | +/// ``` |
| 44 | +/// use bevy_animation_rig::{SkinnedMesh, SKINNED_MESH_PIPELINE_HANDLE}; |
| 45 | +/// use bevy_ecs::{entity::Entity, system::Commands}; |
| 46 | +/// use bevy_pbr::prelude::PbrBundle; |
| 47 | +/// use bevy_render::pipeline::{RenderPipeline, RenderPipelines}; |
| 48 | +/// |
| 49 | +/// fn example_system(mut commands: Commands) { |
| 50 | +/// commands.spawn_bundle(PbrBundle { |
| 51 | +/// render_pipelines: RenderPipelines::from_pipelines( |
| 52 | +/// vec![RenderPipeline::new(SKINNED_MESH_PIPELINE_HANDLE.typed())] |
| 53 | +/// ), |
| 54 | +/// ..Default::default() |
| 55 | +/// }).insert(SkinnedMesh::new( |
| 56 | +/// // Refer to [`SkinnedMeshInverseBindposes`] example on how to create inverse bindposes data. |
| 57 | +/// Default::default(), |
| 58 | +/// // Specify joint entities here. |
| 59 | +/// vec![Entity::new(0)] |
| 60 | +/// )); |
| 61 | +/// } |
| 62 | +/// ``` |
| 63 | +#[derive(Debug, Default, Clone, Reflect)] |
| 64 | +#[reflect(Component, MapEntities)] |
| 65 | +pub struct SkinnedMesh { |
| 66 | + pub inverse_bindposes: Handle<SkinnedMeshInverseBindposes>, |
| 67 | + pub joint_entities: Vec<Entity>, |
| 68 | + pub joint_transforms: Vec<Mat4>, |
| 69 | +} |
| 70 | + |
| 71 | +impl SkinnedMesh { |
| 72 | + pub fn new( |
| 73 | + inverse_bindposes: Handle<SkinnedMeshInverseBindposes>, |
| 74 | + joint_entities: Vec<Entity>, |
| 75 | + ) -> Self { |
| 76 | + let entities_count = joint_entities.len(); |
| 77 | + |
| 78 | + Self { |
| 79 | + inverse_bindposes, |
| 80 | + joint_entities, |
| 81 | + joint_transforms: vec![Mat4::IDENTITY; entities_count], |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + pub fn update_joint_transforms( |
| 86 | + &mut self, |
| 87 | + inverse_bindposes_assets: &Res<Assets<SkinnedMeshInverseBindposes>>, |
| 88 | + global_transform_query: &Query<&GlobalTransform>, |
| 89 | + ) { |
| 90 | + let inverse_bindposes = inverse_bindposes_assets |
| 91 | + .get(self.inverse_bindposes.clone()) |
| 92 | + .unwrap(); |
| 93 | + |
| 94 | + for (joint_transform, (&joint_entity, &inverse_bindpose)) in self |
| 95 | + .joint_transforms |
| 96 | + .iter_mut() |
| 97 | + .zip(self.joint_entities.iter().zip(inverse_bindposes.0.iter())) |
| 98 | + { |
| 99 | + let global_transform = global_transform_query.get(joint_entity).unwrap(); |
| 100 | + *joint_transform = global_transform.compute_matrix() * inverse_bindpose; |
| 101 | + } |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +impl MapEntities for SkinnedMesh { |
| 106 | + fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> { |
| 107 | + for entity in &mut self.joint_entities { |
| 108 | + *entity = entity_map.get(*entity)?; |
| 109 | + } |
| 110 | + |
| 111 | + Ok(()) |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +impl RenderResource for SkinnedMesh { |
| 116 | + fn resource_type(&self) -> Option<RenderResourceType> { |
| 117 | + Some(RenderResourceType::Buffer) |
| 118 | + } |
| 119 | + |
| 120 | + fn write_buffer_bytes(&self, buffer: &mut [u8]) { |
| 121 | + let transform_size = std::mem::size_of::<[f32; 16]>(); |
| 122 | + |
| 123 | + for (index, transform) in self.joint_transforms.iter().enumerate() { |
| 124 | + transform.write_buffer_bytes( |
| 125 | + &mut buffer[index * transform_size..(index + 1) * transform_size], |
| 126 | + ); |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + fn buffer_byte_len(&self) -> Option<usize> { |
| 131 | + Some(self.joint_transforms.len() * std::mem::size_of::<[f32; 16]>()) |
| 132 | + } |
| 133 | + |
| 134 | + fn texture(&self) -> Option<&Handle<Texture>> { |
| 135 | + None |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +impl RenderResources for SkinnedMesh { |
| 140 | + fn render_resources_len(&self) -> usize { |
| 141 | + 1 |
| 142 | + } |
| 143 | + |
| 144 | + fn get_render_resource(&self, index: usize) -> Option<&dyn RenderResource> { |
| 145 | + (index == 0).then(|| self as &dyn RenderResource) |
| 146 | + } |
| 147 | + |
| 148 | + fn get_render_resource_name(&self, index: usize) -> Option<&str> { |
| 149 | + (index == 0).then(|| buffer::JOINT_TRANSFORMS) |
| 150 | + } |
| 151 | + |
| 152 | + // Used to tell GLSL to use storage buffer instead of uniform buffer |
| 153 | + fn get_render_resource_hints(&self, index: usize) -> Option<RenderResourceHints> { |
| 154 | + (index == 0).then(|| RenderResourceHints::BUFFER) |
| 155 | + } |
| 156 | + |
| 157 | + fn iter(&self) -> RenderResourceIterator { |
| 158 | + RenderResourceIterator::new(self) |
| 159 | + } |
| 160 | +} |
| 161 | + |
| 162 | +/// Store joint inverse bindpose matrices. It can be shared between SkinnedMesh instances using assets. |
| 163 | +/// |
| 164 | +/// The matrices can be loaded automatically from glTF or can be defined manually. |
| 165 | +/// |
| 166 | +/// # Example |
| 167 | +/// ``` |
| 168 | +/// use bevy_asset::Assets; |
| 169 | +/// use bevy_animation_rig::{SkinnedMesh, SkinnedMeshInverseBindposes, SKINNED_MESH_PIPELINE_HANDLE}; |
| 170 | +/// use bevy_ecs::{entity::Entity, system::{Commands, ResMut}}; |
| 171 | +/// use bevy_math::Mat4; |
| 172 | +/// use bevy_pbr::prelude::PbrBundle; |
| 173 | +/// use bevy_render::pipeline::{RenderPipeline, RenderPipelines}; |
| 174 | +/// |
| 175 | +/// fn example_system(mut commands: Commands, mut skinned_mesh_inverse_bindposes_assets: ResMut<Assets<SkinnedMeshInverseBindposes>>) { |
| 176 | +/// // A skeleton with only 2 joints |
| 177 | +/// let skinned_mesh_inverse_bindposes = skinned_mesh_inverse_bindposes_assets.add(SkinnedMeshInverseBindposes(vec![ |
| 178 | +/// Mat4::IDENTITY, |
| 179 | +/// Mat4::IDENTITY, |
| 180 | +/// ])); |
| 181 | +/// |
| 182 | +/// // The inverse bindposes then can be shared between multiple skinned mesh instances |
| 183 | +/// for _ in 0..3 { |
| 184 | +/// commands.spawn_bundle(PbrBundle { |
| 185 | +/// render_pipelines: RenderPipelines::from_pipelines( |
| 186 | +/// vec![RenderPipeline::new(SKINNED_MESH_PIPELINE_HANDLE.typed())] |
| 187 | +/// ), |
| 188 | +/// ..Default::default() |
| 189 | +/// }).insert(SkinnedMesh::new( |
| 190 | +/// skinned_mesh_inverse_bindposes.clone(), |
| 191 | +/// // Remember to assign joint entity here! |
| 192 | +/// vec![Entity::new(0); 2], |
| 193 | +/// )); |
| 194 | +/// } |
| 195 | +/// } |
| 196 | +/// ``` |
| 197 | +#[derive(Debug, TypeUuid)] |
| 198 | +#[uuid = "b9f155a9-54ec-4026-988f-e0a03e99a76f"] |
| 199 | +pub struct SkinnedMeshInverseBindposes(pub Vec<Mat4>); |
| 200 | + |
| 201 | +pub fn skinned_mesh_setup( |
| 202 | + mut pipelines: ResMut<Assets<PipelineDescriptor>>, |
| 203 | + mut shaders: ResMut<Assets<Shader>>, |
| 204 | + mut render_graph: ResMut<RenderGraph>, |
| 205 | +) { |
| 206 | + let mut skinned_mesh_pipeline = pipelines |
| 207 | + .get(render_graph::PBR_PIPELINE_HANDLE) |
| 208 | + .unwrap() |
| 209 | + .clone(); |
| 210 | + skinned_mesh_pipeline.name = Some("Skinned Mesh Pipeline".into()); |
| 211 | + skinned_mesh_pipeline.shader_stages.vertex = shaders.add(Shader::from_glsl( |
| 212 | + ShaderStage::Vertex, |
| 213 | + include_str!("skinned_mesh.vert"), |
| 214 | + )); |
| 215 | + pipelines.set_untracked(SKINNED_MESH_PIPELINE_HANDLE, skinned_mesh_pipeline); |
| 216 | + |
| 217 | + render_graph.add_system_node( |
| 218 | + node::SKINNED_MESH, |
| 219 | + RenderResourcesNode::<SkinnedMesh>::new(false), |
| 220 | + ); |
| 221 | + render_graph |
| 222 | + .add_node_edge(node::SKINNED_MESH, bevy_render::render_graph::base::node::MAIN_PASS) |
| 223 | + .unwrap(); |
| 224 | +} |
| 225 | + |
| 226 | +pub fn skinned_mesh_update( |
| 227 | + skinned_mesh_inverse_bindposes_assets: Res<Assets<SkinnedMeshInverseBindposes>>, |
| 228 | + global_transform_query: Query<&GlobalTransform>, |
| 229 | + mut skinned_mesh_query: Query<&mut SkinnedMesh>, |
| 230 | +) { |
| 231 | + skinned_mesh_query.for_each_mut(|mut skinned_mesh| { |
| 232 | + skinned_mesh.update_joint_transforms( |
| 233 | + &skinned_mesh_inverse_bindposes_assets, |
| 234 | + &global_transform_query, |
| 235 | + ); |
| 236 | + }); |
| 237 | +} |
0 commit comments