use crate::{DrawMesh, MeshPipelineKey, SetMeshBindGroup, SetMeshViewBindGroup}; use crate::{MeshPipeline, MeshTransforms}; use bevy_app::Plugin; use bevy_asset::{load_internal_asset, Handle}; use bevy_core_pipeline::core_3d::Opaque3d; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin}; use bevy_render::Render; use bevy_render::{ extract_resource::{ExtractResource, ExtractResourcePlugin}, mesh::{Mesh, MeshVertexBufferLayout}, render_asset::RenderAssets, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_resource::{ PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, view::{ExtractedView, Msaa, VisibleEntities}, RenderApp, RenderSet, }; use bevy_utils::tracing::error; pub const WIREFRAME_SHADER_HANDLE: Handle = Handle::weak_from_u128(192598014480025766); #[derive(Debug, Default)] pub struct WireframePlugin; impl Plugin for WireframePlugin { fn build(&self, app: &mut bevy_app::App) { load_internal_asset!( app, WIREFRAME_SHADER_HANDLE, "render/wireframe.wgsl", Shader::from_wgsl ); app.register_type::() .register_type::() .init_resource::() .add_plugins(( ExtractResourcePlugin::::default(), ExtractComponentPlugin::::default(), )); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .add_render_command::() .init_resource::>() .add_systems(Render, queue_wireframes.in_set(RenderSet::QueueMeshes)); } } fn finish(&self, app: &mut bevy_app::App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app.init_resource::(); } } } /// Controls whether an entity should rendered in wireframe-mode if the [`WireframePlugin`] is enabled #[derive(Component, Debug, Clone, Default, ExtractComponent, Reflect)] #[reflect(Component, Default)] pub struct Wireframe; #[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)] #[reflect(Resource)] pub struct WireframeConfig { /// Whether to show wireframes for all meshes. If `false`, only meshes with a [`Wireframe`] component will be rendered. pub global: bool, } #[derive(Resource, Clone)] pub struct WireframePipeline { mesh_pipeline: MeshPipeline, shader: Handle, } impl FromWorld for WireframePipeline { fn from_world(render_world: &mut World) -> Self { WireframePipeline { mesh_pipeline: render_world.resource::().clone(), shader: WIREFRAME_SHADER_HANDLE, } } } impl SpecializedMeshPipeline for WireframePipeline { type Key = MeshPipelineKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayout, ) -> Result { let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; descriptor.vertex.shader = self.shader.clone_weak(); descriptor .vertex .shader_defs .push("MESH_BINDGROUP_1".into()); descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak(); descriptor.primitive.polygon_mode = PolygonMode::Line; descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0; Ok(descriptor) } } #[allow(clippy::too_many_arguments)] fn queue_wireframes( opaque_3d_draw_functions: Res>, render_meshes: Res>, wireframe_config: Res, wireframe_pipeline: Res, mut pipelines: ResMut>, pipeline_cache: Res, msaa: Res, mut material_meshes: ParamSet<( Query<(Entity, &Handle, &MeshTransforms)>, Query<(Entity, &Handle, &MeshTransforms), With>, )>, mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase)>, ) { let draw_custom = opaque_3d_draw_functions.read().id::(); let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); for (view, visible_entities, mut opaque_phase) in &mut views { let rangefinder = view.rangefinder3d(); let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr); let add_render_phase = |phase_item: (Entity, &Handle, &MeshTransforms)| { let (entity, mesh_handle, mesh_transforms) = phase_item; let Some(mesh) = render_meshes.get(mesh_handle) else { return; }; let mut key = view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); if mesh.morph_targets.is_some() { key |= MeshPipelineKey::MORPH_TARGETS; } let pipeline_id = pipelines.specialize(&pipeline_cache, &wireframe_pipeline, key, &mesh.layout); let pipeline_id = match pipeline_id { Ok(id) => id, Err(err) => { error!("{}", err); return; } }; opaque_phase.add(Opaque3d { entity, pipeline: pipeline_id, draw_function: draw_custom, distance: rangefinder.distance_translation(&mesh_transforms.transform.translation), batch_size: 1, }); }; if wireframe_config.global { let query = material_meshes.p0(); visible_entities .entities .iter() .filter_map(|visible_entity| query.get(*visible_entity).ok()) .for_each(add_render_phase); } else { let query = material_meshes.p1(); visible_entities .entities .iter() .filter_map(|visible_entity| query.get(*visible_entity).ok()) .for_each(add_render_phase); } } } type DrawWireframes = ( SetItemPipeline, SetMeshViewBindGroup<0>, SetMeshBindGroup<1>, DrawMesh, );