Skip to content

Commit e096e8e

Browse files
committed
Add colored wireframes
1 parent e49542b commit e096e8e

File tree

3 files changed

+227
-17
lines changed

3 files changed

+227
-17
lines changed

crates/bevy_pbr/src/render/wireframe.wgsl

Lines changed: 7 additions & 1 deletion
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

Lines changed: 213 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,34 @@
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_ecs::{
8+
prelude::*,
9+
reflect::ReflectComponent,
10+
system::{lifetimeless::*, SystemParamItem},
11+
};
12+
use bevy_math::Vec4;
713
use bevy_reflect::{Reflect, TypeUuid};
814
use bevy_render::{
15+
color::Color,
916
mesh::{Mesh, MeshVertexBufferLayout},
1017
render_asset::RenderAssets,
11-
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
18+
render_phase::{
19+
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
20+
SetItemPipeline, TrackedRenderPass,
21+
},
1222
render_resource::{
13-
PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline,
14-
SpecializedMeshPipelineError, SpecializedMeshPipelines,
23+
std140::AsStd140, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
24+
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BufferBindingType,
25+
BufferSize, DynamicUniformVec,
1526
},
27+
render_resource::{
28+
PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, ShaderStages,
29+
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
30+
},
31+
renderer::{RenderDevice, RenderQueue},
1632
view::{ExtractedView, Msaa},
1733
RenderApp, RenderStage,
1834
};
@@ -38,10 +54,13 @@ impl Plugin for WireframePlugin {
3854
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
3955
render_app
4056
.add_render_command::<Opaque3d, DrawWireframes>()
57+
.init_resource::<GlobalWireframeMeta>()
4158
.init_resource::<WireframePipeline>()
4259
.init_resource::<SpecializedMeshPipelines<WireframePipeline>>()
4360
.add_system_to_stage(RenderStage::Extract, extract_wireframes)
4461
.add_system_to_stage(RenderStage::Extract, extract_wireframe_config)
62+
.add_system_to_stage(RenderStage::Prepare, prepare_wireframes)
63+
.add_system_to_stage(RenderStage::Queue, queue_wireframes_bind_group)
4564
.add_system_to_stage(RenderStage::Queue, queue_wireframes);
4665
}
4766
}
@@ -53,31 +72,179 @@ fn extract_wireframe_config(mut commands: Commands, wireframe_config: Res<Wirefr
5372
}
5473
}
5574

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

62-
/// Controls whether an entity should rendered in wireframe-mode if the [`WireframePlugin`] is enabled
63-
#[derive(Component, Debug, Clone, Default, Reflect)]
147+
/// Controls per-entity wireframe rendering settings (if the [`WireframePlugin`] is enabled)
148+
#[derive(Component, Debug, Clone, Reflect)]
64149
#[reflect(Component)]
65-
pub struct Wireframe;
150+
pub struct Wireframe {
151+
/// The color of this wireframe.
152+
/// For the [`Entity`] that has this [`Wireframe`] component, this color overrides any that was set in [`WireframeConfig::default_color`].
153+
pub color: Color,
154+
}
66155

67-
#[derive(Debug, Clone, Default)]
156+
impl Default for Wireframe {
157+
fn default() -> Self {
158+
Self {
159+
color: Color::WHITE,
160+
}
161+
}
162+
}
163+
164+
/// Configuration for [`WireframePlugin`].
165+
#[derive(Debug, Clone)]
68166
pub struct WireframeConfig {
69-
/// Whether to show wireframes for all meshes. If `false`, only meshes with a [Wireframe] component will be rendered.
167+
/// Whether to show wireframes for all meshes. If `false`, only meshes with a [`Wireframe`] component will be rendered.
70168
pub global: bool,
169+
/// The default color for wireframes. If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe`]
170+
/// component attached to it will have wireframes in this color.
171+
pub default_color: Color,
172+
}
173+
174+
impl Default for WireframeConfig {
175+
fn default() -> Self {
176+
Self {
177+
global: false,
178+
default_color: Color::WHITE,
179+
}
180+
}
181+
}
182+
183+
/// Internal [`WireframePlugin`] component.
184+
/// [`WireframeUniformOffset`] holds the offset of a [`WireframeUniform`] in the [`GlobalWireframeMeta::uniforms`].
185+
#[derive(Component, Copy, Clone, Debug, Default)]
186+
#[repr(transparent)]
187+
struct WireframeUniformOffset(u32);
188+
189+
/// Internal [`WireframePlugin`] state.
190+
/// [`WireframeUniform`] is the GPU representation of a [`Wireframe`].
191+
#[derive(Debug, AsStd140)]
192+
struct WireframeUniform {
193+
color: Vec4,
194+
}
195+
196+
/// Internal [`WireframePlugin`] resource.
197+
/// This is the data required for rendering [`Wireframe`]s.
198+
#[derive(Component)]
199+
struct GlobalWireframeMeta {
200+
uniforms: DynamicUniformVec<WireframeUniform>,
201+
bind_group_layout: BindGroupLayout,
71202
}
72203

73-
pub struct WireframePipeline {
204+
impl FromWorld for GlobalWireframeMeta {
205+
fn from_world(world: &mut World) -> Self {
206+
let render_device = world.get_resource::<RenderDevice>().unwrap();
207+
208+
let bind_group_layout =
209+
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
210+
entries: &[BindGroupLayoutEntry {
211+
binding: 0,
212+
visibility: ShaderStages::FRAGMENT,
213+
ty: BindingType::Buffer {
214+
ty: BufferBindingType::Uniform,
215+
has_dynamic_offset: true,
216+
min_binding_size: BufferSize::new(
217+
WireframeUniform::std140_size_static() as u64
218+
),
219+
},
220+
count: None,
221+
}],
222+
label: Some("wireframe_bind_group_layout"),
223+
});
224+
225+
Self {
226+
uniforms: Default::default(),
227+
bind_group_layout,
228+
}
229+
}
230+
}
231+
232+
/// Internal [`WireframePlugin`] resource.
233+
/// [`WireframePipeline`] is the specialized rendering pipeline for wireframes.
234+
struct WireframePipeline {
74235
mesh_pipeline: MeshPipeline,
236+
wireframe_bind_group_layout: BindGroupLayout,
75237
shader: Handle<Shader>,
76238
}
77239
impl FromWorld for WireframePipeline {
78240
fn from_world(render_world: &mut World) -> Self {
79241
WireframePipeline {
80242
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
243+
wireframe_bind_group_layout: render_world
244+
.get_resource::<GlobalWireframeMeta>()
245+
.unwrap()
246+
.bind_group_layout
247+
.clone(),
81248
shader: WIREFRAME_SHADER_HANDLE.typed(),
82249
}
83250
}
@@ -94,6 +261,11 @@ impl SpecializedMeshPipeline for WireframePipeline {
94261
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
95262
descriptor.vertex.shader = self.shader.clone_weak();
96263
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak();
264+
descriptor
265+
.layout
266+
.as_mut()
267+
.unwrap()
268+
.push(self.wireframe_bind_group_layout.clone());
97269
descriptor.primitive.polygon_mode = PolygonMode::Line;
98270
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
99271
Ok(descriptor)
@@ -160,9 +332,35 @@ fn queue_wireframes(
160332
}
161333
}
162334

335+
/// Internal [`WireframePlugin`] render command.
336+
/// [`SetWireframeBindGroup`]`<bindgroup index>` binds the [`GlobalWireframeBindGroup`] there.
337+
struct SetWireframeBindGroup<const I: usize>;
338+
impl<const I: usize> EntityRenderCommand for SetWireframeBindGroup<I> {
339+
type Param = (
340+
SRes<GlobalWireframeBindGroup>,
341+
SQuery<Read<WireframeUniformOffset>, With<Handle<Mesh>>>,
342+
);
343+
#[inline]
344+
fn render<'w>(
345+
_view: Entity,
346+
item: Entity,
347+
(global_wireframe_bind_group, view_query): SystemParamItem<'w, '_, Self::Param>,
348+
pass: &mut TrackedRenderPass<'w>,
349+
) -> RenderCommandResult {
350+
let wireframe_uniform_offset = view_query.get(item).unwrap();
351+
pass.set_bind_group(
352+
I,
353+
&global_wireframe_bind_group.into_inner().bind_group,
354+
&[wireframe_uniform_offset.0],
355+
);
356+
RenderCommandResult::Success
357+
}
358+
}
359+
163360
type DrawWireframes = (
164361
SetItemPipeline,
165362
SetMeshViewBindGroup<0>,
166363
SetMeshBindGroup<1>,
364+
SetWireframeBindGroup<2>,
167365
DrawMesh,
168366
);

examples/3d/wireframe.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ fn setup(
2626
) {
2727
// To draw the wireframe on all entities, set this to 'true'
2828
wireframe_config.global = false;
29+
// You can also change the default color of the wireframes, which is used only if `global` is set.
30+
// This is the fallback wireframe color, which is used for any entities that do not have a Wireframe component.
31+
wireframe_config.default_color = Color::AQUAMARINE;
32+
2933
// plane
3034
commands.spawn_bundle(PbrBundle {
3135
mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })),
@@ -41,7 +45,9 @@ fn setup(
4145
..default()
4246
})
4347
// This enables wireframe drawing on this entity
44-
.insert(Wireframe);
48+
.insert(Wireframe {
49+
color: Color::FUCHSIA,
50+
});
4551
// light
4652
commands.spawn_bundle(PointLightBundle {
4753
transform: Transform::from_xyz(4.0, 8.0, 4.0),

0 commit comments

Comments
 (0)