Skip to content

Commit 84c5d44

Browse files
committed
wireframe material
1 parent c56bbcb commit 84c5d44

File tree

3 files changed

+265
-164
lines changed

3 files changed

+265
-164
lines changed
Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,16 @@
1-
#import bevy_pbr::mesh_types
2-
#import bevy_pbr::mesh_view_bindings
1+
struct WireframeMaterial {
2+
color: vec4<f32>,
3+
};
34

45
@group(1) @binding(0)
5-
var<uniform> mesh: Mesh;
6-
7-
#ifdef SKINNED
8-
@group(1) @binding(1)
9-
var<uniform> joint_matrices: SkinnedMesh;
10-
#import bevy_pbr::skinning
11-
#endif
12-
13-
// NOTE: Bindings must come before functions that use them!
14-
#import bevy_pbr::mesh_functions
15-
16-
struct Vertex {
17-
@location(0) position: vec3<f32>,
18-
#ifdef SKINNED
19-
@location(4) joint_indexes: vec4<u32>,
20-
@location(5) joint_weights: vec4<f32>,
21-
#endif
22-
};
6+
var<uniform> material: WireframeMaterial;
237

24-
struct VertexOutput {
25-
@builtin(position) clip_position: vec4<f32>,
8+
// This shouldn't be needed, but for some reason when there's no input wgpu gives an error
9+
struct FragmentInput {
10+
#import bevy_pbr::mesh_vertex_output
2611
};
2712

28-
@vertex
29-
fn vertex(vertex: Vertex) -> VertexOutput {
30-
#ifdef SKINNED
31-
let model = skin_model(vertex.joint_indexes, vertex.joint_weights);
32-
#else
33-
let model = mesh.model;
34-
#endif
35-
36-
var out: VertexOutput;
37-
out.clip_position = mesh_position_local_to_clip(model, vec4<f32>(vertex.position, 1.0));
38-
return out;
39-
}
40-
4113
@fragment
42-
fn fragment() -> @location(0) vec4<f32> {
43-
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
14+
fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
15+
return material.color;
4416
}

crates/bevy_pbr/src/wireframe.rs

Lines changed: 150 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,23 @@
1-
use crate::MeshPipeline;
2-
use crate::{DrawMesh, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup};
1+
use crate::{Material, MaterialPipeline, MaterialPipelineKey, MaterialPlugin};
32
use bevy_app::Plugin;
4-
use bevy_asset::{load_internal_asset, Handle, HandleUntyped};
5-
use bevy_core_pipeline::core_3d::Opaque3d;
6-
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
7-
use bevy_reflect::std_traits::ReflectDefault;
8-
use bevy_reflect::{Reflect, TypeUuid};
9-
use bevy_render::Extract;
3+
use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
4+
use bevy_ecs::prelude::*;
5+
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypeUuid};
106
use bevy_render::{
117
extract_resource::{ExtractResource, ExtractResourcePlugin},
128
mesh::{Mesh, MeshVertexBufferLayout},
13-
render_asset::RenderAssets,
14-
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
9+
prelude::{Color, Shader},
1510
render_resource::{
16-
PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline,
17-
SpecializedMeshPipelineError, SpecializedMeshPipelines,
11+
AsBindGroup, PolygonMode, RenderPipelineDescriptor, ShaderRef, SpecializedMeshPipelineError,
1812
},
19-
view::{ExtractedView, Msaa, VisibleEntities},
20-
RenderApp, RenderStage,
2113
};
22-
use bevy_utils::tracing::error;
2314

2415
pub const WIREFRAME_SHADER_HANDLE: HandleUntyped =
2516
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 192598014480025766);
2617

18+
/// A [`Plugin`] that draws wireframes.
2719
#[derive(Debug, Default)]
2820
pub struct WireframePlugin;
29-
3021
impl Plugin for WireframePlugin {
3122
fn build(&self, app: &mut bevy_app::App) {
3223
load_internal_asset!(
@@ -37,139 +28,175 @@ impl Plugin for WireframePlugin {
3728
);
3829

3930
app.register_type::<Wireframe>()
31+
.register_type::<WireframeColor>()
4032
.register_type::<WireframeConfig>()
4133
.init_resource::<WireframeConfig>()
42-
.add_plugin(ExtractResourcePlugin::<WireframeConfig>::default());
43-
44-
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
45-
render_app
46-
.add_render_command::<Opaque3d, DrawWireframes>()
47-
.init_resource::<WireframePipeline>()
48-
.init_resource::<SpecializedMeshPipelines<WireframePipeline>>()
49-
.add_system_to_stage(RenderStage::Extract, extract_wireframes)
50-
.add_system_to_stage(RenderStage::Queue, queue_wireframes);
51-
}
52-
}
53-
}
54-
55-
fn extract_wireframes(mut commands: Commands, query: Extract<Query<Entity, With<Wireframe>>>) {
56-
for entity in &query {
57-
commands.get_or_spawn(entity).insert(Wireframe);
34+
.add_plugin(ExtractResourcePlugin::<WireframeConfig>::default())
35+
.add_plugin(MaterialPlugin::<WireframeMaterial>::default())
36+
.add_system(apply_global)
37+
.add_system(apply_material)
38+
.add_system(wireframe_color_changed)
39+
.add_system(global_color_changed);
5840
}
5941
}
6042

61-
/// Controls whether an entity should rendered in wireframe-mode if the [`WireframePlugin`] is enabled
43+
/// Toggles wireframe rendering for any entity it is attached to.
44+
/// To control the color of the wireframe, you need to use the [`WireframeColor`] component
45+
///
46+
/// This requires the [`WireframePlugin`] to be enabled.
6247
#[derive(Component, Debug, Clone, Default, Reflect)]
6348
#[reflect(Component, Default)]
6449
pub struct Wireframe;
6550

51+
/// Sets the color of the [`Wireframe`] of the entity it is attached to.
52+
/// If this component is present but there's no [`Wireframe`] component,
53+
/// it will still affect the color of the wireframe when [`WireframeConfig`]`::global` is set.
54+
///
55+
/// This overrides the [`WireframeConfig::color`].
56+
#[derive(Component, Debug, Clone, Default, Reflect)]
57+
#[reflect(Component, Default)]
58+
pub struct WireframeColor {
59+
pub color: Color,
60+
}
61+
62+
/// Configuration resource for [`WireframePlugin`].
6663
#[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)]
6764
#[reflect(Resource)]
6865
pub struct WireframeConfig {
69-
/// Whether to show wireframes for all meshes. If `false`, only meshes with a [Wireframe] component will be rendered.
66+
/// Whether to show wireframes for all meshes.
67+
/// If `false`, only meshes with a [Wireframe] component will be rendered.
7068
pub global: bool,
69+
/// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe`] component attached to it will have
70+
/// wireframes in this color. Otherwise, this will be the fallback color for any entity that has a [`Wireframe`],
71+
/// but no [`WireframeColor`].
72+
pub color: Color,
7173
}
7274

73-
#[derive(Resource, Clone)]
74-
pub struct WireframePipeline {
75-
mesh_pipeline: MeshPipeline,
76-
shader: Handle<Shader>,
75+
/// Apply the wireframe material to any mesh with a [`Wireframe`] component.
76+
/// Uses [`WireframeConfig::color`] as a fallback if no [`WireframeColor`] component is found
77+
#[allow(clippy::type_complexity)]
78+
fn apply_material(
79+
mut commands: Commands,
80+
config: Res<WireframeConfig>,
81+
mut materials: ResMut<Assets<WireframeMaterial>>,
82+
wireframes: Query<
83+
(Entity, Option<&WireframeColor>),
84+
(With<Wireframe>, Without<Handle<WireframeMaterial>>),
85+
>,
86+
) {
87+
for (e, color) in &wireframes {
88+
commands.entity(e).insert(materials.add(WireframeMaterial {
89+
color: if let Some(wireframe_color) = color {
90+
wireframe_color.color
91+
} else {
92+
config.color
93+
},
94+
}));
95+
}
7796
}
78-
impl FromWorld for WireframePipeline {
79-
fn from_world(render_world: &mut World) -> Self {
80-
WireframePipeline {
81-
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
82-
shader: WIREFRAME_SHADER_HANDLE.typed(),
83-
}
97+
98+
/// Updates the wireframe material when the color in [`WireframeColor`] changes
99+
#[allow(clippy::type_complexity)]
100+
fn wireframe_color_changed(
101+
mut materials: ResMut<Assets<WireframeMaterial>>,
102+
mut colors_changed: Query<
103+
(&mut Handle<WireframeMaterial>, &WireframeColor),
104+
(With<Wireframe>, Changed<WireframeColor>),
105+
>,
106+
) {
107+
for (mut handle, wireframe_color) in &mut colors_changed {
108+
*handle = materials.add(WireframeMaterial {
109+
color: wireframe_color.color,
110+
});
84111
}
85112
}
86113

87-
impl SpecializedMeshPipeline for WireframePipeline {
88-
type Key = MeshPipelineKey;
114+
/// Updates the wireframe material of all entities without a [`WireframeColor`] or without a [`Wireframe`] component
115+
fn global_color_changed(
116+
config: Res<WireframeConfig>,
117+
mut materials: ResMut<Assets<WireframeMaterial>>,
118+
mut wireframes: Query<&mut Handle<WireframeMaterial>, Without<WireframeColor>>,
119+
) {
120+
if !config.is_changed() {
121+
return;
122+
}
89123

90-
fn specialize(
91-
&self,
92-
key: Self::Key,
93-
layout: &MeshVertexBufferLayout,
94-
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
95-
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
96-
descriptor.vertex.shader = self.shader.clone_weak();
97-
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak();
98-
descriptor.primitive.polygon_mode = PolygonMode::Line;
99-
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
100-
Ok(descriptor)
124+
for mut handle in &mut wireframes {
125+
*handle = materials.add(WireframeMaterial {
126+
color: config.color,
127+
});
101128
}
102129
}
103130

104-
#[allow(clippy::too_many_arguments)]
105-
fn queue_wireframes(
106-
opaque_3d_draw_functions: Res<DrawFunctions<Opaque3d>>,
107-
render_meshes: Res<RenderAssets<Mesh>>,
108-
wireframe_config: Res<WireframeConfig>,
109-
wireframe_pipeline: Res<WireframePipeline>,
110-
mut pipelines: ResMut<SpecializedMeshPipelines<WireframePipeline>>,
111-
mut pipeline_cache: ResMut<PipelineCache>,
112-
msaa: Res<Msaa>,
113-
mut material_meshes: ParamSet<(
114-
Query<(Entity, &Handle<Mesh>, &MeshUniform)>,
115-
Query<(Entity, &Handle<Mesh>, &MeshUniform), With<Wireframe>>,
131+
/// Applies or remove a wireframe material on any mesh without a [`Wireframe`] component.
132+
#[allow(clippy::type_complexity)]
133+
fn apply_global(
134+
mut commands: Commands,
135+
config: Res<WireframeConfig>,
136+
mut materials: ResMut<Assets<WireframeMaterial>>,
137+
mut q1: ParamSet<(
138+
Query<
139+
Entity,
140+
(
141+
With<Handle<Mesh>>,
142+
Without<Handle<WireframeMaterial>>,
143+
Without<Wireframe>,
144+
),
145+
>,
146+
Query<
147+
Entity,
148+
(
149+
With<Handle<Mesh>>,
150+
With<Handle<WireframeMaterial>>,
151+
Without<Wireframe>,
152+
),
153+
>,
116154
)>,
117-
mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase<Opaque3d>)>,
155+
mut is_global_applied: Local<bool>,
118156
) {
119-
let draw_custom = opaque_3d_draw_functions.read().id::<DrawWireframes>();
120-
let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples);
121-
for (view, visible_entities, mut opaque_phase) in &mut views {
122-
let rangefinder = view.rangefinder3d();
123-
124-
let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr);
125-
let add_render_phase =
126-
|(entity, mesh_handle, mesh_uniform): (Entity, &Handle<Mesh>, &MeshUniform)| {
127-
if let Some(mesh) = render_meshes.get(mesh_handle) {
128-
let key = view_key
129-
| MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
130-
let pipeline_id = pipelines.specialize(
131-
&mut pipeline_cache,
132-
&wireframe_pipeline,
133-
key,
134-
&mesh.layout,
135-
);
136-
let pipeline_id = match pipeline_id {
137-
Ok(id) => id,
138-
Err(err) => {
139-
error!("{}", err);
140-
return;
141-
}
142-
};
143-
opaque_phase.add(Opaque3d {
144-
entity,
145-
pipeline: pipeline_id,
146-
draw_function: draw_custom,
147-
distance: rangefinder.distance(&mesh_uniform.transform),
148-
});
149-
}
150-
};
151-
152-
if wireframe_config.global {
153-
let query = material_meshes.p0();
154-
visible_entities
155-
.entities
156-
.iter()
157-
.filter_map(|visible_entity| query.get(*visible_entity).ok())
158-
.for_each(add_render_phase);
159-
} else {
160-
let query = material_meshes.p1();
161-
visible_entities
162-
.entities
163-
.iter()
164-
.filter_map(|visible_entity| query.get(*visible_entity).ok())
165-
.for_each(add_render_phase);
157+
if !config.is_changed() {
158+
return;
159+
}
160+
161+
if !*is_global_applied && config.global {
162+
let global_material = materials.add(WireframeMaterial {
163+
color: config.color,
164+
});
165+
166+
for e in &mut q1.p0() {
167+
commands.entity(e).insert(global_material.clone());
168+
}
169+
170+
*is_global_applied = true;
171+
} else if *is_global_applied && !config.global {
172+
for e in &mut q1.p1() {
173+
commands.entity(e).remove::<Handle<WireframeMaterial>>();
166174
}
175+
*is_global_applied = false;
167176
}
168177
}
169178

170-
type DrawWireframes = (
171-
SetItemPipeline,
172-
SetMeshViewBindGroup<0>,
173-
SetMeshBindGroup<1>,
174-
DrawMesh,
175-
);
179+
/// The material used to render wireframes
180+
#[derive(Default, AsBindGroup, TypeUuid, Debug, Clone)]
181+
#[uuid = "9e694f70-9963-4418-8bc1-3474c66b13b8"]
182+
pub struct WireframeMaterial {
183+
#[uniform(0)]
184+
pub color: Color,
185+
}
186+
187+
impl Material for WireframeMaterial {
188+
fn fragment_shader() -> ShaderRef {
189+
WIREFRAME_SHADER_HANDLE.typed().into()
190+
}
191+
192+
fn specialize(
193+
_pipeline: &MaterialPipeline<Self>,
194+
descriptor: &mut RenderPipelineDescriptor,
195+
_layout: &MeshVertexBufferLayout,
196+
_key: MaterialPipelineKey<Self>,
197+
) -> Result<(), SpecializedMeshPipelineError> {
198+
descriptor.primitive.polygon_mode = PolygonMode::Line;
199+
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
200+
Ok(())
201+
}
202+
}

0 commit comments

Comments
 (0)