Skip to content

Commit 31bd4ec

Browse files
Looooongjames7132mockersf
committed
Mesh Skinning. Attempt #3 (#4238)
# Objective Load skeletal weights and indices from GLTF files. Animate meshes. ## Solution - Load skeletal weights and indices from GLTF files. - Added `SkinnedMesh` component and ` SkinnedMeshInverseBindPose` asset - Added `extract_skinned_meshes` to extract joint matrices. - Added queue phase systems for enqueuing the buffer writes. Some notes: - This ports part of # #2359 to the current main. - This generates new `BufferVec`s and bind groups every frame. The expectation here is that the number of `Query::get` calls during extract is probably going to be the stronger bottleneck, with up to 256 calls per skinned mesh. Until that is optimized, caching buffers and bind groups is probably a non-concern. - Unfortunately, due to the uniform size requirements, this means a 16KB buffer is allocated for every skinned mesh every frame. There's probably a few ways to get around this, but most of them require either compute shaders or storage buffers, which are both incompatible with WebGL2. Co-authored-by: james7132 <[email protected]> Co-authored-by: François <[email protected]> Co-authored-by: James Liu <[email protected]>
1 parent 54d2e86 commit 31bd4ec

File tree

19 files changed

+779
-55
lines changed

19 files changed

+779
-55
lines changed

Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,15 @@ path = "examples/3d/update_gltf_scene.rs"
235235
name = "wireframe"
236236
path = "examples/3d/wireframe.rs"
237237

238+
# Animation
239+
[[example]]
240+
name = "custom_skinned_mesh"
241+
path = "examples/animation/custom_skinned_mesh.rs"
242+
243+
[[example]]
244+
name = "gltf_skinned_mesh"
245+
path = "examples/animation/gltf_skinned_mesh.rs"
246+
238247
# Application
239248
[[example]]
240249
name = "custom_loop"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"scenes":[{"nodes":[0]}],"nodes":[{"skin":0,"mesh":0,"children":[1]},{"children":[2],"translation":[0,1,0]},{"rotation":[0,0,0,1]}],"meshes":[{"primitives":[{"attributes":{"POSITION":1,"JOINTS_0":2,"WEIGHTS_0":3},"indices":0}]}],"skins":[{"inverseBindMatrices":4,"joints":[1,2]}],"animations":[{"channels":[{"sampler":0,"target":{"node":2,"path":"rotation"}}],"samplers":[{"input":5,"interpolation":"LINEAR","output":6}]}],"buffers":[{"uri":"data:application/gltf-buffer;base64,AAABAAMAAAADAAIAAgADAAUAAgAFAAQABAAFAAcABAAHAAYABgAHAAkABgAJAAgAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAD8AAAAAAACAPwAAAD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAwD8AAAAAAACAPwAAwD8AAAAAAAAAAAAAAEAAAAAAAACAPwAAAEAAAAAA","byteLength":168},{"uri":"data:application/gltf-buffer;base64,AAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAABAPwAAgD4AAAAAAAAAAAAAQD8AAIA+AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAAAAgD4AAEA/AAAAAAAAAAAAAIA+AABAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAA=","byteLength":320},{"uri":"data:application/gltf-buffer;base64,AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAvwAAgL8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAL8AAIC/AAAAAAAAgD8=","byteLength":128},{"uri":"data:application/gltf-buffer;base64,AAAAAAAAAD8AAIA/AADAPwAAAEAAACBAAABAQAAAYEAAAIBAAACQQAAAoEAAALBAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAkxjEPkSLbD8AAAAAAAAAAPT9ND/0/TQ/AAAAAAAAAAD0/TQ/9P00PwAAAAAAAAAAkxjEPkSLbD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAkxjEvkSLbD8AAAAAAAAAAPT9NL/0/TQ/AAAAAAAAAAD0/TS/9P00PwAAAAAAAAAAkxjEvkSLbD8AAAAAAAAAAAAAAAAAAIA/","byteLength":240}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":48,"target":34963},{"buffer":0,"byteOffset":48,"byteLength":120,"target":34962},{"buffer":1,"byteOffset":0,"byteLength":320,"byteStride":16},{"buffer":2,"byteOffset":0,"byteLength":128},{"buffer":3,"byteOffset":0,"byteLength":240}],"accessors":[{"bufferView":0,"byteOffset":0,"componentType":5123,"count":24,"type":"SCALAR","max":[9],"min":[0]},{"bufferView":1,"byteOffset":0,"componentType":5126,"count":10,"type":"VEC3","max":[1,2,0],"min":[0,0,0]},{"bufferView":2,"byteOffset":0,"componentType":5123,"count":10,"type":"VEC4","max":[0,1,0,0],"min":[0,1,0,0]},{"bufferView":2,"byteOffset":160,"componentType":5126,"count":10,"type":"VEC4","max":[1,1,0,0],"min":[0,0,0,0]},{"bufferView":3,"byteOffset":0,"componentType":5126,"count":2,"type":"MAT4","max":[1,0,0,0,0,1,0,0,0,0,1,0,-0.5,-1,0,1],"min":[1,0,0,0,0,1,0,0,0,0,1,0,-0.5,-1,0,1]},{"bufferView":4,"byteOffset":0,"componentType":5126,"count":12,"type":"SCALAR","max":[5.5],"min":[0]},{"bufferView":4,"byteOffset":48,"componentType":5126,"count":12,"type":"VEC4","max":[0,0,0.707,1],"min":[0,0,-0.707,0.707]}],"asset":{"version":"2.0"}}

crates/bevy_gltf/src/loader.rs

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use bevy_asset::{
33
AssetIoError, AssetLoader, AssetPath, BoxedFuture, Handle, LoadContext, LoadedAsset,
44
};
55
use bevy_core::Name;
6-
use bevy_ecs::{prelude::FromWorld, world::World};
6+
use bevy_ecs::{entity::Entity, prelude::FromWorld, world::World};
77
use bevy_hierarchy::{BuildWorldChildren, WorldChildBuilder};
88
use bevy_log::warn;
99
use bevy_math::{Mat4, Quat, Vec3};
@@ -16,7 +16,10 @@ use bevy_render::{
1616
Camera, Camera2d, Camera3d, CameraProjection, OrthographicProjection, PerspectiveProjection,
1717
},
1818
color::Color,
19-
mesh::{Indices, Mesh, VertexAttributeValues},
19+
mesh::{
20+
skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
21+
Indices, Mesh, VertexAttributeValues,
22+
},
2023
primitives::{Aabb, Frustum},
2124
render_resource::{AddressMode, Face, FilterMode, PrimitiveTopology, SamplerDescriptor},
2225
renderer::RenderDevice,
@@ -249,6 +252,18 @@ async fn load_gltf<'a, 'b>(
249252
// mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute);
250253
// }
251254

255+
if let Some(iter) = reader.read_joints(0) {
256+
let vertex_attribute = VertexAttributeValues::Uint16x4(iter.into_u16().collect());
257+
mesh.insert_attribute(Mesh::ATTRIBUTE_JOINT_INDEX, vertex_attribute);
258+
}
259+
260+
if let Some(vertex_attribute) = reader
261+
.read_weights(0)
262+
.map(|v| VertexAttributeValues::Float32x4(v.into_f32().collect()))
263+
{
264+
mesh.insert_attribute(Mesh::ATTRIBUTE_JOINT_WEIGHT, vertex_attribute);
265+
}
266+
252267
if let Some(indices) = reader.read_indices() {
253268
mesh.set_indices(Some(Indices::U32(indices.into_u32().collect())));
254269
};
@@ -384,18 +399,45 @@ async fn load_gltf<'a, 'b>(
384399
});
385400
}
386401

402+
let skinned_mesh_inverse_bindposes: Vec<_> = gltf
403+
.skins()
404+
.map(|gltf_skin| {
405+
let reader = gltf_skin.reader(|buffer| Some(&buffer_data[buffer.index()]));
406+
let inverse_bindposes: Vec<Mat4> = reader
407+
.read_inverse_bind_matrices()
408+
.unwrap()
409+
.map(|mat| Mat4::from_cols_array_2d(&mat))
410+
.collect();
411+
412+
load_context.set_labeled_asset(
413+
&skin_label(&gltf_skin),
414+
LoadedAsset::new(SkinnedMeshInverseBindposes::from(inverse_bindposes)),
415+
)
416+
})
417+
.collect();
418+
387419
let mut scenes = vec![];
388420
let mut named_scenes = HashMap::default();
389421
for scene in gltf.scenes() {
390422
let mut err = None;
391423
let mut world = World::default();
424+
let mut node_index_to_entity_map = HashMap::new();
425+
let mut entity_to_skin_index_map = HashMap::new();
426+
392427
world
393428
.spawn()
394429
.insert_bundle(TransformBundle::identity())
395430
.with_children(|parent| {
396431
for node in scene.nodes() {
397-
let result =
398-
load_node(&node, parent, load_context, &buffer_data, &animated_nodes);
432+
let result = load_node(
433+
&node,
434+
parent,
435+
load_context,
436+
&buffer_data,
437+
&animated_nodes,
438+
&mut node_index_to_entity_map,
439+
&mut entity_to_skin_index_map,
440+
);
399441
if result.is_err() {
400442
err = Some(result);
401443
return;
@@ -405,6 +447,21 @@ async fn load_gltf<'a, 'b>(
405447
if let Some(Err(err)) = err {
406448
return Err(err);
407449
}
450+
451+
for (&entity, &skin_index) in &entity_to_skin_index_map {
452+
let mut entity = world.entity_mut(entity);
453+
let skin = gltf.skins().nth(skin_index).unwrap();
454+
let joint_entities: Vec<_> = skin
455+
.joints()
456+
.map(|node| node_index_to_entity_map[&node.index()])
457+
.collect();
458+
459+
entity.insert(SkinnedMesh {
460+
inverse_bindposes: skinned_mesh_inverse_bindposes[skin_index].clone(),
461+
joints: joint_entities,
462+
});
463+
}
464+
408465
let scene_handle = load_context
409466
.set_labeled_asset(&scene_label(&scene), LoadedAsset::new(Scene::new(world)));
410467

@@ -575,6 +632,8 @@ fn load_node(
575632
load_context: &mut LoadContext,
576633
buffer_data: &[Vec<u8>],
577634
animated_nodes: &HashSet<usize>,
635+
node_index_to_entity_map: &mut HashMap<usize, Entity>,
636+
entity_to_skin_index_map: &mut HashMap<Entity, usize>,
578637
) -> Result<(), GltfError> {
579638
let transform = gltf_node.transform();
580639
let mut gltf_error = None;
@@ -645,6 +704,9 @@ fn load_node(
645704
}
646705
}
647706

707+
// Map node index to entity
708+
node_index_to_entity_map.insert(gltf_node.index(), node.id());
709+
648710
node.with_children(|parent| {
649711
if let Some(mesh) = gltf_node.mesh() {
650712
// append primitives
@@ -660,13 +722,13 @@ fn load_node(
660722
}
661723

662724
let primitive_label = primitive_label(&mesh, &primitive);
725+
let bounds = primitive.bounding_box();
663726
let mesh_asset_path =
664727
AssetPath::new_ref(load_context.path(), Some(&primitive_label));
665728
let material_asset_path =
666729
AssetPath::new_ref(load_context.path(), Some(&material_label));
667730

668-
let bounds = primitive.bounding_box();
669-
parent
731+
let node = parent
670732
.spawn_bundle(PbrBundle {
671733
mesh: load_context.get_handle(mesh_asset_path),
672734
material: load_context.get_handle(material_asset_path),
@@ -675,7 +737,13 @@ fn load_node(
675737
.insert(Aabb::from_min_max(
676738
Vec3::from_slice(&bounds.min),
677739
Vec3::from_slice(&bounds.max),
678-
));
740+
))
741+
.id();
742+
743+
// Mark for adding skinned mesh
744+
if let Some(skin) = gltf_node.skin() {
745+
entity_to_skin_index_map.insert(node, skin.index());
746+
}
679747
}
680748
}
681749

@@ -723,7 +791,15 @@ fn load_node(
723791

724792
// append other nodes
725793
for child in gltf_node.children() {
726-
if let Err(err) = load_node(&child, parent, load_context, buffer_data, animated_nodes) {
794+
if let Err(err) = load_node(
795+
&child,
796+
parent,
797+
load_context,
798+
buffer_data,
799+
animated_nodes,
800+
node_index_to_entity_map,
801+
entity_to_skin_index_map,
802+
) {
727803
gltf_error = Some(err);
728804
return;
729805
}
@@ -770,6 +846,10 @@ fn scene_label(scene: &gltf::Scene) -> String {
770846
format!("Scene{}", scene.index())
771847
}
772848

849+
fn skin_label(skin: &gltf::Skin) -> String {
850+
format!("Skin{}", skin.index())
851+
}
852+
773853
/// Extracts the texture sampler data from the glTF texture.
774854
fn texture_sampler<'a>(texture: &gltf::Texture) -> SamplerDescriptor<'a> {
775855
let gltf_sampler = texture.sampler();

crates/bevy_pbr/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ bevy_window = { path = "../bevy_window", version = "0.7.0-dev" }
2929
bitflags = "1.2"
3030
# direct dependency required for derive macro
3131
bytemuck = { version = "1", features = ["derive"] }
32+
smallvec = "1.0"

crates/bevy_pbr/src/material.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,11 +245,11 @@ impl<M: SpecializedMaterial> SpecializedMeshPipeline for MaterialPipeline<M> {
245245
if let Some(fragment_shader) = &self.fragment_shader {
246246
descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone();
247247
}
248-
descriptor.layout = Some(vec![
249-
self.mesh_pipeline.view_layout.clone(),
250-
self.material_layout.clone(),
251-
self.mesh_pipeline.mesh_layout.clone(),
252-
]);
248+
249+
// MeshPipeline::specialize's current implementation guarantees that the returned
250+
// specialized descriptor has a populated layout
251+
let descriptor_layout = descriptor.layout.as_mut().unwrap();
252+
descriptor_layout.insert(1, self.material_layout.clone());
253253

254254
M::specialize(&mut descriptor, key.material_key, layout)?;
255255
Ok(descriptor)

crates/bevy_pbr/src/render/depth.wgsl

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,18 @@ var<uniform> view: View;
1212
[[group(1), binding(0)]]
1313
var<uniform> mesh: Mesh;
1414

15+
#ifdef SKINNED
16+
[[group(1), binding(1)]]
17+
var<uniform> joint_matrices: SkinnedMesh;
18+
#import bevy_pbr::skinning
19+
#endif
20+
1521
struct Vertex {
1622
[[location(0)]] position: vec3<f32>;
23+
#ifdef SKINNED
24+
[[location(4)]] joint_indices: vec4<u32>;
25+
[[location(5)]] joint_weights: vec4<f32>;
26+
#endif
1727
};
1828

1929
struct VertexOutput {
@@ -22,7 +32,13 @@ struct VertexOutput {
2232

2333
[[stage(vertex)]]
2434
fn vertex(vertex: Vertex) -> VertexOutput {
35+
#ifdef SKINNED
36+
let model = skin_model(vertex.joint_indices, vertex.joint_weights);
37+
#else
38+
let model = mesh.model;
39+
#endif
40+
2541
var out: VertexOutput;
26-
out.clip_position = view.view_proj * mesh.model * vec4<f32>(vertex.position, 1.0);
42+
out.clip_position = view.view_proj * model * vec4<f32>(vertex.position, 1.0);
2743
return out;
2844
}

crates/bevy_pbr/src/render/light.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float;
159159
pub struct ShadowPipeline {
160160
pub view_layout: BindGroupLayout,
161161
pub mesh_layout: BindGroupLayout,
162+
pub skinned_mesh_layout: BindGroupLayout,
162163
pub point_light_sampler: Sampler,
163164
pub directional_light_sampler: Sampler,
164165
}
@@ -187,10 +188,12 @@ impl FromWorld for ShadowPipeline {
187188
});
188189

189190
let mesh_pipeline = world.get_resource::<MeshPipeline>().unwrap();
191+
let skinned_mesh_layout = mesh_pipeline.skinned_mesh_layout.clone();
190192

191193
ShadowPipeline {
192194
view_layout,
193195
mesh_layout: mesh_pipeline.mesh_layout.clone(),
196+
skinned_mesh_layout,
194197
point_light_sampler: render_device.create_sampler(&SamplerDescriptor {
195198
address_mode_u: AddressMode::ClampToEdge,
196199
address_mode_v: AddressMode::ClampToEdge,
@@ -256,18 +259,31 @@ impl SpecializedMeshPipeline for ShadowPipeline {
256259
key: Self::Key,
257260
layout: &MeshVertexBufferLayout,
258261
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
259-
let vertex_buffer_layout =
260-
layout.get_layout(&[Mesh::ATTRIBUTE_POSITION.at_shader_location(0)])?;
262+
let mut vertex_attributes = vec![Mesh::ATTRIBUTE_POSITION.at_shader_location(0)];
263+
264+
let mut bind_group_layout = vec![self.view_layout.clone(), self.mesh_layout.clone()];
265+
let mut shader_defs = Vec::new();
266+
267+
if layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX)
268+
&& layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT)
269+
{
270+
shader_defs.push(String::from("SKINNED"));
271+
vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(4));
272+
vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(5));
273+
bind_group_layout.push(self.skinned_mesh_layout.clone());
274+
}
275+
276+
let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;
261277

262278
Ok(RenderPipelineDescriptor {
263279
vertex: VertexState {
264280
shader: SHADOW_SHADER_HANDLE.typed::<Shader>(),
265281
entry_point: "vertex".into(),
266-
shader_defs: vec![],
282+
shader_defs,
267283
buffers: vec![vertex_buffer_layout],
268284
},
269285
fragment: None,
270-
layout: Some(vec![self.view_layout.clone(), self.mesh_layout.clone()]),
286+
layout: Some(bind_group_layout),
271287
primitive: PrimitiveState {
272288
topology: key.primitive_topology(),
273289
strip_index_format: None,

0 commit comments

Comments
 (0)