Skip to content

Commit 5e26fcb

Browse files
committed
Add colored wireframes
1 parent 516e4aa commit 5e26fcb

File tree

3 files changed

+247
-32
lines changed

3 files changed

+247
-32
lines changed

crates/bevy_pbr/src/render/wireframe.wgsl

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ struct Vertex {
99
#endif
1010
};
1111

12+
struct Wireframe {
13+
color: vec4<f32>;
14+
};
15+
1216
[[group(1), binding(0)]]
1317
var<uniform> mesh: Mesh;
18+
[[group(2), binding(0)]]
19+
var<uniform> wireframe: Wireframe;
1420

1521
struct VertexOutput {
1622
[[builtin(position)]] clip_position: vec4<f32>;
@@ -39,5 +45,5 @@ fn vertex(vertex: Vertex) -> VertexOutput {
3945

4046
[[stage(fragment)]]
4147
fn fragment() -> [[location(0)]] vec4<f32> {
42-
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
48+
return wireframe.color;
4349
}

crates/bevy_pbr/src/wireframe.rs

+228-26
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
1-
use crate::MeshPipeline;
2-
use crate::{DrawMesh, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup};
1+
use crate::{
2+
DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup,
3+
};
34
use bevy_app::Plugin;
45
use bevy_asset::{load_internal_asset, Handle, HandleUntyped};
56
use bevy_core_pipeline::Opaque3d;
6-
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
7-
use bevy_reflect::std_traits::ReflectDefault;
7+
use bevy_ecs::{
8+
prelude::*,
9+
reflect::ReflectComponent,
10+
system::{lifetimeless::*, SystemParamItem},
11+
};
12+
use bevy_math::Vec4;
813
use bevy_reflect::{Reflect, TypeUuid};
914
use bevy_render::{
15+
color::Color,
1016
mesh::{Mesh, MeshVertexBufferLayout},
1117
render_asset::RenderAssets,
12-
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
18+
render_component::{ExtractComponent, ExtractComponentPlugin},
19+
render_phase::{
20+
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
21+
SetItemPipeline, TrackedRenderPass,
22+
},
1323
render_resource::{
14-
PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline,
15-
SpecializedMeshPipelineError, SpecializedMeshPipelines,
24+
std140::AsStd140, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
25+
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BufferBindingType,
26+
BufferSize, DynamicUniformVec,
1627
},
28+
render_resource::{
29+
PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, ShaderStages,
30+
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
31+
},
32+
renderer::{RenderDevice, RenderQueue},
1733
view::{ExtractedView, Msaa, VisibleEntities},
1834
RenderApp, RenderStage,
1935
};
@@ -22,6 +38,7 @@ use bevy_utils::tracing::error;
2238
pub const WIREFRAME_SHADER_HANDLE: HandleUntyped =
2339
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 192598014480025766);
2440

41+
/// A [`Plugin`] that draws wireframes.
2542
#[derive(Debug, Default)]
2643
pub struct WireframePlugin;
2744

@@ -36,13 +53,16 @@ impl Plugin for WireframePlugin {
3653

3754
app.init_resource::<WireframeConfig>();
3855

56+
app.add_plugin(ExtractComponentPlugin::<Wireframe>::extract_visible());
3957
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
4058
render_app
4159
.add_render_command::<Opaque3d, DrawWireframes>()
60+
.init_resource::<GlobalWireframeMeta>()
4261
.init_resource::<WireframePipeline>()
4362
.init_resource::<SpecializedMeshPipelines<WireframePipeline>>()
44-
.add_system_to_stage(RenderStage::Extract, extract_wireframes)
4563
.add_system_to_stage(RenderStage::Extract, extract_wireframe_config)
64+
.add_system_to_stage(RenderStage::Prepare, prepare_wireframes)
65+
.add_system_to_stage(RenderStage::Queue, queue_wireframes_bind_group)
4666
.add_system_to_stage(RenderStage::Queue, queue_wireframes);
4767
}
4868
}
@@ -54,31 +74,186 @@ fn extract_wireframe_config(mut commands: Commands, wireframe_config: Res<Wirefr
5474
}
5575
}
5676

57-
fn extract_wireframes(mut commands: Commands, query: Query<Entity, With<Wireframe>>) {
58-
for entity in query.iter() {
59-
commands.get_or_spawn(entity).insert(Wireframe);
77+
#[allow(clippy::type_complexity)]
78+
fn prepare_wireframes(
79+
mut commands: Commands,
80+
config: Res<WireframeConfig>,
81+
render_device: Res<RenderDevice>,
82+
render_queue: Res<RenderQueue>,
83+
mut wireframe_meta: ResMut<GlobalWireframeMeta>,
84+
global_query: Query<(Entity, Option<&Wireframe>), (With<Handle<Mesh>>, With<MeshUniform>)>,
85+
wireframe_query: Query<
86+
(Entity, Option<&Wireframe>),
87+
(With<Handle<Mesh>>, With<MeshUniform>, With<Wireframe>),
88+
>,
89+
) {
90+
wireframe_meta.uniforms.clear();
91+
wireframe_meta.uniforms.push(WireframeUniform {
92+
color: config.default_color.as_linear_rgba_f32().into(),
93+
});
94+
95+
let add_wireframe_uniform = |(entity, wireframe): (Entity, Option<&Wireframe>)| {
96+
let custom_color = wireframe.and_then(|wireframe| wireframe.color);
97+
let uniform_offset = WireframeUniformOffset(if let Some(custom_color) = custom_color {
98+
wireframe_meta.uniforms.push(WireframeUniform {
99+
color: custom_color.as_linear_rgba_f32().into(),
100+
})
101+
} else {
102+
0
103+
});
104+
commands.entity(entity).insert(uniform_offset);
105+
};
106+
107+
if config.on_all_meshes {
108+
global_query.for_each(add_wireframe_uniform);
109+
} else {
110+
wireframe_query.for_each(add_wireframe_uniform);
111+
}
112+
113+
wireframe_meta
114+
.uniforms
115+
.write_buffer(&render_device, &render_queue);
116+
}
117+
118+
/// Stores the [`BindGroup`] of wireframe data that is used on the GPU side.
119+
///
120+
/// Internal [`WireframePlugin`] resource.
121+
struct GlobalWireframeBindGroup {
122+
bind_group: BindGroup,
123+
}
124+
125+
fn queue_wireframes_bind_group(
126+
mut commands: Commands,
127+
render_device: Res<RenderDevice>,
128+
meta: Res<GlobalWireframeMeta>,
129+
bind_group: Option<Res<GlobalWireframeBindGroup>>,
130+
) {
131+
if bind_group.is_none() {
132+
commands.insert_resource(GlobalWireframeBindGroup {
133+
bind_group: render_device.create_bind_group(&BindGroupDescriptor {
134+
entries: &[BindGroupEntry {
135+
binding: 0,
136+
resource: meta.uniforms.binding().unwrap(),
137+
}],
138+
label: Some("wireframe_bind_group"),
139+
layout: &meta.bind_group_layout,
140+
}),
141+
});
60142
}
61143
}
62144

63-
/// Controls whether an entity should rendered in wireframe-mode if the [`WireframePlugin`] is enabled
64-
#[derive(Component, Debug, Clone, Default, Reflect)]
65-
#[reflect(Component, Default)]
66-
pub struct Wireframe;
145+
/// Controls per-entity wireframe rendering settings (if the [`WireframePlugin`] is enabled)
146+
#[derive(Component, Debug, Default, Copy, Clone, Reflect)]
147+
#[reflect(Component)]
148+
pub struct Wireframe {
149+
/// The color of this wireframe.
150+
///
151+
/// If `None`, the [`Entity`] wireframe's color will be [`WireframeConfig::default_color`].
152+
pub color: Option<Color>,
153+
}
154+
155+
impl ExtractComponent for Wireframe {
156+
type Query = &'static Wireframe;
157+
158+
type Filter = ();
159+
160+
#[inline]
161+
fn extract_component(item: bevy_ecs::query::QueryItem<Self::Query>) -> Self {
162+
*item
163+
}
164+
}
67165

68-
#[derive(Debug, Clone, Default)]
166+
/// Configuration resource for [`WireframePlugin`].
167+
#[derive(Debug, Clone)]
69168
pub struct WireframeConfig {
70-
/// Whether to show wireframes for all meshes. If `false`, only meshes with a [Wireframe] component will be rendered.
71-
pub global: bool,
169+
/// Whether to show wireframes for all meshes. If `false`, only meshes with a [`Wireframe`] component will be rendered.
170+
pub on_all_meshes: bool,
171+
/// The default color for wireframes.
172+
///
173+
/// If [`Self::on_all_meshes`] is set, any [`Entity`] that does not have a [`Wireframe`] component attached to it will have
174+
/// wireframes in this color. Otherwise, this will be the fallback color for any [`Wireframe`]s that does have a `None`
175+
/// [`Wireframe::color`].
176+
pub default_color: Color,
72177
}
73178

74-
pub struct WireframePipeline {
179+
impl Default for WireframeConfig {
180+
fn default() -> Self {
181+
Self {
182+
on_all_meshes: false,
183+
default_color: Color::WHITE,
184+
}
185+
}
186+
}
187+
188+
/// Holds the offset of a [`WireframeUniform`] in the [`GlobalWireframeMeta::uniforms`].
189+
///
190+
/// Internal [`WireframePlugin`] component.
191+
#[derive(Component, Copy, Clone, Debug, Default)]
192+
#[repr(transparent)]
193+
struct WireframeUniformOffset(u32);
194+
195+
/// [`WireframeUniform`] is the GPU representation of a [`Wireframe`].
196+
///
197+
/// Internal [`WireframePlugin`] state.
198+
#[derive(Debug, AsStd140)]
199+
struct WireframeUniform {
200+
color: Vec4,
201+
}
202+
203+
/// The data required for rendering [`Wireframe`]s.
204+
///
205+
/// Internal [`WireframePlugin`] resource.
206+
#[derive(Component)]
207+
struct GlobalWireframeMeta {
208+
uniforms: DynamicUniformVec<WireframeUniform>,
209+
bind_group_layout: BindGroupLayout,
210+
}
211+
212+
impl FromWorld for GlobalWireframeMeta {
213+
fn from_world(world: &mut World) -> Self {
214+
let render_device = world.get_resource::<RenderDevice>().unwrap();
215+
216+
let bind_group_layout =
217+
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
218+
entries: &[BindGroupLayoutEntry {
219+
binding: 0,
220+
visibility: ShaderStages::FRAGMENT,
221+
ty: BindingType::Buffer {
222+
ty: BufferBindingType::Uniform,
223+
has_dynamic_offset: true,
224+
min_binding_size: BufferSize::new(
225+
WireframeUniform::std140_size_static() as u64
226+
),
227+
},
228+
count: None,
229+
}],
230+
label: Some("wireframe_bind_group_layout"),
231+
});
232+
233+
Self {
234+
uniforms: Default::default(),
235+
bind_group_layout,
236+
}
237+
}
238+
}
239+
240+
/// [`WireframePipeline`] is the specialized rendering pipeline for wireframes.
241+
///
242+
/// Internal [`WireframePlugin`] resource.
243+
struct WireframePipeline {
75244
mesh_pipeline: MeshPipeline,
245+
wireframe_bind_group_layout: BindGroupLayout,
76246
shader: Handle<Shader>,
77247
}
78248
impl FromWorld for WireframePipeline {
79249
fn from_world(render_world: &mut World) -> Self {
80250
WireframePipeline {
81251
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
252+
wireframe_bind_group_layout: render_world
253+
.get_resource::<GlobalWireframeMeta>()
254+
.unwrap()
255+
.bind_group_layout
256+
.clone(),
82257
shader: WIREFRAME_SHADER_HANDLE.typed(),
83258
}
84259
}
@@ -95,6 +270,11 @@ impl SpecializedMeshPipeline for WireframePipeline {
95270
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
96271
descriptor.vertex.shader = self.shader.clone_weak();
97272
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak();
273+
descriptor
274+
.layout
275+
.as_mut()
276+
.unwrap()
277+
.push(self.wireframe_bind_group_layout.clone());
98278
descriptor.primitive.polygon_mode = PolygonMode::Line;
99279
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
100280
Ok(descriptor)
@@ -153,13 +333,8 @@ fn queue_wireframes(
153333
}
154334
};
155335

156-
if wireframe_config.global {
157-
let query = material_meshes.p0();
158-
visible_entities
159-
.entities
160-
.iter()
161-
.filter_map(|visible_entity| query.get(*visible_entity).ok())
162-
.for_each(add_render_phase);
336+
if wireframe_config.on_all_meshes {
337+
material_meshes.p0().iter().for_each(add_render_phase);
163338
} else {
164339
let query = material_meshes.p1();
165340
visible_entities
@@ -171,9 +346,36 @@ fn queue_wireframes(
171346
}
172347
}
173348

349+
/// [`SetWireframeBindGroup`]`<bindgroup index>` binds the [`GlobalWireframeBindGroup`] there.
350+
///
351+
/// Internal [`WireframePlugin`] render command.
352+
struct SetWireframeBindGroup<const I: usize>;
353+
impl<const I: usize> EntityRenderCommand for SetWireframeBindGroup<I> {
354+
type Param = (
355+
SRes<GlobalWireframeBindGroup>,
356+
SQuery<Read<WireframeUniformOffset>, With<Handle<Mesh>>>,
357+
);
358+
#[inline]
359+
fn render<'w>(
360+
_view: Entity,
361+
item: Entity,
362+
(global_wireframe_bind_group, view_query): SystemParamItem<'w, '_, Self::Param>,
363+
pass: &mut TrackedRenderPass<'w>,
364+
) -> RenderCommandResult {
365+
let wireframe_uniform_offset = view_query.get(item).unwrap();
366+
pass.set_bind_group(
367+
I,
368+
&global_wireframe_bind_group.into_inner().bind_group,
369+
&[wireframe_uniform_offset.0],
370+
);
371+
RenderCommandResult::Success
372+
}
373+
}
374+
174375
type DrawWireframes = (
175376
SetItemPipeline,
176377
SetMeshViewBindGroup<0>,
177378
SetMeshBindGroup<1>,
379+
SetWireframeBindGroup<2>,
178380
DrawMesh,
179381
);

examples/3d/wireframe.rs

+12-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ fn main() {
1212
..default()
1313
})
1414
.add_plugins(DefaultPlugins)
15+
.insert_resource(WireframeConfig {
16+
// To draw the wireframe on all entities, set this to 'true'
17+
on_all_meshes: false,
18+
// You can also change the default color of the wireframes, which controls:
19+
// - all wireframes if `WireframeConfig::on_all_meshes` is set to 'true'
20+
// - the wireframe of all entities whose `Wireframe::color` is None otherwise
21+
default_color: Color::AQUAMARINE,
22+
})
1523
.add_plugin(WireframePlugin)
1624
.add_startup_system(setup)
1725
.run();
@@ -20,12 +28,9 @@ fn main() {
2028
/// set up a simple 3D scene
2129
fn setup(
2230
mut commands: Commands,
23-
mut wireframe_config: ResMut<WireframeConfig>,
2431
mut meshes: ResMut<Assets<Mesh>>,
2532
mut materials: ResMut<Assets<StandardMaterial>>,
2633
) {
27-
// To draw the wireframe on all entities, set this to 'true'
28-
wireframe_config.global = false;
2934
// plane
3035
commands.spawn_bundle(PbrBundle {
3136
mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })),
@@ -40,8 +45,10 @@ fn setup(
4045
transform: Transform::from_xyz(0.0, 0.5, 0.0),
4146
..default()
4247
})
43-
// This enables wireframe drawing on this entity
44-
.insert(Wireframe);
48+
// This enables wireframe drawing on this entity (and it overrides the WireframeConfig's default_color)
49+
.insert(Wireframe {
50+
color: Some(Color::FUCHSIA),
51+
});
4552
// light
4653
commands.spawn_bundle(PointLightBundle {
4754
transform: Transform::from_xyz(4.0, 8.0, 4.0),

0 commit comments

Comments
 (0)