Add support for jointed (skinned) models.

This commit is contained in:
Robin KAY 2022-10-12 18:31:02 +01:00
parent 5a2dc4a728
commit 0f4098714b
7 changed files with 139 additions and 12 deletions

View File

@ -33,3 +33,10 @@ bevy_ui = ["bevy/bevy_ui", "bevy/bevy_sprite", "bevy/bevy_text"]
[[example]]
name = "shapes"
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

Binary file not shown.

87
examples/animated_fox.rs Normal file
View 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;
}
}
}

View File

@ -5,6 +5,12 @@
@group(1) @binding(0)
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 {
var origin = model[3];
var proj_zw = mat4x2<f32>(

View File

@ -3,6 +3,10 @@
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
#ifdef SKINNED
@location(2) joint_indexes: vec4<u32>,
@location(3) joint_weights: vec4<f32>,
#endif
};
struct OutlineViewUniform {
@ -43,8 +47,13 @@ fn mat4to3(m: mat4x4<f32>) -> mat3x3<f32> {
@vertex
fn vertex(vertex: VertexInput) -> @builtin(position) vec4<f32> {
var clip_pos = view.view_proj * (mesh.model * vec4<f32>(vertex.position, 1.0));
var clip_norm = mat4to3(view.view_proj) * (mat4to3(mesh.model) * vertex.normal);
#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 clip_norm = mat4to3(view.view_proj) * (mat4to3(model) * vertex.normal);
var ndc_pos = clip_pos.xy / clip_pos.w;
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);

View File

@ -112,11 +112,25 @@ impl SpecializedMeshPipeline for OutlinePipeline {
mesh_layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut targets = vec![];
let mut bind_layouts = vec![
self.mesh_pipeline.view_layout.clone(),
self.mesh_pipeline.mesh_layout.clone(),
];
let mut bind_layouts = vec![self.mesh_pipeline.view_layout.clone()];
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;
match pass_type {
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)?];
Ok(RenderPipelineDescriptor {
vertex: VertexState {

View File

@ -2,11 +2,20 @@
struct VertexInput {
@location(0) position: vec3<f32>,
#ifdef SKINNED
@location(2) joint_indexes: vec4<u32>,
@location(3) joint_weights: vec4<f32>,
#endif
};
@vertex
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;
return vec4<f32>(ndc_pos, model_origin_z(mesh.model, view.view_proj), 1.0);
}