mirror of
https://github.com/bevyengine/bevy
synced 2024-11-24 21:53:07 +00:00
# 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 <contact@jamessliu.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: James Liu <contact@jamessliu.com>
This commit is contained in:
parent
54d2e86afc
commit
31bd4ecbbc
19 changed files with 779 additions and 55 deletions
|
@ -235,6 +235,15 @@ path = "examples/3d/update_gltf_scene.rs"
|
||||||
name = "wireframe"
|
name = "wireframe"
|
||||||
path = "examples/3d/wireframe.rs"
|
path = "examples/3d/wireframe.rs"
|
||||||
|
|
||||||
|
# Animation
|
||||||
|
[[example]]
|
||||||
|
name = "custom_skinned_mesh"
|
||||||
|
path = "examples/animation/custom_skinned_mesh.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "gltf_skinned_mesh"
|
||||||
|
path = "examples/animation/gltf_skinned_mesh.rs"
|
||||||
|
|
||||||
# Application
|
# Application
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "custom_loop"
|
name = "custom_loop"
|
||||||
|
|
1
assets/models/SimpleSkin/SimpleSkin.gltf
Normal file
1
assets/models/SimpleSkin/SimpleSkin.gltf
Normal file
|
@ -0,0 +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"}}
|
|
@ -3,7 +3,7 @@ use bevy_asset::{
|
||||||
AssetIoError, AssetLoader, AssetPath, BoxedFuture, Handle, LoadContext, LoadedAsset,
|
AssetIoError, AssetLoader, AssetPath, BoxedFuture, Handle, LoadContext, LoadedAsset,
|
||||||
};
|
};
|
||||||
use bevy_core::Name;
|
use bevy_core::Name;
|
||||||
use bevy_ecs::{prelude::FromWorld, world::World};
|
use bevy_ecs::{entity::Entity, prelude::FromWorld, world::World};
|
||||||
use bevy_hierarchy::{BuildWorldChildren, WorldChildBuilder};
|
use bevy_hierarchy::{BuildWorldChildren, WorldChildBuilder};
|
||||||
use bevy_log::warn;
|
use bevy_log::warn;
|
||||||
use bevy_math::{Mat4, Quat, Vec3};
|
use bevy_math::{Mat4, Quat, Vec3};
|
||||||
|
@ -16,7 +16,10 @@ use bevy_render::{
|
||||||
Camera, Camera2d, Camera3d, CameraProjection, OrthographicProjection, PerspectiveProjection,
|
Camera, Camera2d, Camera3d, CameraProjection, OrthographicProjection, PerspectiveProjection,
|
||||||
},
|
},
|
||||||
color::Color,
|
color::Color,
|
||||||
mesh::{Indices, Mesh, VertexAttributeValues},
|
mesh::{
|
||||||
|
skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
|
||||||
|
Indices, Mesh, VertexAttributeValues,
|
||||||
|
},
|
||||||
primitives::{Aabb, Frustum},
|
primitives::{Aabb, Frustum},
|
||||||
render_resource::{AddressMode, Face, FilterMode, PrimitiveTopology, SamplerDescriptor},
|
render_resource::{AddressMode, Face, FilterMode, PrimitiveTopology, SamplerDescriptor},
|
||||||
renderer::RenderDevice,
|
renderer::RenderDevice,
|
||||||
|
@ -249,6 +252,18 @@ async fn load_gltf<'a, 'b>(
|
||||||
// mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute);
|
// mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
if let Some(iter) = reader.read_joints(0) {
|
||||||
|
let vertex_attribute = VertexAttributeValues::Uint16x4(iter.into_u16().collect());
|
||||||
|
mesh.insert_attribute(Mesh::ATTRIBUTE_JOINT_INDEX, vertex_attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(vertex_attribute) = reader
|
||||||
|
.read_weights(0)
|
||||||
|
.map(|v| VertexAttributeValues::Float32x4(v.into_f32().collect()))
|
||||||
|
{
|
||||||
|
mesh.insert_attribute(Mesh::ATTRIBUTE_JOINT_WEIGHT, vertex_attribute);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(indices) = reader.read_indices() {
|
if let Some(indices) = reader.read_indices() {
|
||||||
mesh.set_indices(Some(Indices::U32(indices.into_u32().collect())));
|
mesh.set_indices(Some(Indices::U32(indices.into_u32().collect())));
|
||||||
};
|
};
|
||||||
|
@ -384,18 +399,45 @@ async fn load_gltf<'a, 'b>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let skinned_mesh_inverse_bindposes: Vec<_> = gltf
|
||||||
|
.skins()
|
||||||
|
.map(|gltf_skin| {
|
||||||
|
let reader = gltf_skin.reader(|buffer| Some(&buffer_data[buffer.index()]));
|
||||||
|
let inverse_bindposes: Vec<Mat4> = reader
|
||||||
|
.read_inverse_bind_matrices()
|
||||||
|
.unwrap()
|
||||||
|
.map(|mat| Mat4::from_cols_array_2d(&mat))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
load_context.set_labeled_asset(
|
||||||
|
&skin_label(&gltf_skin),
|
||||||
|
LoadedAsset::new(SkinnedMeshInverseBindposes::from(inverse_bindposes)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut scenes = vec![];
|
let mut scenes = vec![];
|
||||||
let mut named_scenes = HashMap::default();
|
let mut named_scenes = HashMap::default();
|
||||||
for scene in gltf.scenes() {
|
for scene in gltf.scenes() {
|
||||||
let mut err = None;
|
let mut err = None;
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
|
let mut node_index_to_entity_map = HashMap::new();
|
||||||
|
let mut entity_to_skin_index_map = HashMap::new();
|
||||||
|
|
||||||
world
|
world
|
||||||
.spawn()
|
.spawn()
|
||||||
.insert_bundle(TransformBundle::identity())
|
.insert_bundle(TransformBundle::identity())
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
for node in scene.nodes() {
|
for node in scene.nodes() {
|
||||||
let result =
|
let result = load_node(
|
||||||
load_node(&node, parent, load_context, &buffer_data, &animated_nodes);
|
&node,
|
||||||
|
parent,
|
||||||
|
load_context,
|
||||||
|
&buffer_data,
|
||||||
|
&animated_nodes,
|
||||||
|
&mut node_index_to_entity_map,
|
||||||
|
&mut entity_to_skin_index_map,
|
||||||
|
);
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
err = Some(result);
|
err = Some(result);
|
||||||
return;
|
return;
|
||||||
|
@ -405,6 +447,21 @@ async fn load_gltf<'a, 'b>(
|
||||||
if let Some(Err(err)) = err {
|
if let Some(Err(err)) = err {
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (&entity, &skin_index) in &entity_to_skin_index_map {
|
||||||
|
let mut entity = world.entity_mut(entity);
|
||||||
|
let skin = gltf.skins().nth(skin_index).unwrap();
|
||||||
|
let joint_entities: Vec<_> = skin
|
||||||
|
.joints()
|
||||||
|
.map(|node| node_index_to_entity_map[&node.index()])
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
entity.insert(SkinnedMesh {
|
||||||
|
inverse_bindposes: skinned_mesh_inverse_bindposes[skin_index].clone(),
|
||||||
|
joints: joint_entities,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let scene_handle = load_context
|
let scene_handle = load_context
|
||||||
.set_labeled_asset(&scene_label(&scene), LoadedAsset::new(Scene::new(world)));
|
.set_labeled_asset(&scene_label(&scene), LoadedAsset::new(Scene::new(world)));
|
||||||
|
|
||||||
|
@ -575,6 +632,8 @@ fn load_node(
|
||||||
load_context: &mut LoadContext,
|
load_context: &mut LoadContext,
|
||||||
buffer_data: &[Vec<u8>],
|
buffer_data: &[Vec<u8>],
|
||||||
animated_nodes: &HashSet<usize>,
|
animated_nodes: &HashSet<usize>,
|
||||||
|
node_index_to_entity_map: &mut HashMap<usize, Entity>,
|
||||||
|
entity_to_skin_index_map: &mut HashMap<Entity, usize>,
|
||||||
) -> Result<(), GltfError> {
|
) -> Result<(), GltfError> {
|
||||||
let transform = gltf_node.transform();
|
let transform = gltf_node.transform();
|
||||||
let mut gltf_error = None;
|
let mut gltf_error = None;
|
||||||
|
@ -645,6 +704,9 @@ fn load_node(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map node index to entity
|
||||||
|
node_index_to_entity_map.insert(gltf_node.index(), node.id());
|
||||||
|
|
||||||
node.with_children(|parent| {
|
node.with_children(|parent| {
|
||||||
if let Some(mesh) = gltf_node.mesh() {
|
if let Some(mesh) = gltf_node.mesh() {
|
||||||
// append primitives
|
// append primitives
|
||||||
|
@ -660,13 +722,13 @@ fn load_node(
|
||||||
}
|
}
|
||||||
|
|
||||||
let primitive_label = primitive_label(&mesh, &primitive);
|
let primitive_label = primitive_label(&mesh, &primitive);
|
||||||
|
let bounds = primitive.bounding_box();
|
||||||
let mesh_asset_path =
|
let mesh_asset_path =
|
||||||
AssetPath::new_ref(load_context.path(), Some(&primitive_label));
|
AssetPath::new_ref(load_context.path(), Some(&primitive_label));
|
||||||
let material_asset_path =
|
let material_asset_path =
|
||||||
AssetPath::new_ref(load_context.path(), Some(&material_label));
|
AssetPath::new_ref(load_context.path(), Some(&material_label));
|
||||||
|
|
||||||
let bounds = primitive.bounding_box();
|
let node = parent
|
||||||
parent
|
|
||||||
.spawn_bundle(PbrBundle {
|
.spawn_bundle(PbrBundle {
|
||||||
mesh: load_context.get_handle(mesh_asset_path),
|
mesh: load_context.get_handle(mesh_asset_path),
|
||||||
material: load_context.get_handle(material_asset_path),
|
material: load_context.get_handle(material_asset_path),
|
||||||
|
@ -675,7 +737,13 @@ fn load_node(
|
||||||
.insert(Aabb::from_min_max(
|
.insert(Aabb::from_min_max(
|
||||||
Vec3::from_slice(&bounds.min),
|
Vec3::from_slice(&bounds.min),
|
||||||
Vec3::from_slice(&bounds.max),
|
Vec3::from_slice(&bounds.max),
|
||||||
));
|
))
|
||||||
|
.id();
|
||||||
|
|
||||||
|
// Mark for adding skinned mesh
|
||||||
|
if let Some(skin) = gltf_node.skin() {
|
||||||
|
entity_to_skin_index_map.insert(node, skin.index());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -723,7 +791,15 @@ fn load_node(
|
||||||
|
|
||||||
// append other nodes
|
// append other nodes
|
||||||
for child in gltf_node.children() {
|
for child in gltf_node.children() {
|
||||||
if let Err(err) = load_node(&child, parent, load_context, buffer_data, animated_nodes) {
|
if let Err(err) = load_node(
|
||||||
|
&child,
|
||||||
|
parent,
|
||||||
|
load_context,
|
||||||
|
buffer_data,
|
||||||
|
animated_nodes,
|
||||||
|
node_index_to_entity_map,
|
||||||
|
entity_to_skin_index_map,
|
||||||
|
) {
|
||||||
gltf_error = Some(err);
|
gltf_error = Some(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -770,6 +846,10 @@ fn scene_label(scene: &gltf::Scene) -> String {
|
||||||
format!("Scene{}", scene.index())
|
format!("Scene{}", scene.index())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn skin_label(skin: &gltf::Skin) -> String {
|
||||||
|
format!("Skin{}", skin.index())
|
||||||
|
}
|
||||||
|
|
||||||
/// Extracts the texture sampler data from the glTF texture.
|
/// Extracts the texture sampler data from the glTF texture.
|
||||||
fn texture_sampler<'a>(texture: &gltf::Texture) -> SamplerDescriptor<'a> {
|
fn texture_sampler<'a>(texture: &gltf::Texture) -> SamplerDescriptor<'a> {
|
||||||
let gltf_sampler = texture.sampler();
|
let gltf_sampler = texture.sampler();
|
||||||
|
|
|
@ -29,3 +29,4 @@ bevy_window = { path = "../bevy_window", version = "0.7.0-dev" }
|
||||||
bitflags = "1.2"
|
bitflags = "1.2"
|
||||||
# direct dependency required for derive macro
|
# direct dependency required for derive macro
|
||||||
bytemuck = { version = "1", features = ["derive"] }
|
bytemuck = { version = "1", features = ["derive"] }
|
||||||
|
smallvec = "1.0"
|
||||||
|
|
|
@ -245,11 +245,11 @@ impl<M: SpecializedMaterial> SpecializedMeshPipeline for MaterialPipeline<M> {
|
||||||
if let Some(fragment_shader) = &self.fragment_shader {
|
if let Some(fragment_shader) = &self.fragment_shader {
|
||||||
descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone();
|
descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone();
|
||||||
}
|
}
|
||||||
descriptor.layout = Some(vec![
|
|
||||||
self.mesh_pipeline.view_layout.clone(),
|
// MeshPipeline::specialize's current implementation guarantees that the returned
|
||||||
self.material_layout.clone(),
|
// specialized descriptor has a populated layout
|
||||||
self.mesh_pipeline.mesh_layout.clone(),
|
let descriptor_layout = descriptor.layout.as_mut().unwrap();
|
||||||
]);
|
descriptor_layout.insert(1, self.material_layout.clone());
|
||||||
|
|
||||||
M::specialize(&mut descriptor, key.material_key, layout)?;
|
M::specialize(&mut descriptor, key.material_key, layout)?;
|
||||||
Ok(descriptor)
|
Ok(descriptor)
|
||||||
|
|
|
@ -12,8 +12,18 @@ var<uniform> view: View;
|
||||||
[[group(1), binding(0)]]
|
[[group(1), binding(0)]]
|
||||||
var<uniform> mesh: Mesh;
|
var<uniform> mesh: Mesh;
|
||||||
|
|
||||||
|
#ifdef SKINNED
|
||||||
|
[[group(1), binding(1)]]
|
||||||
|
var<uniform> joint_matrices: SkinnedMesh;
|
||||||
|
#import bevy_pbr::skinning
|
||||||
|
#endif
|
||||||
|
|
||||||
struct Vertex {
|
struct Vertex {
|
||||||
[[location(0)]] position: vec3<f32>;
|
[[location(0)]] position: vec3<f32>;
|
||||||
|
#ifdef SKINNED
|
||||||
|
[[location(4)]] joint_indices: vec4<u32>;
|
||||||
|
[[location(5)]] joint_weights: vec4<f32>;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
|
@ -22,7 +32,13 @@ struct VertexOutput {
|
||||||
|
|
||||||
[[stage(vertex)]]
|
[[stage(vertex)]]
|
||||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||||
|
#ifdef SKINNED
|
||||||
|
let model = skin_model(vertex.joint_indices, vertex.joint_weights);
|
||||||
|
#else
|
||||||
|
let model = mesh.model;
|
||||||
|
#endif
|
||||||
|
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.clip_position = view.view_proj * mesh.model * vec4<f32>(vertex.position, 1.0);
|
out.clip_position = view.view_proj * model * vec4<f32>(vertex.position, 1.0);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,6 +159,7 @@ pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float;
|
||||||
pub struct ShadowPipeline {
|
pub struct ShadowPipeline {
|
||||||
pub view_layout: BindGroupLayout,
|
pub view_layout: BindGroupLayout,
|
||||||
pub mesh_layout: BindGroupLayout,
|
pub mesh_layout: BindGroupLayout,
|
||||||
|
pub skinned_mesh_layout: BindGroupLayout,
|
||||||
pub point_light_sampler: Sampler,
|
pub point_light_sampler: Sampler,
|
||||||
pub directional_light_sampler: Sampler,
|
pub directional_light_sampler: Sampler,
|
||||||
}
|
}
|
||||||
|
@ -187,10 +188,12 @@ impl FromWorld for ShadowPipeline {
|
||||||
});
|
});
|
||||||
|
|
||||||
let mesh_pipeline = world.get_resource::<MeshPipeline>().unwrap();
|
let mesh_pipeline = world.get_resource::<MeshPipeline>().unwrap();
|
||||||
|
let skinned_mesh_layout = mesh_pipeline.skinned_mesh_layout.clone();
|
||||||
|
|
||||||
ShadowPipeline {
|
ShadowPipeline {
|
||||||
view_layout,
|
view_layout,
|
||||||
mesh_layout: mesh_pipeline.mesh_layout.clone(),
|
mesh_layout: mesh_pipeline.mesh_layout.clone(),
|
||||||
|
skinned_mesh_layout,
|
||||||
point_light_sampler: render_device.create_sampler(&SamplerDescriptor {
|
point_light_sampler: render_device.create_sampler(&SamplerDescriptor {
|
||||||
address_mode_u: AddressMode::ClampToEdge,
|
address_mode_u: AddressMode::ClampToEdge,
|
||||||
address_mode_v: AddressMode::ClampToEdge,
|
address_mode_v: AddressMode::ClampToEdge,
|
||||||
|
@ -256,18 +259,31 @@ impl SpecializedMeshPipeline for ShadowPipeline {
|
||||||
key: Self::Key,
|
key: Self::Key,
|
||||||
layout: &MeshVertexBufferLayout,
|
layout: &MeshVertexBufferLayout,
|
||||||
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
||||||
let vertex_buffer_layout =
|
let mut vertex_attributes = vec![Mesh::ATTRIBUTE_POSITION.at_shader_location(0)];
|
||||||
layout.get_layout(&[Mesh::ATTRIBUTE_POSITION.at_shader_location(0)])?;
|
|
||||||
|
let mut bind_group_layout = vec![self.view_layout.clone(), self.mesh_layout.clone()];
|
||||||
|
let mut shader_defs = Vec::new();
|
||||||
|
|
||||||
|
if layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX)
|
||||||
|
&& layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT)
|
||||||
|
{
|
||||||
|
shader_defs.push(String::from("SKINNED"));
|
||||||
|
vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(4));
|
||||||
|
vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(5));
|
||||||
|
bind_group_layout.push(self.skinned_mesh_layout.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;
|
||||||
|
|
||||||
Ok(RenderPipelineDescriptor {
|
Ok(RenderPipelineDescriptor {
|
||||||
vertex: VertexState {
|
vertex: VertexState {
|
||||||
shader: SHADOW_SHADER_HANDLE.typed::<Shader>(),
|
shader: SHADOW_SHADER_HANDLE.typed::<Shader>(),
|
||||||
entry_point: "vertex".into(),
|
entry_point: "vertex".into(),
|
||||||
shader_defs: vec![],
|
shader_defs,
|
||||||
buffers: vec![vertex_buffer_layout],
|
buffers: vec![vertex_buffer_layout],
|
||||||
},
|
},
|
||||||
fragment: None,
|
fragment: None,
|
||||||
layout: Some(vec![self.view_layout.clone(), self.mesh_layout.clone()]),
|
layout: Some(bind_group_layout),
|
||||||
primitive: PrimitiveState {
|
primitive: PrimitiveState {
|
||||||
topology: key.primitive_topology(),
|
topology: key.primitive_topology(),
|
||||||
strip_index_format: None,
|
strip_index_format: None,
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
ViewClusterBindings, ViewLightsUniformOffset, ViewShadowBindings,
|
ViewClusterBindings, ViewLightsUniformOffset, ViewShadowBindings,
|
||||||
};
|
};
|
||||||
use bevy_app::Plugin;
|
use bevy_app::Plugin;
|
||||||
use bevy_asset::{load_internal_asset, Handle, HandleUntyped};
|
use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
system::{lifetimeless::*, SystemParamItem},
|
system::{lifetimeless::*, SystemParamItem},
|
||||||
|
@ -11,7 +11,10 @@ use bevy_ecs::{
|
||||||
use bevy_math::{Mat4, Size};
|
use bevy_math::{Mat4, Size};
|
||||||
use bevy_reflect::TypeUuid;
|
use bevy_reflect::TypeUuid;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
mesh::{GpuBufferInfo, Mesh, MeshVertexBufferLayout},
|
mesh::{
|
||||||
|
skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
|
||||||
|
GpuBufferInfo, Mesh, MeshVertexBufferLayout,
|
||||||
|
},
|
||||||
render_asset::RenderAssets,
|
render_asset::RenderAssets,
|
||||||
render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
|
render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
|
||||||
render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass},
|
render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass},
|
||||||
|
@ -22,16 +25,24 @@ use bevy_render::{
|
||||||
RenderApp, RenderStage,
|
RenderApp, RenderStage,
|
||||||
};
|
};
|
||||||
use bevy_transform::components::GlobalTransform;
|
use bevy_transform::components::GlobalTransform;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct MeshRenderPlugin;
|
pub struct MeshRenderPlugin;
|
||||||
|
|
||||||
|
const MAX_JOINTS: usize = 256;
|
||||||
|
const JOINT_SIZE: usize = std::mem::size_of::<Mat4>();
|
||||||
|
pub(crate) const JOINT_BUFFER_SIZE: usize = MAX_JOINTS * JOINT_SIZE;
|
||||||
|
|
||||||
pub const MESH_VIEW_BIND_GROUP_HANDLE: HandleUntyped =
|
pub const MESH_VIEW_BIND_GROUP_HANDLE: HandleUntyped =
|
||||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 9076678235888822571);
|
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 9076678235888822571);
|
||||||
pub const MESH_STRUCT_HANDLE: HandleUntyped =
|
pub const MESH_STRUCT_HANDLE: HandleUntyped =
|
||||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2506024101911992377);
|
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2506024101911992377);
|
||||||
pub const MESH_SHADER_HANDLE: HandleUntyped =
|
pub const MESH_SHADER_HANDLE: HandleUntyped =
|
||||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 3252377289100772450);
|
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 3252377289100772450);
|
||||||
|
pub const SKINNING_HANDLE: HandleUntyped =
|
||||||
|
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 13215291596265391738);
|
||||||
|
|
||||||
impl Plugin for MeshRenderPlugin {
|
impl Plugin for MeshRenderPlugin {
|
||||||
fn build(&self, app: &mut bevy_app::App) {
|
fn build(&self, app: &mut bevy_app::App) {
|
||||||
|
@ -48,13 +59,17 @@ impl Plugin for MeshRenderPlugin {
|
||||||
"mesh_view_bind_group.wgsl",
|
"mesh_view_bind_group.wgsl",
|
||||||
Shader::from_wgsl
|
Shader::from_wgsl
|
||||||
);
|
);
|
||||||
|
load_internal_asset!(app, SKINNING_HANDLE, "skinning.wgsl", Shader::from_wgsl);
|
||||||
|
|
||||||
app.add_plugin(UniformComponentPlugin::<MeshUniform>::default());
|
app.add_plugin(UniformComponentPlugin::<MeshUniform>::default());
|
||||||
|
|
||||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
render_app
|
render_app
|
||||||
.init_resource::<MeshPipeline>()
|
.init_resource::<MeshPipeline>()
|
||||||
|
.init_resource::<SkinnedMeshUniform>()
|
||||||
.add_system_to_stage(RenderStage::Extract, extract_meshes)
|
.add_system_to_stage(RenderStage::Extract, extract_meshes)
|
||||||
|
.add_system_to_stage(RenderStage::Extract, extract_skinned_meshes)
|
||||||
|
.add_system_to_stage(RenderStage::Prepare, prepare_skinned_meshes)
|
||||||
.add_system_to_stage(RenderStage::Queue, queue_mesh_bind_group)
|
.add_system_to_stage(RenderStage::Queue, queue_mesh_bind_group)
|
||||||
.add_system_to_stage(RenderStage::Queue, queue_mesh_view_bind_groups);
|
.add_system_to_stage(RenderStage::Queue, queue_mesh_view_bind_groups);
|
||||||
}
|
}
|
||||||
|
@ -129,7 +144,7 @@ pub fn extract_meshes(
|
||||||
commands.insert_or_spawn_batch(caster_values);
|
commands.insert_or_spawn_batch(caster_values);
|
||||||
|
|
||||||
let mut not_caster_values = Vec::with_capacity(*previous_not_caster_len);
|
let mut not_caster_values = Vec::with_capacity(*previous_not_caster_len);
|
||||||
for (entity, computed_visibility, transform, handle, not_receiver) in not_caster_query.iter() {
|
for (entity, computed_visibility, transform, mesh, not_receiver) in not_caster_query.iter() {
|
||||||
if !computed_visibility.is_visible {
|
if !computed_visibility.is_visible {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -137,7 +152,7 @@ pub fn extract_meshes(
|
||||||
not_caster_values.push((
|
not_caster_values.push((
|
||||||
entity,
|
entity,
|
||||||
(
|
(
|
||||||
handle.clone_weak(),
|
mesh.clone_weak(),
|
||||||
MeshUniform {
|
MeshUniform {
|
||||||
flags: if not_receiver.is_some() {
|
flags: if not_receiver.is_some() {
|
||||||
MeshFlags::empty().bits
|
MeshFlags::empty().bits
|
||||||
|
@ -155,10 +170,92 @@ pub fn extract_meshes(
|
||||||
commands.insert_or_spawn_batch(not_caster_values);
|
commands.insert_or_spawn_batch(not_caster_values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ExtractedJoints {
|
||||||
|
pub buffer: Vec<Mat4>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct SkinnedMeshJoints {
|
||||||
|
pub index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SkinnedMeshJoints {
|
||||||
|
#[inline]
|
||||||
|
pub fn build(
|
||||||
|
skin: &SkinnedMesh,
|
||||||
|
inverse_bindposes: &Assets<SkinnedMeshInverseBindposes>,
|
||||||
|
joints: &Query<&GlobalTransform>,
|
||||||
|
buffer: &mut Vec<Mat4>,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let inverse_bindposes = inverse_bindposes.get(&skin.inverse_bindposes)?;
|
||||||
|
let bindposes = inverse_bindposes.iter();
|
||||||
|
let skin_joints = skin.joints.iter();
|
||||||
|
let mut temp =
|
||||||
|
SmallVec::<[Mat4; MAX_JOINTS]>::with_capacity(bindposes.len().min(MAX_JOINTS));
|
||||||
|
for (inverse_bindpose, joint) in bindposes.zip(skin_joints).take(MAX_JOINTS) {
|
||||||
|
let joint_matrix = joints.get(*joint).ok()?.compute_matrix();
|
||||||
|
temp.push(joint_matrix * *inverse_bindpose);
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = buffer.len();
|
||||||
|
buffer.extend(temp);
|
||||||
|
// Pad to 256 byte alignment
|
||||||
|
while buffer.len() % 4 != 0 {
|
||||||
|
buffer.push(Mat4::ZERO);
|
||||||
|
}
|
||||||
|
Some(Self {
|
||||||
|
index: start as u32,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_buffer_index(mut self) -> Self {
|
||||||
|
self.index *= std::mem::size_of::<Mat4>() as u32;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_skinned_meshes(
|
||||||
|
query: Query<(Entity, &ComputedVisibility, &SkinnedMesh)>,
|
||||||
|
inverse_bindposes: Res<Assets<SkinnedMeshInverseBindposes>>,
|
||||||
|
joint_query: Query<&GlobalTransform>,
|
||||||
|
mut commands: Commands,
|
||||||
|
mut previous_len: Local<usize>,
|
||||||
|
mut previous_joint_len: Local<usize>,
|
||||||
|
) {
|
||||||
|
let mut values = Vec::with_capacity(*previous_len);
|
||||||
|
let mut joints = Vec::with_capacity(*previous_joint_len);
|
||||||
|
let mut last_start = 0;
|
||||||
|
|
||||||
|
for (entity, computed_visibility, skin) in query.iter() {
|
||||||
|
if !computed_visibility.is_visible {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// PERF: This can be expensive, can we move this to prepare?
|
||||||
|
if let Some(skinned_joints) =
|
||||||
|
SkinnedMeshJoints::build(skin, &inverse_bindposes, &joint_query, &mut joints)
|
||||||
|
{
|
||||||
|
last_start = last_start.max(skinned_joints.index as usize);
|
||||||
|
values.push((entity, (skinned_joints.to_buffer_index(),)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad out the buffer to ensure that there's enough space for bindings
|
||||||
|
while joints.len() - last_start < MAX_JOINTS {
|
||||||
|
joints.push(Mat4::ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
*previous_len = values.len();
|
||||||
|
*previous_joint_len = joints.len();
|
||||||
|
commands.insert_resource(ExtractedJoints { buffer: joints });
|
||||||
|
commands.insert_or_spawn_batch(values);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MeshPipeline {
|
pub struct MeshPipeline {
|
||||||
pub view_layout: BindGroupLayout,
|
pub view_layout: BindGroupLayout,
|
||||||
pub mesh_layout: BindGroupLayout,
|
pub mesh_layout: BindGroupLayout,
|
||||||
|
pub skinned_mesh_layout: BindGroupLayout,
|
||||||
// This dummy white texture is to be used in place of optional StandardMaterial textures
|
// This dummy white texture is to be used in place of optional StandardMaterial textures
|
||||||
pub dummy_white_gpu_image: GpuImage,
|
pub dummy_white_gpu_image: GpuImage,
|
||||||
}
|
}
|
||||||
|
@ -276,19 +373,40 @@ impl FromWorld for MeshPipeline {
|
||||||
label: Some("mesh_view_layout"),
|
label: Some("mesh_view_layout"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let mesh_binding = BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
||||||
|
ty: BindingType::Buffer {
|
||||||
|
ty: BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: true,
|
||||||
|
min_binding_size: BufferSize::new(MeshUniform::std140_size_static() as u64),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
};
|
||||||
|
|
||||||
let mesh_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
let mesh_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
entries: &[BindGroupLayoutEntry {
|
entries: &[mesh_binding],
|
||||||
binding: 0,
|
|
||||||
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
|
||||||
ty: BindingType::Buffer {
|
|
||||||
ty: BufferBindingType::Uniform,
|
|
||||||
has_dynamic_offset: true,
|
|
||||||
min_binding_size: BufferSize::new(MeshUniform::std140_size_static() as u64),
|
|
||||||
},
|
|
||||||
count: None,
|
|
||||||
}],
|
|
||||||
label: Some("mesh_layout"),
|
label: Some("mesh_layout"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let skinned_mesh_layout =
|
||||||
|
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
|
entries: &[
|
||||||
|
mesh_binding,
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: ShaderStages::VERTEX,
|
||||||
|
ty: BindingType::Buffer {
|
||||||
|
ty: BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: true,
|
||||||
|
min_binding_size: BufferSize::new(JOINT_BUFFER_SIZE as u64),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: Some("skinned_mesh_layout"),
|
||||||
|
});
|
||||||
|
|
||||||
// A 1x1x1 'all 1.0' texture to use as a dummy texture to use in place of optional StandardMaterial textures
|
// A 1x1x1 'all 1.0' texture to use as a dummy texture to use in place of optional StandardMaterial textures
|
||||||
let dummy_white_gpu_image = {
|
let dummy_white_gpu_image = {
|
||||||
let image = Image::new_fill(
|
let image = Image::new_fill(
|
||||||
|
@ -338,6 +456,7 @@ impl FromWorld for MeshPipeline {
|
||||||
MeshPipeline {
|
MeshPipeline {
|
||||||
view_layout,
|
view_layout,
|
||||||
mesh_layout,
|
mesh_layout,
|
||||||
|
skinned_mesh_layout,
|
||||||
dummy_white_gpu_image,
|
dummy_white_gpu_image,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -429,6 +548,18 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
||||||
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
|
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut bind_group_layout = vec![self.view_layout.clone()];
|
||||||
|
if layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX)
|
||||||
|
&& layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT)
|
||||||
|
{
|
||||||
|
shader_defs.push(String::from("SKINNED"));
|
||||||
|
vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(4));
|
||||||
|
vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(5));
|
||||||
|
bind_group_layout.push(self.skinned_mesh_layout.clone());
|
||||||
|
} else {
|
||||||
|
bind_group_layout.push(self.mesh_layout.clone());
|
||||||
|
};
|
||||||
|
|
||||||
let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;
|
let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;
|
||||||
|
|
||||||
let (label, blend, depth_write_enabled);
|
let (label, blend, depth_write_enabled);
|
||||||
|
@ -467,7 +598,7 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
||||||
write_mask: ColorWrites::ALL,
|
write_mask: ColorWrites::ALL,
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
layout: Some(vec![self.view_layout.clone(), self.mesh_layout.clone()]),
|
layout: Some(bind_group_layout),
|
||||||
primitive: PrimitiveState {
|
primitive: PrimitiveState {
|
||||||
front_face: FrontFace::Ccw,
|
front_face: FrontFace::Ccw,
|
||||||
cull_mode: Some(Face::Back),
|
cull_mode: Some(Face::Back),
|
||||||
|
@ -504,7 +635,8 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MeshBindGroup {
|
pub struct MeshBindGroup {
|
||||||
pub value: BindGroup,
|
pub normal: BindGroup,
|
||||||
|
pub skinned: Option<BindGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_mesh_bind_group(
|
pub fn queue_mesh_bind_group(
|
||||||
|
@ -512,21 +644,72 @@ pub fn queue_mesh_bind_group(
|
||||||
mesh_pipeline: Res<MeshPipeline>,
|
mesh_pipeline: Res<MeshPipeline>,
|
||||||
render_device: Res<RenderDevice>,
|
render_device: Res<RenderDevice>,
|
||||||
mesh_uniforms: Res<ComponentUniforms<MeshUniform>>,
|
mesh_uniforms: Res<ComponentUniforms<MeshUniform>>,
|
||||||
|
skinned_mesh_uniform: Res<SkinnedMeshUniform>,
|
||||||
) {
|
) {
|
||||||
if let Some(binding) = mesh_uniforms.uniforms().binding() {
|
if let Some(mesh_binding) = mesh_uniforms.uniforms().binding() {
|
||||||
commands.insert_resource(MeshBindGroup {
|
let mut mesh_bind_group = MeshBindGroup {
|
||||||
value: render_device.create_bind_group(&BindGroupDescriptor {
|
normal: render_device.create_bind_group(&BindGroupDescriptor {
|
||||||
entries: &[BindGroupEntry {
|
entries: &[BindGroupEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
resource: binding,
|
resource: mesh_binding.clone(),
|
||||||
}],
|
}],
|
||||||
label: Some("mesh_bind_group"),
|
label: Some("mesh_bind_group"),
|
||||||
layout: &mesh_pipeline.mesh_layout,
|
layout: &mesh_pipeline.mesh_layout,
|
||||||
}),
|
}),
|
||||||
});
|
skinned: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(skinned_joints_buffer) = skinned_mesh_uniform.buffer.uniform_buffer() {
|
||||||
|
mesh_bind_group.skinned = Some(render_device.create_bind_group(&BindGroupDescriptor {
|
||||||
|
entries: &[
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: mesh_binding,
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: BindingResource::Buffer(BufferBinding {
|
||||||
|
buffer: skinned_joints_buffer,
|
||||||
|
offset: 0,
|
||||||
|
size: Some(NonZeroU64::new(JOINT_BUFFER_SIZE as u64).unwrap()),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: Some("skinned_mesh_bind_group"),
|
||||||
|
layout: &mesh_pipeline.skinned_mesh_layout,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
commands.insert_resource(mesh_bind_group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct SkinnedMeshUniform {
|
||||||
|
pub buffer: UniformVec<Mat4>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare_skinned_meshes(
|
||||||
|
render_device: Res<RenderDevice>,
|
||||||
|
render_queue: Res<RenderQueue>,
|
||||||
|
extracted_joints: Res<ExtractedJoints>,
|
||||||
|
mut skinned_mesh_uniform: ResMut<SkinnedMeshUniform>,
|
||||||
|
) {
|
||||||
|
if extracted_joints.buffer.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
skinned_mesh_uniform.buffer.clear();
|
||||||
|
skinned_mesh_uniform
|
||||||
|
.buffer
|
||||||
|
.reserve(extracted_joints.buffer.len(), &render_device);
|
||||||
|
for joint in extracted_joints.buffer.iter() {
|
||||||
|
skinned_mesh_uniform.buffer.push(*joint);
|
||||||
|
}
|
||||||
|
skinned_mesh_uniform
|
||||||
|
.buffer
|
||||||
|
.write_buffer(&render_device, &render_queue);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct MeshViewBindGroup {
|
pub struct MeshViewBindGroup {
|
||||||
pub value: BindGroup,
|
pub value: BindGroup,
|
||||||
|
@ -640,7 +823,10 @@ pub struct SetMeshBindGroup<const I: usize>;
|
||||||
impl<const I: usize> EntityRenderCommand for SetMeshBindGroup<I> {
|
impl<const I: usize> EntityRenderCommand for SetMeshBindGroup<I> {
|
||||||
type Param = (
|
type Param = (
|
||||||
SRes<MeshBindGroup>,
|
SRes<MeshBindGroup>,
|
||||||
SQuery<Read<DynamicUniformIndex<MeshUniform>>>,
|
SQuery<(
|
||||||
|
Read<DynamicUniformIndex<MeshUniform>>,
|
||||||
|
Option<Read<SkinnedMeshJoints>>,
|
||||||
|
)>,
|
||||||
);
|
);
|
||||||
#[inline]
|
#[inline]
|
||||||
fn render<'w>(
|
fn render<'w>(
|
||||||
|
@ -649,12 +835,20 @@ impl<const I: usize> EntityRenderCommand for SetMeshBindGroup<I> {
|
||||||
(mesh_bind_group, mesh_query): SystemParamItem<'w, '_, Self::Param>,
|
(mesh_bind_group, mesh_query): SystemParamItem<'w, '_, Self::Param>,
|
||||||
pass: &mut TrackedRenderPass<'w>,
|
pass: &mut TrackedRenderPass<'w>,
|
||||||
) -> RenderCommandResult {
|
) -> RenderCommandResult {
|
||||||
let mesh_index = mesh_query.get(item).unwrap();
|
let (mesh_index, skinned_mesh_joints) = mesh_query.get(item).unwrap();
|
||||||
pass.set_bind_group(
|
if let Some(joints) = skinned_mesh_joints {
|
||||||
I,
|
pass.set_bind_group(
|
||||||
&mesh_bind_group.into_inner().value,
|
I,
|
||||||
&[mesh_index.index()],
|
mesh_bind_group.into_inner().skinned.as_ref().unwrap(),
|
||||||
);
|
&[mesh_index.index(), joints.index],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
pass.set_bind_group(
|
||||||
|
I,
|
||||||
|
&mesh_bind_group.into_inner().normal,
|
||||||
|
&[mesh_index.index()],
|
||||||
|
);
|
||||||
|
}
|
||||||
RenderCommandResult::Success
|
RenderCommandResult::Success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,10 @@ struct Vertex {
|
||||||
#ifdef VERTEX_TANGENTS
|
#ifdef VERTEX_TANGENTS
|
||||||
[[location(3)]] tangent: vec4<f32>;
|
[[location(3)]] tangent: vec4<f32>;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef SKINNED
|
||||||
|
[[location(4)]] joint_indices: vec4<u32>;
|
||||||
|
[[location(5)]] joint_weights: vec4<f32>;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
|
@ -22,15 +26,24 @@ struct VertexOutput {
|
||||||
|
|
||||||
[[group(2), binding(0)]]
|
[[group(2), binding(0)]]
|
||||||
var<uniform> mesh: Mesh;
|
var<uniform> mesh: Mesh;
|
||||||
|
#ifdef SKINNED
|
||||||
|
[[group(2), binding(1)]]
|
||||||
|
var<uniform> joint_matrices: SkinnedMesh;
|
||||||
|
#import bevy_pbr::skinning
|
||||||
|
#endif
|
||||||
|
|
||||||
[[stage(vertex)]]
|
[[stage(vertex)]]
|
||||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||||
let world_position = mesh.model * vec4<f32>(vertex.position, 1.0);
|
|
||||||
|
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.uv = vertex.uv;
|
#ifdef SKINNED
|
||||||
out.world_position = world_position;
|
var model = skin_model(vertex.joint_indices, vertex.joint_weights);
|
||||||
out.clip_position = view.view_proj * world_position;
|
out.world_position = model * vec4<f32>(vertex.position, 1.0);
|
||||||
|
out.world_normal = skin_normals(model, vertex.normal);
|
||||||
|
#ifdef VERTEX_TANGENTS
|
||||||
|
out.world_tangent = skin_tangents(model, vertex.tangent);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
out.world_position = mesh.model * vec4<f32>(vertex.position, 1.0);
|
||||||
out.world_normal = mat3x3<f32>(
|
out.world_normal = mat3x3<f32>(
|
||||||
mesh.inverse_transpose_model[0].xyz,
|
mesh.inverse_transpose_model[0].xyz,
|
||||||
mesh.inverse_transpose_model[1].xyz,
|
mesh.inverse_transpose_model[1].xyz,
|
||||||
|
@ -46,6 +59,10 @@ fn vertex(vertex: Vertex) -> VertexOutput {
|
||||||
vertex.tangent.w
|
vertex.tangent.w
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
out.uv = vertex.uv;
|
||||||
|
out.clip_position = view.view_proj * out.world_position;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,4 +7,10 @@ struct Mesh {
|
||||||
flags: u32;
|
flags: u32;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef SKINNED
|
||||||
|
struct SkinnedMesh {
|
||||||
|
data: array<mat4x4<f32>, 256u>;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
let MESH_FLAGS_SHADOW_RECEIVER_BIT: u32 = 1u;
|
let MESH_FLAGS_SHADOW_RECEIVER_BIT: u32 = 1u;
|
||||||
|
|
66
crates/bevy_pbr/src/render/skinning.wgsl
Normal file
66
crates/bevy_pbr/src/render/skinning.wgsl
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// If using this WGSL snippet as an #import, a dedicated
|
||||||
|
// "joint_matricies" uniform of type SkinnedMesh must be added in the
|
||||||
|
// main shader.
|
||||||
|
|
||||||
|
#define_import_path bevy_pbr::skinning
|
||||||
|
|
||||||
|
/// HACK: This works around naga not supporting matrix addition in SPIR-V
|
||||||
|
// translations. See https://github.com/gfx-rs/naga/issues/1527
|
||||||
|
fn add_matrix(
|
||||||
|
a: mat4x4<f32>,
|
||||||
|
b: mat4x4<f32>,
|
||||||
|
) -> mat4x4<f32> {
|
||||||
|
return mat4x4<f32>(
|
||||||
|
a.x + b.x,
|
||||||
|
a.y + b.y,
|
||||||
|
a.z + b.z,
|
||||||
|
a.w + b.w,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skin_model(
|
||||||
|
indexes: vec4<u32>,
|
||||||
|
weights: vec4<f32>,
|
||||||
|
) -> mat4x4<f32> {
|
||||||
|
var matrix = weights.x * joint_matrices.data[indexes.x];
|
||||||
|
matrix = add_matrix(matrix, weights.y * joint_matrices.data[indexes.y]);
|
||||||
|
matrix = add_matrix(matrix, weights.z * joint_matrices.data[indexes.z]);
|
||||||
|
return add_matrix(matrix, weights.w * joint_matrices.data[indexes.w]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inverse_transpose_3x3(in: mat3x3<f32>) -> mat3x3<f32> {
|
||||||
|
let x = cross(in.y, in.z);
|
||||||
|
let y = cross(in.z, in.x);
|
||||||
|
let z = cross(in.x, in.y);
|
||||||
|
let det = dot(in.z, z);
|
||||||
|
return mat3x3<f32>(
|
||||||
|
x / det,
|
||||||
|
y / det,
|
||||||
|
z / det
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skin_normals(
|
||||||
|
model: mat4x4<f32>,
|
||||||
|
normal: vec3<f32>,
|
||||||
|
) -> vec3<f32> {
|
||||||
|
return inverse_transpose_3x3(mat3x3<f32>(
|
||||||
|
model[0].xyz,
|
||||||
|
model[1].xyz,
|
||||||
|
model[2].xyz
|
||||||
|
)) * normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skin_tangents(
|
||||||
|
model: mat4x4<f32>,
|
||||||
|
tangent: vec4<f32>,
|
||||||
|
) -> vec4<f32> {
|
||||||
|
return vec4<f32>(
|
||||||
|
mat3x3<f32>(
|
||||||
|
model[0].xyz,
|
||||||
|
model[1].xyz,
|
||||||
|
model[2].xyz
|
||||||
|
) * tangent.xyz,
|
||||||
|
tangent.w
|
||||||
|
);
|
||||||
|
}
|
|
@ -3,6 +3,10 @@
|
||||||
|
|
||||||
struct Vertex {
|
struct Vertex {
|
||||||
[[location(0)]] position: vec3<f32>;
|
[[location(0)]] position: vec3<f32>;
|
||||||
|
#ifdef SKINNED
|
||||||
|
[[location(4)]] joint_indexes: vec4<u32>;
|
||||||
|
[[location(5)]] joint_weights: vec4<f32>;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
[[group(1), binding(0)]]
|
[[group(1), binding(0)]]
|
||||||
|
@ -12,10 +16,21 @@ struct VertexOutput {
|
||||||
[[builtin(position)]] clip_position: vec4<f32>;
|
[[builtin(position)]] clip_position: vec4<f32>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef SKINNED
|
||||||
|
[[group(2), binding(0)]]
|
||||||
|
var<uniform> joint_matrices: SkinnedMesh;
|
||||||
|
#import bevy_pbr::skinning
|
||||||
|
#endif
|
||||||
|
|
||||||
[[stage(vertex)]]
|
[[stage(vertex)]]
|
||||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||||
let world_position = mesh.model * vec4<f32>(vertex.position, 1.0);
|
#ifdef SKINNED
|
||||||
|
let model = skin_model(vertex.joint_indexes, vertex.joint_weights);
|
||||||
|
#else
|
||||||
|
let model = mesh.model;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
let world_position = model * vec4<f32>(vertex.position, 1.0);
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.clip_position = view.view_proj * world_position;
|
out.clip_position = view.view_proj * world_position;
|
||||||
|
|
||||||
|
|
|
@ -118,6 +118,12 @@ impl From<Vec<[u32; 4]>> for VertexAttributeValues {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Vec<[u16; 4]>> for VertexAttributeValues {
|
||||||
|
fn from(vec: Vec<[u16; 4]>) -> Self {
|
||||||
|
VertexAttributeValues::Uint16x4(vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Vec<[u8; 4]>> for VertexAttributeValues {
|
impl From<Vec<[u8; 4]>> for VertexAttributeValues {
|
||||||
fn from(vec: Vec<[u8; 4]>) -> Self {
|
fn from(vec: Vec<[u8; 4]>) -> Self {
|
||||||
VertexAttributeValues::Unorm8x4(vec)
|
VertexAttributeValues::Unorm8x4(vec)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
mod conversions;
|
mod conversions;
|
||||||
|
pub mod skinning;
|
||||||
|
pub use wgpu::PrimitiveTopology;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
primitives::Aabb,
|
primitives::Aabb,
|
||||||
|
@ -14,8 +16,8 @@ use bevy_utils::{EnumVariantMeta, Hashed};
|
||||||
use std::{collections::BTreeMap, hash::Hash};
|
use std::{collections::BTreeMap, hash::Hash};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use wgpu::{
|
use wgpu::{
|
||||||
util::BufferInitDescriptor, BufferUsages, IndexFormat, PrimitiveTopology, VertexAttribute,
|
util::BufferInitDescriptor, BufferUsages, IndexFormat, VertexAttribute, VertexFormat,
|
||||||
VertexFormat, VertexStepMode,
|
VertexStepMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0;
|
pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0;
|
||||||
|
@ -78,7 +80,7 @@ impl Mesh {
|
||||||
MeshVertexAttribute::new("Vertex_JointWeight", 5, VertexFormat::Float32x4);
|
MeshVertexAttribute::new("Vertex_JointWeight", 5, VertexFormat::Float32x4);
|
||||||
/// Per vertex joint transform matrix index. Use in conjunction with [`Mesh::insert_attribute`]
|
/// Per vertex joint transform matrix index. Use in conjunction with [`Mesh::insert_attribute`]
|
||||||
pub const ATTRIBUTE_JOINT_INDEX: MeshVertexAttribute =
|
pub const ATTRIBUTE_JOINT_INDEX: MeshVertexAttribute =
|
||||||
MeshVertexAttribute::new("Vertex_JointIndex", 6, VertexFormat::Uint32);
|
MeshVertexAttribute::new("Vertex_JointIndex", 6, VertexFormat::Uint16x4);
|
||||||
|
|
||||||
/// Construct a new mesh. You need to provide a [`PrimitiveTopology`] so that the
|
/// Construct a new mesh. You need to provide a [`PrimitiveTopology`] so that the
|
||||||
/// renderer knows how to treat the vertex data. Most of the time this will be
|
/// renderer knows how to treat the vertex data. Most of the time this will be
|
||||||
|
|
44
crates/bevy_render/src/mesh/mesh/skinning.rs
Normal file
44
crates/bevy_render/src/mesh/mesh/skinning.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use bevy_asset::Handle;
|
||||||
|
use bevy_ecs::{
|
||||||
|
component::Component,
|
||||||
|
entity::{Entity, EntityMap, MapEntities, MapEntitiesError},
|
||||||
|
prelude::ReflectComponent,
|
||||||
|
reflect::ReflectMapEntities,
|
||||||
|
};
|
||||||
|
use bevy_math::Mat4;
|
||||||
|
use bevy_reflect::{Reflect, TypeUuid};
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Default, Clone, Reflect)]
|
||||||
|
#[reflect(Component, MapEntities)]
|
||||||
|
pub struct SkinnedMesh {
|
||||||
|
pub inverse_bindposes: Handle<SkinnedMeshInverseBindposes>,
|
||||||
|
pub joints: Vec<Entity>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MapEntities for SkinnedMesh {
|
||||||
|
fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> {
|
||||||
|
for joint in &mut self.joints {
|
||||||
|
*joint = entity_map.get(*joint)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, TypeUuid)]
|
||||||
|
#[uuid = "b9f155a9-54ec-4026-988f-e0a03e99a76f"]
|
||||||
|
pub struct SkinnedMeshInverseBindposes(Box<[Mat4]>);
|
||||||
|
|
||||||
|
impl From<Vec<Mat4>> for SkinnedMeshInverseBindposes {
|
||||||
|
fn from(value: Vec<Mat4>) -> Self {
|
||||||
|
Self(value.into_boxed_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for SkinnedMeshInverseBindposes {
|
||||||
|
type Target = [Mat4];
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&*self.0
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,8 @@ pub struct MeshPlugin;
|
||||||
impl Plugin for MeshPlugin {
|
impl Plugin for MeshPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_asset::<Mesh>()
|
app.add_asset::<Mesh>()
|
||||||
|
.add_asset::<skinning::SkinnedMeshInverseBindposes>()
|
||||||
|
.register_type::<skinning::SkinnedMesh>()
|
||||||
.add_plugin(RenderAssetPlugin::<Mesh>::default());
|
.add_plugin(RenderAssetPlugin::<Mesh>::default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ git checkout v0.4.0
|
||||||
- [Cross-Platform Examples](#cross-platform-examples)
|
- [Cross-Platform Examples](#cross-platform-examples)
|
||||||
- [2D Rendering](#2d-rendering)
|
- [2D Rendering](#2d-rendering)
|
||||||
- [3D Rendering](#3d-rendering)
|
- [3D Rendering](#3d-rendering)
|
||||||
|
- [Animation](#animation)
|
||||||
- [Application](#application)
|
- [Application](#application)
|
||||||
- [Assets](#assets)
|
- [Assets](#assets)
|
||||||
- [Async Tasks](#async-tasks)
|
- [Async Tasks](#async-tasks)
|
||||||
|
@ -118,6 +119,13 @@ Example | File | Description
|
||||||
`update_gltf_scene` | [`3d/update_gltf_scene.rs`](./3d/update_gltf_scene.rs) | Update a scene from a gltf file, either by spawning the scene as a child of another entity, or by accessing the entities of the scene
|
`update_gltf_scene` | [`3d/update_gltf_scene.rs`](./3d/update_gltf_scene.rs) | Update a scene from a gltf file, either by spawning the scene as a child of another entity, or by accessing the entities of the scene
|
||||||
`wireframe` | [`3d/wireframe.rs`](./3d/wireframe.rs) | Showcases wireframe rendering
|
`wireframe` | [`3d/wireframe.rs`](./3d/wireframe.rs) | Showcases wireframe rendering
|
||||||
|
|
||||||
|
## Animation
|
||||||
|
|
||||||
|
Example | File | Description
|
||||||
|
--- | --- | ---
|
||||||
|
`custom_skinned_mesh` | [`animation/custom_skinned_mesh.rs`](./animation/custom_skinned_mesh.rs) | Skinned mesh example with mesh and joints data defined in code.
|
||||||
|
`gltf_skinned_mesh` | [`animation/gltf_skinned_mesh.rs`](./animation/gltf_skinned_mesh.rs) | Skinned mesh example with mesh and joints data loaded from a glTF file.
|
||||||
|
|
||||||
## Application
|
## Application
|
||||||
|
|
||||||
Example | File | Description
|
Example | File | Description
|
||||||
|
|
171
examples/animation/custom_skinned_mesh.rs
Normal file
171
examples/animation/custom_skinned_mesh.rs
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
use bevy::{
|
||||||
|
pbr::AmbientLight,
|
||||||
|
prelude::*,
|
||||||
|
render::mesh::{
|
||||||
|
skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
|
||||||
|
Indices, PrimitiveTopology,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
/// Skinned mesh example with mesh and joints data defined in code.
|
||||||
|
/// Example taken from <https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_019_SimpleSkin.md>
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.insert_resource(AmbientLight {
|
||||||
|
brightness: 1.0,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.add_startup_system(setup)
|
||||||
|
.add_system(joint_animation)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to mark a joint to be animated in the [`joint_animation`] system.
|
||||||
|
#[derive(Component)]
|
||||||
|
struct AnimatedJoint;
|
||||||
|
|
||||||
|
/// Construct a mesh and a skeleton with 2 joints for that mesh,
|
||||||
|
/// and mark the second joint to be animated.
|
||||||
|
/// It is similar to the scene defined in `models/SimpleSkin/SimpleSkin.gltf`
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
mut skinned_mesh_inverse_bindposes_assets: ResMut<Assets<SkinnedMeshInverseBindposes>>,
|
||||||
|
) {
|
||||||
|
// Create a camera
|
||||||
|
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||||
|
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create inverse bindpose matrices for a skeleton consists of 2 joints
|
||||||
|
let inverse_bindposes =
|
||||||
|
skinned_mesh_inverse_bindposes_assets.add(SkinnedMeshInverseBindposes::from(vec![
|
||||||
|
Mat4::from_translation(Vec3::new(-0.5, -1.0, 0.0)),
|
||||||
|
Mat4::from_translation(Vec3::new(-0.5, -1.0, 0.0)),
|
||||||
|
]));
|
||||||
|
|
||||||
|
// Create a mesh
|
||||||
|
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
|
||||||
|
// Set mesh vertex positions
|
||||||
|
mesh.insert_attribute(
|
||||||
|
Mesh::ATTRIBUTE_POSITION,
|
||||||
|
vec![
|
||||||
|
[0.0, 0.0, 0.0],
|
||||||
|
[1.0, 0.0, 0.0],
|
||||||
|
[0.0, 0.5, 0.0],
|
||||||
|
[1.0, 0.5, 0.0],
|
||||||
|
[0.0, 1.0, 0.0],
|
||||||
|
[1.0, 1.0, 0.0],
|
||||||
|
[0.0, 1.5, 0.0],
|
||||||
|
[1.0, 1.5, 0.0],
|
||||||
|
[0.0, 2.0, 0.0],
|
||||||
|
[1.0, 2.0, 0.0],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
// Set mesh vertex normals
|
||||||
|
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 10]);
|
||||||
|
// Set mesh vertex UVs. Although the mesh doesn't have any texture applied,
|
||||||
|
// UVs are still required by the render pipeline. So these UVs are zeroed out.
|
||||||
|
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0]; 10]);
|
||||||
|
// Set mesh vertex joint indices for mesh skinning.
|
||||||
|
// Each vertex gets 4 indices used to address the `JointTransforms` array in the vertex shader
|
||||||
|
// as well as `SkinnedMeshJoint` array in the `SkinnedMesh` component.
|
||||||
|
// This means that a maximum of 4 joints can affect a single vertex.
|
||||||
|
mesh.insert_attribute(
|
||||||
|
Mesh::ATTRIBUTE_JOINT_INDEX,
|
||||||
|
vec![
|
||||||
|
[0u16, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
// Set mesh vertex joint weights for mesh skinning.
|
||||||
|
// Each vertex gets 4 joint weights corresponding to the 4 joint indices assigned to it.
|
||||||
|
// The sum of these weights should equal to 1.
|
||||||
|
mesh.insert_attribute(
|
||||||
|
Mesh::ATTRIBUTE_JOINT_WEIGHT,
|
||||||
|
vec![
|
||||||
|
[1.00, 0.00, 0.0, 0.0],
|
||||||
|
[1.00, 0.00, 0.0, 0.0],
|
||||||
|
[0.75, 0.25, 0.0, 0.0],
|
||||||
|
[0.75, 0.25, 0.0, 0.0],
|
||||||
|
[0.50, 0.50, 0.0, 0.0],
|
||||||
|
[0.50, 0.50, 0.0, 0.0],
|
||||||
|
[0.25, 0.75, 0.0, 0.0],
|
||||||
|
[0.25, 0.75, 0.0, 0.0],
|
||||||
|
[0.00, 1.00, 0.0, 0.0],
|
||||||
|
[0.00, 1.00, 0.0, 0.0],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
// Tell bevy to construct triangles from a list of vertex indices,
|
||||||
|
// where each 3 vertex indices form an triangle.
|
||||||
|
mesh.set_indices(Some(Indices::U16(vec![
|
||||||
|
0, 1, 3, 0, 3, 2, 2, 3, 5, 2, 5, 4, 4, 5, 7, 4, 7, 6, 6, 7, 9, 6, 9, 8,
|
||||||
|
])));
|
||||||
|
|
||||||
|
let mesh = meshes.add(mesh);
|
||||||
|
for i in -5..5 {
|
||||||
|
// Create joint entities
|
||||||
|
let joint_0 = commands
|
||||||
|
.spawn_bundle((
|
||||||
|
Transform::from_xyz(i as f32 * 1.5, 0.0, 0.0),
|
||||||
|
GlobalTransform::identity(),
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
let joint_1 = commands
|
||||||
|
.spawn_bundle((
|
||||||
|
AnimatedJoint,
|
||||||
|
Transform::identity(),
|
||||||
|
GlobalTransform::identity(),
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
|
||||||
|
// Set joint_1 as a child of joint_0.
|
||||||
|
commands.entity(joint_0).push_children(&[joint_1]);
|
||||||
|
|
||||||
|
// Each joint in this vector corresponds to each inverse bindpose matrix in `SkinnedMeshInverseBindposes`.
|
||||||
|
let joint_entities = vec![joint_0, joint_1];
|
||||||
|
|
||||||
|
// Create skinned mesh renderer. Note that its transform doesn't affect the position of the mesh.
|
||||||
|
commands
|
||||||
|
.spawn_bundle(PbrBundle {
|
||||||
|
mesh: mesh.clone(),
|
||||||
|
material: materials.add(
|
||||||
|
Color::rgb(
|
||||||
|
rand::thread_rng().gen_range(0.0..1.0),
|
||||||
|
rand::thread_rng().gen_range(0.0..1.0),
|
||||||
|
rand::thread_rng().gen_range(0.0..1.0),
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.insert(SkinnedMesh {
|
||||||
|
inverse_bindposes: inverse_bindposes.clone(),
|
||||||
|
joints: joint_entities,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Animate the joint marked with [`AnimatedJoint`] component.
|
||||||
|
fn joint_animation(time: Res<Time>, mut query: Query<&mut Transform, With<AnimatedJoint>>) {
|
||||||
|
for mut transform in query.iter_mut() {
|
||||||
|
transform.rotation = Quat::from_axis_angle(
|
||||||
|
Vec3::Z,
|
||||||
|
0.5 * PI * time.time_since_startup().as_secs_f32().sin(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
70
examples/animation/gltf_skinned_mesh.rs
Normal file
70
examples/animation/gltf_skinned_mesh.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
use bevy::{pbr::AmbientLight, prelude::*, render::mesh::skinning::SkinnedMesh};
|
||||||
|
|
||||||
|
/// Skinned mesh example with mesh and joints data loaded from a glTF file.
|
||||||
|
/// Example taken from <https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_019_SimpleSkin.md>
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.insert_resource(AmbientLight {
|
||||||
|
brightness: 1.0,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.add_startup_system(setup)
|
||||||
|
.add_system(joint_animation)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
// Create a camera
|
||||||
|
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||||
|
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Spawn the first scene in `models/SimpleSkin/SimpleSkin.gltf`
|
||||||
|
commands.spawn_scene(asset_server.load::<Scene, _>("models/SimpleSkin/SimpleSkin.gltf#Scene0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The scene hierachy currently looks somewhat like this:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// <Parent entity>
|
||||||
|
/// + Mesh node (without `PbrBundle` or `SkinnedMesh` component)
|
||||||
|
/// + Skinned mesh entity (with `PbrBundle` and `SkinnedMesh` component, created by glTF loader)
|
||||||
|
/// + First joint
|
||||||
|
/// + Second joint
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// In this example, we want to get and animate the second joint.
|
||||||
|
/// It is similar to the animation defined in `models/SimpleSkin/SimpleSkin.gltf`.
|
||||||
|
fn joint_animation(
|
||||||
|
time: Res<Time>,
|
||||||
|
parent_query: Query<&Parent, With<SkinnedMesh>>,
|
||||||
|
children_query: Query<&Children>,
|
||||||
|
mut transform_query: Query<&mut Transform>,
|
||||||
|
) {
|
||||||
|
// Iter skinned mesh entity
|
||||||
|
for skinned_mesh_parent in parent_query.iter() {
|
||||||
|
// Mesh node is the parent of the skinned mesh entity.
|
||||||
|
let mesh_node_entity = skinned_mesh_parent.0;
|
||||||
|
// Get `Children` in the mesh node.
|
||||||
|
let mesh_node_children = children_query.get(mesh_node_entity).unwrap();
|
||||||
|
|
||||||
|
// First joint is the second child of the mesh node.
|
||||||
|
let first_joint_entity = mesh_node_children[1];
|
||||||
|
// Get `Children` in the first joint.
|
||||||
|
let first_joint_children = children_query.get(first_joint_entity).unwrap();
|
||||||
|
|
||||||
|
// Second joint is the first child of the first joint.
|
||||||
|
let second_joint_entity = first_joint_children[0];
|
||||||
|
// Get `Transform` in the second joint.
|
||||||
|
let mut second_joint_transform = transform_query.get_mut(second_joint_entity).unwrap();
|
||||||
|
|
||||||
|
second_joint_transform.rotation = Quat::from_axis_angle(
|
||||||
|
Vec3::Z,
|
||||||
|
0.5 * PI * time.time_since_startup().as_secs_f32().sin(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue