Add support for jointed (skinned) models.
This commit is contained in:
parent
5a2dc4a728
commit
0f4098714b
@ -33,3 +33,10 @@ bevy_ui = ["bevy/bevy_ui", "bevy/bevy_sprite", "bevy/bevy_text"]
|
|||||||
[[example]]
|
[[example]]
|
||||||
name = "shapes"
|
name = "shapes"
|
||||||
path = "examples/shapes.rs"
|
path = "examples/shapes.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "animated_fox"
|
||||||
|
path = "examples/animated_fox.rs"
|
||||||
|
required-features = [
|
||||||
|
"bevy/animation", "bevy/bevy_gltf", "bevy/png", "bevy/bevy_scene"
|
||||||
|
]
|
||||||
|
BIN
assets/Fox.glb
Normal file
BIN
assets/Fox.glb
Normal file
Binary file not shown.
87
examples/animated_fox.rs
Normal file
87
examples/animated_fox.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
use bevy::{prelude::*, window::close_on_esc};
|
||||||
|
use bevy_mod_outline::{
|
||||||
|
AutoGenerateOutlineNormalsPlugin, Outline, OutlineBundle, OutlinePlugin, OutlineStencil,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_plugin(OutlinePlugin)
|
||||||
|
.add_plugin(AutoGenerateOutlineNormalsPlugin)
|
||||||
|
.insert_resource(AmbientLight {
|
||||||
|
color: Color::WHITE,
|
||||||
|
brightness: 1.0,
|
||||||
|
})
|
||||||
|
.add_startup_system(setup)
|
||||||
|
.add_system(setup_scene_once_loaded)
|
||||||
|
.add_system(close_on_esc)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
// Insert a resource with the current animation
|
||||||
|
commands.insert_resource::<Handle<AnimationClip>>(asset_server.load("Fox.glb#Animation0"));
|
||||||
|
|
||||||
|
// Camera
|
||||||
|
commands.spawn_bundle(Camera3dBundle {
|
||||||
|
transform: Transform::from_xyz(100.0, 100.0, 150.0)
|
||||||
|
.looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Plane
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Plane { size: 500000.0 })),
|
||||||
|
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Light
|
||||||
|
commands.spawn_bundle(DirectionalLightBundle {
|
||||||
|
transform: Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
|
||||||
|
directional_light: DirectionalLight {
|
||||||
|
shadows_enabled: true,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fox
|
||||||
|
commands.spawn_bundle(SceneBundle {
|
||||||
|
scene: asset_server.load("Fox.glb#Scene0"),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once the scene is loaded, start the animation and add an outline
|
||||||
|
fn setup_scene_once_loaded(
|
||||||
|
mut commands: Commands,
|
||||||
|
animation: Res<Handle<AnimationClip>>,
|
||||||
|
mut player: Query<&mut AnimationPlayer>,
|
||||||
|
entities: Query<Entity, With<Handle<Mesh>>>,
|
||||||
|
mut done: Local<bool>,
|
||||||
|
) {
|
||||||
|
if !*done {
|
||||||
|
if let Ok(mut player) = player.get_single_mut() {
|
||||||
|
player.play(animation.clone_weak()).repeat();
|
||||||
|
for entity in entities.iter() {
|
||||||
|
commands.entity(entity).insert_bundle(OutlineBundle {
|
||||||
|
outline: Outline {
|
||||||
|
visible: true,
|
||||||
|
width: 3.0,
|
||||||
|
colour: Color::RED,
|
||||||
|
},
|
||||||
|
stencil: OutlineStencil,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,12 @@
|
|||||||
@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
|
||||||
|
|
||||||
fn model_origin_z(model: mat4x4<f32>, view_proj: mat4x4<f32>) -> f32 {
|
fn model_origin_z(model: mat4x4<f32>, view_proj: mat4x4<f32>) -> f32 {
|
||||||
var origin = model[3];
|
var origin = model[3];
|
||||||
var proj_zw = mat4x2<f32>(
|
var proj_zw = mat4x2<f32>(
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
@location(0) position: vec3<f32>,
|
@location(0) position: vec3<f32>,
|
||||||
@location(1) normal: vec3<f32>,
|
@location(1) normal: vec3<f32>,
|
||||||
|
#ifdef SKINNED
|
||||||
|
@location(2) joint_indexes: vec4<u32>,
|
||||||
|
@location(3) joint_weights: vec4<f32>,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OutlineViewUniform {
|
struct OutlineViewUniform {
|
||||||
@ -43,8 +47,13 @@ fn mat4to3(m: mat4x4<f32>) -> mat3x3<f32> {
|
|||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vertex(vertex: VertexInput) -> @builtin(position) vec4<f32> {
|
fn vertex(vertex: VertexInput) -> @builtin(position) vec4<f32> {
|
||||||
var clip_pos = view.view_proj * (mesh.model * vec4<f32>(vertex.position, 1.0));
|
#ifdef SKINNED
|
||||||
var clip_norm = mat4to3(view.view_proj) * (mat4to3(mesh.model) * vertex.normal);
|
let model = skin_model(vertex.joint_indexes, vertex.joint_weights);
|
||||||
|
#else
|
||||||
|
let model = mesh.model;
|
||||||
|
#endif
|
||||||
|
var clip_pos = view.view_proj * (model * vec4<f32>(vertex.position, 1.0));
|
||||||
|
var clip_norm = mat4to3(view.view_proj) * (mat4to3(model) * vertex.normal);
|
||||||
var ndc_pos = clip_pos.xy / clip_pos.w;
|
var ndc_pos = clip_pos.xy / clip_pos.w;
|
||||||
var ndc_delta = vstage.width * normalize(clip_norm.xy) * view_uniform.scale;
|
var ndc_delta = vstage.width * normalize(clip_norm.xy) * view_uniform.scale;
|
||||||
return vec4<f32>(ndc_pos + ndc_delta, model_origin_z(mesh.model, view.view_proj), 1.0);
|
return vec4<f32>(ndc_pos + ndc_delta, model_origin_z(mesh.model, view.view_proj), 1.0);
|
||||||
|
@ -112,11 +112,25 @@ impl SpecializedMeshPipeline for OutlinePipeline {
|
|||||||
mesh_layout: &MeshVertexBufferLayout,
|
mesh_layout: &MeshVertexBufferLayout,
|
||||||
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
||||||
let mut targets = vec![];
|
let mut targets = vec![];
|
||||||
let mut bind_layouts = vec![
|
let mut bind_layouts = vec![self.mesh_pipeline.view_layout.clone()];
|
||||||
self.mesh_pipeline.view_layout.clone(),
|
|
||||||
self.mesh_pipeline.mesh_layout.clone(),
|
|
||||||
];
|
|
||||||
let mut buffer_attrs = vec![Mesh::ATTRIBUTE_POSITION.at_shader_location(0)];
|
let mut buffer_attrs = vec![Mesh::ATTRIBUTE_POSITION.at_shader_location(0)];
|
||||||
|
let mut shader_defs = if cfg!(feature = "align16") {
|
||||||
|
vec!["ALIGN_16".to_string()]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
bind_layouts.push(
|
||||||
|
if mesh_layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX)
|
||||||
|
&& mesh_layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT)
|
||||||
|
{
|
||||||
|
shader_defs.push("SKINNED".to_string());
|
||||||
|
buffer_attrs.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(2));
|
||||||
|
buffer_attrs.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(3));
|
||||||
|
self.mesh_pipeline.skinned_mesh_layout.clone()
|
||||||
|
} else {
|
||||||
|
self.mesh_pipeline.mesh_layout.clone()
|
||||||
|
},
|
||||||
|
);
|
||||||
let shader;
|
let shader;
|
||||||
match pass_type {
|
match pass_type {
|
||||||
PassType::Stencil => {
|
PassType::Stencil => {
|
||||||
@ -146,11 +160,6 @@ impl SpecializedMeshPipeline for OutlinePipeline {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let shader_defs = if cfg!(feature = "align16") {
|
|
||||||
vec!["ALIGN_16".to_string()]
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
let buffers = vec![mesh_layout.get_layout(&buffer_attrs)?];
|
let buffers = vec![mesh_layout.get_layout(&buffer_attrs)?];
|
||||||
Ok(RenderPipelineDescriptor {
|
Ok(RenderPipelineDescriptor {
|
||||||
vertex: VertexState {
|
vertex: VertexState {
|
||||||
|
@ -2,11 +2,20 @@
|
|||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
@location(0) position: vec3<f32>,
|
@location(0) position: vec3<f32>,
|
||||||
|
#ifdef SKINNED
|
||||||
|
@location(2) joint_indexes: vec4<u32>,
|
||||||
|
@location(3) joint_weights: vec4<f32>,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vertex(vertex: VertexInput) -> @builtin(position) vec4<f32> {
|
fn vertex(vertex: VertexInput) -> @builtin(position) vec4<f32> {
|
||||||
var clip_pos = view.view_proj * (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
|
||||||
|
var clip_pos = view.view_proj * (model * vec4<f32>(vertex.position, 1.0));
|
||||||
var ndc_pos = clip_pos.xy / clip_pos.w;
|
var ndc_pos = clip_pos.xy / clip_pos.w;
|
||||||
return vec4<f32>(ndc_pos, model_origin_z(mesh.model, view.view_proj), 1.0);
|
return vec4<f32>(ndc_pos, model_origin_z(mesh.model, view.view_proj), 1.0);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user