diff --git a/Cargo.toml b/Cargo.toml index 707aca6..9ef53f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" +] diff --git a/assets/Fox.glb b/assets/Fox.glb new file mode 100644 index 0000000..2bb946e Binary files /dev/null and b/assets/Fox.glb differ diff --git a/examples/animated_fox.rs b/examples/animated_fox.rs new file mode 100644 index 0000000..3f78b22 --- /dev/null +++ b/examples/animated_fox.rs @@ -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, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // Insert a resource with the current animation + commands.insert_resource::>(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>, + mut player: Query<&mut AnimationPlayer>, + entities: Query>>, + mut done: Local, +) { + 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; + } + } +} diff --git a/src/common.wgsl b/src/common.wgsl index d7fa949..48a5408 100644 --- a/src/common.wgsl +++ b/src/common.wgsl @@ -5,6 +5,12 @@ @group(1) @binding(0) var mesh: Mesh; +#ifdef SKINNED +@group(1) @binding(1) +var joint_matrices: SkinnedMesh; +#import bevy_pbr::skinning +#endif + fn model_origin_z(model: mat4x4, view_proj: mat4x4) -> f32 { var origin = model[3]; var proj_zw = mat4x2( diff --git a/src/outline.wgsl b/src/outline.wgsl index 9f3d54b..d475762 100644 --- a/src/outline.wgsl +++ b/src/outline.wgsl @@ -3,6 +3,10 @@ struct VertexInput { @location(0) position: vec3, @location(1) normal: vec3, +#ifdef SKINNED + @location(2) joint_indexes: vec4, + @location(3) joint_weights: vec4, +#endif }; struct OutlineViewUniform { @@ -43,8 +47,13 @@ fn mat4to3(m: mat4x4) -> mat3x3 { @vertex fn vertex(vertex: VertexInput) -> @builtin(position) vec4 { - var clip_pos = view.view_proj * (mesh.model * vec4(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(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(ndc_pos + ndc_delta, model_origin_z(mesh.model, view.view_proj), 1.0); diff --git a/src/pipeline.rs b/src/pipeline.rs index 2076f46..3aec117 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -112,11 +112,25 @@ impl SpecializedMeshPipeline for OutlinePipeline { mesh_layout: &MeshVertexBufferLayout, ) -> Result { 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 { diff --git a/src/stencil.wgsl b/src/stencil.wgsl index 501efb6..a98c21b 100644 --- a/src/stencil.wgsl +++ b/src/stencil.wgsl @@ -2,11 +2,20 @@ struct VertexInput { @location(0) position: vec3, +#ifdef SKINNED + @location(2) joint_indexes: vec4, + @location(3) joint_weights: vec4, +#endif }; @vertex fn vertex(vertex: VertexInput) -> @builtin(position) vec4 { - var clip_pos = view.view_proj * (mesh.model * vec4(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(vertex.position, 1.0)); var ndc_pos = clip_pos.xy / clip_pos.w; return vec4(ndc_pos, model_origin_z(mesh.model, view.view_proj), 1.0); }