From d9fb61d474518eba06d0c3dce5cb81db9f7ed66a Mon Sep 17 00:00:00 2001 From: Zhixing Zhang Date: Thu, 4 Mar 2021 01:23:24 +0000 Subject: [PATCH] Wireframe Rendering Pipeline (#562) This PR implements wireframe rendering. Usage: This is now ready as soon as #1401 gets merged. Usage: ```rust app .insert_resource(WgpuOptions { name: Some("3d_scene"), features: WgpuFeatures::NON_FILL_POLYGON_MODE, ..Default::default() }) // To enable the NON_FILL_POLYGON_MODE feature .add_plugin(WireframePlugin) .run(); ``` Now we just need to add the Wireframe component on an entity, and it'll draw. its wireframe. We can also enable wireframe drawing globally by setting the global property in the `WireframeConfig` resource to `true`. Co-authored-by: Zhixing Zhang --- Cargo.toml | 4 + crates/bevy_render/src/lib.rs | 1 + crates/bevy_render/src/wireframe/mod.rs | 122 ++++++++++++++++++ crates/bevy_render/src/wireframe/pipeline.rs | 30 +++++ .../bevy_render/src/wireframe/wireframe.frag | 8 ++ .../bevy_render/src/wireframe/wireframe.vert | 16 +++ examples/3d/wireframe.rs | 59 +++++++++ 7 files changed, 240 insertions(+) create mode 100644 crates/bevy_render/src/wireframe/mod.rs create mode 100644 crates/bevy_render/src/wireframe/pipeline.rs create mode 100644 crates/bevy_render/src/wireframe/wireframe.frag create mode 100644 crates/bevy_render/src/wireframe/wireframe.vert create mode 100644 examples/3d/wireframe.rs 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() + }); +}