diff --git a/Cargo.toml b/Cargo.toml index cea638cdf43eb..c1e1f34230dfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -147,6 +147,10 @@ path = "examples/3d/texture.rs" name = "update_gltf_scene" path = "examples/3d/update_gltf_scene.rs" +[[example]] +name = "wireframe" +path = "examples/3d/wireframe.rs" + [[example]] name = "z_sort_debug" path = "examples/3d/z_sort_debug.rs" diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 8097cfefd6860..62e97060735f0 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -10,6 +10,7 @@ pub mod render_graph; pub mod renderer; pub mod shader; pub mod texture; +pub mod wireframe; use bevy_ecs::{IntoExclusiveSystem, IntoSystem, SystemStage}; use bevy_reflect::RegisterTypeBuilder; diff --git a/crates/bevy_render/src/wireframe/mod.rs b/crates/bevy_render/src/wireframe/mod.rs new file mode 100644 index 0000000000000..2194e56ed9544 --- /dev/null +++ b/crates/bevy_render/src/wireframe/mod.rs @@ -0,0 +1,122 @@ +use crate::{ + draw::DrawContext, + mesh::Indices, + pipeline::{PipelineDescriptor, PipelineSpecialization, RenderPipeline}, + prelude::*, + shader::Shader, +}; +use bevy_app::prelude::*; +use bevy_asset::{Assets, Handle, HandleUntyped}; +use bevy_ecs::{IntoSystem, Mut, Query, QuerySet, Res, With}; +use bevy_reflect::{Reflect, ReflectComponent, TypeUuid}; +use bevy_utils::HashSet; + +mod pipeline; + +pub const WIREFRAME_PIPELINE_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(PipelineDescriptor::TYPE_UUID, 0x137c75ab7e9ad7f5); + +#[derive(Debug, Default)] +pub struct WireframePlugin; + +impl Plugin for WireframePlugin { + fn build(&self, app: &mut AppBuilder) { + app.init_resource::() + .add_system_to_stage(crate::RenderStage::Draw, draw_wireframes_system.system()); + let resources = app.resources(); + let mut shaders = resources.get_mut::>().unwrap(); + let mut pipelines = resources.get_mut::>().unwrap(); + pipelines.set( + WIREFRAME_PIPELINE_HANDLE, + pipeline::build_wireframe_pipeline(&mut shaders), + ); + } +} + +#[derive(Debug, Clone, Reflect, Default)] +#[reflect(Component)] +pub struct Wireframe; + +#[derive(Debug, Clone)] +pub struct WireframeConfig { + pub global: bool, +} + +impl Default for WireframeConfig { + fn default() -> Self { + WireframeConfig { global: false } + } +} + +pub fn draw_wireframes_system( + mut draw_context: DrawContext, + msaa: Res, + meshes: Res>, + wireframe_config: Res, + mut query: QuerySet<( + Query<(&mut Draw, &mut RenderPipelines, &Handle, &Visible)>, + Query<(&mut Draw, &mut RenderPipelines, &Handle, &Visible), With>, + )>, +) { + let iterator = |(mut draw, mut render_pipelines, mesh_handle, visible): ( + Mut, + Mut, + &Handle, + &Visible, + )| { + if !visible.is_visible { + return; + } + + // don't render if the mesh isn't loaded yet + let mesh = if let Some(mesh) = meshes.get(mesh_handle) { + mesh + } else { + return; + }; + + let mut render_pipeline = RenderPipeline::specialized( + WIREFRAME_PIPELINE_HANDLE.typed(), + PipelineSpecialization { + sample_count: msaa.samples, + strip_index_format: None, + shader_specialization: Default::default(), + primitive_topology: mesh.primitive_topology(), + dynamic_bindings: render_pipelines + .bindings + .iter_dynamic_bindings() + .map(|name| name.to_string()) + .collect::>(), + vertex_buffer_layout: mesh.get_vertex_buffer_layout(), + }, + ); + render_pipeline.dynamic_bindings_generation = + render_pipelines.bindings.dynamic_bindings_generation(); + + draw_context + .set_pipeline( + &mut draw, + &render_pipeline.pipeline, + &render_pipeline.specialization, + ) + .unwrap(); + draw_context + .set_bind_groups_from_bindings(&mut draw, &mut [&mut render_pipelines.bindings]) + .unwrap(); + draw_context + .set_vertex_buffers_from_bindings(&mut draw, &[&render_pipelines.bindings]) + .unwrap(); + + match mesh.indices() { + Some(Indices::U32(indices)) => draw.draw_indexed(0..indices.len() as u32, 0, 0..1), + Some(Indices::U16(indices)) => draw.draw_indexed(0..indices.len() as u32, 0, 0..1), + None => draw.draw(0..mesh.count_vertices() as u32, 0..1), + }; + }; + + if wireframe_config.global { + query.q0_mut().iter_mut().for_each(iterator); + } else { + query.q1_mut().iter_mut().for_each(iterator); + } +} diff --git a/crates/bevy_render/src/wireframe/pipeline.rs b/crates/bevy_render/src/wireframe/pipeline.rs new file mode 100644 index 0000000000000..70276ceb979d6 --- /dev/null +++ b/crates/bevy_render/src/wireframe/pipeline.rs @@ -0,0 +1,30 @@ +use crate::{ + pipeline::{ + CullMode, FrontFace, PipelineDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology, + }, + shader::{Shader, ShaderStage, ShaderStages}, +}; +use bevy_asset::Assets; + +pub(crate) fn build_wireframe_pipeline(shaders: &mut Assets) -> PipelineDescriptor { + PipelineDescriptor { + name: Some("wireframe".into()), + primitive: PrimitiveState { + topology: PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: FrontFace::Ccw, + cull_mode: CullMode::None, + polygon_mode: PolygonMode::Line, + }, + ..PipelineDescriptor::default_config(ShaderStages { + vertex: shaders.add(Shader::from_glsl( + ShaderStage::Vertex, + include_str!("wireframe.vert"), + )), + fragment: Some(shaders.add(Shader::from_glsl( + ShaderStage::Fragment, + include_str!("wireframe.frag"), + ))), + }) + } +} diff --git a/crates/bevy_render/src/wireframe/wireframe.frag b/crates/bevy_render/src/wireframe/wireframe.frag new file mode 100644 index 0000000000000..6d3971d8518ed --- /dev/null +++ b/crates/bevy_render/src/wireframe/wireframe.frag @@ -0,0 +1,8 @@ +#version 450 + +layout(location = 0) out vec4 o_Target; + + +void main() { + o_Target = vec4(1.0, 1.0, 1.0, 1.0); +} diff --git a/crates/bevy_render/src/wireframe/wireframe.vert b/crates/bevy_render/src/wireframe/wireframe.vert new file mode 100644 index 0000000000000..47828402121a0 --- /dev/null +++ b/crates/bevy_render/src/wireframe/wireframe.vert @@ -0,0 +1,16 @@ +#version 450 + +layout(location = 0) in vec3 Vertex_Position; + +layout(set = 0, binding = 0) uniform Camera { + mat4 ViewProj; +}; + +layout(set = 1, binding = 0) uniform Transform { + mat4 Model; +}; + +void main() { + vec3 v_Position = (Model * vec4(Vertex_Position, 1.0)).xyz; + gl_Position = ViewProj * vec4(v_Position, 1.0); +} diff --git a/examples/3d/wireframe.rs b/examples/3d/wireframe.rs new file mode 100644 index 0000000000000..51e9e8c292014 --- /dev/null +++ b/examples/3d/wireframe.rs @@ -0,0 +1,59 @@ +use bevy::prelude::*; +use bevy_internal::{ + render::wireframe::{Wireframe, WireframeConfig, WireframePlugin}, + wgpu::{WgpuFeature, WgpuFeatures, WgpuOptions}, +}; + +fn main() { + App::build() + .insert_resource(Msaa { samples: 4 }) + .insert_resource(WgpuOptions { + features: WgpuFeatures { + // The Wireframe requires NonFillPolygonMode feature + features: vec![WgpuFeature::NonFillPolygonMode], + }, + ..Default::default() + }) + .add_plugins(DefaultPlugins) + .add_plugin(WireframePlugin) + .add_startup_system(setup.system()) + .run(); +} + +/// set up a simple 3D scene +fn setup( + commands: &mut Commands, + mut wireframe_config: ResMut, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // To draw the wireframe on all entities, set this to 'true' + wireframe_config.global = false; + // add entities to the world + commands + // plane + .spawn(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })), + material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), + ..Default::default() + }) + // cube + .spawn(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + ..Default::default() + }) + .with(Wireframe) // This enables wireframe drawing on this entity + // light + .spawn(LightBundle { + transform: Transform::from_xyz(4.0, 8.0, 4.0), + ..Default::default() + }) + // camera + .spawn(PerspectiveCameraBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0) + .looking_at(Vec3::default(), Vec3::unit_y()), + ..Default::default() + }); +}