|
| 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 | +} |
0 commit comments